Those who cannot remember the past are condemned to repeat it — George Santayana
Justification of poor programming practices often stems from rationalizing one’s actions through the lens of one’s experiences. Long after the original architectural decisions and implementation, the problems start to manifest themselves. The tasks often should be small but end up as large tasks because of the difficulty to modify the implementation and architecture of the system.
At great expense, lessons are often re-invented or not headed at all. This often creates a case of cognitive dissonance in individuals and teams: they see themselves as competent or good developers and yet they do not head the lessons of the past.
A Static Harangue
Several lessons have been learned over the last several decades in regards to application coupling, but few are more lackadaisically transgressed than those learned about static methods. Those who do not head these lessons essentially use static methods as class namespaced functions that are used throughout the application from anywhere.
It can be easier, in some respects, to program in this manner because it’s often procedural in nature and similar to things (functions) that are learned early in one’s programming career. Therefore their usage generally requires a lower level of skill. However, they tightly couple an application and make it almost wholly untestable because the dependencies are not explicitly declared and they can be used anywhere from anywhere.
So if you want to trade maintainability to lower the skill required to build an application, it might be the choice for you. However, you’ll be adding significant technical debt by doing so. It is a well-known principle that “the only way to go fast is to go well.” Therefore, you should only use statics to declare true global constants (not global variables, mind you) and for assisting with object creation. Anything else is asking for trouble.
The Service Folly
However, the lessons on coupling are often not only forgotten when implementing concretions but also conceptually when architecting applications and system. Way back in the early nineties, the Gang of Four (GoF) authored the authoritative book, Design Patterns. Therein they made the statement that “tight coupling leads to monolithic systems”. One thing that we know about human nature, and developers, in particular, is that they tend to be faddists: they chase whatever seems to be the coolest or shiniest. Indeed, this happened when service-oriented architecture (SOA) came on the scene. Teams indiscriminately rearchitected their applications so that their boundaries were separated by a network ostensibly to avoid a monolithic architecture. However, they did not consider what actually made some monolithic applications unmaintainable—tight coupling.
What many didn’t see was that the real problem was a lack of layers or real boundaries. Twenty years after the GoF, Robert C. Martin, in his book Clean Architecture made the point that “microservices can still be coupled [to each other] by shared resources [or] by the data they share.” Furthermore, Martin stated that this is the sort of architectural decision that should be delayed as long as possible since microservices in and of themselves do not define an architecture. Microservices don’t fix coupling—good object-oriented design does.
Microservicing is just one of many decoupling strategies (along with object-oriented techniques, packages, etc.) though it is the hardest and most definite boundary. Martin Fowler in his work, Patterns of Enterprise Application Architecture, exhorted his readers to “limit your distribution boundaries as much as possible since they are expensive”. Microservicing is the sort of architectural decision that Martin believes should be delayed as long as possible and used only when you have questioned decoupling in this manner thoroughly. Otherwise, “services that simply separate application behaviors are little more than expensive function calls” (Martin).
The Insistent Framework
Lastly, another case of disregarding lessons, foolish over-reliance, and disregarding the dangers of coupling is an infatuation with frameworks. Frameworks are often used as the high-level architecture that defines an application. According to some of Martin’s recent writings, the high-level directory structure and architecture of frameworks does not communicate the intent of the application, and he contends that an application’s architecture should be centered around Use Cases.
Architectures are not (or should not) be about frameworks. Architectures should not be supplied by frameworks. Frameworks are tools to be used, not architectures to be conformed to. If your architecture is based on frameworks, then it cannot be based on your use cases.
While not communicating an application’s intent through its high-level directory structure may not be wholly wrong in and of itself, what is wrong is tight coupling to the framework’s utilities and structural classes. Doing so makes your application less testable, portable, and flexible. However, this line of thinking isn’t new. It’s mainly a rehashing of existing principles. Over two decades before Martin wrote his treatise on the nature of application architecture and its relation (or lack thereof) to frameworks, the Gang of Four counseled their readers to be wary of coupling to frameworks.
Applications are dependent on the framework, and as such, they are particularly sensitive to changes in the framework interfaces. Therefore, the application should only be loosely coupled to the framework.
Sooner or later, everything old is new again. However, is it that the old is new or is it that we forget the lessons of the past?
To architect great applications and teams, you not only need to study the past, you need to apply the principles learned from such study. If you’re thinking about disregarding a lesson of the past, especially ones as well thought out as object-oriented architectural principles and industry best practices, you had better ask yourself why and default to assuming that you’re wrong.