12 ms·
Prefer duplication over the wrong abstraction (2016)
- cryo32 3mo agoYou can do both with microservices!
- zephen 3mo agoBut wait! There's more! For $19.95, you can replace your single single point of failure with multiple single points of failure!
- flawn 3mo agoOr for 100$, get a 5x increase on all failure points - maximum vibes, maximum excitement.
- DJBunnies 3mo agoExcept 9/10 times microservices end up wildly dependent on each other, yielding a distributed monolith. Better to use service oriented architecture and just ship the monolith, you can test easier and skip the extra layers of serialization / deserialization.
- loevborg 3mo agoI think you missed GP's point
- zephen 3mo agoPoe's Law FTW!
- mystifyingpoi 3mo ago> end up So it just happens, right? There is no remedy to this? You know the answer :) BTW I'm all for monolith.
- mohamedkoubaa 3mo agoPlease, stop it
- mystifyingpoi 3mo agoI get the joke, but in ideal world, in microservices, there is no such thing as code duplication across services. As a maintainer of a service, I should not give a crap about code present in some other service - it's some other team's code, why would I care? I don't have to even know that the other team exists. In big systems, it happens that I can't even feasibly know the existence of all the applications.
- cryo32 3mo agoYou mean you don’t have 300 versions of your badly developed rapidly evolving platform services rotting away underneath the bit you didn’t duplicate?
- mystifyingpoi 3mo agoI don't. Other teams - maybe they do, maybe they don't. Who cares, not me. I have responsibility for services of my team, I think we are doing a good job. Being selfish is the core principle of microservice architecture.
- cryo32 3mo agoUntil half your company gets laid off and you have to adopt other people’s shit.
- throwaway2037 3mo agoIn [an] ideal world, in monoliths, there is no such thing as code duplication across subsystems.
- antonymoose 3mo agoTwice a coincidence, thrice a pattern.
- anon-3988 3mo agoThe problem with coming up with a rule that works for everyone is that everyone have a different idea of what makes a good abstraction. Do you want to iterate using for loop or using .iter().step(2).map()? I would rather have consistency than a mixed bag of levels of abstractions.
- metaltyphoon 3mo ago> Do you want to iterate using for loop or using .iter().step(2).map()? I don’t think it matters, specially for sort sized loop scopes
- doix 3mo ago> Do you want to iterate using for loop or using .iter().step(2).map()? This isn't really a good example, assuming both can be used to represent the same thing. The problem with the wrong abstraction is when your abstraction doesn't let you represent something. Then, because of you've already invested so heavily into it, you start contorting the problem to fit your abstraction and it becomes a shit show.
- christophilus 3mo agoYes. I’m dealing with a graphql, urql, Next, Prisma stack at the moment. Something that would be a handful of lines of code in a different stack ends up being hundreds in this one. The Node ecosystem is full of wrong abstractions.
- Rohansi 3mo agoThe problem is self-inflicted. You do not need to keep jumping to the next trendy framework.
- RussianCow 3mo agoI don't know about you, but I generally don't write code in a vacuum. Other people may have touched it before me. Those other people may have made poor decisions. Not that I'm immune from choosing the wrong abstraction sometimes. More than once the "other people" was me. We all make mistakes.
- Rohansi 3mo agoOf course, but we should all be doing our best to push back against unnecessary framework churn.
- Capricorn2481 3mo agoDo you want us to call the previous company and explain what their framework choices did?
- em-bee 3mo agoif the majority of the team agrees, sure. but if i am in the minority then i'll appear uncooperative, and that may not be a position i want to be in.
- em-bee 3mo agomy paycheck needs me to.
- 3mo ago
- KHRZ 3mo agoThis is the biggest lesson I got from LMMs. I have a 1 million LOC vibe coded project that I can only imagine would fit in a few hundred thousand lines. But it's still holding up, I expected some kind of development collapse long before this point.
- cassianoleal 3mo agoI don't think that's a good lesson. OP is right that code duplication is far cheaper than the wrong abstraction, but the opposite is also true - the right abstraction is far cheaper than code duplication.
- gavmor 3mo agoWell sooner or later I would expect a developer who intimately understands their code base to feel compelled to start refactoring and extracting fitting, meaningful well-leveraged abstractions.
- imhoguy 3mo agoI don't think that will happen anytime soon. Prompts are the code now, and programming languages code is compilation product. Almost nobody optimizes compiled assembly code. Perhaps "recompilation" - rewrite by replaying all prompts in strict code quality context (linters, complexity & dedup checks) would make better abstractions. The only problem now is that LLMs are non-deterministic.
- gavmor 3mo ago> Almost nobody optimizes compiled assembly code. Compiled assembly code is not an input to the next compilation; source code is an input to the LLM's next inference. Sure, maybe "prompts are the code," but you must realize that code is also the prompt.
- gb2d_hn 3mo agoIt's made me wonder the same, but most LLM generated codebases haven't been around long enough to judge maintainability. I have noticed issues in some of my more LLM heavy code when I expect a change to be replicated in multiple areas, assuming common code / styling was reused, only to find it wasn't. It's for that reason I can't use LLMs for client codebases without heavy scrutiny of every line generated (for my own hobby projects I'm a lot more lenient)
- platz 3mo ago2016 (up to 2018 or so) may have been the peak of such varied activity in the developer ecosystem, including articles like this, whether it was discussion, ideation, OSS variety, language development. There has been growth since but it's been concentrated into fewer channels and somewhat industrialized.
- agentifysh 3mo agoi recall very early in my career i did exactly this. i took what worked duplicated it—my reasoning being that it was far safer to reuse what has been battle tested and leave refactoring at a later stage it wasn't received well and senior developer told me that 'good developers know exactly what patterns to use all the time before writing any piece of code and that he will clean up my mess' long story short his refactoring caused what was otherwise a stable system into a complete mess and it reminded me of Nassim Taleb's book
- nicoburns 3mo agoIt's definitely an "it depends" thing. It's easy to overabstract. On the other hand, I've also met junior developers who just didn't know how to use function parameters.
- dimgl 3mo ago> long story short his refactoring caused what was otherwise a stable system into a complete mess Yeah that totally happened
- rf15 3mo ago"use the right pattern" coming from a senior smells like a senior who can't freely design new patterns. Established wisdoms are a starting point, not the go-to solution.
- bhouston 3mo agoI used to struggle with abstractions back in my OOP days but since moving pretty much to a purely functional approach I find that code duplication is rare. Just have a function and call it in two parts. The main abstraction issue is then data structures but with TypeScript interfaces being duck typing essentially I run into few problems there as well. So code duplication because of abstraction issues is rare. Code duplication because of siloed developers is so much more common.
- platz 3mo agowhat exactly is 'calling a function in two parts'
- lysium 3mo agoI read it as „calling it from two places“
- saghm 3mo agoI assume they mean to call the function from two (or more) parts of the code (i.e locations). It's not immediately apparent why this is meaningfully different than what would be possible in Java though, since ostensibly a function is the same as a method by just moving the callee to the list of parameters. (There are some things in a Java method that you can do that don't translate to most functional languages, like invoking the version of the method from a superclass, but there's nothing forcing you to do any of those from the language perspective, so it seems a bit strange to claim that the language itself is the issue rather than maybe the specific patterns that were chosen, maybe by their coworkers or just not common in the ecosystem).
- bhouston 3mo agoYou can do functional in any language. I haven’t designed a new class in years in TypeScript and I’ve been more productive as a result.
- odo1242 3mo agoI believe they’re referring to callbacks / dependency injection / higher order functions to customize the behavior of a function?
- atmanactive 3mo agoNo it's not.
- znkr 3mo ago+1 The worst code I had to maintain was code that tried to follow DRY (without the trying to understand what the original intention of that principle was). The only way out of that mess was widespread code duplication.
- zadikian 3mo agoYep. Keyword "tried," as in they did it for a while then hit a point where it's impossible to faithfully follow the abstractions because they're wrong.
- tomjakubowski 3mo agoIt'll be fine, don't worry about it: just add a couple more obscure boolean parameters to that reusable function to support your new use case and ship it.
- bazoom42 3mo agoDepends. If the abstraction is just a level of indirection, then it is usually pretty simple to eliminate - just hit “inline function” in the refactoring tool a few times. On the other hand it is pretty difficult and error prone to consolidate duplicated code which have drifted apart over time. If in doubt, chose the approach which is simplest and least risk to revert if you discover in the future you made the wrong choice. I do agree a bad abstraction can cause huge problems. But it’s usually not the kind of abstractions introduced to eliminate code duplication, but the kind of top-down “architecture astronaut” abstractions, where a model is chosen which does not fit the complexity of the problem.
- strongpigeon 3mo agoEchoing the article, anyone who has experienced both will agree: it’s far easier to work with an under engineered code base than an over engineered one.
- tarcon 3mo agoContrary to that. The saying - Better to have a bad abstraction than none - was born from spaghetti code pain.
- HeavyStorm 3mo agoEven better to work with one that isn't shit.
- jstimpfle 3mo agoCode duplication is the wrong abstraction too -- unless it's not really code duplication but code that only happens to be similar for some really "unstable" reason.
- dofm 3mo agoI would agree that there are good "de minimis" reasons not to abstract code that isn't ready to be abstracted at all. If the pattern has not settled it shouldn't be forced into an abstraction (beyond those that make sure it is e.g. not vulnerable) But beyond that, any stable abstraction is better than duplicated code.
- dofm 3mo agoNo it's not. This has always been a needlessly iconoclastic rather than sensible suggestion. At the very least it is not once you're working at the wrong kind of scale. Once you have an awkward number of customers (more than five and less than a hundred), maintaining duplicated code that should have been abstracted and modularised will only seem cheap if you don't mind that you burn through even junior employees at a pace. And in the LLM era the wrong kind of scale appears in different ways; code generated and duplicated without proper abstraction and then maintained by an LLM that cannot be trusted to do the same modification each time it encounters a pattern or to have enough of an overview to slowly rescue duplicated code through good abstractions. I would go as far as to say that any abstraction you can maintain (that is in active maintenance, I mean) is better than code duplication once you are past a de minimis threshold.
- mawadev 3mo agoI think you applied this idea into the era of LLMs but consider an abstraction that takes in multiple god structs for branches it may or may not call in the case you are looking at and has a lot of if conditions that explode in combinatory complexity across a deep call chain. Now the bottle neck is that you need to call this function 144 times a second. That is where you start to have clusters of hot code paths where the latency stacks depending on the angle the god structs come in. Not sure what LLMs do here, I don't vibe code
- dofm 3mo agoI am applying it to LLMs on the basis of twenty years of seeing smaller programming shops tie themselves in knots by using duplication to avoid developing an abstraction that would help them because they were unsure of it. Everyone always thinks duplication is fine when you can bill the modifications by the hour. But they never think to understand that the reason they've had so many employees is that they've turned their change process into firefighting all the different versions of the same code and all these young developers burn out from the sheer anxiety of not knowing where all the little fires are. I once had to rescue a site that had become a victim of its own popularity, that was written by subcontractors who clearly believed that duplication is better than the wrong abstraction. Until one day, along came a change — MySQL 4 to MySQL 5 — and a significant duplicated query no longer worked due to its new, proper strictness. The problem was compounded; not only was the broken pattern in hundreds of places where it had sat, stable and predictable, but the pattern was broken because it, itself, was avoidance of another abstraction that would solve it. They quit: they said they couldn't and wouldn't fix it. It had always worked how they had done it, and it would have to stay on MySQL 4 (which the hosting provider refused to accommodate). I don't think it helped that they were severely misguided in their understanding of SQL, but the code had become beholden to duplication and then crippled by a new problem in the duplicated pattern. I had to first find all the contexts in which that pattern appeared (which required me to spend half a day on a bespoke script) and then work out a new pattern and as few variations of it as possible to fix the duplicated code in each place, because there was no proper budget to rewrite the whole thing. And then I sat at my desk, for days, working through each one, figuring out how to change it to fit the slightly different expression of the pattern. Even a total bullshit abstraction would have saved that client both time and money. And this is only one of dozens of times I've seen small firms simply duplicate and change code that would later become unmaintainable because of a straw breaking a camel's back.
- bob1029 3mo agoIf you work backward from the schema these sorts of things tend to evaporate before they can become a problem. Some of the biggest rabbit holes come from naming conventions not aligning across the business and technology silos. If everyone agrees that Customer has exactly 34 attributes, then it is possible to move to the next step of sharing libraries of types across the team. Getting your POCOs/DTOs 1:1 across the board is when the duplication really starts to melt away.
- sebastianconcpt 3mo agoOh the self-contradiction here... Generalizing this in the abstract is a wrong abstraction.
- northisup 3mo agoDuplication is fine, triplication and above is the issue.
- mjevans 3mo agoTriplication tends to be where it becomes more clear what the correct thing to abstract or de-duplicate is. It's of course possible to functional-ize segments of logic, but then the question of state mutation must be brought up. How isolated are these changes from other parts of the code / system state. Can this be run in parallel or is it something that must be serial? What potential race conditions exist?
- williadc 3mo agoThe "99 Bottles of OOP" book mentioned at the bottom was an excellent introduction to refactoring. I highly recommend it if you struggle with finding the right data models for the problems you work on.
- jbvlkt 3mo agoIt depends if duplication is accidental or real. I.e. if two taxes are using the same formula, it is accidental. If you use the same physic formula on multipla places, it is real duplication.
- originalcopy 3mo agoWhile I see the point, I think I more often encounter the opposite. Duplication, but not exactly duplication. Then the "sunk cost fallacy" is not an issue but there is huge maintenance cost and no-one feels like refactoring it. I'd rather refactor bad abstraction than 10x duplication.
- em-bee 3mo agobut those are exactly the cases where the distinction matters. when you have a situation where you can't duplicate the code exactly, then you really have to look carefully if this is actually the right place for a shared abstraction. i tend to wait and see if i can refactor one or the other to get them to be exact duplicates and only then see if i can fit in a common abstraction. and yes, finding that i later need to make the same change in both places is a sign that a common abstraction is probably the right call.
- Rendello 3mo agoTwo talks come to mind here: Mike Acton's Data-Oriented Design and C++ [1] and Brian Cantrill's The Complexity of Simplicity [2]. Mike's talk argues that code solutions need not be modelled on the real world, and that different data creates different problems, which need different solutions. I can't do the talk justice, but it's had a big impact on me. Brian's talk is about abstraction generally, and how it's difficult to find the "right" abstraction. 1. https://www.youtube.com/watch?v=rX0ItVEVjHc https://www.youtube.com/watch?v=rX0ItVEVjHc 2. https://www.youtube.com/watch?v=Cum5uN2634o https://www.youtube.com/watch?v=Cum5uN2634o
- lucas_t_a 3mo agothis one comes to mind https://youtu.be/17KCHwOwgms https://youtu.be/17KCHwOwgms
- saghm 3mo ago> Mike's talk argues that code solutions need not be modelled on the real world, and that different data creates different problems, which need different solutions. I've always found it odd when even fairly smart engineers sometimes prioritize real-world metaphors over the actual needs of the codebase. Years ago when I was only a few years out of school, I was implementing a connection pool in Rust, and the most reasonable way to implement it was to have the connection hold a weak reference to the pool so that it could get checked back in automatically when dropped. My manager (an extremely experienced engineer) didn't like this idea because "a library holds library books, not the other way around". I didn't feel like this was a compelling reason to design things differently, but he refused to engage with the issue in any way other than through the lens of that metaphor. Eventually the impasse was solved by one of the other managers in my department suggested that while library books don't contain libraries, they do have the name of the library stamped in the back as a reference to where they should be returned, and I guess my manager found this to be a reasonable extension of the analogy. If I were more experienced, maybe I would have recognized that I could find a way to engage with the analogy like the other manager did without ceding the point, but even today I still feel that it was completely bizarre to insist on that as the canonical way to frame things rather than just considering the ramifications of the abstraction in the code and the experience of using the library based on it.
- deleted 3mo ago[deleted]
- Ozzie-D 3mo ago[flagged]
- ChrisArchitect 3mo ago(2016) Some previous discussions: 2023 https://news.ycombinator.com/item?id=35927149 https://news.ycombinator.com/item?id=35927149 2021 https://news.ycombinator.com/item?id=27095503 https://news.ycombinator.com/item?id=27095503 2020 https://news.ycombinator.com/item?id=23739596 https://news.ycombinator.com/item?id=23739596 2018 https://news.ycombinator.com/item?id=17578714 https://news.ycombinator.com/item?id=17578714 2016 https://news.ycombinator.com/item?id=11032296 https://news.ycombinator.com/item?id=11032296
- ultim8k 3mo agoNobody wants to listen. Nobody. In 90% of the companies there are some so called senior devs that get ecstatic when they create a new abstraction. Overengineering, abstractions and premature optimisation are the 3 worst plagues of engineering. At the same time I’m happy they exist because it means we’ll always have a job.
- rzmmm 3mo agoThere are codebases out there with enormous amounts of duplication, filled with implicit dependencies. You just haven't encountered them to appreciate good abstraction.
- HeavyStorm 3mo agoI think that all this people defending this post have no idea that this rationale can cause. I'm sitting at a codebase that has the same code for iterating copied around 20 times. As a senior I will tell you to dedup whether I can to avoid this shit.
- codemog 3mo agoYep Kubernetes, more micro services than engineers, some complicated protocol that saves a few bytes of overhead, cloud everything, and tons of classes that could have been simple functions.
- throwaway2037 3mo agoThe part that no one want to say out loud: Making boring technology decisions makes you job boring and does not help to build your resume. This is the core reason why over-engineering exists.
- skydhash 3mo agoMaking the job boring is a great way to get free time to browse HN.
- globular-toast 3mo ago
- MrGando 3mo agoI once had to work with a system that was refactored and abstracted away heavily to use Redux. It didn't work then, the implementation had way too many abstractions, doing any change meant you had to touch dozens of files. It was insanity. Left me with a bitter taste regarding the redux pattern for ever (probably not the pattern's fault).
- SoftwareMaven 3mo agoOver-abstraction is as much of a problem as under-abstraction. If the abstraction isn’t improving your ability to produce good code, it’s a bad abstraction. I’ve worked with a lot of abstraction patterns in a lot of languages over the 30 years of my career. Any of them can be good or bad. Unthinkingly applying them is always a problem. Unsurprisingly, that goes for just about any idea in software development. I worked in one code base that heard small functions are best, so every function was less than three lines long. You don’t gain anything by replacing `lst.get(0)` with `get_first_item_in_list(lst)` (in fact, understanding becomes much more difficult), but breaking down functions into the smallest units that make sense independently within the business domain can be very helpful, both for understanding and testing.
- MrGando 3mo ago100% agreed. It's interesting since I see over-abstraction often abused by "clever" engineers (sometimes quite experienced actually). Sometimes I wonder if they do that to make themselves indispensable on purpose and create their silos in the codebase.
- aappleby 3mo agoThe smallest amount of simple code that solves the problem wins. Everything else is irrelevant.
- hedora 3mo agoI’ve seen code bases that evolved like that. The problem is almost always outside the abstraction that has a pile of conditionals. Usually, some moron decided to copy paste things a few levels up and then the top half of the system metastasized into two parallel universes of broken garbage. For instance, one might decide to perform auth later in the flow so unauthorized handlers can run and set a “this requires auth” bit that defaults to false, and the other flow could add a forged auth header before the auth step. Now, the auth handler needs a “allow forged header” flag and a “already authenticated” flag. I’ve seen that grow to a half dozen cases until massive production dataloss occurred. A buggy client tried to delete something local to their account without specifying a userid as a parameter (this codebase was garbage!) and deleted the something for all users instead. I can’t remember how the dataloss was “fixed”, but it definitely wasn’t “all requests go through a simple auth check, and all handlers declare/implement their auth requirements in the same way”. Getting a design approved to require a user id be specified exactly once for account-level operations was fantasy land for that team. (Most hires with any sort of engineering talent bounced in under a year.) Anyway the “abstractions are hard so copy paste” approach did provide job security for the lifers on that product. I can’t imagine them holding a job elsewhere, but they were completely immune to layoffs (hostage style). This is a pretty valid approach if you’re an agent hired to perform industrial sabotage, or if you keep replacing keyboards after you knaw through the corner.
- fjfaase 3mo agoI once used code duplication to implement a fourth type of dialog that looked somewhat similar to the others, that were sharing a lot of code, because I felt that although it looked much the same as the others, there was some fundamental difference. Took me about a day to implement. When some other engineer saw this, he spend the next three weeks trying to integrate all of them with some shared class. His work was not completely worthless, because he did find some small bug during all his efforts to avoid any possible code duplication. I already had predicted that it would take a lot effort, but I did not object, because I hoped that he would learn something from it and the next time think twice before always trying to avoid code duplication.
- irishloop 3mo agoToo many abstractions are bad. Too many code duplication is bad. Part of being a good engineer is finding the right balance. I know engineers who would gladly duplicate code all over the code base to avoid creating a new abstraction. I know engineers who create polymorphic abstractions for a single caller with a very obvious set of parameters. So much of wisdom is in finding balance and not being dogmatic about rules.
- lokar 3mo agoI feel like the balance has shifted over the last 30 years, and is speeding up. Semi-automatic and fully automatic re-factoring has made dealing with duplicated code much faster, cheaper and safer. Changing abstraction is still high risk.
- heisenbit 3mo agoChanging abstraction is a high risk unlike agents refactoring scores of almost identical code.
- lokar 3mo agoI thought this discussion was limited to situations where you care about code quality
- chuckadams 3mo agoI have regularly watched agents forget to update one duplicated pattern after changing it somewhere else. If it's within a single file or related class, it'll catch it, but if it's off in some other package in the monorepo, it's a crapshoot.
- crazygringo 3mo agoIsn't it the opposite? Automated re-factoring means you can refactor duplicated code only as long as it is exactly duplicate. Whereas the whole problem is that when somebody changes 3 out of 10 of the duplicate cases in a simple way that they are no longer exactly duplicate, and then somebody fixes a bug in one of the other 7/10 cases, they can update the bug across the 7 "duplicate" cases but they'll miss the 3 that aren't. The problem with duplicate code is always when some of the instances get changed/fixed but not all of them. And that when somebody edits one instance, they often aren't even aware of all the other instances. Abstractions are low-risk, because you know where the code is. If it's the wrong abstraction, you can fix that and know what you're fixing. Whereas with duplicated-yet-modified code, you've now lost the connections between them.
- TexanFeller 3mo ago> Code duplication is far cheaper than the wrong abstraction Very true in some sense, but I continue to encourage DRY-bias because I've literally never seen teams duplicate code responsibly and later dedupe it when it's the right time. 95% of the time this sentiment is quoted to justify shipping quick slop and stable reusable bits are never extracted into a shared lib later.
- bluefirebrand 3mo agoIn my experience if your organization can't commit to doing WET (write everything twice) code then it probably also will fail at doing DRY (don't repeat yourself) code Maybe this is an area where AI can help identify duplicate code though to show opportunities for de-duping
- lg5689 3mo agoI believe that "single source of truth" is a principle that should always be followed. If there's duplicated code where it'd be a bug if they diverge, then you should refactor. It creates a long-distance coupling in your code that may be invisible to future developers until a bug emerges. But with that in mind, I mostly agree with the article: if it's not a violation of "single source of truth", then abstractions are just a convenience. If it starts being inconvenient, then it's not doing its job and there's no reason to use it. It's a serious code smell if a function needs several flags for custom behavior; that means it's probably the wrong abstraction or violating the single responsibility principle. If there is a legit need for lots of customization, an often-good way to handle is to take a function/functor as an argument for the customization. E.g., rather than `solve(f:double -> double, max_iters = 99, x_abs_tol = 1e-15, x_rel_tol = 1e-15, ...)` you can do `solve(f:double -> double, stopping_criteria: StoppingCriteriaClass)`
- jonahx 3mo ago> I believe that "single source of truth" is a principle that should always be followed Fundamentally, the article addresses cases where it's not clear yet how many sources of truth there will be. Are the two spots in the code using the same algorithm, or slightly different versions? More importantly, will they change for the same sorts of reasons? The title adage (correctly, imo) argues that making two different things the same will cause you more pain than making two same things different via duplication. In the latter thing case, the "damage" is just having to make the same changes twice, or doing a refactor to introduce the abstraction. In the former case, you have to keep adding to your abstraction, or undo it. Most crucially, it breaks "locality", which is the only property you really care about when making changes. I just want to make this change and not worry about side effects to unrelated parts of the system.
- stanmancan 3mo agoThe issue with not having a single source of truth is not the fact that you have to update code in 2-3 places, it’s that you have to know to update code in 2-3 places. Accidental divergence is the problem, not intentional.
- joshmoody24 3mo agoI've seen the pendulum swing between duplication and abstraction a few times in my career, and I'm currently on team "it's usually not that hard to find a good abstraction up front." IMO it's easier to inline a bad abstraction than it is to consolidate a bunch of subtly different things that should have been abstracted from the beginning. But I expect people's opinions on this differ wildly based on their personal experiences. Just my anecdotal take.
- luckystarr 3mo agoHow I see this: Refactoring code to reduce the number of lines is _compression_, akin to RLE coding. Refactoring the code to lift conceptually coherent parts is _abstraction_. Less compression, more abstraction. Then you're fine.
- tetha 3mo agoI watched a talk by her about this, and this post is missing half of the equation, which is really important: Having a wrong abstraction means you end up with a class/function/module with a huge amount of configurations through boolean/enum parameters. It's not even clear that all combinations of configurations is even valid. This situation may be simplified by duplicating, and then eliminating code, thus creating more streamlined code for each use case. This may require fixing similar or cross-cutting bugs in multiple places (eg: JSON serialization is stupid, need to hack a workaround), but keeps the business logic changes simple. Maybe a bit more numerous, but the code is able to raise all the scenarios to consider. Having no abstraction means you may have to change business logic consistently in multiple places, or you have to fix exactly the same misconception (aka a bug) in multiple cases. e.g. tax rate management in a multi-national context. This is also terrible, because you may fix an important problem in one place and forget other places with the same issue. Now you missed 12 potential bugs by fixing one. This can however allow you to discover a true abstraction. Maybe these 12 places should call just one place? But for code evolving across a team understanding this tension, a bit of duplication while waiting for confirmation that these pieces of code break together and change together is better than just shoving the same 3 if-statements into a function to avoid "line duplication". Concept duplication is more important.
- gb2d_hn 3mo agoInterface over inheritance is the paradigm I try and stick to. I'd rather maintain orthogonal code than code with overuse of inheritance because of over adherence to DRY.
- dmos62 3mo agoIf it's duplication, it's the same abstraction by definition. The fundamental unit of programming is intent, not code.
- ozgrakkurt 3mo agoThe discussion around this topic would be nicer if the title had "can be" instead of "is". Otherwise what is better is better and we don't know what we don't know
- slopinthebag 3mo agoI prefer the go mantra: a little copying is better than a little dependency. Abstraction is a vague term when used here. Is a shared function an “abstraction”? It’s more like implementation hiding, maybe some data hiding. But you definitely have a dependency on it now. Acronyms like DRY are for beginners. Once you get good you know when to break the “rules” (and when not to).
- mcculley 3mo agoYes, if your programming language/environment is weak.
- aplomb1026 3mo ago[flagged]
- mohamedkoubaa 3mo agoDuplication is often a small price to pay for isolation
- omoikane 3mo ago> Programmer A sees duplication. This step should also be parameterized by how many times the duplication has occurred. Refactoring preemptively may lead to poor abstractions, but not refactoring after seeing the exact same thing tens of times would also be weird. See also: https://wiki.c2.com/?DuplicationRefactoringThreshold https://wiki.c2.com/?DuplicationRefactoringThreshold https://wiki.c2.com/?ThreeStrikesAndYouRefactor https://wiki.c2.com/?ThreeStrikesAndYouRefactor
- meerita 3mo ago[flagged]
- ilvez 3mo agoJust three words: rule of three.
- LunicLynx 3mo agoThe generic repository "pattern" is the prime example of this. There might be CRUD operations shared between repositories, but they should not be that base of every repository. I've seen this so many times, because in the beginning the CRUD stuff is what you code over and over again and then suddenly business logic emerges and everything breaks down, but the repository prevails because sunk cost fallacy ...
- DmitryOlshansky 3mo agoI would argue that _premature_ abstraction is worse than _some_ duplication of code. Also I’ve seen the kind of codebase that seems to be LZW packed due to the sheer desire to DRY everything out. Not pleasant thing, by the time you goto 10 layers deep on some “helper” function you forgot why you in there.
- Verdex 3mo agoCheaper is skipping a step. Code duplication and 'wrong' abstractions both count themselves amongst the other foibles of programming. But they don't directly produce a cost which can be cheap or expensive. They produce some other high dimensional intermediate value which can then produce highly variable cost dependent on the domain, goals, and scenario. As ever, it depends. The depends is quantifiable, but it doesn't fit in a blog post. Think more along the lines of war and peace.
- time4tea 3mo agoYou dont know immediately if something that superficially seems the same actually is. Copy and paste once is fine, twice, not so much. Often I've seen two totally different things exist in one bit of code, no overlap! Premature generification is bad, and leads the developer to believe that two things are the same, making it harder to see they are not. Also, can make it much harder to see that a different abstraction would give a cleaner outcome....
- nullbio 3mo agoCode duplication is terrible in the age of LLMs, unless you want to maximize on drift.
- andix 3mo agoI always try to design in a way, that using abstractions/shared logic is optional. I've worked in too many projects, where every new feature needs to be built on top of existing abstractions, that often lead to severe restrictions if something slightly different is required. I always try to create reusable units/components, that can either be used as intended or replaced by something that behaves slightly different if needed. Components are not necessarily frontend components, this extends also to backend logic.
- johnwheeler 3mo agoThese are not mutually exclusive.
- dang 3mo agoRelated. Others? The Wrong Abstraction (2016) - https://news.ycombinator.com/item?id=35927149 https://news.ycombinator.com/item?id=35927149 - May 2023 (69 comments) The Wrong Abstraction (2016) - https://news.ycombinator.com/item?id=27095503 https://news.ycombinator.com/item?id=27095503 - May 2021 (17 comments) The Wrong Abstraction (2016) - https://news.ycombinator.com/item?id=23739596 https://news.ycombinator.com/item?id=23739596 - July 2020 (240 comments) The Wrong Abstraction (2016) - https://news.ycombinator.com/item?id=17578714 https://news.ycombinator.com/item?id=17578714 - July 2018 (207 comments) Prefer duplication over the wrong abstraction - https://news.ycombinator.com/item?id=12061453 https://news.ycombinator.com/item?id=12061453 - July 2016 (96 comments) The Wrong Abstraction - https://news.ycombinator.com/item?id=11032296 https://news.ycombinator.com/item?id=11032296 - Feb 2016 (119 comments)
- jeffypoo 3mo agoI've always told engineers to duplicate until the abstraction is punching them in the face.
- dang 3mo agoI dislike duplicate code as much as anyone, but agree with the OP that bad abstractions can be worse. They add confusion and complexity which compounds over time, since people are forced to build on top of them in ways that (by definition) don't suit the underlying domain and ultimately become self-referential. This leads to contortions, workarounds and even more bad abstractions which ought not to be there—they're reactions to the code not fitting the problem, or as Fred Brooks called it, accidental complexity. You end up in an evolutionary dead end where the system is hard to extend because it's too hard to understand. I've learned to tolerate a small amount of duplicate code for this reason. If the duplication remains small, it's not that harmful, and if it starts to grow, one has a better shot at finding a good abstraction for it. Bad abstraction is premature abstraction. One thing I'm not sure this thread has mentioned yet is how LLMs alter the cost-benefit curve of this. They are much better at managing duplication than humans are, and much better at noticing inconsistencies - the sort of small bugs which duplication traditionally leads to. I don't know if this is enough to count as a different kind of good abstraction; I doubt it. It reminds me of a petroleum economist I once knew who had 200 duplicate spreadsheets analyzing different projects and who hired a junior analyst to keep them all consistent. An LLM would be like the junior analyst.
- vcryan 3mo agoThe sweet spot is really duplicating the wrong abstraction: I see you Claude!
- stcg 3mo agoThis is like saying "A slow leak is cheaper than a burst pipe" Yes, okay. But with both you will have a bad time cleaning up. There is a third option: good abstractions. I did see this pattern described in the blog in practice a lot (and fell victim to it myself) and I think that in general this comes down to inexperienced programmers. Object oriented programming makes it worse. Teaching these programmers that they should not abstract is not the solution. It is blocking their growth. Teach them how to make better interfaces instead.
- bogrollben 3mo agoOne thing I don't see talked about often is the fact that not all duplication is equal. Duplicated html/xml/markup does not equal template-based boiler plate, which does not equal almost everything else. I'm far more forgiving of duplicate html/markup because that code is so cheap.
- alkhimey 3mo agoThe disadvantages of duplication are greatly reduced in the world of AI. From my experience it can easily detect the duplicates and refactor code safely. On the other hand, code without abstractions is easier to read and easier for AI. With AI, we really need to rethink the clean code principles.
- more-coffee 3mo agoSeems to me like the last thing you want to do is worry whether the LLM has a large enough context window to keep an eye on all duplicates. So I'd argue to deduplicate directly, where possible.
- aarjaneiro 3mo agoPersonally I've seen way more duplication as a result of AI in large codebases
- felooboolooomba 3mo agoThe bad thing with abstractions is when you start it too early in your code base for things. It's also a bad thing when you start it too late, although not as bad. If you start it way, way, way too late it's very, very, very bad. Of course, the worst abstractions are the ones you don't need at all.
- fpoling 3mo agoWith LLMs the cost of duplication is much lower both to write and maintain. So abstractions needs much higher justification.
- ninalanyon 3mo agoYou have some evidence for that assertion?
- lazide 3mo agoYes, but counterpoint - code duplication is also the wrong abstraction. Pro tip - which is the least bad abstraction? Answer: it depends!
- deleted 3mo ago[deleted]
- zadikian 3mo agoI always felt like I abstract and modularize things way less eagerly than other programmers. Was pleasantly surprised to find that LLMs do it mostly my way by default, then again they're also bad at abstracting when it's actually needed.
- dan-robertson 3mo agoI think LLMs are trained to not refactor. I think it’s either that you would need to do something in training to make them want to do it and the labs don’t do that, or that the labs correctly guess that it would be very annoying for LLMs to go and refactor your existing code as they go. This creates bad effects (eg crazy hacks to avoid refactoring and, much worse, not refactoring the code they only just wrote as required) but I think the alternative would be worse – it’s not something you always want to read and the refactoring is often done incorrectly, restructuring the code to the best shape for the current task rather than something that balances many different needs.
- davnicwil 3mo agoOne way I like to think about is that often abstraction is an automation for a task that doesn't need automating. You hardly ever change the thing and if you do, changing it in two or three places 'manually' is really not a big deal. Now changing something fairly often, that affects logic in 50+ places? Then it makes sense to automate with an abstraction so it all flows through the same lines of code. I know I've personally spent way more time over the years debugging bad abstractions than changing things in a few places.
- hakunin 3mo agoI think what this advice is really getting to, is that you should prefer everything generally build-time/hardcoded/static rather than runtime/dynamic. Wrote about this in 2013 (calling it a CMS trap[1], back then seemed pertinent). [1]: https://max.engineer/cms-trap https://max.engineer/cms-trap
- SameerVers3 3mo ago[dead]
- Waterluvian 3mo agoI think about this on occasion. Most recently I ran into an issue during a personal project: 2d sprites for RTS units were packed on spritesheets in a consistent manner: 5 sprites for 8 directions (you mirror 3). Packed in order of: stand, move, attack, die. So I made a loader that understands how to take action + direction and offer an array of sprites to play through. But then I came across more cases: sprites with no directionality (an explosion), and corpse sprites (which were only 4 directions, 2 mirrors, and most except the first four were shared by both orcs and humans). I agonized for a little bit on what the hell the common abstraction is for all this. In the end, I factored out some of the loading code, and made a UnitLoader, CorpseLoader, EffectLoader and moved on. Now, there's probably a better abstraction in there because all 3 loaders have to reason about the same things a little bit. But I will discover that abstraction later on and it's easier to just de-duplicate the code then, rather than try to identify the abstraction now and make some complicated EverythingLoader that handles all those cases.
- galleywest200 3mo agoThis is the way. Making games is supposed to be fun. You can do the hard boring stuff when you get to the final 10% of the project. Besides, sometimes your duplication creates "bugs" which may turn out to be fun features that players enjoy.
- andai 3mo agoI like this quote, "things should be made as simple as possible, but no simpler." I think the natural instinct with programming is to try and simplify the code by means of generalization. But we often over-simplify, and reality is messy. Or as TFA mentions, time passes and new requirements arise, so it turns out that we have simplified prematurely! Sounds like this should be an aphorism. Premature abstraction is the root of much suck!
- dahart 3mo agoYou probably already have the common abstraction factored - the code to load pixels for a single sprite, and to display it? It makes sense to me that the level above that, interpreting the sprite sheet layout and modes of playback, come in different flavors and don’t have a common abstraction that fits all cases. Personally I prefer what you’re doing over trying to come up with a non-obvious abstraction or trying to make an imperfect abstraction fit. Waiting til the abstraction is totally obvious and the need is crystal clear is a good thing. The flipside (antidote?) of DRY is WET - write everything twice/thrice. More important, IMO, is to abstract only over things I have an actual, demonstrated use case for, usually demonstrated first via duplication, and not speculate about possible future uses I might want. Code written for future use cases we don’t have is so often the code that gets in the way of abstracting the things we do have, and it cracks me up when that happens.
- _pdp_ 3mo agoThe biggest mistakes young engineers make is working out a problem from bottom up... i.e. building frameworks and libraries, rather than exploring the problem space which is more chaotic. You cannot find the edges of the system with structure you don't understand because once the abstraction are set in place solutions often have the same shape as the frameworks which leads to ultimately really bad systems. The best way is often not the obvious way. Once you reach the edges then you can think how to program the abstraction but that is many versions down the line from the original.
- stevefolta 3mo ago"If you have a procedure with ten parameters, you probably missed some." -- Alan Perlis
- aftbit 3mo agoSimilarly, I've seen some developers who seem to think that any inline string or numeric constant is evil. In one PR, I saw: HTTPS_SCHEME = 'https' DOMAIN = 'www.example.com' url = HTTPS_SCHEME + '://' + DOMAIN I don't understand what they think this is buying, other than just cargo culting "don't embed constants." And of course, the constant definitions were at the top of the file and the url building code was hundreds of lines away.
- preg_match 3mo agoI’m a big fan of closeness in code. I prefer defining things as closely to where it’s used as possible. This is a big pet peeve for me! Do not put regex at the top of the file either! Put it where you use it. Languages are smart, they’ll probably be able to tell that it’s constant anyway. Also for tiny functions just use a lambda. Please don’t make a one line function a million miles away that you use once or twice.
- robotresearcher 3mo agoAmen! The existence of 'helpers.js', 'utils.cc', makes me twitch.
- deleted 3mo ago[deleted]
- what 3mo agoIf multiple things use the same regex, which one should it be close to? Or do you propose duplicating it?
- preg_match 3mo agoMy go-to is actually wrapping it in a lambda. The reason being you get nice syntax highlighting you don’t get with string literals! (In PHPStorm the string part of preg_match gets regex highlighting)
- 3mo ago
- gilleswr 3mo ago[flagged]
- andai 3mo agoSee also: Muratori, Semantic Compression ("Compression-Oriented Programming") https://caseymuratori.com/blog_0015 https://caseymuratori.com/blog_0015 Previously discussed: https://news.ycombinator.com/item?id=17090319 https://news.ycombinator.com/item?id=17090319 https://news.ycombinator.com/item?id=36455794 https://news.ycombinator.com/item?id=36455794 https://news.ycombinator.com/item?id=46183091 https://news.ycombinator.com/item?id=46183091
- infinitebit 3mo agoI feel this deeply. Although abstraction isn’t a one way door, “deduplicating” logic tends to be much easier than breaking big functions back down, and so these days I tend to leave a comment with the date wondering if it is too similar to some other code. then if i come across it again months later and it still is, then maybe it is safe to make it DRY. I think DRY is the first heuristic for “good code” that most junior devs actually grasp, and so they become very dogmatic about it for a while
- threethirtytwo 3mo agoDuplication and the wrong abstraction are looking more and more to be implimentation details handled by AI. A possible future may be a place where none of this matters.
- hyperpallium2 3mo agoprefer semantic abstraction even when it creates duplication
- jongjong 3mo agoAlso, I prefer having all the code inside a single 5000-lines file than split up into many small files representing incorrect abstractions. The urge to split the code up since they beginning is generally a bad idea; it forces early abstraction; more likely to be wrong.
- he0001 3mo agoIf you can’t fit into the same abstraction, is it really a “duplication” or is it just a slightly, but incomparable, function?
- ketozhang 3mo agoI like to think most seniors know to not blindly follow DRY. However, I can tell many of us are uncomfortable with the idea of needing to maintain multiple duplicated sources of code. To help with that, I think the simple model of two callers depending on a common code needs to be scrutinized. If the common code needs to change because only one of the caller needs it, then it doesn’t belong in the common. The wrong goal for DRY is attempting to do it with encapsulation. Encapsulation shifts the refactoring work from the caller to the common code. However this is not what you want because there’s a lot more consequence in updating the common code than the caller. You can avoid encapsulation and still be DRY by having multiple thin abstractions that the caller needs to be aware about is better. In OOP you are taught SRP and IoC for this. In procedural programming, this just comes naturally as code calling series of helper functions.
- corysama 3mo agoI always liked the advice "Abstract for replacement. Not for reuse." If you have code that is reusable, you'll want it to have a nice interface. But, you don't need an abstraction on top of a nice interface. Just use it. For abstraction, what you need to focus on is "What is most likely to change in the future?" You want to put in abstractions that will make those changes low-cost. Ex: At work there was a small debate about which C++ JSON parser to use with no stand-out winner for our framework's needs. So, we picked one and I put a thin layer over it for everyone to use. We have since then swapped out the parser and swapped it back over the years of a hundred devs using it in our framework and no one noticed the swaps.
- ninkendo 3mo agoTo me it’s distracting to think about duplicating vs creating an abstraction, because the answer is always “it depends”, which is not really an answer. To me, the question is: can you look at this abstraction and understand why it exists, without knowing who’s calling it? If so, it’s probably fine. If an abstraction only makes sense because of the particular weird details of these 3 callers that have to pass mutually exclusive arguments to it to get their desired behavior, it’s probably wrong. An abstraction needs “a place to live” in your architecture. It needs to be self-evident in justifying its existence. If you find yourself repeating code, but de-duping it would create these sort of weird non-self-justifying abstractions, your architecture is probably a bad fit for the problem you’re trying to solve. Maybe that’s because the problem changed since the software started (which is a bit of a pickle: do you re-architect, or do you continue writing weird inscrutable code?) or maybe it’s because you just picked the wrong abstraction in the first place. But you should recognize it: duplicating vs wrong-abstraction is about choosing the lesser evil. If the abstraction was a natural fit for the problem, you wouldn’t need to answer this question in the first place.
- codr7 3mo agoI certainly learned this the hard way. When I started writing code 40 years ago, I used to over estimate my understanding and abstraction skills a lot. As a result I created overly complicated and difficult to maintain/evolve solutions. Turns out I need to see more examples of patterns before making good choices, which means becoming comfortable with seeing and tracking duplication over time.
- throwatdem12311 3mo agoMy company paid to have her do one her day long workshop at HQ. Don’t know how much they paid but it was worth every penny. Changed my life.
- gaigalas 3mo agoI like the mantra: "prefer duplication over the wrong abstraction". Combined with another interesting idea "whatever I dislike is wrong", it makes me _always be right_, which is awesome. I can never lose a discussion about abstraction with this powerful combo.
- skreem 3mo agoNice to see Sandi mentioned! If anyone liked her philosophy / writing style I highly recommend you check out her books I read “Practical Object-Oriented Design (POODR)” ages ago at this point, but it reshaped how I approached OOP Granted… OOP as a default paradigm has fallen out of favor (at least for me), but it’s still everywhere & won’t be going away. She gives a great framework for making it sane
- lericzhang 3mo agoOf course, as you said it's "wrong abstraction". The real problem is it's hard to tell if an abstraction is correct before you see enough duplication.
- danpalmer 3mo agoThat's exactly what the article is about.
- danpalmer 3mo agoAI: Why not both?
- deleted 3mo ago[deleted]
- monkamonme 3mo ago[flagged]
- digitaltrees 3mo agoSandi is amazing. I learned so much from her.
- moi2388 3mo agoI don’t mind duplication at all. I mind undiscoverable duplication. But if I have an interface and three subclasses with duplicated or almost duplicated code, this is quite easy to find. That’s much nicer than an abstract base class where only some children override the methods, because now I need to check which ones actually do.
- edge_trader_41 3mo ago[flagged]
- jwpapi 3mo agoWhilst I understand cases in which duplication is preffered. I generally think abstractions are underused. Sometimes I would abstract something away that is only done once, not because i want to have less code, but because it allows me to solve bigger problems and when i look at a function I don’t have to worry about it. It allows to create systems. Obviously your abstractions should be good
- Beigale 3mo ago[flagged]
- esailija 3mo agoAnother way to put it: Things that should be tightly coupled but are not is preferable to things that should not be tightly coupled but are. I agree especially because coupling things is easier than uncoupling things.
- so-cal-schemer 3mo agoIf you know.. Software Design for Flexibility: How to Avoid Programming Yourself into a Corner by Chris Hanson and Gerald Jay Sussman https://archive.org/details/software-design-for-flexibility_how-to-avoid-programming-yourself-into-a-corner_hanson_sussman https://archive.org/details/software-design-for-flexibility_...
- insaneisnotfree 3mo agoThat's why you never hire juniors as tech leaders nor "architects"