I tend to try to make parallels between my work and whatever else is going on in my life, even when they’re inappropriate. Case in point: I got married at the end of July (yay!) which meant that my wife and I spent a large percentage of our free time for the prior year or so planning the wedding (d’oh!). As anyone who’s gone through that knows, it’s kind of a never-ending list of things to do and, more specifically, of decisions to be made. Not only that, but each decision is about something that’s costing you hundreds or even thousands of dollars. Normally, when making decisions involving large sums of money, I tend to be pretty careful; I read product reviews online, I comparison shop, etc. But that all takes time, and when you have to make several such decisions per day for months and months, you just can’t put that level of effort into each and every decision. In the end, if you don’t want to go crazy you have to just accept that you’re going to make some mistakes, and that some of those decisions will turn out to be sub-optimal.
So what in the world does that have to do with software development? Perhaps this is a stretch, but as I reflected on that experience I started to realize that most development work is a similar never-ending series of decisions, each of which could potentially have a large long-term impact if it’s done wrong, or at least could require a large amount of effort to fix. What do I name this method? Where do I put this class? Do I store this value as two booleans or a tri-state enum? Do I add this extra field to the object, or should it logically be its own object/row in the database? Do I mock this out in testing, or use the production version of the class? Do I use a factory to build this or just a normal constructor? Do I make this method public, protected, or private? Is this class properly decomposed? Should this class really be split into multiple pieces? On and on it goes. Even if all those decisions are relatively neutral in the sense that you can make any of them work right now, each one of those decisions can have a large downstream impact on your ability to test, maintain, extend, and understand the code base. One bad decision could literally be the difference between making a future feature take 15 minutes and making it take 15 hours, or between making debugging easy or turning it into a multi-day expedition.
One interesting complication with software, though, is that the time to make the decision itself is a cost, and with software development time is usually everything (assuming you take correctness for granted, i.e. that you will spend whatever time is necessary to test and debug and ensure correctness). So even though the name of a method is critical to people’s understanding of it, their ability to read code that calls it and their ability to discover it, how much time do you spend making the decision? Is the name that you come up with after fifteen minutes of deliberation likely to, on average, save you fifteen minutes of development time compared to the name you came up with right away? Well, it depends on how bad your initial name is, and how much better the other name could be.
As developers, we all make hundreds of those meta-decisions a day: do I make a snap judgment and keep going, or do I spend a bunch of time thinking or talking or prototyping? It’s almost like you need to draw a three-dimensional curve, plotting, for each amount of time that could be spent deliberating, what the the probability is for different kinds of time savings over, say, the next five years: if we spend 15 minutes on this, there’s a 5% chance we’ll come up with a decision that’s worse by 1 hour over five years and a 50% chance we’ll find something that’s 1 hour better and a 45% chance we’ll come up with something exactly as good, but if we spend 30 minutes on it we now have a 60% chance of saving 1 hour . . . then all you have to do is do a little calculus to find the amount of deliberation time that maximizes the expected savings – that deliberation time . . . and then throw in some extra variables to account for the potential that your probability graph itself has a certain probability of being wrong in certain ways . . . not to mention a discount factor on the value of time now versus the value of your time five years from now to represent the importance of shipping on time . . .
Clearly, I’ve thought about this way too much.
The same reasoning applies to decisions beyond just the “do we spend time talking about this” decision. Do I just hack this in? How much will it cost us down the line? Do I refactor this? Will the time spent refactoring be paid back in the long run?
By now, you’re probably wondering where I’m going with this. Honestly, as much as I wish I did, I don’t have an answer as to how to determine when to spend the time and when to just hack away. My point is merely that it’s a subject worth thinking about. Most of us have an internal dial that’s set one direction or the other, where we’re either biased to just hack away or we’re biased to deliberate a ton and refactor a ton and try to make everything perfect. And I think much of the time we take for granted that our dial is set correctly, and that we’re making the right tradeoffs. If we’re just hacking away, it’s obviously because we just need to get this done and this is probably close enough to optimal anyway, so why bother talking about it? And if we’re refactoring or talking about how to do things, it’s because it’s critical to contain complexity and avoid technical debt so it’s really important to get things as right as we can.
Every so often, it’s good to do a little meta-cognition and ask yourself if you have the dial cranked to the appropriate place given your project and all the other constraints. Are you bounding ahead just because you want to, when some extra time and discussion would lead to a better decision? Or are you overthinking decisions that are so speculative or so inconsequential that you should just make the decision and move on?