One of the most talked about and reviled architectural anti-patterns is the big ball of mud. Characterized by a lack of structure and haphazard design, changes become more and more expensive over time. Teams don't set out to create messy designs, it usually happens because lack of focus and direction.
Team turnover also poses a problem. As new leadership comes on to the team, rather than keeping status quo, the new leaders try to put their own design stamp on the codebase. I see this most often in database designs, where personal preference in naming schemes leads to schemas with 11 different styles of naming tables.
As more and more opinions weave their way into the codebase, design fractures and eventually developers simply give up.
Complexity through overengineering
On the other end of the spectrum is overengineering. Designing complicated abstractions and extraneous layers where there is more plumbing and infrastructure than code that provides true business value. We're presented with the opposite problem now: Changes are costly because every modification requires touching code in many different files.
Incremental and iterative architecture
There is a third alternative: evolutionary architecture. Start with the simplest architecture that could possibly work, verify it works, and leave it alone.
As you build more and more features in the system, patterns of usage and concepts should emerge. At a critical point, usually around a dozen examples, we apply a new design to our system and migrate those existing features to the new design.
This is a critical time in the system: It is undergoing a metamorphosis from one architecture to another. At this point, it's easy to try and tweak our design as we migrate, but that way lies the path to the big ball of mud. Only when our architecture has been completely migrated can we iterate on that design. At most two versions of a design concept can exist in the system at any given time, and a plan must be in place to whittle that number to one.
It's only through applying an architecture across an entire codebase can we have our design truly verified. The next iteration becomes better overall as well, since we have more examples to look at for what our next step should be.
Evolutionary architecture requires patience, but it allows us to start with the simplest design that can work, shipping our software faster. Only when the limitations of the existing design become apparent and obvious do we move on to our next iterative design, ensuring the design and architecture in place are always exactly what is needed and nothing more.
Sign up for MIS Asia eNewsletters.