Yesterday I attended a NEJUG meeting presented by Alexander von Zitzewitz called "Java Architecture Management or how to avoid the structural erosion of your code base". It was a pretty entertaining talk where he reviewed a number of scenarios that lead to the creation of unmaintainable complexity. He confirmed a lot of what I already knew, but nicely formulated and explained. He argued that no documentation is going to help you, it's prohibitively expensive to create and always outdated by the latest code. A general theme was that you need to have an architecture that slices horizontally (architectural layers) as well as vertically (functional slices). Think of vertical slices as your java package naming and how packages end up in a jar. This way you end up with a grid of subsystems. Next you can start analyzing the dependencies between the subsystems. He described a number of code metrics that can be used to quantify the number of architecture violations in the code. He came up with a list of 6 rules that one should follow to write maintainable code:
Rule 1: Define a cycle free logical architecture down to the level of subsystems and a strict and consistent package naming convention
Rule 2: Do not allow cyclic dependencies between different packages
Rule 3: Keep the relative ACD low (< 7% for 500 compilation units, NCCD < 6)
Rule 4: Limit the size of Java files (700 LOC is a reasonable value)
Rule 5: Limit the cyclomatic complexity of methods (e.g. 15)
Rule 6: Limit the size of a Java package (e.g. less than 50 types)
To break a dependency cycle you can should introduce an interface; this reverses the dependency. It is that simple. This leads to the observation that "good code" can be recognized using certain metrics (see also a white paper by Robert Martin. Good code should have a certain ration of abstractness (classes vs interfaces), and if more code depends on your code, you will likely need more interfaces. The two extremes of this "metric distance" from the optimal ratio, are:
zone of uselessness which means a high number of interfaces and few things depending on you. This happens when coders plan for complexity that never happened.
zone of pain which means very few interfaces and lots of code depends on you. So each change in your class can break lots of other things elsewhere. This usually happens when people cut corners to make a deadline.
The meeting really confirmed my own experiences, that the process of creating good code is simple. You just have to follow it *all the f***ing time* (much like Bryan Liles' TATFT framework). Bad code is like a black hole attracting other bad code. So simply don't put black holes in your code! If your system gets large, make sure you use tools to monitor that none of these simple rules are broken. Hook them up to the nightly build and have any violations be fixed the very next day. Note that you need an architecture before you can use any of these tools!