Maybe the company was betting at the time on an integrations marketplace, or a lot of partnerships, so a robust integration platform was built. Then the company moved on to another bet after only writing one or two integrations on the new platform. Nobody over-engineered here. Everything was built to spec. But three years down the line the new dev is going to get assigned a bug in one of the integrations and lament how the code was over-engineered. Hindsight is 20/20.
Lots of stories from the trenches including many in this thread make this mistake. The same goes for 'tech debt'. If you weren't there, you don't know.
Now is over or under engineering a bad thing? that depends on how many resources the company can muster, your customer tolerance to bugs/failures, and whether you can build a small subset of features well. In practice I've observed products with fewer, but well built features tend to succeed more so than buggy featureful products. Large companies may opt to overbuild everything simply so that they can prove that the product was bad rather than having to decide whether it was product or execution that failed.
Often when you do one thing really well you may find there is more value in the primitives
I'm currently walking away from a code-base (and the company who let it be built) that was obviously built by someone who's mental model of how the code works was incorrect. At some very basic levels, it under-performs compared to literally every other example of this sort of code I've ever seen (and it's extremely common), which results in a subpar user-experience and an EC2 instance way bigger than necessary to run this sort of code. A lot of code running doesn't need to run in the first place, and due to the incorrect mental model, there's a substantial amount of code that doesn't run.
Nobody really understands how bad it is, though, because it's barely tested (and the engineer who wrote most of the code has argued that we don't need tests, for various reasons) and all of the engineers on the team who didn't build it initially, have told me that they're not familiar enough with how this sort of product should work to understand that it doesn't work like that (again, this is a super common kind of product).
There's a lot of other anti-patterns (I started keeping a list in my first week) but I think these are the most damning. This code is in production. Nobody at the org brought up these issues earlier in development (like saying "hey, nobody else's product does [annoying thing that doesn't need to happen], why does ours?"), which leads me to believe that the whole technical org is at fault.
It sucks and I can't wait to be done with this experience.
1) The product is horizontally scalable to 3 orders of magnitude more traffic than the product will ever receive.
2) Bespoke assembler/hand crafted memory management/other do not touch code to shave 5ms off a 5 ms call on an API that isn't latency sensitive.
3) Ability to handle N customer tenants while only handling 1 customer 3 years later.
4) multi-region redundant deployment for 1 customer strictly based out of the US with no availability requirements.
5) 100% coverage for integration tests, unit tests, CI/CD, for a single tenant blog that is never updated and no one reads.
6) Custom internal framework with micro-service splits to support team of 100 engineers working on app when there are 2.
7) Automated continuously trained ML ranking model which performs .001% better than sorting by popularity or other trivial heuristic on a social media app with 10 users.
The common theme in many of these cases is that these are pretty good ideas if you have unlimited people, but have fairly low impact. In some cases these may be counter-productive to successful delivery at the company/product level. A piece of software built wrongly due to poor understanding of the product domain is often considered under-engineering.
It's completely wrong given the context 99% of companies are operating in. It's no wonder a Zuckerberg can come along and build a crazy successful company in his early 20s. He likely didn't know any "best practices". Common sense was enough.
In my org, we love to call this out with an (often private) "We need to do it because Google Does it!" -comment.
Some of it makes sense when the reasons were explained (e.g. "we figured this would make it easier for [business unit] to do [thing] which turned out not to be something [business unit] actually has interest in doing").
Other examples are purely of this sort (e.g. "we'll be glad we have this in place when we finally hit [x] users or enter [market]").
Design wise, I know it when I see it but other people may see different things.
I agree completely on pushing off features that are not core because it is better to learn in production than to daydream in meetings. I would say most phase-2 features never get implemented.
Complexity kills projects and the will to work on them. It can be caused by either over or under-engineering. With the first you end up with extraneous layers of abstraction and badly applied design patterns that make it hard to understand or reason about the code...in the second you end up with a big ball of mud that's impossible to understand or tease apart.
In both cases the code gets harder and harder to change to the point that no one really knows how it works or wants to risk touching it.
No tests? Nobody knows how it works? It doesn't perform well and doesn't fit the problem it's solving? That's very much not over engineered in my book.
> and the engineer who wrote most of the code has argued that we don't need tests, for various reasons
Sounds like you made the right choice to walk away. "My code doesn't need tests" smacks of match-over-the-shoulder type of chaos creation. This person has likely never had to maintain their own code or someone else's, nor has anyone ever had to maintain their code. 0/5 would not work with.
Tests are wonderful. Tests are proof your code works. They are something to be proud of. And they defend your code against the chaos monkey of future people refactoring and rearranging.
All it takes is a good enough sales and marketing team, or the right executive relationships, especially if the team being “supported” is small.
You can say the company or division is presently functioning, but the time constant on the product is different, and the coupling is too loose to say anything else with accuracy.
That's the point though. Getting caught up in engineering purity or even whether it's objectively _good_ is a waste. The product exists to drive business (financial) metrics. If it's doing that, it's working.
If the product is poorly crafted, and especially if it's also poorly tested, adding features can introduce bugs, and fixing bugs in one place can cause issues elsewhere. Development then slows to handle these occurrences, which can then impact product viability.
> Does the product generate the revenue for their paychecks, plus extra for the company?
You want to be comfortably generating/contributing more revenue than the expense of employing you.
The specs and the decisions surrounding the code are usually part of the over-engineering.
> Everything was built to spec.
Even with reasonable specs, I've worked with, and later managed, engineers who had a strong drive toward over-engineering solutions. I've also done it myself, and I suspect most people reading this have done the same.
The problem is that many of us engineers became developers because we enjoy building things and we enjoy solving complex problems. It can be exciting to imagine a particularly complex version of a problem and how elegant and fun a complex solution would be.
It can be as simple as a programmer who tries to write their own CMS when the spec could be solved with a simple blogging platform. If you're an ambitious programmer, writing a CMS is fun! Installing Wordpress is blech.
I didn't truly understand the ubiquity of the problem until managing ambitious engineers who wanted to do complex things. At times it felt like 1/4 of my job was just asking "If we wanted to simplify this solution, what would that look like?" Repeat for a few rounds and eventually the team has shed a lot of unnecessary complexity without sacrificing the spec at all.
If you’re trying to answer “did the people who built it exhibit poor judgment resulting in the system being over-engineered?” then you need to know more than just the after-the-fact outcome.
A codebase that pivoted multiple times makes more sense the more time you spend in it, an overengineered one makes less.
What that distinction recognizes is that it's possible for something to have been well-engineered at one time, while still being over-engineered today. Header files in C and C++ are an example of this phenomenon. They solved a very real problem with technical constraints from 40, 50 years ago, both in terms of computers' capabilities and compiler technology, but, since those problems don't exist anymore, they function as over-engineering.
Basically, the way every programming language newer than C++ or Objective-C does it.
Technically speaking, for any given situation there's no way you can be wrong.
But this post describes a distinct and, by my experience, dominant industry phenomenon. And the stakes are as high is OP says (dead products).
Industry engineers in the large are massively divorced from product outcome. This should be no surprise, as it is a seller's market:
Delivering product simply and directly can be, depending on perspective, somewhat of a mundane and Sisyphean activity.
Engineering "stuff" is far more intellectually engaging, and there's no market pressure (for engineers) against over-engineering. If my company fails to deliver a product outcome, whether a viable company or startup, in this market there is no recourse at all on my salary let alone my hireability.
I say "depending on perspective" because if you can find a way to be more intellectually and emotionally stimulated by shipping stable product over time, then ime you _can_ unite your joie de vivre with market outcomes.
But that does not at all describe most engineers in industry who generally at best shed off some market value while spinning unnecessary webs of toy abstractions (ie liabilities).
Having a 'user info' table with 190+ boolean columns for 'country' ('US','CA','DE','IT', etc) in case someone wants to indicate they're 'from' two countries.
Joining views on views of joined views which are themselves built on joins of other views is, likely, not a terribly sound data decision. (X: "It worked fine last year - you must have broken something." Me: "well... last year you had 450 users, and now there are 37000 users - this isn't a performant way of doing this". X: "Can't be true - the person who wrote it is a DBA")
I agree on the point regarding the decision being 'stupid' but in this case it was not because of inexperience. On the contrary, it was because an overruling decision by an experienced manager. So the point is that it is not always technical or due to inexperience (though that does happen) - from what I've seen, it is quite common for such things to happen due to hierarchical/ego/political issues as well.
Not for long…
Can you prove it in a court of law? No. But you still can often easily diagnose it. And you'll usually be correct.
For example the conditions under which https://wiki.c2.com/?SecondSystemEffect happens are well-known, and the pattern has been repeated often in the half-century since it was named. If it is a second system, and you can find complicating abstractions in it that get used exactly once in practice, then with better than 90% odds it was over-engineered. And the farther you dig, the stronger the evidence will become.
I would argue this is a case of over-engineering. Though, it extends beyond pure engineering.
This is poor resource management and business de-risking. An effective approach here would have been: "Let's build out a handful of integrations. Make sure they're going to deliver the value we want. Then abstract it into a repeatable pattern".
The trouble with "Let's build out a handful of integrations. Make sure they're going to deliver the value we want. Then abstract it into a repeatable pattern" is that when you're building an integration marketplace, you need partners, and they don't want to be your research bitches.
They just want to build their integrations once, and gtfo. If they think you're signing then up for a revolving wheel of experimentation, they'll just politely stay away until someone else has done the hard yards.
Sure if you're a Microsoft or a Google you'll have any number of willing partners who will put up with anything to be the pioneers in your integrations marketplace.
But otherwise, they're using your integrations marketplace purely for the benefits, and they don't want to be building on sand.
First line in the second paragraph of every dead company's obituary.
Having rewritten a bunch of systems (sometimes several times) I can attest that it will not always lead to the death of the company. The trick is of course having modular enough systems that they can be rewritten from scratch in a reasonable amount of time.
It can also be a great way to increase the simplicity of the system as typically the old version was designed with a very imperfect understanding of the problem and no operational experience servicing it; further learning were usually crudely patched on top and you often end up in a conceptual hodge-podge where words mean subtly different things depending on the context and translation layers need to be inserted between the contexts etc.
Often a (good) rewrite starts by clarifying the conceptual model. I like the saying "clear writing reflects clear thinking", and in programming there is a lemma "clear thinking produces clear code".
Arguably it can be better to float a completely different boat and see if it swims but that can result in a product positioning problem where you then have two versions of the same product but with an uneven feature set.
Old developers have left. New ones come in. The systems the old devs built suck, so the new team convinces management to do a rewrite. The new devs don’t really understand the problems encountered by the old ones so they repeat all the same mistakes. New system, same problems.
The lesson we’re supposed to take away “no one should ever do full rewrites”. It’s a stretch, but IMO the proper takeaway should be 1) really understand the old system and 2) have a very good reason before doing full rewrites.
You evolve your code with refactoring and rewriting only pieces at a time. This is opposed to revolution, also known as "the big rewrite", which replaces the entire application all at once.
Your "modular enough systems" seems to indicate that you also favor evolution over revolution.
Actually normally the evidence is lots of other companies that failed to do rewrites. It's just that one was a full scale fuck up. I'm currently working at a company that literally it's echoing Netscape. The issue wasn't the rewrite it was rushing a half finished rewrite out the door. It was stopping product development for so long.
My current employer started a rewrite but called it a migration gave it a 3 month deadline. 3-months to write all the features it took 7 years to write. They realised this was impossible and remove a bunch of features and decided this rewrite would remove features they will add back later. But they still kept on setting months for something that has taken 18 months so far with even more features removed. It almost a daily thing that yet another product feature was removed to cut down time. They claimed they were feature complete in september because it had to be done under all circumstances, they found out they hadn't done 50% of the features they said they would. So with more rushing of the features they hadn't written they then started talked about releasing it before it had passed QA. They announced the release date before it had passed QA. We have partners using it and saying it's broken for them. They don't have all the data. And yet they're still releasing it on Monday. Why? Because it had to be done in 2021. They're rushing a half finished rewrite out the door to hit a target set by management. So they spent 18-months removing features and when they release it, it will be buggy.
So, yea I mention Netscape a lot because honestly, this sounds the same. Rush out a half done rewrite while allowing the competitors to improve their product while we made ours worse.
> Having rewritten a bunch of systems (sometimes several times) I can attest that it will not always lead to the death of the company. The trick is of course having modular enough systems that they can be rewritten from scratch in a reasonable amount of time.
I would say that the trick is not to do the rewrite. You refactor each part until the entire system is rewritten.
This is exactly the right way to approach this. The best way I’ve seen to rewrite a complex system is to literally work off a branch and deploy it in QA beside the old version. The hardest part is figuring out the right way you want to direct traffic to the “new version”.
My team inherited a massive system that was the key revenue generator for a multi billion dollar company. It was an operational nightmare from deployments to stability. It had at least 1 24 hour outage that was nearly impossible to root cause.
We slowly chipped away at it for 8 months running in parallel in QA until we were satisfied that it was functionally equivalent. Started running traffic in prod while we tuned it to start taking real traffic and had the whole thing replaced in 18 months.
The system was replaced, is handling 2X the load in prod of the old system and hasn’t had an outage years
So now the Director has job security for about 2 years, gets the big launch near a promotion cycle they have a small chance of being considered for promo, and gets to blame the predecessor for all the problems.
This is like saying that water is lethal - both are true in the sense that some x (water, complexity) is ok, but too much is bad.
Complexity is an unfortunate (but necessary) side-effect of (1) adding features and (2) optimizing for performance, both of which are critical for building a product.
If you try to remove all complexity from your product, you won't have a product. Instead, you have to try to minimize complexity while still delivering the same set of features and level of performance.
If you try to remove all complexity from your product,
This wasn't what the commenter was advocating, of course.
Rather it was an attempt to simplify the basic message of: "Complexity has attendant risks which are too easily ignored -- and which in short enough order, can kill hour product."
Of course in practice it's always messy and rightly so, but I've learned the hard way that you better don't loose control about your state and data - because that's where the ugly bugs are.
The article is very good. Just ignore my drivel :)
until they change - again.
If you have to find the smartest people to keep the wheels on, you’ve already lost.
Disdain for the bell curve is the fastest way to get overengineering. Very few things have to be rocket science to create a good company. Everything else should be dead simple so that anyone can work on it. That also means you have to compartmentalize the hard bits so they don’t leak across the entire codebase.
But some people get bored with mundane code and will make things interesting by replacing a boring task with an exciting one. It’s part of the Curse of Knowledge. You can’t run a company like an action movie. Everyone gets burnt out.
1. Clearly define a vision for the future/ goal(s) that should be achieved
2. Get out of the fucking way of your minions, and trust that you hired correctly, to let them figure out how to get to the finish line
3. Your temperament is in the Goldilocks' zone of neither being too much of a spendthrift, nor too much of a miser, when setting budgets, i.e. you're not some rando without P&L experience that was tied to your bonuses.
Otherwise they’ll burnout and the resentment of the state of the codebase will be jet fuel to that brand new revolving door on your team.
Commiserate to some degree, ‘look, I know this shit sucks’. If they don’t know you empathize, they won’t trust you.
Are you spending your time second-guessing the developers and micro-managing them on the exact thing they are supposed to be experts?
Your phrasing has that "feel", but it's far from a sure conclusion. Anyway, if it is the case, you may need to reevaluate both your hiring (should you get more senior people?) and management (this is a clear "should never be done", unless your people are explicitly under training) practices.
That's a lot of trade offs to avoid maintaining the subset of features you need from said software in house, being able to leverage internal knowledge, being able to streamline all your environments and tooling to suit your needs instead of catering to the needs of said software.
This isn't an argument one way or another, it's just pointing out that there are trade offs you need to make consciously or otherwise.
In your position I would be extremely concerned about pushing off the shelf software onto Devs that either lack the clout to be comfortable pushing back or lack the communication skills to clearly articulate all the trade offs really taking place.
It's very easy for a boss figure to push through requirements with off the cuff pointed questions. The reports often need to push back with orders of magnitude more research and thoughtfulness than went into the question or suggestion.
Overengineered what-if everywhere often causes way too complex systems where even simple things become hard, de-engineering is nigh impossible because it'd break things by taking away functionality (unless an alternative manifests itself).
Non-engineering hackjobs where we basically have N copy-pastes or more or less the same functionality everywhere, cleaning up anything will require a fair bit of testing to avoid breaking anything in the process, possible but causes a lot of uncertainty and risky if you won't see it unless failing in prod (because it goes hand in hand with bad deployment practices).
As mentioned in the article and above already, you need people with experience and to give those the business knowledge to find the correct balance.
Plus the annoyance as a user that whenever you use this thing, all your tools are in a slightly different place and work slightly differently to how you left them. IMO there's a need for better balance between "it works well enough, leave it alone" and "we haven't fully optimised this workflow yet" in product development.
The list goes on.
If you find yourself defending your bad over-engineering and those that sold you the lifestyle, understand you are fully pimped and are now defending the pimp, even to your own detriment.
Happens to all of us from time to time, snap out of it. Don’t get pimped by ‘faangs’ or ‘notable person of interest’. Don’t listen to everything I said either, lest you want to get pimped by me.
Unless you can wholeheartedly make an objective argument for why you used a pattern, a tech stack, a process, in plain simple words, sans ‘that’s how the big boys do it’, sans ‘this makes me a real developer’, you are simply pimped and spewing out pimped out thoughts of your overlord pimps. Be ready to be wrong and backtrack and own the mistake and correct course, but don’t you dare hold on to it, or I’ll probe to see who is pimping you out.
I was never more free from my overlord developer pimps until the day I realized they evangelized impractical solutions. Never hoe’ing for anyone ever again.
It's also important to try stuff out, fail, recover, and try again. That "Code Complexity vs. Experience" graph in the article is not completely a joke. Very few people can tunnel through the complexity hump without years of failures and successes behind them. Moreover, you might not have a choice.
You might find yourself dropped into an obstacle course of complexity that other people created and that you have to keep running following their arcane patterns and practices-- while at the same time implementing new features and refactoring it into something workable before it becomes completely intractable. I think almost everyone faces this problem (except for maybe the most orderly and elite workplaces?).
No one would argue against trying things, it’s where all creativity and innovation comes from. I argue against dysfunction, the whole ‘the ship is not sinking’, when in fact it is.
Anyway, perhaps I’m speaking too personally, because I am on a literal Titanic right now, so apologies for that.
In a startup you don't know if you're building the right thing so trying to build it right is premature optimization. Since you have limited resources you really have to focus on ensuring that you're building the right thing...if you aren't it doesn't matter how well designed or built it is no one is going to use it.
Once you've validated you're building the right thing you can start focusing on building it right, but by they you probably know where the pain points are and where you need to spend the effort.
The trick with this though is that everyone needs to be aligned on that approach and be honest about the fact that corners may be being cut to get something working fast. Where I've seen this go badly is where a crappy initial version was built to get to market fast, but then no time was made available to address the defencies in the initial release.
Overly simple, hacky, narrowly spec'd code produces the same tech debt as overengineering. Anybody who's worked in a move-fast-break-things type startup will know how much engineering resources are wasted on rewrites/bugfixes due to this.
Ultimately, as with many things in life, you need to find the right balance.
Devs will build software until they can no longer do so because the codebase is larger than their collective abilities to manage.
I'm developing a principle of radical simplicity to attempt to combat this: always keep things absolutely as simple as they can be.
It is always easy to add complexity later, never to remove it. So the only conscientious choice you can make is to keep things as simple as possible while satisfying the requirements.
You should also critically evaluate requirements that introduce complexity.
Also, I don't think people mention this enough here: complexity == bugs.
Then you hit performance problems, any sysadmin could help you handle 2x/4x/10x scaling with simple separations of service and maybe some hours of downtime. In the meantime you probably have weeks/months to think about going really crazy with your infrastructure.
What kind of numbers am I looking for? Thousands of users? Ops/sec? I realize it is partly due to what your thing is doing eg. website vs. a complicated app.
Yeah it is a good problem to have assuming you have cash flow.
To explain the number, I assume a user will spend 1% of it's time on your app (that mean the average user spend around 15min per day (EVERY DAY) on your app, that may not sound impressive but it aldready is) and an nginx server on a powerfull machine could handle around 1k connection at the same time.
If you want to dig about an exemple of number far less abstract I remember stack overflow published their settings :
The 2013 article state they could handle 148,084,883 page load per day with a single database server (but they did use two) for exemple.
For example, premature optimisation is frequently mentioned as over-engineering but is it premature optimisation to use a map/dictionary instead of an array for key-based access? Nope. That is just correct. Is it over-engineering to know that if your product succeeds, you could end up with X-hundred objects which will use up all the RAM you are storing them in and therefore you might want to make them smaller/store them off-board? Of course, you can come back and refactor later but it is so much easier for the person who writes that code to understand what can be done on day 1 rather than assuming it is cheaper to refactor later only when needed.
I think a better take is for people to accept that no app lasts forever. If we build that assumption into our worldview, it will help us make better decisions. I still think some engineers think there is some perfection that means the app will live forever.
Maybe, maybe not. While the map/dictionary is easier to use, if the number of items is small (which it often is) the array will be substantially faster because the CPU will pre-fetch the next element into the cache while you are still checking if the current one is the right key. Maybe - check with a cache aware profiler, and you need to design your array to be cache friendly.
Of course the above assumes your map/dictionary is on the hot path (seems unlikely), your code is too slow (probably subjectively, but in some real time applications this can be objectively measured), and you are writing in an efficient language. IF All of the above are true, only then should you go to the complexity of using an array to do the job of a map/dictionary.
There needs to be a "sweet spot," where we have enough complexity to achieve goals (which can be business and cultural, as well as technical), and as much simplicity as possible.
A lot of folks think that using dependencies makes the project "simpler."
It does, for the final implementation team, but that complexity still lives there; along with a whole bunch of cruft, tech debt, potential legal issues, security issues, etc.
Unfortunately, you can't do complex stuff, simply. Some level of complexity is always required. Managing that complexity is where we get to play adults.
Figuring out the sweet spot in architectural design seems to me still an art/craft, that comes from intuition and by experience.
[As far as dependencies... using dependencies may sometimes be simpler than the alternative(s), and sometimes not, sure. It depends on the nature of the dependencies, the problem, and the alternatives. :) ]
I call this squeezing the balloon, the complexity doesn't go anywhere, it's inherent to the requirements of the system. You just put it somewhere else.
This is just code factoring at a macro(-ish) level.
For well maintained deps there is the extra boon that it takes work off your hands though. For instance building a React app with Next.js saves you from ever having to deal with webpack and you get big upgrades for free.
Absolutely. Nowadays, it's pretty much impossible to write anything without some level of dependency; even if just the development toolchain and standard libraries.
The problem is that a lot of outfits and people are releasing subpar dependencies that smell like the kinds of high-quality deps we are used to, but, under the hood, are ... not so good ...
Nowadays, it's fairly easy to write up a Web site with lots of eye candy, and gladhand a bunch of corporations, enough to get their brand onto your Step and Repeat Page, so it looks like your dependency is "big league," when it is not. In fact, the kinds of people that couldn't design a complex system to save their lives, are exactly the ones that are experts at putting up a wonderful façade.
What gets my goat, are proprietary SDKs, often released by peripheral companies. These can be of abominable quality, but are also the only way to access their products, so you need to load a bloated, sluggish, memory-leaking, thread-safety-is-optional, crashes-are-so-much-fun library into your app, and hand that library the keys to your sandbox, just so you can get at the device.
I've been writing exactly that kind of SDK for years, and know that we can do better. I'm a big proponent of open SDKs, but many peripheral manufacturers are absolutely dead-set against that.
These SDKs often mask significant complexity. That's what they are supposed to do. They also generally translate from the workflow of the device, to one that better suits application developers. Some SDKs are just a simple "glue" layer, that directly map the low-level device communication API to the software. That can be good, sometimes, but is usually not so good.
Probably missing few core points:
1. Without good development practices due to lack of Experience or plain old Rational Thought the only possible outcome is Operational Deficiency.
Every Best Practice is context-dependent, thus having Deficient Resources makes them inapplicable in certain cases. Some teams can really suck with the same tech stack when others flourishing using it.
2. Basic organizational anti-patterns, like Mushroom Management, and broken retrospective lead to rediculous outcomes.
Even plain old Micro-services and Micro-frontends can be a basis of Stovepiping and applying Mushroom Management. Usually, again, due to Lack of Competence and Sheer Hubris.
3. “Premature Optimization” only used in context of over-engineering by those who didn’t read the book, but use Halo-effect cognitive bias to project compensated qualities onto the term itself. There are a lot of Psychological Compensational Needs under the hood.
It’s like “Why Agile has nothing to do with Discipline ?” or “Why senior developers turning the project into a sandbox due to the lack of self-fulfillment ?” or “Why most of the MVP’s lack Concise and Validated Definition of Viability ?”
Complex doesn’t mean Hard or Expensive. Simple doesn’t mean Easy or Cheap.
Too often “over-engineering” is just an organizational and psychological issue and not an Engineering one.
Stop operating on Feelings.
Six Sense of the Fifth Body Anchor Point is not a reliable Key Performance Indicator.
Had we gotten decent logging in place early, we could have saved a terrible change that got passed our canary rings, which got us called to see the CEO and made news.
So, I learned early that "overengineering" can also be a management excuse for cutting corners that shouldn't be cut.
It's all OOP
99,6713% typed (according to psalm)
Purposely written to be unit-testable in PHPUnit strict mode (but no actual harness yet)
...I'll show myself out
I've had this exact ticket in the past: A customer built a complex Java client for our public API which assumed no surplus fields in the responses, and as we added a new field, their entire application broke down, causing huge losses for the customer. I wasted so much time on explaining how our stability promise does not extend to added fields!
In terms of power structures, Product Managers decisions largely go unchecked in a lot of places. Engineering decisions face significantly more scrutiny, especially in places that work in short sprint cycles.
Your argument is akin to, "PMs can't code, so alas we need engineers, and that's why shitty engineering exists, and there is no way to make it better"! Nope! We need product practices, akin to engineering practices, with 360° feedback and analysis, and the product management should be held accountable for their decisions!
Engineers would be far happier if they don't have to do good engineering and can do away with shitty software without accountability. But there are checks and balances to improve engineering quality, and those aren't created by PMs. It's the engineering org that champions good engineering practices and accountability and post mortems. Same should be done in PM organizations.
I'm sure this can all be solved with modeling out your design system or whatever, but as a product manager who needed to make a quick mockup, I found it a little overwhelming.
Every competent engineers I have met has been capable of grasping that:
- They shouldn't rely on the use of crystal balls.
- Complexity should be minimised.
- If it can be done today, it can be done tomorrow.
So if a business provides its engineers with:
- A process that helps them develop an intuitive understanding of their customers' needs.
- At least one other similarly capable person to work with so they know they're not the only person going to be looking after the project off into the future.
- A procurement process for off-the-shelf solutions that is less painful for them than rolling their own.
- Time to test and document where projects should go if the initial version is successful.
- And crucially, confidence that they'll be given enough time to do the additional work if it becomes necessary.
Then the business, at least in my experience, will be in a pretty good position to prevent almost all over engineering.
Reason is that with "bare" languages like C, you can choose your own design more freely. Case in point: Serenity OS.
Author of that OS replicated entire libc and still refuses to use C++'s STL. He created his own abstractions that he feels confident using. That is true engineering.
What is the state of Golang's Windows support?
I see devs freaking out when you use the word abstraction now...
Suggest extracting business logic from a React component and you don't know anymore if someone is gonna raise the "overengineering" flag.
If you start a new project, do you not use any library or framework at the beginning?
Obviously you take decisions according to how much the project/feature is expected to scale.
If your estimation was too low, you might cripple your development at some point, and need a rewrite or at least some significant refactoring.
If it was too high, I guess you overengineered.
What matters is asking yourself the question, and of course as engineers we like to challenge ourselves into building the best possible solution, but we equally need to consider how likely it is that such a solution is never needed.
This is why I used to throw things into simple kubernetes setups (that I just ran the same terraform scripts to create) and just tell the management "yeah, now it's ready for all of your features".
I argued for a while, then realized I didn't have to.
What is overengineering?
Code or design that solves problems you don’t have.
I've noticed two distinct types:
* The "AWS cert" admins who have a dozen different cloud certs, but little practical experience. Every problem is to be solved using some over-priced, over-engineered conglomeration of cloud service. It doesn't matter if it's just an internal app to be temporarily used by a few dozen users for 6 months, they immediately begin following some "best practice" guide by setting up some complex multi-region, multi-subnet, read-replicated, load-balanced, auto-scaled, content-delivery-networked, containerized, Cloud-Formated, hourly-snapshotted, hot-swappable, OAuth secured and social media-login-enabled, automated environment that would be appropriate for only some retail giant's Black Friday operations, not a single CRUD app, to be used only temporarily by 10 users in HR dept.
* The "automation expert" who takes the requirements to set up and maintain a few environments (e.g. dev, test, and production) that might need to be re-created only a few hours, 1-2x per year, and instead spends weeks crafting some monstrosity in Ansible or Cloud Formation or Terraform, complete with all sorts of required infrastructure servers that themselves bigger and more complex than the actual working environment itself. And what's worse is that none of these frameworks like Cloud Formation are ever 100% anyway, so you can't just "push a button" and create a new environment. Instead, there are a dozen holes in the different tools that need to be manually plugged when run, so it's not like a developer or junior devops person who doesn't know the environment inside and out or understand the undocumented quirks could use it themselves anyway, if and when the original guy leaves the company.
Stick with those who put the user and simplicity ahead of simply technology solutions.
Users are #1. It’s a constant battle between diving into the code/technology and reaching out to users and customers.
It was a terrible choice, and their idiotically overengineered solutions are a big reason why.
The most egregious one is that instead of weighing packages to find out their weight, they have an algorithm that estimates the weight. My packages are designed to come in at just under a pound (it's a big cutoff for shipping prices). Needless to say, slight problems with their algorithm can and do lead them to overbill me.
A ton of work when they could just use a scale to do the job much, much better (and I'm sure save time overall with all the refunds of overbilling factored in).
Not having users / customers can kill your product.
Not building the right features can kill your product.
Not doing enough testing can kill your product.
Doing too much testing can kill your product.
Having toxic / inexperienced / unmotivated staff can kill your product.
Having a bad marketing plan can kill your product.
Not having enough staff can kill your product.
Not having enough funding can kill your product.
Technical debt of all kind can kill your product.
Bad data schemas can kill your product.
Under-engineering can kill your product.
Over-engineering can kill your product.
This is in no way a complete list, but from my experience the items on this list are ordered with the ones most likely to kill your product put at the top.
unless there is an org level commitment to not using "what if in future" type scenarios, overengineers will win everytime.
In some cases over-engineering may even lead you to stumble upon interesting discoveries or to innovate.
This is a true story about an entrepreneur who was suffering a bad case of perfectionism. Instead of showing his MVP to potential customers, he just kept investing in the software, chasing an almost impossible ideal of high availability
As much as I hate the cult of Elon Musk, I have to admit that's one aspect of engineering he really gets, and I suggest you listen to his interviews with Tim Dodd / Everyday Astronaut, where he gets technical, trust me, it is not the usual bullshit.
All that to say that "overengineering" is bad doesn't mean you shouldn't have a lot of engineers working hard on a problem. In fact, a lot of "ovengineering" is the result of not enough engineering. They picked a (complex) solution without thinking instead of really studying the problem.
Over engineering is a term that is as useful as saying
- product requirements list was too big
- features were never communicated clearly (tree swing comic..)
- right people / people with relevant experience were not hired..
I make sure to always "Engineer Just Enough TM"
I've found is that once a dev has an idea of how something is suppose to work it's hard to get them to think in a different way about that thing. So when that thing doesn't work out for some reason they just keep adding exceptions and adding exceptions until you have the horribly over-engineered solution.
But as the author defines overengineering, it is clearly not a good development practice.
Further, there is no evidence provided to support the claim.
Maybe a truer lesson is that the authors work suffers under too much scrutiny. We're all better off imagining a similar thesis and then imagining it is actually true.
Similarly, walls of text are bad.
I use line breaks after every sentence to fix that.
I see no reason why not.
This way you can clearly tell apart sentences.
Adding new lines doesn't require any more time than just writing a space.
So why not just do it always?
By following this rule I never wrote a wall of text again!
Do I need unit tests when my code is dead simple, or written in Rust?
How would you look if you just invoked APIs to get your work done versus designed and implemented an end to end solution.
Many times, I have seen over engineered solutions come from promotion attached initiatives.
As we get more competent and read more books, we have the tendency to get enamored by new fancy abstractions. We get too clever and then we get in our own way.
Best real-world example: I inherited a project that was an giant "microservice" with 50-ish endpoints, and a Mongo database and dozens of collections. After probably 3 months of wrestling with this thing I had a realization: "this whole thing can be just a simple command line tool and one collection in Mongo". That reduced the code by almost half and it became so much easier to work with. It's frustrating that it could have just started this way.
He designed his own programming language, compiler and, why not, the database and tools as well. It was a mess. It got all the limitations of a poorly projected side project and no benefit in the long run besides from huge technical debt.
15 years later, with dozens of clients' sites with the software installed and running locally, the company was locked in a horrible software stack that was next to impossible to move away from by gradually replacing modules, because the programming language had zero interoperability, the original creator was retired and no one besides himself had worked on it for years; while continue giving support to existing customers, because there were fires everyday, and with the same engineering team headcount.
That is one of the top 5 "oh my" in my career. I'm glad I left that behind.
Is it true? I don't know, I think it matches my... experience.
Part of it is that the "experience" needed to avoid over-engineering is helped if it's not just engineering experience, but domain experience too. If someone is constantly jumping industries/domains, it might take longer to get there. I think they still would eventually.
After discussing it with the customer, a simpler solution often emerges, where they change the requirements slightly allowing for a much simpler solution that might even solve the actual needs better.
While it's not just down to experience, I'd say it has a strong influence on being able to see beyond the given requirements towards a better outcome.
And aren’t those projects easier to fix…because they are so simple?
In my experience it can be just as difficult to fix, at least if constrained by backwards compatibility.
Under-abstracted code for example often introduce cross-dependencies which can be hard to break in a backwards compatible manner.
- Audio interfaces are never added or removed over the app's lifetime
- Audio is never rerouted between interfaces, or at least the app doesn't need to know about it
- The details of an audio interface, like number of channels or supported sample rates, do not change while the interface is running
- Audio latency never changes and callback timing is always precise, so timestamps are not really needed and a single number is enough
Of course, none of these are really true and adding support for some of them would require rewriting a few APIs and many, many implementations. On the overengineering side, of course it has its own smart pointers, string class, thread implementation etc just in case someone needs to build for a target that doesn't support C++11.
If someone didn't take the time to do actual "engineering" - which is to say, using mathematics to formalize design requirements (again, possibly a foreign concept to the app-building class) and just YOLOed the design based on intuition, you can end up with something so fundamentally broken in concept that "fixing" it requires a bottom-up redesign starting from fundamentals. A child's drawing of an airplane is not progress towards a blueprint of one!
I see far more problems on a day-to-day basis with patterns and anti-patterns taken too far. For example, I never use the factory pattern, because it leads one down the Java road where everything ends up an object with mutable state. Which isn't scalable over some metric (like a million lines of code) because a human brain can't trace execution, even with a debugger. A far better pattern generally is to take a functional approach of accepting data, swizzling it, and returning the resulting data without mutability or side effects.
Another really bad pattern is when execution suspends and resumes somewhere else (goto hell). Any project which uses queuing, eventual consistency, promises, nonblocking streams, even basic notifications or things as complex as monads will encounter this nondeterminism and inability to statically analyze code. Note that pretty much all web development suffers from some aspect of this due to its async and multi-signaling nature.
So what I do now, which I don't see much these days, is solve problems abstractly in a spreadsheet (pure functional programming), in the shell (the Actor model) or as a flowchart (declarative and data-driven design), and then translate that to whatever crappy language/framework I have to use for the project. I find that today, roughly 90% of developer effort goes to discovery, refactoring and testing of ill-conceived code. Only 10% is "actual work" and that's probably a stretch.
Which is incredibly heartbreaking for me to see, since I grew up on software like HyperCard, FileMaker and Microsoft Access which solved much of this in the 1980s and 90s in a no-code fashion. One of the very first "languages" I used was a visual programming environment called Visual Interactive Programming (VIP) for the Macintosh by Mainstay, which unfortunately today I can find almost nothing about, to show what an impact it had on computer science hah: https://duckduckgo.com/?q=Visual+Interactive+Programming+VIP...
With mainstream languages and frameworks like Node.js and React, and their predecessors like Ruby on Rails and Angular, I just keep thinking to myself "never have I seen so much code do so little". It's all overengineered man!
Some better alternatives to the status quo:
Simple Made Easy: https://www.youtube.com/watch?v=LKtk3HCgTa8
Object-Oriented Programming is Bad: https://www.youtube.com/watch?v=QM1iUe6IofM