Nerdy tidbits from my life as a software engineer

Tuesday, November 25, 2008

The Good Side of Bad Code

How many times have you inherited some crappy code?  It seems to be the norm rather than the exception.  Systems always seem to be poorly written, and the incomprehensible thing is you can’t figure out why that is.  Can it really be that so many people around the world don’t know how to write robust, reliable, well-tested software?  Don’t people go to college and learn the basic principles of software engineering?  Haven’t they gained enough experience to learn what to do and what to avoid?

I’ve pondered these questions many times over the years.  Maybe everything is a daily WTF.  Maybe there’s no such thing as good programmers – or maybe you just never seem to meet them.  But I think there’s a more logical conclusion than this.  Obviously, there are a fair number of morons in this field who are too ignorant to write good code, but there are also plenty of talented people who write crappy systems.  What’s up with that?

Well, I think there are two main reasons.  The first is preventable, and that is…to employ a good process.  Yes, all those naive project managers who never took software engineering and are more interested in meeting deadlines than producing quality software are largely responsible for the garbage that runs our world.  There are lots of instances where good programmers write crappy code because their bosses don’t understand what “unit testing” or “code coverage” means (or don’t allow them for strange reasons).  And there are plenty of people who skip design documents because, ”nobody’s going to read them anyways.”  And then there are folks who have never heard of use-cases or functional specifications.  These people are all, collectively, responsible for 90% of every bad piece of software ever written.

But there are 10% of systems out there that are written by good engineers with strong skills.  How does that happen?  I think the answer is that, despite everybody’s best intentions, often times the weaknesses in a system aren’t apparent until the whole thing has been put together.  And by the time it’s clear that your design is flawed, it’s too late to go back to the drawing board.  You can tweak your process all you want.  You can employ risk mitigation strategies like prototyping.  You can (and should) practice test-driven development.  And these techniques will help – a lot in fact - but they will not eliminate all of your problems.  Sometimes, the weakest link cannot be discovered until the entire chain has been put together.  And by that point, the most cost-effective solution is usually to let the beast linger around until it’s no longer necessary.  How many times have you heard, “Well, it seemed like a good idea at the time!

But here’s the good news: a crappy system often gives you the knowledge necessary to refactor it so that it’s no longer crappy.  And now that you know what works and what doesn’t work, you’ll be far less likely to make the same mistake twice.  Now, with the benefit of hindsight and a few years of painful maintenance and testing behind you, you can revisit the project and give yourself an opportunity to do it properly.  So the problem was your object-relational-mapping?  That’s awesome!  Now you know what was wrong with your original design!  Now you can make it work the way you intended it to work the first time!

(It’s just too bad you weren’t able to figure that out two years ago.  Oh well.)

The problem of course is that this requires everybody to admit that they made some mistakes.  And it also requires everybody to agree that it’s worth the investment to fix the problem before it gets too large.  And that’s usually not so easy to do.  But if you ever need to look on the bright side of bad code, maybe this will lift your spirits.