Software Anti-Patterns
Half the time I talk to other software engineers about anti-patterns, I get confused looks because they’ve never heard of them. And just as often, I’m told by some “senior”-level or higher (ie: Architect) that anti-patterns aren’t real. In worst-case scenarios, I hear that software design patterns in general are themselves an anti-pattern and everything requires a novel approach.
This is like asking a statistician about off-by-one errors and them responding with, “There’s no such thing as a math error,” or worse, “unless you count things by hand, you’re doing it wrong.” The last time I checked, finding fast, efficient ways to calculate information from large data sets by not counting manually is the entire point of math. Therefore, always counting manually means doing something that’s known to be wrong or suboptimal.
When it comes to software, not all designs are created equal. Some are awesome, incredibly efficient, lightweight solutions that are easy for a software engineer to fully grok. And some are atrocities that spend pages and pages of code on an implementation that flouts the very concept of Big O.
When a developer uses the wrong pattern for a given problem, or uses a pattern that is known to be sub-optimal (or in worst-case scenarios, outright counterproductive), it is an anti-pattern.
Let’s look at a particularly egregious offender: the Inner Platform. The Inner Platform, and its result – the Inner Platform Effect – are notoriously bad for at least doubling memory pressure within a system, increasing CPU load, and rendering code unintelligibly decoupled from the underlying platform.
The Inner Platform is a design pattern whereby a sub-platform is built on top of some chosen base platform, and which reproduces various pieces of functionality already implemented and exposed by the base platform rather than using the base platform directly. While this may at first blush seem fine – after all, it’s just abstracting away the underlying service layer – the Inner Platform Effect arises at runtime, particularly when the system is under load. The act of re-implementing functionality that is already available in the base platform almost always results in doubling the number of objects in memory.
This happens because the underlying platform already begins the process of handling a request by instantiating its own internal objects according to its own rules, and then the inner platform intercepts the handler and proceeds to allocate a different set of objects according to its own rules. Now the system has two sets of objects in memory to perform the same operation, both of which need to be garbage collected. This, in turn, adds CPU overhead, including the overhead to parse requests twice and instantiate handlers for them.
The end goals of the Inner Platform are seemingly noble: it’s usually designed to protect developers from the particular semantics of how the base platform handles requests – including tricky error handling – while also preventing certain other anti-patterns such as Vendor Lock-In. But the results are abysmal. Not only does the application necessarily perform far worse – especially under extreme load – but it also precludes the ability to hire developers with expertise in the actual platform in use. Candidates may have knowledge of ASP.NET MVC or Apache Tomcat, but if the application abstracts those away then those candidates effectively have no real experience other than general concepts. Some candidates with experience on the base platform might be hired to work on integrations with the inner platform, but that’s a massive waste of talent that will not benefit those developers if and when they eventually move on to other software projects with better architecture.
“But what about decoupling the business logic from the underlying platform?” some may be tempted to ask, “Isn’t that a good thing”.
Yes it, but there’s a far better solution in the form of hexagonal architecture. In hexagonal architecture, business logic is placed in a separate project from both its consumers and its own providers and dependencies, and treated as its own set of services to be injected through DI. The B/L layer declares required interfaces implementations in the form of “ports”, which are implemented in other projects as “adapters”. This way, the business logic is completely decoupled from every technology required to implement it – such as the particular DB in use for the persistence layer, or a specific event queue – and the public API or UI layers are never mixed into the B/L itself. Implementations are free to swap out technologies at will without ever requiring the business logic to be refactored (see Dependency Inversion Principle above).
The Inner Platform Effect is just one of many software design anti-patterns. Just to name a few more: Cash Cow, Lava Flow, Stovepipe System, Ball of Mud, God Object/Interface, Cargo Cult Programming, Database as IPC, and the Anemic Domain Model (which is debatable as a thing, but who are we to disagree with the likes of Martin Fowler?). There are also organizational and project management anti-patterns that aren’t directly related to coding, but can still affect a software development team: Silo, Mushroom/Seagull Management, Peter Principle, Bicycle Shed, Typecasting, Death March, Ninety-Nine Rule, and many more.