I once asked a coworker with an official title of Senior Software Architect, how he makes decisions and gives recommendations on the architecture of our product. “Slowly and carefully”, he replied. He was an honest and hard-working person, a very smart guy, and probably a decent architect. Yet our product’s architecture was terrible, and it was not improving as time went by.
Two deadly sins of software architecture were ubiquitous in our code base
- Everything was tightly coupled with everything else
- Same information was defined in multiple places
In day-to-day work these problems caused lots of hard-to-trace bugs, showing up in seemingly unrelated portions of the code. Changes required touching lots of code, leading to more bugs. And it was very hard to write tests – so tests were not written.
So, what was my colleague doing wrong? The architectural changes he and the team inflicted on the code base were fine - mostly because very little changes were made. There was no opportunity for a serious refactoring effort to improve on the underlying structure. The bad stuff that accumulated over time continued to accumulate. The new features represented good ideas, but were poorly implemented – or, rather, were implemented in much the same way as old ones, creating more bad code.
Looking back, there was a culture problem with the approach of “slowly and carefully, with no objective criteria, teamwork, or testing”. The architect did not write code. He provided suggestions that were implemented by other people. Programmers wrote code per instructions, and did not create tests to fully exercise the added or modified code. The architect did not have the feedback on how well his ideas fit into the code base, and programmers had very vague feedback on how their changes affected the system.
Most of the time, the architect's solutions were fine. He was familiar with the code, has been on the project for a long time, and knew the team well. But since his suggestions were hardly ever challenged, and never tested against competitive ideas, it is impossible to tell whether what he offered was really good - or barely good enough.
Every so often, a developer would complain that the solution suggested by the architect did not work, and it became a point of pride for others to write some “clever” code to fit the square peg (architect’s suggestion) into a round hole (existing code). It is well established that the “clever” code is more bug-prone and harder to maintain, compared to straight-forward and easily readable code.
Sometimes, no amount of cleverness could fit a new elegant idea into the clunky existing code, and then people improvised on their own. Most of the time, those were good, solid solutions, but not always. There also was an illicit feel to the situation, and some developers tried to avoid code reviews in these cases. There were a few scary monstrosities quietly sneaked into the code base as a result.
Splitting the implementation and design work prevents everybody from learning from their successes and mistakes. Having an architect dictate unchallenged solutions creates sub-par performance, tension and funny feelings even on the best-intentioned team.