[intro]Unilateral decision-making can leave lasting damage. Here's why the Abacus Engineering team adopted a consensus-driven approach.[/intro]
“When Stanley came to a set of two open doors, he entered the door on his left.”
So goes Kevan Brighting’s iconic narration in the award-winning experimental video game, The Stanley Parable. As the player, you are more than capable of entering either door, or just walking away from your computer and leaving Stanley in the room facing the set of two open doors forever. And that’s the whole point. The Stanley Parable is a game about choices and free will, a game about decisions.
A decision is coming to a set of two open doors. A point of divergence. It may be multiple choice or comprise indeterminate possibilities, but it demands a direction. Decisions are also stateful. They come with information, specific circumstances, and implications. Based on decision A, new decisions will be needed. Once a decision is made, it is part of the 'state' going forward.
Because decisions have far-reaching consequences, they rarely benefit from being made immediately or in isolation. Some decisions require quick action; others can be delayed for months or even years. But the more urgent a choice seems, the more important it is to have reliable decision-making processes in place.
We associate decision-making with leadership because it is expedient to concentrate responsibility with one person. But deciding when and how decisions are made is rarely considered, and worth reevaluating.
At Abacus, we have found that expedient decision-making structures are less desirable than they sound. They can lead to rash choices, unstable architectures, and team-members who feel their voices go unheard. Instead, we’ve cultivated what I think of as a ‘consensus culture’. This culture hinges on a decision-lifecycle punctuated by proposal, discussion, and consensus. It means even relatively small changes are made publicly and as a group, inviting questions, criticism, and clarification.
Good decisions drive the direction of the product. They remove barriers to productivity and clearly structure ideas. As product-builders, we make many decisions daily. These decisions range from the micro-level to the macro; "what should I name this variable?" or "how should we structure our authentication flow?" But no decision is made in a vacuum. Today, we'll discuss the Abacus philosophy around decision-making. We'll examine strategies for achieving a high standard of consensus on macro-level decisions. And we'll show that doing so benefits down-stream decisions and the developer experience.
Interpretation and Convention
Before anyone writes a line of code, dozens of decisions have already been made. Someone selected a programming language, a file structure, and framework. You may also have some development guidelines, or core values for the team. These are the contextual realities on which we iterate; what a developer might term ‘initial state’. And while there may be associated best practices, there is room for interpretation.
Interpretation is not itself bad. But it consumes time and resources, so we should make sure developers are spending it on the right things. Decisions should stem from a team's values and cascade to their ways of working.
At Abacus, we believe it is better for code to be verbose and explicit than brief and expedient. To extrapolate, we favor clear, readable multi-line functions over chained inline operations. To get even more granular, we oppose abbreviating variable names; in fact we're strict about nomenclature in all things. These may seem like obvious conclusions to draw, but they are the result of much discussion over time. And a quick survey of the codebase would tell you these decisions haven't always had unanimous backing.
Decisions should stem from a team's values and cascade to their ways of working.
This is where the notion of 'consensus' becomes important. Decisions not understood and agreed upon by all team members become development liabilities. If not agreed upon, inconsistency is inevitable, and if not well-understood or well-documented, the reasoning behind a course of action is lost. In simple terms, poorly communicated decisions muddy the process, rather than pointing the way.
So what are the outcomes of consensus-driven decision making?
- We remove ambiguity and increase productivity. A developer doesn't have to think about how to name a variable or nest a Promise chain. It becomes second-nature.
- We standardize code style and structure, thereby improving maintainability. If everyone writes code the same way, it's easier to understand and recognize patterns.
- And finally, we reach more perfect solutions by virtue of group wisdom. We make space for every contributor's opinion; then drive toward agreement. Achieving team accord requires convincing analytical or evidentiary support. Anything less may suggest that the proposal is not quite sound.
Well understood macro-decisions make smaller decisions less contentious. For example, there is rarely much friction when a developer proposes a new linting rule to enforce a particular code style. More dramatic are those proposals of brand new frameworks, architectures, or tools.
Now that we understand the benefits of consensus, let's examine strategies for reaching it.
Defining an Evaluation Process
In an earlier post, I wrote about migrating our React application to TypeScript. But before we decided to actually try writing code in TypeScript, I had to make a case for it to the team. Introducing new technologies to solve problems is always tempting. The 'new-kid-on-the-block' syndrome of the fast moving web dev community is real. TypeScript is a fantastic technology, but there are lots of ways we could have fumbled the adoption process.
It's never a bad idea to push back on an idea, even one with wide support and a clear value proposition. Consensus isn't about blind acceptance; it's about distilling the best and most clear manifest from any given proposal.
It's helpful to standardize as much of this process as possible. At Abacus we use Kellan Elliott-McCrea’s excellent 7 point evaluation for new technologies, but the exact approach is less important. It could be a 4 point evaluation, or a flowchart. The point is to have common questions that your team knows to ask and answer. For example, we ask:
- What problem are we trying to solve?
- How could we solve the problem with our current tech stack?
- What new costs will we incur with the new technology?
- What about our current stack makes solving this problem difficult or non-cost-effective?
- If the new technology replaces a current tool, are we committed to moving everything to this new technology in the future? Or are we proliferating new technologies for specific tasks?
- Who do we know and trust who uses this tech? What do we know about their experience with it?
- What's a low risk way to get started?
Test Against the Team
This evaluation goes a long way towards supporting a successful decision. Proposers aren't expected to fill it all out in advance either; it can be part of the discussion process. The evaluation itself can be iterated upon, to the end of a better understanding across the team.
"What's a low risk way to get started?" was a particularly important question when it came to the TypeScript proposal. At first, I suggested starting with our database interaction layer, but the team judged that too risky. It was through iteration that we decided to target the React app. Another outcome was the decision to automate some TypeScript interface generation, which was a huge help.
After everyone was satisfied with the evaluation, I converted an example component and we moved into a code review process. Everyone had a chance to ask questions about TypeScript-specific code styles, and we landed on conventions we could all agree with.
Of course, things don’t always come together with ease. As I alluded to earlier, unsound proposals will come up against more resistance, and this is by design.
Unsuccessful Bids for Consensus
At Abacus today, we organize our code into a few large buckets by 'layer', i.e. API, helpers, and database abstraction. This is fine for a small team, but it will grow harder to maintain when our engineering headcount hits the double-digits. Last fall, I proposed reorganizing our file structure by feature module. I was excited about the idea, but it got sent back to the drawing board. And for good reason.
"I'm concerned that the proposal is very much at risk of having wildly diverging results based on different perspectives."
Another engineer pointed out that there weren’t clear lines of delineation between features. What constitutes a 'major' feature? A 'minor' feature? Even more troublesome, components at the intersection of two features would be ambiguous. Does 'accounting tags' belong in the 'accounting' feature? Or the 'expense tags' feature?
Consensus of rejection is still consensus, and if you trust the process, it will protect your team from poor strategic choices.
It was difficult to reconcile this with our values. We shelved the proposal, and instead began discussing more objective, dependency-driven structures.
These sorts of disappointments are a natural outcome of demanding more team buy-in. While no one likes to have their work thrown out, it's important to think of these not as failures but as successes. Consensus of rejection is still consensus, and if you trust the process, it will protect your team from poor strategic choices.
In a consensus-driven culture, failure to reach agreement signals one of two things: either we are failing to communicate, or the idea itself is faulty.
Defining a Consensus Culture
It is worth noting that we have the advantage of a very small team. It is easier and faster to achieve consensus with five people than with fifty. How this philosophy will scale is a problem we have yet to really contend with. Perhaps in the future, we will have to more clearly delineate between teams, creating microcosmic consensus cultures around specific functional groups. Or perhaps we will need to more clearly stratify the process around different classes of decisions. These are decisions we have not yet come to.
Large decision-making structures tend toward the autocratic. Even democracies end up pulling subsets of the population into policies they don't fully support or understand. Luckily, no product team is operating at that level. In whatever way you can, holding team decisions to a very high standard of consensus will unlock a host of benefits. Strict conventions will be easier to enforce. Technical debt will be cheaper to pay off. And you will empower your team by committing to a shared holistic vision of the product.
No company sets out to be a dictatorship. But many companies never explicitly define their strategic philosophy. What is noteworthy about the decision process at Abacus is not our step-by-step method but our commitment to team buy-in and conventionality.
Unlike in The Stanley Parable, there is no narrator telling us what to do. Instead, our ‘narrator’ is a never-ending stream of data from every direction at once, the opinions of our friends and co-workers, a quick web search, a gut feeling. Our process helps us filter out the noise and move forward in a unified manner. Consensus is not the absence of disagreement; it is the conclusion of shared understanding.