I'm all for pushing back against "let's not make perfect the enemy of good." I hear that all the time in reference to software that is usually quite bad, and often a little evil to boot--nothing good anywhere in sight except some poor schmuck trying to find a way to make their job something worth taking pride in.
I'm not sure I agree that systems are products though. The product mindset is toxic. It means that you've got goals which are independent of the user's goals (typically to make money, which sometimes means doing something dastardly to the users on behalf of the shareholders).
All the best software is more in the "tool" category and less in the "product" category. Usually it's made by the users, only bothers with solving problems they have, and has no ulterior motives.
I'm also wondering if "perfect" and "good enough" are not really as important now vs. when a team of software engineers had to spend sprints implementing features. The rate of iteration is faster now, the rate of regenerating entire code bases is days. We can perhaps over engineer /more/ today than before.
The real challenge is reigning in the size and complexity of the codebase if you're using AI to generate it. I have a bunch of skills (YAGNI / KISS inspired) but it still requires a significant amount of human effort. Knocked down about 9000 lines of cruft this month and I know there's another 10k in there sitting around.
You've definitely used non-toxic and thoughtful products or you wouldn't be making such a distinction.
You are throwing the baby out with the bathwater by retreating to "tools". If you want to stop the enshittification, this is not the way. You're just opening yourself up to new scams. i.e. AI tools, political agendas, etc.
When I think of software that is minimally tainted by productism, these come to mind:
- linux
- nix
- nushell
- helix
Are these products... at all? Do the open me up to new scams? I don't think so.
I'm not in a position to demand anything from the people who make these things. If they were subject to demands, their craft would be tainted by compromises made in acquiescence to those demands, and I'd probably be less enthusiastic about their software (because presumably, my tastes don't align with whichever others are also in a position to be making demands).
Supply and demand are well and good if what you're after is barley. But when you compare what there's demand for with what's being supplied re: software, there appears to be no correlation.
We gotta stop selling picks and shovels and start learning to be miners who have good taste in picks and shovels and the ability to make and remake our tools as needed. The disconnect is creating a hell for our users.
I get what you mean about the conflict of interest for consumer software, but I don’t think a product mindset is inherently toxic. It’s more a function of where you are in the enshittification cycle.
I believe perfection is a dirty word because it has a pernicious effect on the human mind.
Striving towards perfection too often leads to yes over-engineering but I think more importantly it also: leads to way too much bike shedding due to the need to be perfect and allot of even emotional baggage when we don't make that perfect things.
Even the author admits that their definition of perfect can only arise with stringent requirements and I'll take it a step further, maybe it was perfect for that problem at that time but guess what change happens all the time, and as architects we have to think about future problems as much as current ones. With that in mind I think that having something that is good in many scenarios is better than the thing that is perfect in the scenario you start in.
I wouldn't say that "over-engineering means solving the wrong problem". It's possible that the idea is basically correct, but people are directing effort towards optimizing for constraints that don't really exist or can be dealt with once a better picture is in place, whether it's the mythical PMF or just "we now understand what the users want, let's build that".
The worst clusterfuck I've ever worked on was a web application that was actually solving a real problem fairly well, but the team was spending time building an absurd Rube-Goldberg contraption of microservices when the entire platform had less MAU than my hobby website. It wasn't the wrong problem, but it certainly was over-engineered!
> but the team was spending time building an absurd Rube-Goldberg contraption of microservices
This is literally the example that I use, the most common case of over-engineering, having more microservices than team members. Microservices are the right solution for certain problems, but those were not problems they had.
Some anecdotal evidence. I worked in many of these places, and the most common tell of over-engineering is that when you ask "what problem were we solving when we decided to have all these many microservices?" the answers you will get is problems they either didn't have (for example, high availability) or they state a problem they actually had but could have been solved in the monolith.
In other words, they "overshoot" and - as I write in the post - end up with "a system that solves multiple problems partially, none of them completely, while introducing a bunch of problems you wouldn't have had otherwise."
> Microservices are the right solution for certain problems
Just one problem, actually: Conway's law.
When your organization needs to operate like the macro economy, where independent groups provide services for each other, then you are going to see the same structure found in the macro economy mirrored in the microcosm of that single organization. Hence the name microservices. Same as services, except not across business lines.
At Google scale you have no choice but to have an internal economy, but trying to build an entire economy inside a small business with less activity than a personal blog is crazy.
While I agree with you, a large number of people use the term to mean that something was engineered to a complexity they have difficulty understanding. Abstraction is also treated as a dirty word, with people focusing on only one interpretation of the word.
While I don't actually agree with OP, I do agree with their sentiment. I've seen people say something is "over engineered" when there's an elegant design. Elegance isn't over engineering, it is solving problems effectively. It's something we should chase! Elegance is solving the right problem, which usually people are having a hard time seeing. (It's not always easy)
If we constantly let people drag quality down then we get into this frustrating world where everything is constantly half broken.
That is incorrect when dealing in the real world with many different humans using a complex system. I’ve heard other people say this, and it always confuses me how it is not self evident. Human interaction is not a maths equation. There are too many random variables.
"We're not trying to build a perfect solution here" is not something said to assuage over-engineering or encourage sloppy work.
It's said to head off a specific complaint from a specific kind engineer who will object that the proposed solution won't work because it doesn't cover some obscure edge case which rarely comes up in production.
"We're not trying to build a perfect solution here" is saying "We acknowledge not everything will be covered, we're setting the requirements at the 90th percentile use case".
The bane of my engineering career is working under engineers like this. It's like we forget we are doing a very analog thing (collaboration and building) under the guise of something digital. We should accept that there will be edge cases, there will be crashes. And unless you're actually in a life-saving industry, that is ok. (I say this with the idea in mind of a 10+ year old code bases spanning many new coding patterns that achieves over 99.5% crash-free)
Agreed. The much better question is: Are we locking ourselves out of a feature, or do we just leave a gap for a rare case?
At work we agreed that some use cases are very niche. These have guards in place to log an alerted-upon marker + return HTTP/500. They have not tripped for years by customers. So, it's fine to not support some rare cases and to deliberately leave known gaps in some contexts. As long as you don't close these paths forward if you need them.
On the other hand, we have contexts like our PostgreSQL instances. Those have a very well defined scope and rooting out all known problems has been the right choice. Most issues we have ignored in that scope have bitten us in the butt sooner rather than later. Very hard in some cases, I may add.
Correct. That is one of the worst types of engineers too. Constant paralysis by analysis. It's why Agile became a thing because those people just refuse to ship anything until it's utterly perfect and spotless. Fortunately they are a minority though.
This was my initial reaction to reading this post as well.
Additionally, as I get older, I find the sentiment of "we're not trying to build a perfect system here" is less about "let's just go fast vroooom" and more akin to saying "I've been humbled before by thinking I had the perfect mental model of the universe before a single user touched the product."
I've been that engineer and with good reason. That obscure edge case which rarely comes up in production is very disruptive when it does come up. The product person waving it off is also not the one that will get paged at 2am to address the issue when it occurs. Accepting that 90th percentile use case is infuriating because it is tacit consent for an unfinished solution with the rest being made up later with additional toil, now constrained by load bearing things you cannot change. Thanks for rushing the thinking and making my life harder later for no reason.
I think over-complicated and over-engineered are not the same thing.
Over-complicated is adding too many features, too much mechanism, too many moving parts. Over-engineering is far exceeding the requirements in an unhelpful way. For example: build me a treehouse. Said treehouse could probably be made of wood. If you made it out of concrete and steel, it might be a heck of a lot stronger and last a heck of a lot longer, without being more complicated (just more expensive). Too strong is over-engineered. If you made a treehouse with 13 bedrooms and an elevator, glass windows, solar power, and running water, that's overcomplicated.
I generally use:
- Overbuilt: Designed to handle more of the same. Too strong.
- Overengineered: Complexity not justified by the requirements.
Sometimes overbuilding (e.g. using really thick wood on a treehouse) means you can lower complexity (less bracing needed) at the cost of being more wasteful with material. That is often a great tradeoff.
"With one big caveat: you need a very clear set of requirements"
Most new product launches are an exercise in figuring out what the product requirements should be through trial and error (really: through ongoing dialogue with your users). Even mature products can have requirements change over time as the market changes.
I think internalizing this reality is why most senior engineers who work in domains that touch the messy real world will reflexively push back against perfectionism.
But this article allows framing this decision in a nice way. It's not a push back against over engineering, it's deliberate trade-off for speed by lowering quality, because quality is relative to the targeted outcome, which is yet fuzzy.
I concur. Most of the most painfully over-engineered swamp projects I've dealt with have been a result of not understanding the requirements.
Of course, this is not to imply that it's necessarily possible to know every requirement perfectly before you begin - I find that I frequently don't really understand the requirements until I've built the damn thing and it's made first contact with the real world.
So I usually toss that first version out, and build the second one. And that one is usually good enough that I don't immediately want to throw it out the window.
And by the time I get through the third version, usually a fair bit after the second is done, it's pretty damn good.
Because now I understand the problem and the requirements so much better than I did at the start, and I better understand what the system needs to be to solve that problem.
Will it be perfect? Never, except in a static and unchanging universe. But we can approach optimality for a given moment in time, which is functionally the same thing.
> Most of the most painfully over-engineered swamp projects I've dealt with have been a result of not understanding the requirements.
For me it's been a complete lack of requirements. Absolutely no one in the company seeming to want to make a firm decision on what we're building right at the moment. Every decision coming with a hedge "but maybe we might want to do this sometime later so we should keep our options open"
So any time there was a tradeoff we would take the option that leaves our options open, which takes way more time and effort to get right
> Every constraint on the table. Tighten those enough and something interesting happens, you end up with only one possible solution.
Is there always a unique solution? If constraints A is tightened and then determines the outcome, could that not also happen with a tightened constraint B that determines a different outcome?
Is it though? Engineering is providing the cheapest solution that corresponds to what your customer asked. As it goes: anyone can build a bridge with infinite budget, only an engineer can do it for cheap.
A system that's un-necessarily complex for what it does is over built, and under engineered.
I think it's important beyond semantics, because sometimes you have to propose designs that wouldn't be in books, yet they're the cheap and fit with all requirements, and are thus well engineered.
Now the difficulty is to figure the requirements that are not being expressed. "Allowing an occasional resurfacing" and "don't explode when there's icing" are obvious ones; the hard one for our job are "allow for adding another lane" or "allow support for trains as well"... because you know these might come.
I disagree, requirements are only a part of the engineering process. Perfection will be redefined after the product, tech, team knowledge changes, which will constantly.
Spend too much time thinking into perfection, trying to achieve it and you will get a convoluted system with lava-layer patterns full of complexity astronauts.
I agree with the actual principle I think this article is trying to put across (don't give up on technical quality too early by dismissing it as "perfection") but I strongly disagree with the framing.
My biggest problem comes from:
> Every constraint on the table. Tighten those enough and something interesting happens, you end up with only one possible solution. And that solution is, somewhat ironically, the perfect one.
Constraints are not rigid and unchangable [1] but flexible things which can bend to solve the problem at hand. Constraints can be: deadlines you impose, frameworks you are working within, the shape of the output schema, performance characteristics etc. And because they trade off against each other, tightening them leaves you with several solutions, each fitting a different trade-off.
Constraints also change over time as you and your team's collective understanding of the problem, the environment or priority of the problem change. This needs to be considered when building the initial system too: baking in some flexibility means giving up some of today's "perfection" to deal with future variance.
Understanding what constraints matter, which can be shaped and trading them off against each other is, to a very large degree, the job of software engineering. So no, I don't think you enumerate the constraints and find the only solution which fits; this is a very simplistic take on the complexities of building software.
[1] unless you work in a dictatorial company in which case my condolences!
I'd like to add an anecdote about machining.
Some designers impose incredibly tight tolerances on part shapes that hardly change the overall behavior of the final machine, but are so difficult to produce that sometimes special tools have to be bought and instead of being able to produce 4 parts an hour it might just be one instead.
Maybe this sentiment stems from being software engineers, but at least for hardware it becomes abundantly clear that "good enough" is a desirable state unless you want to spend years in development. The only direct comparison I can find in software is computer graphics, where the endeavor often halts as soon as the average person can't perceive a difference anymore.
> For someone else it's the wrong choice, they don't know Python
The author ruins their entire argument with this one claim.
If "we picked this tool because we know it best" is a legitimate requirement, then every tool choice is justified as a "perfect" choice because it's what the architect was most comfortable with. If your emotions and current knowledge levels are considered reasonable justifications for a "perfect" solution, then all solutions are perfect solutions; they simply haven't had enough emotional justification yet. If all solutions are perfect solutions, then none are.
There are, ultimately, two kinds of software - those that need to ship by a deadline, and those that don't. A deadline forces you to eject dead weight that you don't need - requirements have a habit of getting clarified real fast when you need to build to a deadline. If you had time to over-engineer despite a deadline, you should consider working for a more productive organization. Meanwhile, the concept of "over-engineering" is a little vague for software that doesn't have a deadline. If you don't have a deadline, you don't have to compromise on quality. "Over-engineering" is then just a value judgement that you made poor use of your infinite timescale and built the wrong things with it. But who is making that judgement? Not the person who built it, not the person who funded it (i.e. usually self-funded as a hobby project), and not the person who uses it (since over-engineering is an implementation detail, rather than a product choice), so who cares?
edit: to clarify: "pick a stack you know already since we don't have time to learn a new one" is a totally valid requirement. But I disagree that it means that you built a "perfect" solution with it. I also disagree that you usually need to build perfect software - getting comfortable with adequate is how most people ship most software.
I disagree, you’re optimising many different things and there is not a simple way to compare them. The very concept of Pareto optimal is about this! There is not one uniquely perfect solution to a problem most of the time.
Spending too much time gathering requirements can be a bad business decision too. Those requirements are often not even accurate.
Building a perfect system may not be over engineering to solve the problem perfectly… but what if you don’t need to solve it perfectly? What if it’s better to fail on some requirements to deliver sooner or cheaper or deliver this and some other project?
If something can be defined in rigorous mathematical terms and proven correct, that can save a lot of trouble downstream dealing with exceptions. Not all problems can be solved so neatly, but it's often worth putting in the effort to get from 99% to 100% correct if it means you never have to read or touch it again.
I really like the article above in that complexity does have to live somewhere. If you have a hard problem to solve, there is a minimum level of complexity (essential complexity) that can't be further reduced, only moved from one place to another.
And yet we often add accidental complexity by making bad choices (thinking of you, microservices). If your choice is bad enough, you get all the disadvantages (challenges with distributed systems, complicated debugging), but without any of its advantages (a distributed monolith doesn't get you any deployment or team independence).
Telling one from the other, well, is something I'd like to learn and that seems to require both experience and some sort of intuition.
As engineering is the act of solving technical problems, over engineering is about putting too much engineering effort on aspects, features or products that don't have a linear payoff to the budget spent.
E.g. I worked in a company that was obsessed with unit test coverage metrics and the effort of maintaining the test suite was considerably biting in the ability to move the product. The ratio was 25% of product and 75% of unit tests.
And the payoff was small if not even negative, impacted morale, productivity and actively pushed back against refactors, because any large refactor was met with a disproportionate effort in unit test rewriting. Let alone the fact that as you were mocking external dependencies, and the mocks costed engineering effort and internals digging, it also suppressed work to keep it up to date.
I never got to convince the org to shift focus on E2E testing, which answered the real questions: does the product make money and works as expected. Uni test had to be used when writing a parser, not to validate some react scroll component as browser apis and the triggers mimicking costed days of work.
In fact we often had gigantic all green test suites for broken products.
That's to me over engineering an aspect of the engineering cycle.
This is a good way of putting it. People who advocate for over-engineering don't consider cost-benefit vs not doing it. This is where FMEA analysis comes in when developing products, you assign values for severity, occurrence, and detection to determine which potential issues need extra effort implemented, but just as important is identifying the potential issues that aren't worth putting effort into to prevent over-engineering.
I've recently had to deal with a problem where the parent company has dictated 100% production testing for a spec that we meet for the spec sheet but customers practically don't ever need. But since the parent company test their products this way they say we have to. I've been very vocally against this because it has delayed us shipping products that are otherwise ready, prevented starting new products, and added a lot of production cost when our RMA rate without the test is something on the order of 0.01% and most of those are due to installation errors, not true failures.
It's not over-engineering if you are committed to what you are building for the long term, and know you'd very likely need stronger architecture at a certain time.
No one starts a project for perfectly objective reasons. Your choice of careers was not perfectly objective, you decided you liked a field, and it seemed you had enough aptitude for it, and then you did the work to get into it, learning as you went along and adjusting.
So it is with all kinds of decisions that go along with starting or changing a software project. Of course are your architectural and engineering acumen increases, you get a goo feel for how to choose good constraints and requirements, and then within the those constraints it is now up to you to find optimal solution for them, not the most over-engineered one.
Like I said before, sometimes going from A-B-C-D is much less efficient (and can actually be counter-productive) in the aggregate than going form A-D directly, even though YAGNI would dictate A-B-C-D. So long as you generally know that D would be needed sooner or later, A-B-C-D can be counter-productive because it causes to you to miss the forest for the trees. Even if you are not doing D immediately, some thought given to D can improve greatly your understanding of the context of A and the roadmap, leading to better design.
Note that string design instincts honed over time make this process work, not building out stuff just because of fads (e.g., microservices) or for some functionality you do not actually need. So here is my point of agreement with the article: the reason teams might waste time over-engineering is not because they are exploring a design space and objectives that is larger than what is strictly required for now; it is because they lack good architectural skill and experience and instincts. Again, considering D upfront can actually simplify the A you build now.
Let me do that for you. I think perfection is subjectively met at the end of one's consciousness about a problem. A higher being will see the problem more deeply, and its perfect solution could therefore be more perfect than that of a lesser being.
Imagine that one has to implement a function to multiply two large integers. A junior programmer will likely use the default multiplication operator. A mid-level programmer will add a couple of strategies with a weak heuristic for routing, and perhaps with basic caching. A senior will sample the incoming requests to identify which strategy might empirically be good, and ensure that it is being hit, with optimal fallbacks. A superintelligent AI might design custom kernels, and design a custom model for routing. A god will... we don't know what a god will do. Perfection, like beauty, is in the eyes of the beholder.
Perfection is always relative to a purpose or telos. So in the case of artifacts - which are the products of engineering, arts, and techne in general - the purpose isn't inherent to the object in the manner it is in oak trees and human beings, but projected onto the thing. It lives in the mind of the designer and the user. That's why we can say that technology is an "accidental" extension of human beings by which human purposes are realized and mediated in ways that inherent human powers cannot directly accomplish.
So, yes, if you have a purpose in mind, you can build something that can be used for that purpose, more or less effectively. The more general the purpose, the less specific the implementation; the more specific the purpose, the less general the implementation. Over-engineering here is something that indeed goes beyond the purpose of something, with feature creep as an example of moving goalposts and a slippery grasp of the limit.
However, production is only one layer of concern in engineering. You can talk about ideal artifacts whose forms perfectly enable the realization of some purpose, but there is the practicality of constructing one that must also be considered. Think of diminishing returns. Does it makes sense to invest a certain amount of time and resources into producing a more perfect artifact? If not, then pursing such perfection is itself an imperfection on the part of human judgement. That is, a more perfect engineer might know the limit of perfection for an idealized artifact, but he also knows what the perfect practical limit of justifiable effort is. Perfectionism is a sign of an imperfect engineer.
Returning to feature creep, the author might argue that the moving goalposts are a sign that the purpose is being refined, but feature creep can also mean that the shifting purpose is detached from real needs. So, in that case, the purpose served is no longer the purpose that ought to be served, and in that sense, while one may realize an end perfectly in a relative sense, the end pursued is itself the result of imperfect understanding, which is effectively what the author is gesturing at when discussing requirements.
Perfection is not "perfectionism". Former is provable given set of constraint. Later is anxiety due to fear of criticism.
"We don't want to build the perfect solution." Can both mean "no need to cover use cases we don't care about" and "no need to deal with divizion by zero in a product, where a number is divided by the user entered number".
Anyone saying "don't make perfection the enemy of good" is using a thought terminating cliché, avoiding the conversation of what is good enough. Worse, it is often used by people to drag quality down. If you're creating the "minimum viable product" you usually create a product that isn't working.
But, perfection doesn't exist. Most solutions in the world have no global optima. There are always tradeoffs. You must choose. You must argue with your peers to figure out that tradeoff. Perfection has infinite depth in detail. You must optimize. You just learn the unknown unknowns in an every moving landscape.
But you should still chase perfection. Like you chase a utopia. Like you chase your dreams. There's always something to improve on. Chasing perfection while knowing it can't exist means you will continue to search for the flaws. It means you will continue to improve. "Over engineer", because that's just engineering. Make things actually work, while recognizing they're always broken somewhere. Don't get offended when someone points out a flaw, you already know it's not perfect, so figure out if it is a tradeoff or can be fixed. Just keep improving things, because otherwise they keep getting worse
I'm all for pushing back against "let's not make perfect the enemy of good." I hear that all the time in reference to software that is usually quite bad, and often a little evil to boot--nothing good anywhere in sight except some poor schmuck trying to find a way to make their job something worth taking pride in.
I'm not sure I agree that systems are products though. The product mindset is toxic. It means that you've got goals which are independent of the user's goals (typically to make money, which sometimes means doing something dastardly to the users on behalf of the shareholders).
All the best software is more in the "tool" category and less in the "product" category. Usually it's made by the users, only bothers with solving problems they have, and has no ulterior motives.
I'm also wondering if "perfect" and "good enough" are not really as important now vs. when a team of software engineers had to spend sprints implementing features. The rate of iteration is faster now, the rate of regenerating entire code bases is days. We can perhaps over engineer /more/ today than before.
The real challenge is reigning in the size and complexity of the codebase if you're using AI to generate it. I have a bunch of skills (YAGNI / KISS inspired) but it still requires a significant amount of human effort. Knocked down about 9000 lines of cruft this month and I know there's another 10k in there sitting around.
You've definitely used non-toxic and thoughtful products or you wouldn't be making such a distinction.
You are throwing the baby out with the bathwater by retreating to "tools". If you want to stop the enshittification, this is not the way. You're just opening yourself up to new scams. i.e. AI tools, political agendas, etc.
Better products are just better. Demand them.
When I think of software that is minimally tainted by productism, these come to mind:
- linux
- nix
- nushell
- helix
Are these products... at all? Do the open me up to new scams? I don't think so.
I'm not in a position to demand anything from the people who make these things. If they were subject to demands, their craft would be tainted by compromises made in acquiescence to those demands, and I'd probably be less enthusiastic about their software (because presumably, my tastes don't align with whichever others are also in a position to be making demands).
Supply and demand are well and good if what you're after is barley. But when you compare what there's demand for with what's being supplied re: software, there appears to be no correlation.
We gotta stop selling picks and shovels and start learning to be miners who have good taste in picks and shovels and the ability to make and remake our tools as needed. The disconnect is creating a hell for our users.
I get what you mean about the conflict of interest for consumer software, but I don’t think a product mindset is inherently toxic. It’s more a function of where you are in the enshittification cycle.
Let's not make fixing bugs and backwards compatibility the enemy of shoveling out more features
I believe perfection is a dirty word because it has a pernicious effect on the human mind.
Striving towards perfection too often leads to yes over-engineering but I think more importantly it also: leads to way too much bike shedding due to the need to be perfect and allot of even emotional baggage when we don't make that perfect things.
Even the author admits that their definition of perfect can only arise with stringent requirements and I'll take it a step further, maybe it was perfect for that problem at that time but guess what change happens all the time, and as architects we have to think about future problems as much as current ones. With that in mind I think that having something that is good in many scenarios is better than the thing that is perfect in the scenario you start in.
I wouldn't say that "over-engineering means solving the wrong problem". It's possible that the idea is basically correct, but people are directing effort towards optimizing for constraints that don't really exist or can be dealt with once a better picture is in place, whether it's the mythical PMF or just "we now understand what the users want, let's build that".
The worst clusterfuck I've ever worked on was a web application that was actually solving a real problem fairly well, but the team was spending time building an absurd Rube-Goldberg contraption of microservices when the entire platform had less MAU than my hobby website. It wasn't the wrong problem, but it certainly was over-engineered!
> optimizing for constraints that don't really exist or can be dealt with once a better picture is in place
I think these both fall comfortable under the umbrella of "solving the wrong problem"
> but the team was spending time building an absurd Rube-Goldberg contraption of microservices
This is literally the example that I use, the most common case of over-engineering, having more microservices than team members. Microservices are the right solution for certain problems, but those were not problems they had.
Some anecdotal evidence. I worked in many of these places, and the most common tell of over-engineering is that when you ask "what problem were we solving when we decided to have all these many microservices?" the answers you will get is problems they either didn't have (for example, high availability) or they state a problem they actually had but could have been solved in the monolith.
In other words, they "overshoot" and - as I write in the post - end up with "a system that solves multiple problems partially, none of them completely, while introducing a bunch of problems you wouldn't have had otherwise."
> more microservices than team members
Or more microservices than customers
> absurd Rube-Goldberg contraption of microservices
Such a lovely analogy. FYI it’s mine now.
> Microservices are the right solution for certain problems
Just one problem, actually: Conway's law.
When your organization needs to operate like the macro economy, where independent groups provide services for each other, then you are going to see the same structure found in the macro economy mirrored in the microcosm of that single organization. Hence the name microservices. Same as services, except not across business lines.
At Google scale you have no choice but to have an internal economy, but trying to build an entire economy inside a small business with less activity than a personal blog is crazy.
While I agree with you, a large number of people use the term to mean that something was engineered to a complexity they have difficulty understanding. Abstraction is also treated as a dirty word, with people focusing on only one interpretation of the word.
While I don't actually agree with OP, I do agree with their sentiment. I've seen people say something is "over engineered" when there's an elegant design. Elegance isn't over engineering, it is solving problems effectively. It's something we should chase! Elegance is solving the right problem, which usually people are having a hard time seeing. (It's not always easy)
If we constantly let people drag quality down then we get into this frustrating world where everything is constantly half broken.
> I believe a perfect solution exists.
That is incorrect when dealing in the real world with many different humans using a complex system. I’ve heard other people say this, and it always confuses me how it is not self evident. Human interaction is not a maths equation. There are too many random variables.
"We're not trying to build a perfect solution here" is not something said to assuage over-engineering or encourage sloppy work.
It's said to head off a specific complaint from a specific kind engineer who will object that the proposed solution won't work because it doesn't cover some obscure edge case which rarely comes up in production.
"We're not trying to build a perfect solution here" is saying "We acknowledge not everything will be covered, we're setting the requirements at the 90th percentile use case".
The bane of my engineering career is working under engineers like this. It's like we forget we are doing a very analog thing (collaboration and building) under the guise of something digital. We should accept that there will be edge cases, there will be crashes. And unless you're actually in a life-saving industry, that is ok. (I say this with the idea in mind of a 10+ year old code bases spanning many new coding patterns that achieves over 99.5% crash-free)
Agreed. The much better question is: Are we locking ourselves out of a feature, or do we just leave a gap for a rare case?
At work we agreed that some use cases are very niche. These have guards in place to log an alerted-upon marker + return HTTP/500. They have not tripped for years by customers. So, it's fine to not support some rare cases and to deliberately leave known gaps in some contexts. As long as you don't close these paths forward if you need them.
On the other hand, we have contexts like our PostgreSQL instances. Those have a very well defined scope and rooting out all known problems has been the right choice. Most issues we have ignored in that scope have bitten us in the butt sooner rather than later. Very hard in some cases, I may add.
Realizing this about a domain is very important.
Correct. That is one of the worst types of engineers too. Constant paralysis by analysis. It's why Agile became a thing because those people just refuse to ship anything until it's utterly perfect and spotless. Fortunately they are a minority though.
This was my initial reaction to reading this post as well.
Additionally, as I get older, I find the sentiment of "we're not trying to build a perfect system here" is less about "let's just go fast vroooom" and more akin to saying "I've been humbled before by thinking I had the perfect mental model of the universe before a single user touched the product."
I've been that engineer and with good reason. That obscure edge case which rarely comes up in production is very disruptive when it does come up. The product person waving it off is also not the one that will get paged at 2am to address the issue when it occurs. Accepting that 90th percentile use case is infuriating because it is tacit consent for an unfinished solution with the rest being made up later with additional toil, now constrained by load bearing things you cannot change. Thanks for rushing the thinking and making my life harder later for no reason.
I think over-complicated and over-engineered are not the same thing.
Over-complicated is adding too many features, too much mechanism, too many moving parts. Over-engineering is far exceeding the requirements in an unhelpful way. For example: build me a treehouse. Said treehouse could probably be made of wood. If you made it out of concrete and steel, it might be a heck of a lot stronger and last a heck of a lot longer, without being more complicated (just more expensive). Too strong is over-engineered. If you made a treehouse with 13 bedrooms and an elevator, glass windows, solar power, and running water, that's overcomplicated.
I generally use: - Overbuilt: Designed to handle more of the same. Too strong. - Overengineered: Complexity not justified by the requirements.
Sometimes overbuilding (e.g. using really thick wood on a treehouse) means you can lower complexity (less bracing needed) at the cost of being more wasteful with material. That is often a great tradeoff.
"With one big caveat: you need a very clear set of requirements"
Most new product launches are an exercise in figuring out what the product requirements should be through trial and error (really: through ongoing dialogue with your users). Even mature products can have requirements change over time as the market changes.
I think internalizing this reality is why most senior engineers who work in domains that touch the messy real world will reflexively push back against perfectionism.
But this article allows framing this decision in a nice way. It's not a push back against over engineering, it's deliberate trade-off for speed by lowering quality, because quality is relative to the targeted outcome, which is yet fuzzy.
Beautifully written! I want to live in a world where we strive towards perfection, rather than strive to destroy it.
"Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away
This was my first thought too. I actually use this in my Agent prompts
I concur. Most of the most painfully over-engineered swamp projects I've dealt with have been a result of not understanding the requirements.
Of course, this is not to imply that it's necessarily possible to know every requirement perfectly before you begin - I find that I frequently don't really understand the requirements until I've built the damn thing and it's made first contact with the real world.
So I usually toss that first version out, and build the second one. And that one is usually good enough that I don't immediately want to throw it out the window.
And by the time I get through the third version, usually a fair bit after the second is done, it's pretty damn good.
Because now I understand the problem and the requirements so much better than I did at the start, and I better understand what the system needs to be to solve that problem.
Will it be perfect? Never, except in a static and unchanging universe. But we can approach optimality for a given moment in time, which is functionally the same thing.
> Most of the most painfully over-engineered swamp projects I've dealt with have been a result of not understanding the requirements.
For me it's been a complete lack of requirements. Absolutely no one in the company seeming to want to make a firm decision on what we're building right at the moment. Every decision coming with a hedge "but maybe we might want to do this sometime later so we should keep our options open"
So any time there was a tradeoff we would take the option that leaves our options open, which takes way more time and effort to get right
> Every constraint on the table. Tighten those enough and something interesting happens, you end up with only one possible solution.
Is there always a unique solution? If constraints A is tightened and then determines the outcome, could that not also happen with a tightened constraint B that determines a different outcome?
> Over-engineering is solving the wrong problem
Is it though? Engineering is providing the cheapest solution that corresponds to what your customer asked. As it goes: anyone can build a bridge with infinite budget, only an engineer can do it for cheap.
A system that's un-necessarily complex for what it does is over built, and under engineered.
I think it's important beyond semantics, because sometimes you have to propose designs that wouldn't be in books, yet they're the cheap and fit with all requirements, and are thus well engineered.
Now the difficulty is to figure the requirements that are not being expressed. "Allowing an occasional resurfacing" and "don't explode when there's icing" are obvious ones; the hard one for our job are "allow for adding another lane" or "allow support for trains as well"... because you know these might come.
I disagree, requirements are only a part of the engineering process. Perfection will be redefined after the product, tech, team knowledge changes, which will constantly.
Spend too much time thinking into perfection, trying to achieve it and you will get a convoluted system with lava-layer patterns full of complexity astronauts.
I agree with the actual principle I think this article is trying to put across (don't give up on technical quality too early by dismissing it as "perfection") but I strongly disagree with the framing.
My biggest problem comes from:
> Every constraint on the table. Tighten those enough and something interesting happens, you end up with only one possible solution. And that solution is, somewhat ironically, the perfect one.
Constraints are not rigid and unchangable [1] but flexible things which can bend to solve the problem at hand. Constraints can be: deadlines you impose, frameworks you are working within, the shape of the output schema, performance characteristics etc. And because they trade off against each other, tightening them leaves you with several solutions, each fitting a different trade-off.
Constraints also change over time as you and your team's collective understanding of the problem, the environment or priority of the problem change. This needs to be considered when building the initial system too: baking in some flexibility means giving up some of today's "perfection" to deal with future variance.
Understanding what constraints matter, which can be shaped and trading them off against each other is, to a very large degree, the job of software engineering. So no, I don't think you enumerate the constraints and find the only solution which fits; this is a very simplistic take on the complexities of building software.
[1] unless you work in a dictatorial company in which case my condolences!
I'd like to add an anecdote about machining. Some designers impose incredibly tight tolerances on part shapes that hardly change the overall behavior of the final machine, but are so difficult to produce that sometimes special tools have to be bought and instead of being able to produce 4 parts an hour it might just be one instead.
Maybe this sentiment stems from being software engineers, but at least for hardware it becomes abundantly clear that "good enough" is a desirable state unless you want to spend years in development. The only direct comparison I can find in software is computer graphics, where the endeavor often halts as soon as the average person can't perceive a difference anymore.
> For someone else it's the wrong choice, they don't know Python
The author ruins their entire argument with this one claim.
If "we picked this tool because we know it best" is a legitimate requirement, then every tool choice is justified as a "perfect" choice because it's what the architect was most comfortable with. If your emotions and current knowledge levels are considered reasonable justifications for a "perfect" solution, then all solutions are perfect solutions; they simply haven't had enough emotional justification yet. If all solutions are perfect solutions, then none are.
There are, ultimately, two kinds of software - those that need to ship by a deadline, and those that don't. A deadline forces you to eject dead weight that you don't need - requirements have a habit of getting clarified real fast when you need to build to a deadline. If you had time to over-engineer despite a deadline, you should consider working for a more productive organization. Meanwhile, the concept of "over-engineering" is a little vague for software that doesn't have a deadline. If you don't have a deadline, you don't have to compromise on quality. "Over-engineering" is then just a value judgement that you made poor use of your infinite timescale and built the wrong things with it. But who is making that judgement? Not the person who built it, not the person who funded it (i.e. usually self-funded as a hobby project), and not the person who uses it (since over-engineering is an implementation detail, rather than a product choice), so who cares?
edit: to clarify: "pick a stack you know already since we don't have time to learn a new one" is a totally valid requirement. But I disagree that it means that you built a "perfect" solution with it. I also disagree that you usually need to build perfect software - getting comfortable with adequate is how most people ship most software.
I disagree, you’re optimising many different things and there is not a simple way to compare them. The very concept of Pareto optimal is about this! There is not one uniquely perfect solution to a problem most of the time.
Spending too much time gathering requirements can be a bad business decision too. Those requirements are often not even accurate.
Building a perfect system may not be over engineering to solve the problem perfectly… but what if you don’t need to solve it perfectly? What if it’s better to fail on some requirements to deliver sooner or cheaper or deliver this and some other project?
If something can be defined in rigorous mathematical terms and proven correct, that can save a lot of trouble downstream dealing with exceptions. Not all problems can be solved so neatly, but it's often worth putting in the effort to get from 99% to 100% correct if it means you never have to read or touch it again.
The idea sounds related to the distinction between essential and accidental complexity.
https://ferd.ca/complexity-has-to-live-somewhere.html
I really like the article above in that complexity does have to live somewhere. If you have a hard problem to solve, there is a minimum level of complexity (essential complexity) that can't be further reduced, only moved from one place to another.
And yet we often add accidental complexity by making bad choices (thinking of you, microservices). If your choice is bad enough, you get all the disadvantages (challenges with distributed systems, complicated debugging), but without any of its advantages (a distributed monolith doesn't get you any deployment or team independence).
Telling one from the other, well, is something I'd like to learn and that seems to require both experience and some sort of intuition.
I don't share the definition of over engineering.
As engineering is the act of solving technical problems, over engineering is about putting too much engineering effort on aspects, features or products that don't have a linear payoff to the budget spent.
E.g. I worked in a company that was obsessed with unit test coverage metrics and the effort of maintaining the test suite was considerably biting in the ability to move the product. The ratio was 25% of product and 75% of unit tests.
And the payoff was small if not even negative, impacted morale, productivity and actively pushed back against refactors, because any large refactor was met with a disproportionate effort in unit test rewriting. Let alone the fact that as you were mocking external dependencies, and the mocks costed engineering effort and internals digging, it also suppressed work to keep it up to date.
I never got to convince the org to shift focus on E2E testing, which answered the real questions: does the product make money and works as expected. Uni test had to be used when writing a parser, not to validate some react scroll component as browser apis and the triggers mimicking costed days of work.
In fact we often had gigantic all green test suites for broken products.
That's to me over engineering an aspect of the engineering cycle.
This is a good way of putting it. People who advocate for over-engineering don't consider cost-benefit vs not doing it. This is where FMEA analysis comes in when developing products, you assign values for severity, occurrence, and detection to determine which potential issues need extra effort implemented, but just as important is identifying the potential issues that aren't worth putting effort into to prevent over-engineering.
I've recently had to deal with a problem where the parent company has dictated 100% production testing for a spec that we meet for the spec sheet but customers practically don't ever need. But since the parent company test their products this way they say we have to. I've been very vocally against this because it has delayed us shipping products that are otherwise ready, prevented starting new products, and added a lot of production cost when our RMA rate without the test is something on the order of 0.01% and most of those are due to installation errors, not true failures.
It's not over-engineering if you are committed to what you are building for the long term, and know you'd very likely need stronger architecture at a certain time.
No one starts a project for perfectly objective reasons. Your choice of careers was not perfectly objective, you decided you liked a field, and it seemed you had enough aptitude for it, and then you did the work to get into it, learning as you went along and adjusting.
So it is with all kinds of decisions that go along with starting or changing a software project. Of course are your architectural and engineering acumen increases, you get a goo feel for how to choose good constraints and requirements, and then within the those constraints it is now up to you to find optimal solution for them, not the most over-engineered one.
Like I said before, sometimes going from A-B-C-D is much less efficient (and can actually be counter-productive) in the aggregate than going form A-D directly, even though YAGNI would dictate A-B-C-D. So long as you generally know that D would be needed sooner or later, A-B-C-D can be counter-productive because it causes to you to miss the forest for the trees. Even if you are not doing D immediately, some thought given to D can improve greatly your understanding of the context of A and the roadmap, leading to better design.
Note that string design instincts honed over time make this process work, not building out stuff just because of fads (e.g., microservices) or for some functionality you do not actually need. So here is my point of agreement with the article: the reason teams might waste time over-engineering is not because they are exploring a design space and objectives that is larger than what is strictly required for now; it is because they lack good architectural skill and experience and instincts. Again, considering D upfront can actually simplify the A you build now.
There is not a perfect solution. There are many bad solutions. There are a few solutions that are OK but with different tradeoffs.
Fast. Cheap. Good. You can pick two of those, can never have all three.
Hang on. Let me overthink this.
Let me do that for you. I think perfection is subjectively met at the end of one's consciousness about a problem. A higher being will see the problem more deeply, and its perfect solution could therefore be more perfect than that of a lesser being.
Imagine that one has to implement a function to multiply two large integers. A junior programmer will likely use the default multiplication operator. A mid-level programmer will add a couple of strategies with a weak heuristic for routing, and perhaps with basic caching. A senior will sample the incoming requests to identify which strategy might empirically be good, and ensure that it is being hit, with optimal fallbacks. A superintelligent AI might design custom kernels, and design a custom model for routing. A god will... we don't know what a god will do. Perfection, like beauty, is in the eyes of the beholder.
Another point that goes along with this: good engineering is not premature optimization[1].
[1]: https://max.engineer/premature-optimization
Perfection is always relative to a purpose or telos. So in the case of artifacts - which are the products of engineering, arts, and techne in general - the purpose isn't inherent to the object in the manner it is in oak trees and human beings, but projected onto the thing. It lives in the mind of the designer and the user. That's why we can say that technology is an "accidental" extension of human beings by which human purposes are realized and mediated in ways that inherent human powers cannot directly accomplish.
So, yes, if you have a purpose in mind, you can build something that can be used for that purpose, more or less effectively. The more general the purpose, the less specific the implementation; the more specific the purpose, the less general the implementation. Over-engineering here is something that indeed goes beyond the purpose of something, with feature creep as an example of moving goalposts and a slippery grasp of the limit.
However, production is only one layer of concern in engineering. You can talk about ideal artifacts whose forms perfectly enable the realization of some purpose, but there is the practicality of constructing one that must also be considered. Think of diminishing returns. Does it makes sense to invest a certain amount of time and resources into producing a more perfect artifact? If not, then pursing such perfection is itself an imperfection on the part of human judgement. That is, a more perfect engineer might know the limit of perfection for an idealized artifact, but he also knows what the perfect practical limit of justifiable effort is. Perfectionism is a sign of an imperfect engineer.
Returning to feature creep, the author might argue that the moving goalposts are a sign that the purpose is being refined, but feature creep can also mean that the shifting purpose is detached from real needs. So, in that case, the purpose served is no longer the purpose that ought to be served, and in that sense, while one may realize an end perfectly in a relative sense, the end pursued is itself the result of imperfect understanding, which is effectively what the author is gesturing at when discussing requirements.
Perfection is not "perfectionism". Former is provable given set of constraint. Later is anxiety due to fear of criticism.
"We don't want to build the perfect solution." Can both mean "no need to cover use cases we don't care about" and "no need to deal with divizion by zero in a product, where a number is divided by the user entered number".
There is no such thing as perfect.
Anyone saying "don't make perfection the enemy of good" is using a thought terminating cliché, avoiding the conversation of what is good enough. Worse, it is often used by people to drag quality down. If you're creating the "minimum viable product" you usually create a product that isn't working.
But, perfection doesn't exist. Most solutions in the world have no global optima. There are always tradeoffs. You must choose. You must argue with your peers to figure out that tradeoff. Perfection has infinite depth in detail. You must optimize. You just learn the unknown unknowns in an every moving landscape.
But you should still chase perfection. Like you chase a utopia. Like you chase your dreams. There's always something to improve on. Chasing perfection while knowing it can't exist means you will continue to search for the flaws. It means you will continue to improve. "Over engineer", because that's just engineering. Make things actually work, while recognizing they're always broken somewhere. Don't get offended when someone points out a flaw, you already know it's not perfect, so figure out if it is a tradeoff or can be fixed. Just keep improving things, because otherwise they keep getting worse