Join the discussion

Write your take first — we'll ask for email only when you're ready to publish.

  • Hacker News
  • I 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.

    by dang
  • i 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

  • "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.
    by rf15
  • > long story short his refactoring caused what was otherwise a stable system into a complete mess

    Yeah that totally happened

  • It'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.
  • Too 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.

  • The most difficult codebases are those with every little thing some bespoke abstraction that went through 3 rounds of committee reviews that results in having to click through 12 files to figure out what anything is doing. Factory factory factories each with their own little frankenframework to understand before using anything.
  • Duplication is often less harmful than abstraction.

    Duplications can often be cleaned up over time, bad abstractions can quickly become a bottleneck, that severely slow down everyone working on the project.

  • I 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.
  • You can do both with microservices!
  • Except 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.
  • Please, stop it
  • I 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.
  • But wait! There's more!

    For $19.95, you can replace your single single point of failure with multiple single points of failure!

  • Similarly, 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.
  • That particular example doesn't quite fit, but I've certainly seen cases where otherwise perfectly ordinary fixed strings needed to be broken up to meet linting rules.
  • I ran into this as well. If an Event has a name, you can instantly grep across a giant monolith (or a big folder of microservice repos) and find every file that is concerned with that event.

    If you pull it out into a constant, you're back to opening up projects one-by-one to 'find usages'

  • Having the constants at the top is more easily customizable, especially should this file get duplicated. If devs need to switch to http instead of https for testing or staging, it makes sense to separate the scheme from the domain and put the constants up top or even in another file. It also matters whether ‘url’ was constructed in multiple places or a single place. Having named constants at the top of the file is a very common style, and sometimes is part of the group coding standards.

    Anyway, maybe there are other reasons too, so see Chesterton’s Fence. In any case, it’s never a good idea to assume cargo culting. Someone could easily say the same thing about using inline literals. If it looks weird, ask around and maybe you’ll find out there are good reasons, or maybe you’ll find out nobody cared and that people will like it if you refactor and embed the constants.

  • I’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.

  • Nobody 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.

  • Yep 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.
  • The 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.
  • Remember, everyone else's job is simple and pointless, only your job is difficult and important. Therefore only your job could possibly need abstractions. Everyone else is just over engineering.
  • There are codebases out there with enormous amounts of duplication, filled with implicit dependencies. You just haven't encountered them to appreciate good abstraction.
  • +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.
    by znkr
  • Yep. 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.
  • It'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.
  • Echoing 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.
  • Even better to work with one that isn't shit.
  • Contrary to that. The saying - Better to have a bad abstraction than none - was born from spaghetti code pain.