I’m getting started (after I finish 2 others that are already late) on a new article roughly titled “What does ‘Good’ Design Mean?” My whole thesis for the paper is really a fullblown refutation of the “quality doesn’t matter” folks.
I’ve moved around professionally quite a bit the last 6-7 years, and I’ve gotten to work in quite a few different codebases. What I’ve found is that some of those codebases were very easy to work with, and we were able to add or change functionality in those systems with great success and minimal friction. Other codebases clearly displayed what Uncle Bob calls “Code Viscosity.” Changing the code was hard and laborious. Verifying the code changes was nasty. Progress was fleeting. I’ve even seen the exact same team of people succeed with one codebase and grind to a halt with another codebase. My point here is that I definitely believe that there are certain “qualities” of a system’s design that either hinder or accelerate the progress of the system. Software quality isn’t strictly an end in and of itself, it’s also the means towards delivering value to the client. I’m going to define “quality” in software code and design as the attributes of a software design and code that enable us developers to be efficient.
In rough order of severity, here are the things that I’ve found greatly retarded my teams’ progress. I’m not counting people issues or artificial barriers from stupid processes or overly bureaucratic configuration management. I’m also not counting requirements churn, but I’d argue that eliminating the problems below would do a lot to mitigate the damage done by requirements churn.
- God/Blob classes and bloated methods. Four years later, I still think that long methods and classes are evil. We talk about patterns and SOLID principles and TDD and DSL’s and whatnot, but the single biggest problem in the code I’ve seen is the usage of very messy procedural code. Big classes that do too many unrelated things are also a symptom of procedural thinking. I should qualify this by saying that procedural programming doesn’t automatically mean that it’s bad, but really bad procedural code seems to be the norm out there in the wild. The structured programming guys of yore wouldn’t be happy with a lot of the code I’ve seen either.
- Poorly organized code. It’s always going to talk a bit to understand a code structure that’s unusual or new to you, but as long as a codebase is consistent in its internal rules, you’re going to be fine eventually. A codebase without internal consistency is a disaster.
- Over engineering. Bad abstractions that flat out don’t work or add extra weight to writing new code and in understanding the system as is.
- Inadequate or just flat out no build automation. You can’t work on code until you can make it run on your box and actually test it. Chad says it best here. You can’t efficiently extend a system either if it takes days or even weeks just to migrate the code from development to testing to production.
- Tight coupling between disparate elements of the code structure. I.e., changing one class inevitably breaks something else, or the fact that you can’t simply work in one area of the code at a time. I want to work with business rules without wading through thread management code or UI code at the same time. That isn’t just a testability issue, it’s also an understandability problem. There’s also a significant tax to tightly coupling your application’s behavior to infrastructure concerns.
- Code that is hard to test. Sometimes it’s just a side effect of the problem domain, other times it’s caused by poor cohesion and coupling qualities. Sometimes the problem is just that it takes way too much work just to set up the system to test the scenario you’re interested in at that very moment. Regardless of how you feel about or use TDD/BDD or even just plain unit testing, you have to have some sort of feedback mechanism to know that what you did actually worked without unforeseen side effects – even if it’s just an easy way to stuff some data into the database and hit F5 to look at the UI.
- Slow builds, slow compile times, and slow tests. On my deathbed I really don’t want to tally up the time I’ve spent waiting for a build to complete.
- Crazy-ass database schemas. See #6. I’ve been absolutely hamstrung by database models that were almost impossibly to load data into without using the existing UI.
- Putting important behavior in unusual or out of the way places. Business logic in a database trigger. Doing important things in a static initializer. Programming in configuration files. I don’t really need to explain this one, do I?
- No automated tests to know when you’ve broken existing code. Related to #6 obviously, but I’m still calling it a separate problem. The presence of #10 virtually guarantees the presence of #6 (an inconvenient fact for the “TAD” folks)
- Duplication. Any time changing one part of the code almost always requires a second or third change in another area of the code, you’re in for trouble. Duplication hinders change.
- Really nasty inheritance chains in the class structure. There’s no possible way to make coupling any tighter than an inheritance relationship.
- Crazy attention paid to micro optimizations that cause more harm in code obfuscation than value in performance gains. You know the line about premature optimization and the root of all evil. My experience has been consistent with that old saying.
- Bad or misleading variable names. I think most developers of any experience level do fine with this, but I did hit a system a couple years back that killed me with really bad variable names. Bad variables names + very long methods and big classes is the kiss of death.
Okay, by my count, software design can negatively or positively impact numbers: 1, 2, 3 (usually a result of a designer having more intelligence and creativity than wisdom or experience. See my “second system syndrome” for an example), 5, 6, 7 (definitely related to design decisions), 8, 9, 11, and 12 are completely a matter of the quality of the system’s design. # 4 is impacted somewhat by the design. #10 can be made much cheaper with a good design, and far too expensive to do with a poor design. #13 is both a problem in its outcome and an opportunity cost as it usually results in wasted effort. #14 is related to having quality coding standards.
Note that I didn’t mention anything about documentation. I don’t think that good code requires much documentation (“Much” > none, and I think that a functioning build script and well written unit tests do a lot of good for documenting a system anyway), and all the documentation in the world won’t help with very bad code.
I have to save the real content for the actual article, so I’m more interested now in *your* comments. I’ll write the obvious follow up in a couple days.