7 ms·
No 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
by dofm 4mo ago
No 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 4mo 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 4mo 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.
- Capricorn2481 4mo agoAgain, this is the opposite of what the author argues for, which is waiting for a couple instances before committing to an abstraction. Not duplicating a SQL query across hundreds of places. I would be curious if the previous coders you're talking about actually cited duplication as a good thing. You seem to be implying they are. But almost every instance I've seen of massive code duplication was just from bad programmers shooting from the hip, not from some ideological stance.
- dofm 4mo ago> Again, this is the opposite of what the author argues for, which is waiting for a couple instances before committing to an abstraction. Not duplicating a SQL query across hundreds of places. Right. But this is a hypothetical, in-a-vacuum situation. In the real world, your two, three duplicates are in production. "We really should now de-duplicate this" "There is not the time or budget, just copy it again; we'll replace all this one day".
- deleted 4mo ago[deleted]
- Capricorn2481 4mo agoI don't run into that because people I work with with physically cringe at copy pasting more than two blocks of code in multiple places. If anything, we revisit old code and realize how overly abstracted it is. I don't know of anyone that duplicates code to save time, but I believe it happens.
- fny 4mo agoCode duplication is cheaper than the wrong abstraction. If you have a good abstraction, you should run with it. If you haven't figured out a good abstraction at 5-100 customers, God help you.
- dofm 4mo agoI disagree. But also it's very possible to not realise you needed an abstraction until it catches fire in multiple places. And quite often it's not you that got the codebase to a hundred customers, is it? Sometimes it is a sequence of fresh-faced young developers who didn't have the authority to say "this duplication is bullshit" and were instead compelled to repeat it. I think a lot of these discussions happen in nice little blog-post vacuums of progressive thinking, where people can go "mmm, object oriented coding obscures intent and clarity, mmm", blog posts with "an X is a Y", "the unreasonable effectiveness of foobar" etc. In the real world, every duplication that works sticks for good; there is rarely budget to electively replace code that isn't broken. Until one day it doesn't work. And then… how many times is it actually duplicated? How many of the duplicates diverged? How many of these do we no longer need?
- chairmansteve 4mo ago> I disagree. So... the wrong abstraction, no matter how bad, is better than code duplication?
- dofm 4mo agoIf you read my original comment I said pretty much this, yes. > 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. I appear to be in a solid minority thinking this. But I'm OK with it. I'm probably not going to write a blog post.
- feoren 4mo agoA good abstraction? As in one? I'd go so far as to say the process of discovering and refining abstractions is the most important part of software engineering. A large project has dozens of abstractions, and some of them are "wrong" at any time, as you discover over time. None are ever perfect. If you wait to stop duplicating code until you have the "right" abstraction, you are just putting off the hard part of developing software and taking on tech debt. Half of your abstractions are wrong. The hard part is knowing which half.
- Capricorn2481 4mo ago> 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 passed a de minimis threshold. Pretty much everyone arguing for duplication has argued what you are saying, which is wait to see a few instances of it before committing to an abstraction. No one is saying duplicate everything 100 times. So I don't think this discussion was ever iconoclastic.
- dofm 4mo agoThe point is it sounds all smart and sophisticated and principled in the abstract environment of a code discussion in a blog post. In the real world, duplication happens in an emergent way, there isn't the time each time to judge whether it's really time to just quietly abstract that code, you may not get the permission, budget or window to do it, and if you don't stop the rot really early you are locked into the pattern.
- jbeninger 4mo agoBut... it shouldn't. People are arguing that a bad abstraction is better than none at all. Badly-implemented abstraction is the same. If you hit code that is duplicated organically a dozen times, you don't make it a baker's dozen. You spend a bit of extra time at least stubbing out the abstraction so future organic duplication can at least share an entry point. Abstractions grow organically too, in well-tended codebases.
- coldtea 4mo agoHardly iconoclastic, it's a very sensible suggestion. It would be iconoclastic if the common sense basic approach would be to start with abstraction. It's not, the common sense default is to write possibly duplicate behavior until you actually discover several cases to abstract away, until you bevalop a sensible idea of which functionality unites them and which doesn't carry over all of them. >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 Maintaining the wrong abstraction, or, god help, abstractions, would be even worse.
- bluefirebrand 4mo agoYeah, "Write Everything Twice" is a pretty common and sensible direction for any codebase
- cwmoore 4mo agoYeah, ~"Write Everything Twice"~ “Copy and Paste Working Code” is a pretty common and sensible direction for any codebase
- lanstin 4mo agoIn C I used to make it so my standard per-file and per lib code could be cut and pasted to other files/libs without modification. (E.g. every file had a mLocal variable that was file-visibility symbols, every module had a #module define for logging, there was always a mLocal.stats member, etc. ) I think some of this duplicate vs. abstract depends on your languages expressiveness - Rust or Lisp with good compile type power make it possible to squeeze out a lot of duplication that in less expressive languages are just idioms - here’s the five lines to make a syscall, or here’s the skeleton of parsing a portable network buffer into a native object. Having a lot of if/else in your code is definitely a cost. My weakness isn’t so much the libraries and APIs, but the actual binary - once I have a service that does A very well, and I run into needing A’ I mostly just add in a config line “op_mode = A|A’” and have the else/if chains in the server driving code. Moreso for CLIs that I use myself than production services, but I have added tunables for consistency and replication to datastores to allow new use cases and expand my footprint in the data center.
- deleted 4mo ago[deleted]
- agumonkey 4mo agoYou seem to have experience, I dont mind factoring / unifying logic, when done sensibly with enough history in the trenches. It pains me more whenever a young dev comes in and barks "we must merge these two things!" repeatedly without planning for more than two cases and starting to add more and more boolean variables. Crystal makers. Then the obvious issue comes, the two variants weren't that close and now there's one god class trying to handle all forces in one big state. I agree that LLMs are naturally anti abstraction machines.. I'm often trying to find way to reverse that.
- dofm 4mo ago> I agree that LLMs are naturally anti abstraction machines.. I'm often trying to find way to reverse that. I am a bit of an LLM cynic but I am trying to learn it all, and I have to say I have spent most time trying to work out: how do you explain how a brown-field codebase actually works, in such a way that the LLM won't pervert it through misunderstanding. It does encourage you towards the "conventional" coding standard for any new project, because you want to use a pattern that it will have seen in its training set. But for example there are differences of opinion in how wordpress plugins (which have a very complex control flow) should be structured. LLMs are incredible at knowing how WP works, actually, but what is difficult is explaining how your methodology for a large plugin is going to work. It is a battle — but a useful one because it can be used for, er, studying the comparative belief systems of the LLMs.
- wonnage 4mo agoThey don’t have a useful belief system, one of the rookie mistakes of using LLMs is asking them what you “should” do
- dofm 4mo agoAbsolutely. I think the bit I still struggle with is finding a way to get them to join my team (which is a team of one very tired person). A story I like is that in the now lost era of handwriting recognition on PDAs, Jef Raskin concluded that the easiest way to solve the problem was to change handwriting so as to meet the algorithm in the middle. That is, to find a noticeable simplification of handwriting that people could learn quickly and that eliminated hard-to-process quirks. I feel I am there with the LLM at the moment, trying to work out what the common ground is.
- mytydev 4mo agoIt sounds to me like you are describing a good abstraction. This article does not claim that code duplication is better than any abstraction. It claims that code duplication is better than the wrong abstraction. I'm sure this author would agree that a good abstraction is better than code duplication.
- dofm 4mo agoI'm afraid this comment reads in a rather gnomic way. Of course it's a truism if you just say any abstraction that works is a good abstraction. That is not what I am saying at all. Bullshit abstractions at least let you control the problem. Duplication doesn't.
- vlunkr 4mo agoBut it’s never going to be 1:1 duplication is it? Sometimes it’s better to copy code as a template for something new, rather than try to immediately force a new abstraction. I agree with you that it’s a truism, but it’s useful advice for people who have a habit of trying too hard to DRY their code. IIRC the author comes from the Ruby world, where DRY was a big thing, and this talk was part of the pendulum swinging back away from this DRY obsession that sometimes just resulted in convoluted code.
- tracerbulletx 4mo agoHuh? If anything having lots of customers makes the argument for duplication stronger. The issue is almost always once you get huge and 5 product teams are trying to achieve 5 different goals by using the same overwrought abstraction instead of just copying and decoupling. The abstractions that are actually stable end up becoming libraries or platform team owned systems that no one ever really touches.
- nfw2 4mo agoOver-engineering and "abstraction hell" are very much not iconoclastic concepts
- ChrisMarshallNY 4mo agoIn my experience, the answer is always "It Depends." That's about the only thing that I can hang "always" on. It really depends on the exact type of code we're working with, and what our objectives are. In my case, I often use object inheritance. It's a damn cheap way to DRY. However, when people hear "inheritance," they often think "polymorphism." There's a really big difference between the two, but popular culture has jammed them into one ball, and it's not worth the agita, to try to explain the difference. But if you are doing optimization, long stacks can be your enemy, and inheritance tends to have long, windy stacks. In these cases, the copy/pasta method may well be the best approach. Like I said, "It Depends."
- tomjakubowski 4mo ago> In my case, I often use object inheritance. It's a damn cheap way to DRY. However, when people hear "inheritance," they often think "polymorphism." There's a really big difference between the two, but popular culture has jammed them into one ball, and it's not worth the agita, to try to explain the difference. I agree that we should think of inheritance and polymorphism separately. If we want to express this intent in object-oriented code, how can we use inheritance to deduplicate code, while preventing misuse of the resulting object hierarchy i.e. the use of base classes in a polymorphic context? In C++, IIRC private inheritance would do the trick (you cannot static_cast DerivedWidget * to BaseWidget * if DerivedWidget : private BaseWidget), but most OO languages don't support private inheritance. It's also not possible, as far as I know, to "lock down" BaseWidget * so it cannot be used as a base class pointer from any derived class: instead, you have to apply the private inheritance to every derived class to enforce this rule. Another approach is to use has-a instead of is-a: i.e. instead store a BaseWidget object as a member of DerivedWidget. This allows for re-use without supporting polymorphism.
- ChrisMarshallNY 4mo agoOr...we could hire folks that actually know how to write code, without screwing the pooch. This is especially true, with languages like C++. Someone (I have heard it attributed to Bjarne, but I don't think he said it) said "With C, you can shoot yourself in the foot. With C++, you can blow your whole leg off." But there's stuff that can basically, only be done in C++. It's a very powerful, mature, and storied tool; meant to be used by competent grownups. In tech, we have folks that seem to be absolutely convinced that we can have tools, so marvelous, that we can hire total incompetents, and that they will magically write good code. I know of no other engineering discipline, or craft, where people think like this. They usually have rigorous career ladders, with lots of gates. Maybe Finance sometimes lets knuckleheads behind the wheel, but then, you get things like the Barings Bank disaster. "What's Barings Bank?" you ask. "It doesn't exist! Is it a hallucination?" No, it is not. Unfortunately, they let a rather junior trader, named Nick Leeson, behind the wheel... It's possible that LLMs may finally give us something like what people want, but I suspect that we'll be seeing folks stumping around on one leg...
- Thaxll 4mo agoSo you centralize 3 liners?
- dofm 4mo agoI said "beyond a de minimis threshold". But in one of the scenarios I mention earlier, I earned a chunk of money once fixing an issue that emerged in a subcontractor's four or five line duplication that had ended up rippled through a long-lived codebase. A ground truth (MySQL version) changed, and the pattern broke everywhere, including places where it had evolved. So I tend towards thinking, yes, any three-line pattern that is likely to appear everywhere should, perhaps, be centralised. It's certainly worthy of serious consideration. Usually pretty easy to maintain the surface of such an abstraction.
- thinkloop 4mo agoThe key lesson is that duplicate code is not necessarily "code duplication" - it was always really about abstraction duplication. If two unrelated variables happen to momentarily share a value, it doesn't mean that value should be made common between them, they are fundamentally different things. It would be a confusing lie and error-prone if the code implied they were the same and that efforts should be made for them to be in sync.
- dofm 4mo agoI guess any blog post can remain true if you can optionally take one of the key terms and redefine it so it can also mean the opposite?
- jimmypk 4mo ago[flagged]
- ubertaco 4mo agoI'd recommend clicking through the headline to watch the talk. Metz talks a lot about types of similarity: similarity by coincidence vs similarity due to an actual semantic or functional equivalence. Code that is coincidentally similar very often diverges in either the short or long term, and DRYing it up aggressively tends to result in functions that have many boolean parameters that each trigger disjoint sets of behavior - which is a bit of a nightmare to maintain due to the high cognitive overhead of remembering how all the interleaved-but-actually-unrelated behaviors should work. This outcome is low-cohesion code. It's a useful concept to be aware of - worth clicking through to the actual content of the talk rather than just the headline.
- dofm 4mo ago> I'd recommend clicking through the headline to watch the talk. Metz talks a lot about types of similarity: similarity by coincidence vs similarity due to an actual semantic or functional equivalence. I've seen this article and AFAIR the video before, and FWIW having been a Rails developer from the very early days and fitfully until maybe even 2014, I now interpret the phrase "my Railsconf talk…" quite negatively. ETA: nice to be back to disagreeing with people on HN about coding principles again though. Hopefully this is a sign.
- tcfhgj 4mo ago"tends to result in functions that have many boolean parameters that each trigger disjoint sets of behavior " What tends to happen is in your hands.
- cjfd 4mo ago100% agree. 'Code duplication is far cheaper than the wrong abstraction' is a very good candidate for the worst programming article ever.
- a-dub 4mo agoi agree with the author. i argue a preference for loose coupling over centralized abstractions. sure it's pleasing to compress the code, but if the use cases actually are sufficiently divergent (as well as bugs and externally driven changes) ultimately it becomes brittle, littered with edge cases behind if fences and both challenging and daunting to change. ideal case: support libraries and then very simple duplicated code that is easy to read and modify. critically the core control flow should remain duplicated, but simplified by the support libraries.
- cyberax 4mo agoMy general rule is to start refactoring once you have three copies of the code. Starting with abstraction when you are only beginning something rarely works well and leads to code bases littered with interfaces having only one implementation. Abstracting the code when you have two copies does not always pay off, especially when you end up not needeing more than just two copies anyway. But once you have three copies, it's indeed time to start generalizing.
- yowlingcat 4mo agoOne of the most challenging kinds of thought to work through with my engineers in professional communication is nuance. For example, they may say something like this, but actually mean "For a particular situation, this is wrong." The context a decision is evaluated is particularly important for "rules of thumb" like this. There's the rule of 3 (which many senior engineers imparted to me earlier on in my career) - don't refactor until you've actually duplicated it thrice, but even so, what they speak of is a catch-22 that's pretty important to reason about carefully. On one hand, if you overcorrected on the fear of abstraction, you could easily end up with 500 duplicates that are slightly different and need to be maintained 500 different ways, slowly causing slightly wrong behavior some of the time, data corruption, combinatoric explosion. Surely, once there is such a situation, some degree of abstraction is the only right decision. On the other hand, if you overcorrected on the fear of duplication early on, you could easily end up with a premature optimization and complexity -- complexity which, most importantly, could be rooted in a gap of understanding of how the code will be used and what direction it may go in over time (often based on which direction the business will go over time). The only answer that actually works, of course, is "somewhere in the middle." Obviously, that's pretty vague and not very useful. Where, exactly, in the middle IS the right place? As the years have gone by, I've become more and more steadfast that the answer to that question is and must be an art and not a science. Of course, it must always be rooted in practicality, the actual context of the code around it and where the code/business was in the past and where it will be in the future. But just as importantly, some of it must be based around beliefs in the face of imperfect information about what you want to invest in for the sake of the technology, the team that develops it, and the business that relies on it. It could be that for your team, your values make it make sense to go a little bit further than "good enough" on normalizing your data modeling, because the way you like to run your business requires that normal form to do the analytics and make decisions productively. It could be that for your team, your values make it make sense to go a little bit further than "good enough" on splitting service boundaries and ensuring clean queues and message passing infrastructure, because you have seasonal spikes where you need to scale up to a ton of load and then scale down after without constantly doing a song and dance or pre-provisioning fragile infrastructure. But the most common thread there is - art, not a science. Every single decision depends on YOUR team, YOUR business, YOUR needs - and like any art, there is no universal rule or discovery or best practice in the industry that will magically work for your needs without working through the details of whether it appropriately fits your situation or not. So with that said - I can't really agree with you. At any place I've ever worked with a competent team, maintaining duplicate code is just not that hard and follows the same process for being dealt with. Built a robust test suite that encodes the actual differences and the shared structure. Pull out the pieces that have a good reason to be abstracted and redesign the pieces that encode the true differential structure in a way that is intuitive. Lather rinse repeat. It's always straightforward because it's known - by the time you are doing this process, you've had tons of repetitions and data on what is driving you to develop the abstraction, so when you make the decision, you are making it empirically. Conversely, I have seen many otherwise competent teams slowed to a halt with premature abstraction. Frameworks that were well intended and reduced duplication, but encoded coupling between components that at a certain point in the businesses progression, fought with reality rather than aided, and all because they were frozen into place before anyone empirically had really clear data about whether the abstraction would be worth it long term. Well intended "clean code" refactors that were meant to solve the old "bad duplication" but instead created a far more difficult to reason about "abstracted base" of code that didn't really solve any of the domain modeling problems and was just as difficult to maintain without introducing buggy behaviors (if not more so) than before. The biggest problem is that premature abstraction is sexy and fun. There are incentives and dopamine hits from doing it extraneously. But fixing legacy duplication is not fun. And so when it gets done, it tends to get done in a pragmatic way to relieve pain rather than to elicit pleasure. That, I believe is one of the biggest confounding sociological aspects of this whole discussion.
- nextaccountic 4mo agoThe trouble with the wrong abstraction is that sometimes you really do want to change one and not the other. It's code that superficially look the same, but only temporarily (taking a snapshot at the current time) - they are meant to be distinct in the long run