So you’ve got a big idea to improve your system. You’ll split the monolith into services, or merge the services into a monolith. You’ll have strong API boundaries, or tear down silos. You’ll be event-driven or data-driven or domain-driven or just driven mad. Congratulations, you’re on the road to leadership in the part of the software world that makes money.
What now? You find the hook.
The hook is a benefit unlocked by your improvement. It’s a benefit to the end customer, or the whole business - but it absolutely must benefit someone outside your team.
Here’s some good hooks:
But these are not good hooks:
So - don’t improve the system? Just wait for feature requests?
No. The hook is not the entire reason for a system improvement - it may be a tiny fraction of the value provided. Intangible engineering values - simplicity, separation of concerns, robustness - are important, even if they are not hooks.
The hook is part of the sales process for our improvement. A promise of “better delivery in the future” doesn’t mean much, but a complex feature made easier is a win.
More importantly, the hook keeps us honest. It grounds us in reality. The system exists for its users, and the business that owns it - not to fulfill our engineering aesthetics. If we can’t find a hook - or if the hook is easily met with a smaller change - then we need to rethink our proposal.
]]>But I do think that “walking the board” helps to reinforce the right values and build a healthier team. In this format, we skip the around-the-circle summaries, and discuss each item on the team board.
The standup is not a status meeting. It is not where we justify our salaries to our managers. (This is part of the team’s collective responsibility - but not daily, and not in standup.)
The standup is also not family dinner. We don’t go around the circle to discover fun things about each others’ day. (Family dinner is awesome. Family dinner is essential, in one form or another, and we might do it in our daily meeting - but it’s not the core of standup.)
So standup isn’t about people - it’s about the team, and its goals and responsibilities. The question to answer is “how should we spend the next workday?” Like a chessboard, the team board should capture the current “state of play” - all the work recently done, in progress, and next up. So let’s put it at the center of our daily conversation, and talk about how to move the team forward together.
]]>Here’s the thing: in 2021, there are three general ways to write a webapp, three ways to staff a webdev company, and three ways to approach a webdev career.
But here’s the important thing: all three are legitimate choices. If you’re breaking ground on a new app, company, or career, take your pick. I’ll add my opinions later.
But first, let’s take a tour.
In this world, we love Javascript (or Typescript). As “full stack developers”, we approach the application client-side first. When server-side work is needed, we take responsibility for that as well. We might write API endpoints with Node, or do server-side rendering for entire pages, but our native environment is the browser.
In this world, we’re also “full stack developers”, but we keep as much code server-side as possible. Our preferred languages (Ruby, Python, etc) live on the server. But we recognize that we’re writing webapps, and a rich UX is required. We prefer server-rendered markup, but sprinkles of Javascript and tricks like Turbolinks keep things snappy.
In this world, we’re building separate apps: one (or more) on the client, and one (or more) on the server. As developers, we focus on “front end” or “back end”. We might dabble in the other world, but mostly we specialize. We expect our API to be a well-designed communication point between separate people or teams, and it may be reused by multiple clients.
These are three approaches, but they aren’t quite equivalent. A hard frontend / backend split is powerful, but it comes with real communication challenges. Most features will need changes on both sides of the API, so you’ll need two specialists involved. On the other hand, if you’re supporting multiple clients and you need a stable API anyway, then it’s reasonable to treat the web app as just another client.
So my general advice - to both companies and developers - is to stay “full stack” as long as you can, and draw an API boundary when you must.
When it comes to “front end” and “back end” focus - there are advantages to each, but it’s mostly about aesthetics. Each approach has its problems, but tools and techniques are filling in the gaps.
If you’re an executive, go where your developers prefer. If you’re a junior developer, follow the best teachers and mentors. And if you’re like me - a mid-career developer with opinions - learn enough to appreciate both approaches.
]]>In the past, I’ve generally followed Givewell’s recommendations, and donated through them to a “most good per dollar” charity. I still think they’re a great option. But this year I’m doing something a little different.
The CES advocates for better voting systems - specifically, approval voting.
I’ve been following Ezra Klein’s podcast, and I’ve become deeply concerned about polarization in politics. Two sides, with their own hardcore fans, their own media, and their own facts. Approval voting can’t solve the whole issue, but it’s a step in the right direction.
The other serious voting reform effort is FairVote, which advocates for ranked choice voting (RCV). I looked at the case for RCV, and it doesn’t address my concern - in fact, it may make polarization worse. The problem is the “center squeeze” effect. If there is a compromise candidate, acceptable to most voters, I want that candidate to win - even if they are no one’s first choice.
I thought long and hard about directing some of my “charity” budget to a political organization, instead of the truly poor. It’s a long-term bet, with very uncertain benefits. I think it’s justified, because a more functional government in the US would be a huge benefit to everyone.
Here’s my money for global poverty - my attempt to do the most good, for the most people, right now. One EA program is a GiveWell top charity.
I gave directly to EA, without directing my donation to a particular program. To qualify for Givewell’s endorsement, a program needs seriously proven effectiveness. That takes time and money. EA runs several programs, including their “accelerator” which tries new small-scale initiatives. More importantly, EA is committed to doing the most good possible, and they have shut down programs that failed to show results. I’m trusting the folks at EA to place bets where reasonable, and double down on the programs that work.
I gave a few other donations, but I consider these closer to “buying things” than giving to charity.
If you have “strong opinions”, keeping them “weakly held” is difficult. More importantly, communicating “weakly held” is nearly impossible. Anyone with loud, strong opinions can shut down discussion, preventing others’ voices from being heard.
Michael suggests adding an uncertainty measurement to your statements. “I’m 90% sure we should use Postgres over Mongo”, or “I’m 40% that the bug is in this method”.
Around the same time, Camille Fournier wrote a post about Other People’s Problems. Every company has problems, and everyone has opinions about those problems. When should you get involved, and when should you stay silent? How hard should you push when another team’s problem affects your team? Camille outlines five steps to navigate these delicate situations.
Together these posts have led me to a useful framework for decision-making. As a senior engineer, I rarely have direct authority to just “fix it” - at least not for the large and interesting problems. But I also have a responsibility to raise issues and advocate for better solutions. I need to find a balance:
The two factors (certainty and caring) both feed into a 10 point scale. If I’m advocating for something, I try to put a number on it first - either explicitly for others, or just in my own head. This helps reign in heated arguments, and leads me to a more effective and stress-free existence.
Yes, the scale starts at 0 - because we’re programmers, but also because a 0-point opinion is a useful thing to have. It’s reserved for explicitly “free-form spaces” like brainstorming sessions. I’m usually a reserved sort of person, but sometimes it’s worth throwing out ideas with no filter. And hey, maybe we should rewrite everything in Javascript?
This is where most work happens, but it’s still a range. For example, let’s consider a code review. If I suggest a five-line simplification, that’s probably a 3. I’m pretty sure it will work - but I haven’t applied the change and run the tests. I’m pretty sure it makes the code better, but I’d listen to an opposing argument. Either way, this is just a discussion between two engineers.
For a larger example, let’s say my team is developing a new feature. I may write a high-level “vision” for the work - similar to the pitch in Basecamp’s “Shape Up” process. For the first draft, I might have a 4-level conviction that this is the right approach. As other team members help to improve it, I may go up to a 5 or 6 - strong enough to cheerlead the work into our roadmap.
A 7 or 8 issue is rarely a purely technical or product question. These are where managers earn their money. Company values, like a “no assholes” rule, are in the 7-8 range to me. These aren’t “big bright lines”, but they are important considerations when making decisions about my career.
A technical issue can rise to the 7/8 range, if it illustrates something about the company culture. Facebook’s infamous “move fast and break things” slogan told everyone about their priorities. An engineer’s agreement (or disagreement) with that slogan may be a 7/8 issue.
I’m reserving 9 and 10 for ethical issues. Fortunately I’ve never encountered a 9+ issue at work, because these are the “resign in protest” sort of decisions.
If Natkin’s percentages are estimating odds, my point system is like placing bets with real money. The “money” is time, effort, political capital - and yes, occasionally real money. If something’s a 3, I’ll happily discuss it for a few minutes. If something’s a 6, I’ll write up a document and have a series of discussions. If something’s an 8, I’ll raise it up the management chain.
Numbering these bets help me - first within my own head, then when communicating to others. It’s easy to get caught up in a discussion and lose perspective - “no, that design is terrible because…” Ok, how “terrible”? Company-threatening terrible? Are you convinced that this can’t work at all, or are you predicting future maintenance problems?
This process has worked for me personally over the past several months. Will it work as a more general communication pattern? I hope so!
Right now I think:
Does it work for you? Let me know!
]]>But let’s forget about individuals for a minute. Let’s talk about teams. A team might be an entire startup, or a division of a company, or a product team, but it includes all of the people responsible for delivering a software product.
The team has to hit four goals:
Everybody’s gotta eat.
Kathy Sierra has a whole book arguing that this is the most important piece of the puzzle.
Software conferences focus on this goal - creating things that work well and look beautiful.
Hiring, diversity, education, mentoring, development process, management, politics - all of the the things that keep the people working smoothly together.
–
In an ideal situation, these goals naturally flow into one another. The team has great people, an effective process, and works well together. They build a product that’s “high quality” to them - whether that means clean code, beautiful design, lots of features, or anything. Their users appreciate that high quality product, because their needs match what the team is delivering. Then the users shower them with money, since the users happen to be rich.
In the real world, there’s friction between these steps. Make an extra buck by upselling your users with features they don’t need. Save your users time with a feature delivered today, but at the price of technical debt. Deliver a high-quality product on time, but burn out your team in the process. Be a great place to work, until the company goes under.
So it’s all about tradeoffs and focus, not oaths and absolutes.
]]>As part of a big company, we’re limited to the traditional pattern (resumes, interviews and whiteboards, oh my!) - unfortunately, we can’t do a 40 hour test project. But there are better and worse ways to do a traditional interview, and this post outlines my current thoughts.
If you’re looking for a job, consider this a guide - “how to get my hire recommendation”. Read it, follow it, send me a resume, and mention this post for ten bonus points.
(Disclaimer: Bonus points are fake. I’m one developer of many, so YMMV if you interview here. This post is also a “how we should hire” case to my teammates.)
Code, if you’ve got it
The entire process becomes much easier if I can review some of your code before the interview. Open source, class project, side business, whatever - just send me a github link to something production-quality. This lets me skip the boring “can-you-code-at-all” questions, and turn the interview into a code review. We’ll sketch your architecture on the whiteboard, talk about design choices, maybe sketch out a new feature to bolt onto the project. It does not have to be perfect (all code sucks in its own way). Just having something that works puts you head and shoulders above most candidates.
But most people don’t have recent code samples to demonstrate. Many of the best candidates are not seriously looking, just kicking the tires for better gig. If you’ve got no code, that’s not a disqualification, but it means we need to spend time on the basics.
The Basics
The first question is, can you program at all? So we’ll ask something like FizzBuzz in a phone screen.
Then we’ve got to check for a basic understanding of our technical stack - relational database, Ruby, Rails, and Javascript. I’ll outline a very basic app, like a single-item online store, and ask for a database schema. With that 3-4 table schema, we’ll write some SQL queries, then some light ActiveRecord classes. I’ll describe a page in the system, and we’ll write out the simplest possible HTML, and do some Javascript transformations on it.
A “full-stack” developer should get through these questions quickly. Holes in your knowledge may be ok, but even a front-end developer should be able to hand-wave some kind of database schema, and even a back-end specialist should know what jQuery is.
The Meat
Now that we’ve established some level of bona fides, my goal is to get into a design discussion, code review, or outright debate, just like we would have as teammates. We might start with something on your resume - “You wrote feature X, how does that work?” Or we’ll start with a theoretical discussion - “Users complain that a page is slow, where do you start?”
At this point, feel free to blatantly manipulate the discussion - “Let me tell you about this awesome feature I wrote…” I want to know what you’ve done, what tradeoffs you made, and what other designs you considered. War stories of horrendous code are great - I want to know what you find ugly, and how you handled it. If you’re in love with a particular architecture pattern, tell me about it.
In the middle of this discussion, we need to talk a bit about “soft factors”. How do you work with your current teammates? Have you ever used strict by-the-books Scrum? How about grab-this-ticket-whenever informal “process”? What do you prefer, and why? What kind of problems have you had with coworkers? Honestly, I don’t have an agenda or a list of questions on these factors, but they are considerations in the hiring decision.
Conclusion
The ultimate goal is to establish three things: Can you deliver working code? Will you make our codebase better? Will you make our team better, by teaching, learning, getting along, and encouraging others?
I’ve outlined one process to establish these factors - but it’s certainly not the only one. If you’ve got a better idea, drop me a line.
]]>You have the standard periodic table of the elements, and a list of words (ie the OSX spell-check dictionary). How can we find the longest word that appears in the table? For example, “NO” starting at #7.
After a bit of thought, I started sketching out an algorithm based on iterating through the table. At each cell, we start trying to build words that start at that cell. For example, at #1 we consider “H”, then “HLi”, etc. For each candidate word, we consult the dictionary: is this a word? (if so, we can consider it for the “longest word” prize). Then we ask a related question: is this a prefix for a word? That is, does any word start with this character sequence? If not, then we can abandon this direction, and return to the current starting cell. After considering each direction from that cell, we can move on to the next one.
It turns out, the interviewer was thinking of a different algorithm. First, sort the dictionary by word length. Starting at the longest word, search through the table for an occurrence of that word. Stop when we’ve found a word.
What followed in the interview was a lot of hand-waving about algorithmic complexity and sketched pseudocode. Running through the table for every single word in the dictionary (until a match is found) seemed very slow to me. With my algorithm, I can consider each candidate word in O(log n) on the size of the dictionary, by binary searching on a sorted dictionary. With his, we’re considering each candidate word against one dictionary word at a time, but doing this for each word in the dictionary. That algorithm will end up something like O(n) on the size of the dictionary. I’m sure there are optimizations possible to both approaches, but sitting in a conference room with a whiteboard and some notebook paper, I was pretty sure that my approach was correct.
A few weeks later, I was still curious about the question: was my approach really faster? To answer the question, I whipped up a prototype implementation in ruby.
Short answer: I was right. Solving the puzzle with his algorithm took ~26 seconds on my machine. My algorithm runs 1000 times in just 5.6 seconds. Neither version is well-optimized, but I think I’ve fairly represented the guts of the slower algorithm.
]]>Here’s the thing: you are not ruthless enough. You are certainly not ruthless enough to your objects, and you probably need to be more ruthless to yourself.
This is true. Be ruthless. But not yet. Let’s see how Chris continues:
Being ruthless to yourself means every time you say “oh, I’ll just open up this internal bit over here…” use that moment to give yourself whatever negative feedback you need to go back and write the correct interface.
When you have an internal bit “over here”, and you have a good idea about what you’re trying to accomplish, then you should be absolutely ruthless. But when you’re first building something new, even if it’s just a new set of classes for a small feature, you should be a mild-mannered, dope-smoking, peace-loving hippy. Toss things out there. Share private internals with wild abandon. Build the messy, cobbled-together version, just to prove that it can work. Then clean it up.
Remember the old aphorism:
Many people skip the second step, and they will encounter the evils that Chris explains. But other people get stuck on the first step, seeking the correct or elegant solution before they have any sense of the real problem space.
This rhythm reoccurs at all scales of the software development process. In minute-to-minute development, we can write a test, get it to pass, then refactor a little. With a small feature or bugfix, often “just do it” is the way to start - but before committing the code, review with a critical eye. At the week-to-week level, you need to mind your application architecture, but you also need to deliver real functionality and make sure that real users are happy with the software.
So be ruthless with code, not with ideas or concepts or half-formed architecture diagrams floating in your head. Produce code with joy, with courage, and (occasionally) with wild abandon - before you ruthlessly shape it into something great.
]]>executing "cd (your deployment directory) && #<Capistrano::Configuration::Namespaces::Namespace:0x107e01908> RAILS_ENV=production db:migrate"
This error occurs with Capistrano 2.9, but does not occur with 2.5 - I’m not going to bother tracking down the exact version that introduced this issue.
]]>