Software development is all about trade-offs. I was watching Dan North‘s fantastic presentation, Decisions, decisions, in which he talks about the fact that every decision is a trade-off. This got me thinking about my own decision making process. Every decision we make has its pros and cons. But sometime we seem to overstate the advantages while understating the disadvantages. I am guilty of this myself. In this blog post I’ll explain some of the mistakes I’ve made while making a decision and provide some tips on how to avoid them.
Common Mistakes in the decision making process
In this section I’ll list some of the flaws that I’ve recognized in my own decision making process at one point or another.
The Any Benefit approach
I first read about the Any Benefit mind set in Cal Newport’s book Deep Work (although I have been guilty of it way before that). This way of thinking leads to justifying a decision with a possible small benefit, ignoring all the negatives. I have been guilty of the any benefit approach many times. It’s easy to read an article on the web and immediately jump on the new trend bandwagon, without proper thought. How many times you wanted to immediately update to the newest major version of a framework, but that pesky senior said that it’s too risky? Or use an alpha release of this new library because it’s cool and then have to work around breaking changes. I’m not saying that this is the wrong decision, but that you should carefully analyze the trade-offs. Do the pros outweigh the cons?
Rich Hickey stated that “Programmers know the benefits of everything and the trade-offs of nothing“. This is because we focus too much on ourselves. But we should focus more on the quality of software, its maintainability and its fit for purpose. This is why I try to think of the main pros and cons of every important decision that I make. Before I go proposing a new solution, I try to play the devil’s advocate and think about its main disadvantages. This is a great way to show other people that you have thought this through. So, before making your next decision, think about the main reason why you shouldn’t do it.
Relying too much on Best Practices
Some of the the today’s antipatterns have been yesterday’s best practices. Singleton and lazy loading come to mind, but there are many others. The problem with best practices is that they are applicable only in a given context. The Cynefin framework suggests that best practices are a good strategy only in the obvious domain, where the relationship between cause and effect is clear. The problem is that we aren’t always in the obvious domain, so we should change our decision making strategy depending on the domain. Let’s take, for example, the Complicated Domain. This is the domain of “known unknowns”. A good decision making strategy in this domain is Sense – Analyze – Respond. This means that we should assess the facts, analyze the situation and respond by following good practices. This decision strategy relies on the fact that there might be many good options and a best practice does not exist.
I’ve been guilty of over relying on best practices too. Best practices like pair programming, automated testing, code review have their own downsides. I think they are valuable in most contexts, but this doesn’t undermine the fact that you should think about your context before applying them. Sometimes I don’t know the answer to questions like “Why do we use X and not Y?”, even though I helped make that decision. At the time, the choice seemed obvious, so I didn’t really thought it through. Even if it’s the right choice, you should still have the right arguments. Answers like “this is a best practice” can seem random and don’t usually convince people.
Ignoring Cognitive Biases
Although we would like to think that we make only rational decisions, cognitive biases prove otherwise. A cognitive bias is a flaw in our judgment that can lead to irrational decisions. Let’s take a look at two different types of biases: decision making biases and social biases.
Decision making biases
The Confirmation bias is the tendency to interpret information in a way that confirms our preconceptions. As an example, if you want to switch to microservices and do a google search on the advantages of microservices architecture, you’ll find plenty of compelling reasons. But before making the move, you should also search for its drawbacks.
Another bias that you should be aware of is loss aversion. This bias states that people feel loses more deeply than gains of the same value. This is also related to the sunk cost effect – we invest more in a decision so we don’t lose what we have already invested in it. Basically, we ignore the negative outcomes of a choice because of our fear to lose what we have already invested in that decision. It’s always better to try to be objective and cut your loses early.
I fell victim to this bias while I was researching how to put coarse grained locks on NServiceBus Saga instances when using NHibernate persistence. After several hours of searching how to use coarse grained locks by using NHibernate listeners and checking if an entity is dirty, I noticed something in the NServiceBus documentation. They were stating that pessimistic locking is enabled by default on saga instances. So, if you use the default settings, you can’t get into an inconsistent state. But, after I invested so much time in researching a general solution to the NHibernate coarse grained lock problem, I was almost inclined to use this alternative, more complex solution with no extra benefits. Fortunately, I noticed that I was a victim of the sunk cost fallacy and deleted the extra code.
Social biases can also play an important role in our decision making process. In Your Code as a Crime Scene, Adam Thornhill mentions two social biases that might lead to bad decisions: Pluralistic Ignorance and Inferring the popularity of an opinion based on its familiarity.
Pluralistic ignorance happens when everyone in a group privately rejects the norm, but assume that the majority accepts it, so they go along with it. An example will probably better explain this bias. Let’s say that you have a large suite of brittle broad stack tests that fail often. These tests rarely catch production bugs and usually fail because of the test code. Each team member knows that the return on investment for these tests has become negative. But, because they think that everyone else finds these tests valuable, they accept the norm and carry on fixing the tests, without solving the real problem. This is also an example of the sunk cost effect – because of the effort invested in implementing the tests, you don’t want to delete them.
Inferring the popularity of an opinion based on its familiarity is another common social bias. If someone in the team keeps repeating a strong opinion, we come to think that opinion is more common than it actually is. Most of us work in teams with people that have strong opinions. This is a good thing, as long as we do our own research and support our decisions with data. Your own experience might lead you to make the wrong decision. If you’ve been working in the same context for a long time, groupthink might lead to everyone having the same opinions. This can make the team members think that their opinions are more wide spread than they are, because of their familiarity inside the team.
Tips for improving your decision making process
After seeing these common mistakes, what can we do to make better decisions? Here are some tips that I find useful:
- Be aware of the most common cognitive biases. Knowing them is the first step in overcoming them.
- Support your decisions with data. Always do your research. Make pros and cons lists. Don’t base decisions only on instinct. Data is much harder to argue against.
- Play the devil’s advocate. Before making a decision, play the devil’s advocate. Think of the top three reasons why your idea won’t work.
- Know your context. Be aware of context. The consultant’s answer – “it depends” – is many times the correct answer. A good decision in one context can become a bad decision when applied in a different context. The Cynefin framework is a good place to start if you want to make sense of your context. It can help you use the correct decision making process, based on your domain.
- Use guiding principles. Every time you make a decision you should consider the high level principles that govern the product. Examples of these are business drivers or architectural principles. You might make a different decision if you’re optimizing for time to market than if you’re optimizing for maintainability.
What are the most common flaws in your decision making process and how do you overcome them?