Warning Signs
Haven’t kvetched in a while, but then I haven’t had anything to kvetch about. (This will change once I start judging IFComp ’09 next week.)
After doing some accidental research, I’ve come across a set of new anti-patterns that are bothering me:
- Design-by-ferret
-
We’ve all come across systems like this before. The older section follows one convention, and the newer section follows a different and completely incongruous convention.
These architectural chimeras are the result of a lack of systemwide vision: the senior engineer is unwilling (or unable) to enforce a unified vision on the project so his bored subordinates adopt the latest sparkling technology no matter how inapposite. You can tell the age of such a project by cutting through the various layers: this was implemented when Spring was in style, that was written when Rails was the rage.
- Design-by-anecdote
-
Almost the exact opposite of the above: sticking to an outdated model because a single incident found that a standard technology was of dubious quality.
- Type-style blending
-
Some people prefer to have the compiler catch their errors early; others prefer to have the flexibility of a dynamically typed language. Who can confidently say which is better?
I can. There is no reasonable justification for leaving potential errors to be caught at the time of execution, and a properly designed system will make sure types represent the semantic usage of the variables.
Religious arguments aside, the one thing everyone should agree on is that while they both have their trade-offs, no good can result when the two models are blended in a single system: you get the instability of a dynamically typed language with all the overhead of a statically typed one.
Type-style blending is another unpleasant byproduct of an architectural chimera.
- Over-reliance on legacy
-
When nobody really understands the code, the oldest sections take on a formidable sacrosanctity: they are so important and so convoluted that no one dare touch them lest the entire system irrevocably collapse. Newer features have to link into the legacy core even if they could be written efficiently with more modern techniques.
I once had to inject code before every call of a core function rather than adding it to the core function itself because the core had been written by developers who were no longer with the company.
- Same signature, duplicate code
-
This fallout from cargo-cult programming is from misunderstanding overloaded functions. The functions have similar implementation but only differ on the number or types of arguments. Rather than deem the most-specific variant as authoritative, the same code with only minor changes is cut-and-pasted. That’s the fast track to misery right there.
- Trivial one-use methods
-
The extreme opposite: simple operations that are wrapped up in opaque methods just because you can. Usually the result of overaggressive refactoring, but can also result from a misguided attempt to make the code more literate. Trust me: x + y is much more readable than addIntegers(x, y).
I sometimes refer to this as Hilfinger’s Syndrome, but usually get blank stares when I say that.
- Management-by-ferret
-
This is when a manager (usually an over-promoted engineer) grafts a development process onto a project that was built without one. The graft is usually one from the XP family, and it is more often than not only tokenisticly followed. It’s an instance of the general problem of cargo-cult management: adopting a development process because of the belief that going through the ritual will somehow magically make development easier.
- Untestable requirements
-
Untestable requirements lead to unintelligible code which leads to unfulfilling results. Like logical inference, the consequences are disturbing: even horrifically bad code satisfies untestable requirements (noted succinctly by William Wimberly).
- TDD on untestable code
-
You can’t write unit tests for code that was not designed to be tested: code that is tightly coupled, is dependent on external resources, or in general is opaque and intractable. If the code is untestable, safe refactoring is impossible.
- Unit tests that test the entire system
-
These are not unit tests, and calling them such will not magically make them unit tests. A unit test by its very name tests a unit of code: a method, a single path through a module, a simple state transition.
- Rockstar developers
-
This isn’t really an anti-pattern; more like an annoying trend. I have seen far too many job requirements containing the phrase “rockstar developers only.”
Do you really want rockstars? Do you really want prima donnas that will storm out if you do not have a bowl of green M&Ms on their desk every morning? Rockstar developers have rockstar egos and rockstar demands. When they become irreplaceable, you end up working for them.
I’m cranky and demanding. Does that automatically make me a rockstar developer?
