As programmers we’re continually accused of doing a sloppy job. There are countless programs in the wild, crashing, locking up and accidentally writing “I am a fish” a million times over someone’s mid-term essay. The effect? Something like this:
This damn computer and excel r fuckin my life up! Hatin life right now
— MissAlauren (and everyone else at one time or another)
It’s experiences like this that cause people to rant about Microsoft and curse the anonymous programmers who suddenly (yet inevitably) betrayed them. We all know this; it’s burned into our very souls by countless hours of tech support provided to family and friends. Time after time we see that programmers who do quick, sloppy work make other people suffer. And so we try, we try so damn hard not to be like that. We try to be the good programmer who checks every return value and handles every exception.
If we stopped at competent error handling and sufficient testing, all would be well. In truth, we actually go too far and, it has to be said, largely in the wrong direction.
A vast proportion of software at work today is horribly over-engineered for its task. And I’m not talking about the interfaces, about having too many controls or options for the users. These are, indeed, terrible sins but they are the visible ones. The worst of the overengineering goes on under the surface, in the code itself.
You’re Doing It Wrong
Have you ever seen someone using the strategy pattern when they should’ve used a 5 line switch statement? There are a million ways to turn something like this:
case OP_ADD: return a + b;
case OP_SUBTRACT: return a - b;
case OP_MULTIPLY: return a * b;
default: throw new UnknownOperationException(operation, a, b);
… into a hideous, malformed mutant beast like this one, which I haven’t inlined because it’s far too long.
The most insidious cause of overengineering is over-generalizing. We will over-generalize anything given half a chance. Writing code to work with a list of students? Well, we might want to work with teachers and the general public someday, better add a base People class and subclass Student from that. Or Person and then EducationPerson and then Student. Yes, that’s better, right?
Only, now we have three classes to maintain each with their own virtual methods and interfaces and probably split across three different files plus the one we were working in when a one-line dictionary would have been fine.
Perhaps we do it because it’s relaxing to rattle off three classes worth of code without needing to pause and think. It feels productive. It looks solid, bulletproof, professional. We look back on it with a comforting little glow of self-satisfaction – we’re a good programmer, no messy hacks in our code.
Except, this doesn’t make us good programmers. Overengineering like this isn’t making anyone’s lives better; it’s just making our code longer, more difficult to read and work with and more likely to contain or develop bugs. We just made the world a slightly worse place. It lies somewhere between tossing an empty drinks bottle on the street and grand theft auto.
The extra effort caused by our overengineering carries a hefty opportunity cost:
- Less time spent refining the user experience
- Less time spent thinking about the meaningful implications of the feature we’re working on
- Less time available to look for bugs and – with harder-to-read code – more time spent debugging them
Yes, by overengineering the Student class you indirectly ruined MissAlauren’s day.
We have to stop championing each ridiculous feat of overengineering and call it what it is. It’s not ‘future-proof’, because we can’t see the future. It’s not robust, it’s hard to read. Applying a generic solution to a single case isn’t good programming, it’s criminal overengineering because like it or not somebody, somewhere will pay for it.
Don’t Worry, Be Happy
I suspect all the best programmers have already realized this, but they’re not shouting about it loudly enough for everyone else to hear. Paul Graham is completely right when he suggests that succinctness is valuable:
Use the length of the program as an approximation for how much work it is to write. Not the length in characters, of course, but the length in distinct syntactic elements– basically, the size of the parse tree. It may not be quite true that the shortest program is the least work to write, but it’s close enough… look at a program and ask, is there any way to write this that’s shorter?
— Paul Graham, The Hundred Year Language
He’s actually talking about language design here; indeed, in Succinctness is Power he’s careful to note that it’s clearly possible to write a program that’s too succinct. This is because, these days, Paul Graham is more a language designer than a working programmer. Otherwise he might have said:
If you’re about to take a hundred lines to write what you could in ten, stop and ask yourself this: what the fuck?
— Mark, Criminal Overengineering
When I feel tempted to over-generalize or over-engineer a bit of code, it’s often because of fear. Fear that someone will find a really good reason I shouldn’t have done it the easy way. Fear that I’ll have to rewrite the code again. Fear of finding myself on the wrong side of an argument about the merits of the visitor pattern. But fear does not naturally lead us to the most elegant solutions.
Next time you feel the compulsion to write a nice, general solution to a simple case, stop and ask yourself what’s stopping you just writing it the simple, specific, succinct way:
- Am I worried I’ll have to rewrite it?
- Am I worried someone will criticize it or that I’ll look bad?
- Am I worried that it’s not professional enough?
Are any of these true? Then relax. Don’t worry. You worry, you call me, I make you happy.
Just write the code the simple, specific way and then add a short comment, something like: Replace with the Strategy pattern if this gets any bigger.
This is the perfect solution. It’s a great reminder to you next time you come here about what you wanted to do. It shows other programmers on your team that you considered the ‘correct’ way to do it and have a good reason not to do it just yet. It’s very hard to argue with a comment like that, because you’re not arguing about the strategy pattern vs the switch statement, you’re arguing about whether to use the strategy pattern after 3 cases or after 4 cases – not a discussion that can reflect badly on you, in any case.
A few months later you can go back and look at how many of your comments eventually turn into more complicated, engineering code. I’ll bet you it’s not very many. That’s how much time and effort you’ve saved, right there. That’s setting yourself free to pursue the solution and that’s making the world a slightly better place.
Note: Yield Thought has moved to http://yieldthought.com – check there for the latest posts!