About the author
Albert Row is a senior software engineer from the San Francisco Bay Area with over 12 years of experience as an individual contributor, technical lead, architect, open-source contributor, and manager.
Albert has been a certified reviewer on PullRequest since December 2019.
As a software engineer, it’s virtuous to write code that anticipates the future needs rather than one-dimensionally fulfill the business requirements of the here and now. But at what point does one fall victim to over-engineering and begin to inflict more harm than good?
This article highlights warning signs that what you or your team is building may be over-engineered so you can stop the problem before it gets bigger and course-correct when and where you’re able.
You’re Not at All Concerned About Scaling
If you’re sure that the code will hold up to massive scale in 10 years with no alterations at all, chances are that you’ve wasted time building it. The chances of the system requirements being the same in a decade are next to nothing and planning for the next year of scale is usually sufficient. If you find this type of over-optimization in place, try to avoid it in future projects. Talk about the level of scale required and question projects intended to create a scalable solution or one that will “stand the test of time”.
New Engineers Can’t Understand the Code
Admittedly there are a number of possible reasons for this but for now, let’s assume the code isn’t spaghetti since you’re reading this article. If the system is so abstracted and “beautifully” designed that it’s hard to edit or understand, congratulations, you have applied so much gold plating and so many spinning rims that no one is sure if it’s a car or a truck under there - your project is likely over-engineered. Some solutions to this are to look at the codebase’s class structure, perhaps in a diagram, and start combining concerns that shouldn’t be separate. If this isn’t possible, make sure the code is well-documented to help avoid the worst impacts of previous bad planning.
There are Lots of Classes or Functions Available “For Later”
Remember the old programming adage of “YAGNI”. You Ain’t Gonna Need It. By writing code that won’t be used any time soon, software engineers try to plan for a future that is entirely unknowable. These unused code paths will suffer from a high rate of bitrot and drag down quality over time. Bizarrely, planning too much for the future can result in over-engineering in a way that destroys quality - even making the codebase more prone to an accumulation of technical debt. The solution here is to discuss the inherent uncertainty in development, the likelihood of requirements changes and new features, and making sure that the current scope is well-enough understood and sufficiently fixed that the team knows what they are building for the use cases right now and can apply appropriate design patterns. Otherwise, the developers may simply be trying to cover a solution for all available problems in the space so they aren’t caught out when business requirements finally settle and enhancements need to be made.
Organizations that subscribe to software development methodologies such as Agile encourage flexibility and adaptiveness; an over-engineered codebase, built with the best of intentions, will limit your ability to adapt.
Time Put Into the Solution Is Out of Scale With the Problem
There can be a number of causes for this issue so it’s worth digging in deeply to figure out what’s really happening but if a developer is spending far too much time on a specific issue, especially if it’s an unimportant but technically interesting one, there’s a chance that over-engineering is happening. Bear in mind it’s also very possible that a rabbit hole has been discovered or the problem was much more difficult to solve than originally thought; so for this one, it’s best not to jump to conclusions. The solution to gold plating is to discuss the trade-offs and opportunity costs of investing more here vs. making time to invest more deeply in other functionality.
There Are Too Many Service Boundaries
In general, an organization should not have more services than it has teams and should only split out new microservices when it makes sense to do so architecturally. If you find that every function seems to be calling a different service and there is a huge amount of cross-chatter inside your VPN it’s possible that your team has gone service-happy. This type of over-engineering tends to result in slow performance due to network latency and serialization/deserialization costs. The solution to this issue is to refactor, ax, or combine services, to return to sanity. If that’s not possible, at least put down the shovel. Add new functionality to existing services rather than creating new ones whenever possible.
Your Server Costs Are Too High
If your AWS or Microsoft Azure costs are immense for serving a small amount of traffic there’s a chance that you’re heavily over-provisioned. Sometimes teams lean into redundancy to make sure there’s no chance of an outage. This is all well and good as long as the top line can support it. This pattern is particularly common in first-time startup software developers who were veterans at their former, larger companies. Sometimes habits don’t scale down as well as one might hope. The solution here is to have an honest conversation about risk tolerance and the budget available to mitigate risk.
You’re Building Commodity Functionality In-House
Commodity functionality is a commodity for a reason. If your developers are building their own JSON parser, for instance, you best have some really specialized needs in that area! Much of software development is the art of reuse - making preexisting components work well together. If your team is building everything from scratch in-house rather than relying on well-maintained dependencies, they’re wasting valuable time both up-front and over time. The maintenance burden for software is high, so it makes sense to reduce duplication and own as little of it as you can. The solution to these situations is to rip out the custom code and replace it with commodity open-source libraries or purchase some functionality from a well-respected vendor.
A Low-Traffic System Is in a High-Performance Language
Let’s say you have a simple back-end API that returns information from a SQL database to another service. It won’t be frequently accessed and it’s basically blocked on database IO. The developer wants to write it in… Java. Or Rust. Or Go. Time to call foul. This system can easily be written in an interpreted scripting language without much performance implication in a much shorter timeframe. If this approach is discovered early a lot of time can be saved by switching to a different base language. This is one particularly bad form of over-optimization, but other forms exist that are similar.
Unnecessary Libraries Abound
Sometimes you’ll find small systems built on libraries meant for larger problems - for instance using Rails when Sinatra would do, or Django when all you need is Flask. In these cases, the framework setup and maintenance is greater without any corresponding benefit. This is one of the few instances where over-engineering may be a symptom of programmer laziness, especially if your team is more on the junior end. They may simply be solving a problem in the way they know how to with the tools they’re used to. The solution here may be a bit of education or pointing out that the base framework is too heavy for the problem at the early stages of the project.
Go Forth and Focus on the Important
If you’re seeing some or all of these patterns in your organization it’s time now to go forth and set priorities. The underlying problem in most of these cases will come down to unclear priority setting. Leading a team of developers, it’s important to communicate what the organization is optimizing for - is it time to ship (velocity), system performance, maintainability, quality, something else? Everything in software engineering can be thought of in terms of trade-offs, and the ones your team should be making needs to be an ongoing discussion. It will change over time as the team and problems evolve. Make sure to stay on the same page and you’ll avoid wasting a lot of time!
Also be sure to check out: