While (procrastinating) cleaning off old files on my computer I found this small essay I wrote last spring that was never published. This is an introduction to this talk online.
You want your codebase to last right? It’s one thing to dash off some throwaway code to solve an immediate problem, but quite another to craft a codebase that can evolve over time to handle new requirements. A long lived codebase must change as the requirements change, the underlying platform evolves, and the expectations of the users increase.
I’ve learned quite a bit about this issue of long lived codebases as the principal developer of an open source tool named StructureMap for Dependency Injection and Inversion of Control with .Net. The original release of StructureMap happened all the way back in 2004, but development has continued ever since. At this point, there is very little code remaining from the initial release, and the current functionality set bears little resemblance to the rudimentary features of the 2004 timeframe.
The biggest source of StructureMap changes has been to reduce the user’s friction in configuring the tool. Our expectations for framework usability have changed dramatically over the past five years. In 2004 we were enamored with using Xml configuration as a way to create dynamicism and extensibility in our software systems. StructureMap circa 2004 could only work when it was fed copious amounts of repetitive, error prone Xml configuration. Today, the “Xml Hell” style of framework configuration is unacceptable to most users. Instead, I’ve had to streamline the Xml configuration and introduce new a programmatic Domain Specific Language for configuration that is easier to use. Going even farther, many open source tools in .Net are adopting “Convention over Configuration” approaches to further reduce the mechanical effort of configuration.
My long lived system has had to change over time, and it’s very likely that yours will too. It turns out that the design decisions we make, the way that we write our code, and the manner in which we express and organize our unit tests will have a substantial impact on the efficiency of changing a codebase and the effective lifespan of that codebase to keep up with those changing needs.
At one point last year my continuing work on StructureMap was being hindered by the internal structure. I finally realized that the key abstractions inside StructureMap no longer matched the newer requirements. My progress on StructureMap improved dramatically after I first restructured the design to more appropriate abstractions.
In many cases, I have not been able to complete changes until I refactored to clean up code duplication or decrease harmful coupling by more closely following the “Law of Demeter.” The point being that “design in the small” is significantly important to the continuing health of a codebase.
Lastly, I’ve seen observed that Test Driven Development is a fantastic tool we can use to support the future evolution of our systems – except when it’s applied badly. One of my biggest challenges in evolving StructureMap over the years has been brittle unit tests that were too tightly coupled to the original implementation and automated tests that were very hard to understand and troubleshoot when a code change broke those tests years after the original writing of the test. My negative experiences with unit tests in StructureMap have led me to finally appreciate the benefits of Behavior Driven Development (BDD). I believe that adopting the BDD style of expressing specifications and organizing tests around features and behaviors has improved my development experience.