[HN Gopher] Don't DRY Your Code Prematurely
       ___________________________________________________________________
        
       Don't DRY Your Code Prematurely
        
       Author : thunderbong
       Score  : 704 points
       Date   : 2024-05-30 15:45 UTC (1 days ago)
        
 (HTM) web link (testing.googleblog.com)
 (TXT) w3m dump (testing.googleblog.com)
        
       | PaulHoule wrote:
       | Not a conclusive example.
       | 
       | In the industry code that isn't DRY is a much bigger problem than
       | code that is too DRY.
        
         | znkr wrote:
         | I am the industry for over 10 years now. Whenever I have to
         | work with a project where someone used DRY consciously, I know
         | I am in for a world of pain. Consolidating code is easy,
         | pulling it apart is a lot harder.
        
           | actionfromafar wrote:
           | Can concur. Mostly it was I causing the pain, earlier.
        
           | lpapez wrote:
           | > Consolidating code is easy, pulling it apart is a lot
           | harder.
           | 
           | I absolutely agree with this, and the only thing I would add
           | is that is difference is even more pronounced in codebases
           | using a dynamic language.
           | 
           | Sure it's not easy to navigate a bowl of duplicated
           | spaghetti, but navigating opaque DRY service classes without
           | explicit types is a _nightmare_.
           | 
           | Luckily as an industry we've realized the benefits of static
           | typing, but your point still holds true there.
        
           | mytailorisrich wrote:
           | How do you consolidate code?
           | 
           | Good way to go at it is to isolate the functionality that is
           | used many times and to pull it aside in its own function (or
           | similar). That's just good code practice and also makes it
           | easy to refactor and modify as needed.
        
             | znkr wrote:
             | It's not about being used many times, but about the
             | necessity to evolve in the same direction. When that
             | happens, it usually manifests as toil for the team.
             | Consolidating code means to change the structure of the
             | code so that only one piece needs to be modified in the
             | future. That can take many forms, but it usually involves
             | creating a new shareable component.
             | 
             | Shareable components are more effort to maintain, so just
             | creating them because they consolidate code is not always a
             | good idea. You really want to have positive ROI here and
             | you only get that if you actually reduce maintenance
             | burden. For raw code duplication that doesn't have a
             | maintenance issue on it's own, the bar is a lot higher than
             | most people think.
        
               | PaulHoule wrote:
               | Well, this morning I just fixed a case where somebody had
               | used btoa to base64 encode something in Javascript and
               | used methods from Buffer somewhere else because they'd
               | been intimidated away from using btoa. (Ok, it is dirty
               | to use UTF-8 codepoints if it is byte values, you can
               | write btoa("A") but btoa("Zhong ") is a crash.)
               | 
               | It would have been OK if they'd used the right methods on
               | Buffer but they didn't.
               | 
               | These encoding/decoding methods are a very good example
               | of code that should be centralized, not least so you can
               | write tests for them. (It is a favorable case for testing
               | because the inputs and outputs are well defined and there
               | are no questions of whether execution is done like you
               | might encounter testing a React component) It is so easy
               | to screw this kind of thing up in a gross way or an a
               | subtle way (I'm pretty sure btoa's weirdness doesn't
               | affect my application because codepoints > 255 never show
               | up... I think)
               | 
               | There's the meme that you should wait until something
               | used 3 times before you copy it but here is a case where
               | two repetitions were too many and it had a clear impact
               | on customers.
        
               | mytailorisrich wrote:
               | Raw code duplication is always a maintenance issue when
               | centralising it when you notice the duplication (instead
               | of keeping copy-pasting it) costs nothing.
        
           | EugeneOZ wrote:
           | I have 20 years in the industry and one of the rules I
           | learned is: Articles justifying laziness are ALWAYS warmly
           | welcomed and praised.
           | 
           | To get internet points easily, write something of that:
           | 
           | "Clean code is overrated"
           | 
           | "SOLID is holding you back"
           | 
           | "Tests are less important than profits"
           | 
           | "KISS is the only important principle"
           | 
           | "Declarative programming is only suitable for pet projects"
           | 
           | "Borrow checker is the plague of Rust"
           | 
           | and so on.
        
           | 12_throw_away wrote:
           | I basically agree, but doesn't this just mean, if I'm
           | consolidating non-DRY code, that I'm now the one using DRY
           | consciously, and the next dev will be cursed with all of my
           | newly introduced DRY abstractions?
        
             | znkr wrote:
             | If you don't have another reason for consolidation than
             | consolidation then yes :)
        
           | a1369209993 wrote:
           | > Whenever I have to work with a project where someone used
           | DRY [ _]consciously[_ ], I know I am in for a world of pain.
           | 
           | Huh. When you put it that way, that's actually a good point.
           | In my experience, competent programming will try to
           | consolidate repeated code, and then cite "because DRY" if
           | asked why, but I can't think of any case where I or anyone
           | else competent _started_ with  "needs more DRY" as the
           | original motivation (as opposed to "this is a
           | incomprehensibly verbose mess" or the like).
           | 
           | Conversely, _starting_ with  "don't repeat yourself [and
           | don't let anything else repeat itself]" as a design goal does
           | seem to correlate well with cases where someone temporarily
           | (newbie) or permanently (moron/ideologue) incompentent
           | followed that design principle off a cliff.
        
           | rqtwteye wrote:
           | "Consolidating code is easy, pulling it apart is a lot
           | harder."
           | 
           | My experience is the opposite. The less code, the better. I
           | just spent a week on refactoring UI automation test code
           | where they had copied the same 30 lines of code into almost
           | 100 places. Every time with an ID changed and some slightly
           | different formatting. It took me a few days to figure out
           | that these sections do the same thing so I decided to
           | introduce a function with ID as parameter. It was a lot of
           | work to identify all sections and then to make sure they are
           | really equivalent.
           | 
           | Saved us 3000 lines of code and now we can be sure that
           | timeouts and other stuff is handled correctly everywhere. An
           | we can respond to changes quickly.
           | 
           | that's DRY to me. Don't copy/paste code. Introduce functions.
           | Ideally in the simplest way. When you have functions, you
           | declare the same behavior everywhere.
        
         | nkozyra wrote:
         | > In the industry code that isn't DRY is a much bigger problem
         | than code that is too DRY.
         | 
         | As with anything dogmatic, it truly depends. There are times
         | when the abstraction cost isn't worth it for a few semi-
         | duplicate implementations you want to combine into a single
         | every-edge-case function/method.
        
           | PaulHoule wrote:
           | There's a certain psychological attraction to messy and
           | confused situations which people are just too comfortable
           | with but it explains why things like GraphQL (didn't have a
           | definition for how it worked for years because "Facebook is
           | going to return whatever it wants to return") inevitably win
           | out over SPARQL (which has a well-defined algebra).
           | 
           | One of my biggest gripes (related to the post) is the data
           | structure                  create table student (
           | ...           applied_date                         datetime,
           | transcript_received                  datetime,
           | recommendation_letter1_received      datetime,
           | recommendation_letter2_received      datetime,
           | rejected_date                        datetime,
           | accepted_date                        datetime,
           | started_classes_date                 datetime,
           | suspended_date                       datetime,
           | leave_of_absence_start_date          datetime,
           | leave_of_absence_end_date            datetime,           ...
           | graduated_date                       datetime,           ...
           | gave_money_date                      datetime,
           | died_date                            datetime        )
           | 
           | which is of course an academic example but that I've seen in
           | many kind of e-business application. Nobody ever seems to
           | think of it until later but two obvious requirements are: (1)
           | query to see what state a user was in at a given time, (2)
           | show the history of a given user. The code to do that in the
           | above is highly complex and will change every time a new
           | state gets added. The customer also has experiences like "we
           | had a student who took two leaves of absence" or "some
           | students apply, get rejected, apply again, then get accepted"
           | When you find data designs like this you also tend to find
           | some of the records are corrupted and when you are recovering
           | the history of users there will be some you'll never get
           | right.
           | 
           | If you think before you code you might settle on this design
           | create table history (            student_id
           | integer primary key,            status
           | integer not null,            begin_date
           | datetime not null,            end_date
           | datetime         )
           | 
           | which solves the above problems and many others in most
           | situations. (For one thing the obvious queries are trivial
           | and event complex queries about times and events can be
           | written with the better schema.) I can't decide if the thing
           | I hate the most about being a programmer is having to clean
           | up messes like the above or having to argue with other
           | developers about why the first example is wrong.
           | 
           | If "No code" is to really be revolutionary it's going to have
           | to have built-in ontologies so that programmers get correct
           | data structures for situations like the above that show up
           | everyday in everyday bizaps where there is a clear right
           | answer but it is usually ignored.
        
             | gls2ro wrote:
             | Two points here just for fine grain discussion:
             | 
             | 1. The first table structure is a flat non-normalized table
             | structure that trades normalization for easy to query and
             | select computed properties
             | 
             | 2. Second structure is a normalized table structure that
             | trades the normalization for joins.
        
               | PaulHoule wrote:
               | Either one is normalized so far as I know.
               | 
               | It is easy to write a query for the first that gets a
               | list of students names and the dates they applied. That
               | query is harder for the second one. On the other hand
               | figuring out what state a user was in at time _t_ could
               | be a very hard problem with the first table.
               | 
               | My experience with the first is that you find corrupted
               | data records, one cause of that will be that people will
               | cut and paste the SQL queries so maybe 10% of the time
               | they wind up updating the wrong date. Systems like that
               | also seem to have problems with data entry mistakes.
               | 
               | The biggest advantage of #2 is ontological and not
               | operational, which is that in a business process an item
               | is usually in exactly one state out of a certain set of
               | possible states. Turns out that this invariant influences
               | the set of reasonable requirements that people could
               | write, the subconscious expectations of what users
               | expect, needs to be implicitly followed by an
               | application, etc.
               | 
               | Granted some of the dates I listed up there don't quite
               | correspond to a state change, for instance the system
               | needs to keep track of when a student started an
               | application and when the last document (transcripts,
               | letters, etc.) has been received. With 5 documents you
               | would have 32 possible states of received or not and
               | that's unreasonable, particularly considering that a
               | student with just one letter and a very strong
               | application in every other way might get accepted despite
               | that. It's fair to say the student can have an "open
               | application" and a "complete application". Similarly you
               | could say the construction of an airplane or a nuclear
               | power plant can be defined by several major phases but
               | that these systems have many parts installed so if the
               | left engine is installed but the right engine is not
               | installed these are properties of the left and right
               | engine as opposed to the plane.
        
         | danielmarkbruce wrote:
         | In your part of the industry, perhaps. My experience has been
         | the opposite.
        
           | ravenstine wrote:
           | Same. From what I've seen, most code is written with
           | abstractions and DRY as a high priority rather than writing
           | code that is performant and doesn't take jumping between 5
           | different files to make sense of it.
        
             | danielmarkbruce wrote:
             | I started writing Go around 2012 or so because of the file
             | jumping thing. Drove me nuts. I'm sure there were many
             | folks doing the same thing.
        
         | jacknews wrote:
         | "In the industry code that isn't DRY is a much bigger problem
         | than code that is too DRY."
         | 
         | which industry is that?
         | 
         | in general programming, absolute nope
         | 
         | not-DRY code can be weaseled out with a good ide
         | 
         | badly abstracted code, not so much
         | 
         | in fact in a way, DRY is the responsibility of the IDE not the
         | programmer - an advanced IDE would be able to sync all the
         | disparate code segments, and even DRY them if necessary
         | 
         | but when I read DRYed code, the abstraction better be a
         | complete and meaningful summary, like 'make a sandwich', and
         | without many parameters (and no special cases), or else I'd
         | rather read the actual code
         | 
         | i understand the impulse to try to factorize everything but it
         | just doesn't work beyond a certain point in the real world;
         | it's too difficult to read, and there's always an 'oh, can you
         | just' requirement that upends the entire abstract tower.
        
           | goatlover wrote:
           | You didn't provide any evidence for this, you just stated
           | your coding preference. Which is usually the case in these
           | discussions. Some anecdotes, and then people making grand
           | claims based on personal preference. Obviously, some
           | programmers have thought the opposite and have their own
           | anecdotes.
        
             | jacknews wrote:
             | the comment I replied to was merely a strong opinion
             | 
             | same same
             | 
             | i don't believe there _is_ much evidence, certainly nothing
             | conclusive, in this debate
             | 
             | but factorizing code concentrates the logic
             | 
             | that can be an advantage, to a certain degree, but it also
             | reduces resilience, by specializing the code, and can
             | reduce readability by forcing lookups of nested
             | abstractions
        
         | ldjkfkdsjnv wrote:
         | Abstraction too early is usually a mistake, no one is smart
         | enough to predict all the possible edge cases. Repeated code
         | allows someone to go in there and add an edge case easily. Its
         | a more fool proof way of programming
        
         | swatcoder wrote:
         | Having specialized in project rescue, touring all over "the
         | industry", you can't possibly make that generalization.
         | 
         | For every purported best practice, there are teams/orgs that
         | painted themselves into a corner by getting carried away and
         | others that really would have benefited from applying it more
         | than they did.
         | 
         | In the case of DRY, it's an especially accessible best practice
         | for inexperienced developers and the project leads many of them
         | become. Many many teams do get carried away, mistaking "these
         | two blocks of code have the same characters in the same
         | sequence" with "these two delicate blocks of code are doing the
         | same thing and will likely continue to do so"
         | 
         | Having advice articles floating around on both sides of
         | practices like this helps developers and teams find the
         | guidance that will get them from where they are to where they
         | need to be.
         | 
         | Context, nuance, etc, etc
        
           | PaulHoule wrote:
           | If that's what they wanted to prove they should have shown a
           | better example.
        
             | swatcoder wrote:
             | That's fair. I think the insight/concept behind the essay
             | is sound, but I agree that the example (and writing) could
             | be a lot better.
        
           | AnimalMuppet wrote:
           | In Zion National Park, there's a hike called Angel's Landing.
           | For part of the hike, you go along this ridge, where on one
           | side you have a cliff of 500 feet straight down, and on the
           | other side, you have a cliff of 1000 feet straight down. And
           | in places, the ridge is only a couple of feet wide.
           | 
           | Best practices can be like that. "Here's something to avoid!"
           | "OK, I'll back far away from that." Yeah, but there's another
           | cliff behind you, of the opposite error that is also waiting
           | to wreck your code base.
           | 
           | Listen to best practices. Don't apply them dogmatically, or
           | without good judgment.
        
         | jeltz wrote:
         | Not from my experience. Unnecessarily duplicated code, even
         | when there are small differences which are likely accidental,
         | is usually much easier to fix than too DRY code. Pulling apart
         | false sharing can be really hard.
        
           | kitkat_new wrote:
           | example? duplicating (literally copy paste) is easier than
           | even finding duplicated code with small differences.
        
         | barryrandall wrote:
         | The number of person-hours wasted on over-engineered products
         | that never even made it to release could have: solved the
         | halting problem, delivered AGI v2.0, made C memory-safe without
         | compromising backward-compatibility, or made it easy to adjust
         | mouse pointer speed on Linux.
        
       | idontwantthis wrote:
       | Your code should be WET before it's DRY (Write Everything Twice).
        
         | thefaux wrote:
         | Yes, why?
        
           | idontwantthis wrote:
           | Because you're unlikely to write a good abstraction until you
           | need it more than twice.
           | 
           | And if you only need the code twice, you very likely wasted
           | time writing the abstraction because copying updates between
           | the two locations is not hard.
           | 
           | This is a rule of thumb, I'm not trying to tell anyone how to
           | do their job.
        
         | zendist wrote:
         | The rule of three[1] also comes to mind and is a hard learned
         | lesson.
         | 
         | My brain has a tendency to desire refactoring when I see two
         | similar functions, I want to refactor--it's almost always a bad
         | idea. More often than not, I later find out that the premature
         | refactoring would've forced me to split the functions again.
         | 
         | 1:
         | https://en.m.wikipedia.org/wiki/Rule_of_three_(computer_prog...
        
           | swader999 wrote:
           | Nice, I advocate for this but never new it was a more formal
           | thing.
        
           | kitkat_new wrote:
           | better understand DRY first, before you add lots of technical
           | debt to your code:
           | https://news.ycombinator.com/item?id=40525064#40525690
        
         | fellowniusmonk wrote:
         | Or alternatively, Write Everything Today.
         | 
         | DRY when it's a wielded as a premature optimization (like all
         | other premature optimization) prevents working code that is
         | tailored to solving a problem from shipping quickly.
        
       | cjbgkagh wrote:
       | I thought it was Don't Repeat Yourself more than three times.
        
       | S0y wrote:
       | "premature optimization is the root of all evil"
        
       | blowski wrote:
       | I find the term DRY to be pretty vague.
       | 
       | Let's say you have a business rule that you can never have more
       | than 5 widgets. You can make this assumption in multiple places,
       | even with totally different code, and that's damaging DRY when
       | the rule changes to allowing 6. On the other hand, having a bit
       | of duplicated HTML can help, as they may only be the same by
       | accident.
        
         | mytailorisrich wrote:
         | In thise case, '5 widgets max' would be a parameter that should
         | be defined as a global constant instead of having a hard-coded
         | 5s all over the place or, worse, pieces of code copy-pasted 5
         | times... That's a standard good coding practice.
        
       | softwaredoug wrote:
       | Generality can really hurt performance. Duplicating specialized
       | code to handle different cases can really help optimize specific
       | code hot spots for certain data patterns or use cases.
       | 
       | So DRY isn't an obvious default for me.
        
         | marcandre wrote:
         | I'd love examples where DRY can really hurt performance.
         | Typically what matters most in terms of performance is the
         | algorithm used, and that won't change.
         | 
         | More importantly, cleverer people than me said "premature
         | optimization is the root of all evil"
        
           | mikepurvis wrote:
           | IMO it hurts developer productivity more than performance,
           | because it introduces indirection and potentially unhelpful
           | abstractions that can obscure what is actually going on and
           | make it harder to understand the code.
           | 
           | In raw performance this could manifest as issues with data
           | duplication bloating structures and resulting in cache
           | misses, generic structures expressed in JSON being slower
           | then a purpose-built struct, chasing pointers because of
           | functions buried in polymorphic hierarchies. But I doubt that
           | any of this would really matter in 99% of applications.
        
           | rhdunn wrote:
           | Premature optimization is about not making a micro-
           | implementation change (e.g. `++i` vs `i++`) for the sake of
           | percieved performance. You should always measure to identify
           | slow points in expected workloads, profile to identify the
           | actual slow areas, make high-level changes (data structure,
           | algorithm) first, then make more targetted optimizations if
           | needed.
           | 
           | In some cases it makes sense, like writing SIMD/etc. specific
           | assembly for compression/decompression or video/audio codecs,
           | but more often than not the readable version is just as good
           | -- especially when compilers can do the optimizations for
           | you.
           | 
           | A lot of times I've found performance increases have come
           | from not duplicating work -- e.g. not fetching the same data
           | each time within a loop if it is fixed.
        
             | nsguy wrote:
             | Not really. Knuth was talking about putting effort to make
             | a non-critical portion of the software more optimized. He's
             | saying put effort into the smaller parts where performance
             | is critical and don't worry about the rest. It's not about
             | `++i` vs. `i++` (which is semantically different but
             | otherwise in modern compilers not an optimization anyways
             | but I digress).
        
               | ummonk wrote:
               | The optimizations he was talking about were things like
               | writing in assembly or hand-unrolling loops. It was
               | assumed that you've already picked an performant
               | algorithm / architecture and are writing in a performant
               | low level language like C.
               | 
               | Also, your digression about modern compilers is
               | irrelevant to the context of the quote, since Knuth
               | talked about premature optimization at a time when
               | compilers were much simpler than today.
        
               | rhdunn wrote:
               | That was my point, though. Don't worry about minor
               | possible changes to the code where the performance
               | doesn't matter. For example, if the ++i/i++ is only ever
               | executed at most 10 times in a loop, is on an integer
               | (where the compiler can elide the semantic difference)
               | and the body of the loop is 100x slower than that.
               | 
               | If you measure the code's performance and see the ++i/i++
               | is consuming a lot of the CPU time then by all means
               | change it, but 99% of the time don't worry about it. Even
               | better, create a benchmark to test the code performance
               | and choose the best variant.
        
               | nsguy wrote:
               | That's not my interpretation. If you're profiling and
               | benchmarking you're already engaging in (premature)
               | optimization. This process you're describing of finding
               | out whether `i++` is taking a lot of CPU time and then
               | changing it is exactly what Knuth is saying not to worry
               | about for 97% of your code. Knuth is saying it doesn't
               | matter if `i++` is slow if it's in a non-performance
               | critical part of your code. Any large piece of software
               | has many parts where it doesn't matter for any practical
               | purpose how fast they run and certainly one loop in that
               | piece of software doesn't matter. For example, the
               | software I'm working on these days has some fast C code
               | and then a pile of slow Python code. In your analogy all
               | the Python code is known to be much slower than the C
               | code, we don't need a profiler or benchmarks to tell
               | that, but it also doesn't matter because the core
               | performant functionality is in that C code.
        
               | randomdata wrote:
               | Knuth says forget about _small_ efficiencies in 97% of
               | your code. Indeed, the `i++` optimization isn 't apt to
               | make more than a small difference, even with the most
               | naive compiler, but other decisions could lead to larger
               | chasms. It seems he is still in favour of optimizing for
               | the big wins across the entire codebase, even if it
               | doesn't really matter in practice.
               | 
               | But it's your life to live. Who cares what someone else
               | thinks?
        
           | candiddevmike wrote:
           | In an effort to DRY, you add a bunch of if statements to
           | handle every use case.
        
           | swatcoder wrote:
           | In the general case, it usually depends on the latency of
           | what you'd DRY your code to vs the latency of keeping the
           | implementation local and specialized.
           | 
           | If you're talking about consolidating some code from one in-
           | process place to another in the same language, you're mostly
           | right: there's only going to be an optimization/performance
           | concern when you have a very specific hotspot -- at which
           | point you can selectively break the rule, following the
           | guidance you quoted. This need for rule-breaking can turn out
           | to be common in high-performance projects like audio,
           | graphics, etc but is probably not what the GP had in mind.
           | 
           | In many environments, though, DRY'ing can mean moving some
           | implementation to some out-of-language/runtime, out-of-
           | process. or even out-of-instance service.
           | 
           | For many workloads, the overhead of making a bridged, IPC, or
           | network call swamps your algorithm choice and this is often
           | apparent immediately during design/development time. It's not
           | premature optimization to say "we'll do a lot better to
           | process these records locally using this contextually tuned
           | approach than we will calling that service way out over
           | there, even if the service can handle large/different loads
           | more efficiently". It's just common sense. This happens _a
           | lot_ in some teams /organizations/projects.
        
           | laborcontract wrote:
           | Langchain. Helps on the initial productivity, is a nightmare
           | on the debugging and performance improvement end.
        
           | eyelidlessness wrote:
           | > I'd love examples where DRY can really hurt performance.
           | 
           | A really common example is overhead of polymorphism, although
           | that overhead can vary a lot between stacks. Another is just
           | _the effect_ caused by the common complaint about premature
           | abstraction: proliferation of options /special cases, which
           | add overhead to every case even when they don't apply.
        
             | kitkat_new wrote:
             | use compile time polymorphism
             | 
             | premature abstraction -> not understood dry
             | (https://news.ycombinator.com/item?id=40525064#40525690)
        
           | nsguy wrote:
           | This quote is often taken out of context, here's the full
           | quote: "Programmers waste enormous amounts of time thinking
           | about, or worrying about, the speed of noncritical parts of
           | their programs, and these attempts at efficiency actually
           | have a strong negative impact when debugging and maintenance
           | are considered. We should forget about small efficiencies,
           | say about 97% of the time: premature optimization is the root
           | of all evil. Yet we should not pass up our opportunities in
           | that critical 3%."
           | 
           | If you want a specific example look at something that needs
           | to be performant, i.e. in those 3%, let's say OpenSSL's AES
           | implementation for x86, or some optimized LLM code, you'll
           | see the critical performance sections include things that
           | could be reused, but they're not.
           | 
           | Also the point Knuth is making is don't waste time on things
           | that don't matter. Overuse of DRY falls squarely into that
           | camp as well. It takes more work and doesn't really help. I
           | like Go's proverb there of "A little copying is better than a
           | little dependency."
        
             | rgrmrts wrote:
             | Knuth was talking about a very specific thing, and the
             | generalization of that quote is a misunderstanding of his
             | point.
             | 
             | Source: Donald Knuth on the Lex Fridman podcast, when Lex
             | asks him about that phrase
        
               | nsguy wrote:
               | I wasn't aware this was discussed, thanks for the
               | pointer! I'm curious now what _he_ says he was talking
               | about ;)
        
               | nsguy wrote:
               | Here's that segment:
               | https://www.youtube.com/watch?v=74RdET79q40
        
           | xiasongh wrote:
           | This might not be a perfect example, but there's a paper by
           | Michael Stonebraker "One size fits all": an idea whose time
           | has come and gone
           | 
           | It might not specifically be DRY, but still related generic
           | vs specialized code/systems.
           | 
           | https://ieeexplore.ieee.org/document/1410100
        
         | mikepurvis wrote:
         | I think it really depends and it's a case where a lot of
         | engineering judgment and taste comes to bear. For example right
         | now I'm maintaining a Jenkins system that has two large and
         | complicated pipelines that are about 90% overlapping but for
         | wretched historical reasons were implemented separately and the
         | implementations have diverged over the years in subtle ways
         | that now make it challenging to re-unify them.
         | 
         | There is no question in my mind that this should always have
         | been built as either a single pipeline with some parameters to
         | cover the two use-cases, or perhaps as a toolbox of reusable
         | components that are then used for the overlapping area. But I
         | expect the mentality at the time the second one was being stood
         | up was that it would be less disruptive to just build the new
         | stuff as a parallel implementation and figure out later how to
         | avoid the duplication.
        
           | kccqzy wrote:
           | You are describing technical debt, not conscious decisions to
           | be DRY or not DRY.
        
             | mikepurvis wrote:
             | Hmm. Certainly there's no doubt that there's technical debt
             | ("do it this way for now, we'll clean it up later") here
             | too, but I think there was also a conscious decision to
             | build something parallel _rather_ than generalizing the
             | thing that already existed to accommodate expanding
             | requirements.
        
             | kitkat_new wrote:
             | not drying is technical debt
        
         | sys_64738 wrote:
         | > Generality can really hurt performance.
         | 
         | Only in critical regions of code though.
        
         | jprete wrote:
         | I agree for very specific situations, but compilers tend to get
         | better at optimization over time, and it can be better to
         | express plain intent in the code and leave low-level
         | optimization to the compiler, rather than optimizing in code
         | and leaving future hardware/compiler improvements on the table.
        
       | DanielHB wrote:
       | Boilerplate that you can't get wrong is better than DRY in most
       | cases
       | 
       | by "get wrong" I mean through static analysis (linters or type
       | checkers) or if it is plainly obvious by running it.
        
         | kitkat_new wrote:
         | isn't encoding a requirement in the type applying DRY as well
         | then?
        
       | pydry wrote:
       | I don't know about anyone else, but I've been _deeply_
       | unimpressed with the output of the google testing blog.
       | 
       | This example is not wrong, but it's not particularly insightful
       | either. Sandi Metz said it better here, 8 years ago
       | https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction
       | 
       | The testing pyramid nonsense is probably the worst one though.
       | Instead of trying to find a sensible way to match the test type
       | to the code, they pulled some "one size fits all" shit while
       | advertising that they aren't that bothered about fixing their
       | flaky tests.
        
         | sitkack wrote:
         | Google doesn't test! That is what production and SREs and users
         | are for.
        
         | nrook wrote:
         | I think you're holding some of these to too high of a bar. This
         | is a one-page article intended to be posted in company
         | bathrooms. Of course it's less comprehensive than a longer blog
         | post.
        
           | pydry wrote:
           | It's not like the subtitle says "not to be taken seriously"
           | and they are representing a brand that is supposed to stand
           | for engineering excellence.
        
         | bitcharmer wrote:
         | Most seasoned software engineers stopped following google in
         | that respect a long time ago. They are not a tech shop any
         | more; it's just an add business now with lots of SRE work.
        
       | localfirst wrote:
       | also never write tests for code that doesn't exist because you
       | gradually slow down learning to a crawl and you are no longer
       | writing features but tests and mockups that offer nothing to the
       | end user.
        
       | hardwaregeek wrote:
       | It's important to remember that all best practices are not
       | created equal. I'd prioritize readability over DRY. I'd
       | prioritize cohesion over extensibility. When people talk about
       | best practices, they don't talk about how a lot of them are
       | incompatible, or at least at odds with each other. Writing code
       | is about choosing the best practices you want to prioritize as
       | much as it's about avoiding bad practices.
        
         | unnouinceput wrote:
         | Maintenance is 90% of a project life time. Sometime those "best
         | practices" rigid implemented means the project won't live to
         | see even it's 1st birthday.
        
         | 0xbadcafebee wrote:
         | Readability doesn't matter much when you have 10,000+ lines of
         | code. You aren't going to read all that code, and new code
         | introduced by other people continuously isn't something you can
         | keep track of, so even if you understand one tiny bit of code,
         | you won't know about the rest. You need a system of code
         | management (documentation, diagram, IDE, tests, etc), to
         | explain in a human-friendly way what the hell is going on.
         | Small chunks of code will be readable enough, and the code
         | management systems will help you understand how it relates to
         | other code.
        
           | ugh123 wrote:
           | > You need a system of code management (documentation,
           | diagram, IDE, tests, etc), to explain in a human-friendly way
           | what the hell is going on
           | 
           | I think this is where AI could be helpful in explaining and
           | inspecting large codebases, as an assist to a developer.
        
             | rohansingh wrote:
             | Maybe but hallucinations become a real problem here. Even
             | with publicly available API's that are just slightly off
             | the beaten path, I've gotten full-on hallucinations that
             | have derailed me and wasted time.
        
             | chipdart wrote:
             | > I think this is where AI could be helpful in explaining
             | and inspecting large codebases, as an assist to a
             | developer.
             | 
             | That's a great point. Everyone lauds the benefits of
             | chatgpt/copilot in generating new code, but I'm starting to
             | learn that the places they might shine is onboarding onto
             | projects and preliminary code reviews. What LlMs excel at
             | is context, and they should excel in activities where
             | context-awareness is key.
        
           | dieortin wrote:
           | Even if you're not going to read 10.000+ lines, if the few
           | you read are easy to understand you're still going to have a
           | much better time maintaining the codebase.
        
           | chipdart wrote:
           | > Readability doesn't matter much when you have 10,000+ lines
           | of code. You aren't going to read all that code (...)
           | 
           | You got it entirely backwards. Readability becomes far more
           | important with the size of your project.
           | 
           | When you get a bug report of a feature request, you need to
           | dive into the code and update the relevant bits. With big
           | projects, odds are you will need to change bits of the code
           | you never knew they existed. The only way that's possible is
           | if the code is clear and it's easy to sift through,
           | understand, and follow.
           | 
           | > You need a system of code management (documentation,
           | diagram, IDE, tests, etc), to explain in a human-friendly way
           | what the hell is going on.
           | 
           | That system of code management is the code itself. Any IDE
           | supports searching for references, jump to definitions, see
           | inheritance chains, etc. Readable code is code that is easy
           | to navigate and whose changes are obvious.
        
           | foresto wrote:
           | > Readability doesn't matter much when you have 10,000+ lines
           | of code. You aren't going to read all that code,
           | 
           | As someone who has read 10,000+ lines in order to track down
           | surprising behavior in other people's code, I can say without
           | a doubt that readability still matters at that scale.
           | 
           | Code management systems can sometimes be helpful, but they
           | are no substitute.
        
           | fmbb wrote:
           | > Small chunks of code will be readable enough
           | 
           | Ravioli code is a real problem though. Saying small chunks
           | are readable is not enough. The blast radius of a five byte
           | change can be fifteen code paths and five million requests
           | per hour.
        
           | HumblyTossed wrote:
           | 10KLoC is a very small app. Ours isn't that big and it's
           | 140KLoC and I have read almost all of it.
        
             | chipdart wrote:
             | To be fair, not all lines of code are equal. A project with
             | a state machine, commands, strategy patterns, etc requires
             | an awful lot of repetitive boilerplate.
             | 
             | A number-crunching app or a data processing pipeline packed
             | with spaghetti business logic is far harder ti read.
        
               | HumblyTossed wrote:
               | And that is why KLoC is a very piss poor metric.
               | 
               | >A project with a state machine, commands, strategy
               | patterns, etc requires an awful lot of repetitive
               | boilerplate.
               | 
               | Yeah, good thing we're not a java shop...
        
         | drojas wrote:
         | I agree and would add that one of the goals for technical
         | design or architecture work is to choose the architecture that
         | minimizes the friction between best practices. For example if
         | you architecture makes cohesion decrease readability too much
         | then perhaps there is a better architecture. I see this
         | tradeoff pop up from time to time at my work for example when
         | we deal with features that support multiple "flavors" of the
         | same data model, then we have either a bunch of functions for
         | each providing extensibility or a messy root function that
         | provides cohesion. At the end both best practices can be
         | supported by using an interface (or similar construct depending
         | on the language) in which cohesion is provided by logic that
         | only cares about the interface and extensibility is provided by
         | having the right interface (offload details to the specific
         | implementations)
        
         | englishspot wrote:
         | I have a pessimistic view that ultimately the only best
         | practices that matter are the ones your boss or your tech lead
         | likes.
        
           | jkaptur wrote:
           | What about when you are the boss or tech lead?
        
             | znkr wrote:
             | Then the only best practices that matter are the ones that
             | your team believes are correct
        
               | rvnx wrote:
               | The best practices are the ones that allow you to do
               | business and where the maintenance work is relatively not
               | too painful considering the budgeted development time.
               | 
               | Your task is to deliver a good product, not necessarily
               | good code.
        
               | englishspot wrote:
               | > The best practices are the ones that allow you to do
               | business and where the maintenance work is relatively not
               | too painful considering the budgeted development time.
               | 
               | the problem is even that in concrete terms can be
               | controversial. everyone wants to minimize maintenance
               | work; not everyone agrees on what kind of code will
               | achieve that.
        
         | delichon wrote:
         | "Read" isn't quite the right word for code. "Decode" is better.
         | We have to read to decode, but decoding is far less linear than
         | reading narrative text. Being DRY usually makes decoding
         | easier, not harder, because it makes the logic more cohesive.
         | If I know you only fromajulate blivers in one place I don't
         | have to decode elsewhere.
        
           | hathawsh wrote:
           | Well, "read" is still the verb we use most often to describe
           | a human interpreting code. Also, many information-dense books
           | are not intended to be read linearly, yet we still say we're
           | "reading" (or "studying") the book.
        
           | wwfn wrote:
           | I was just mulling this over today. DRY = easier-to-decode is
           | probably true if you're working on groking the system at
           | large. If you just want to peak in at something specific
           | quickly, DRY code can be painful.
           | 
           | I wanted to see what compile flags were used by guix when
           | compiling emacs. `guix edit emacs-next` brings up a file with
           | nested definitions on top of the base package. I had to trust
           | my working memory to unnest the definitions and track which
           | compile flags are being added or removed. https://git.savanna
           | h.gnu.org/cgit/guix.git/tree/gnu/packages...
           | 
           | It'd be more error prone to have each package using redundant
           | base information, but I would have decoded what I was after a
           | lot faster.
           | 
           | Separately, there was a bug in some software aggregating
           | cifti file values into tab separated values. But because any
           | cifti->tsv conversion was generalized, it was too opaque for
           | me to identify and patch myself as a drive-by contributor.
           | https://github.com/PennLINC/xcp_d/issues/1170 to https://gith
           | ub.com/PennLINC/xcp_d/pull/1175/files#diff-76920...
        
             | emidln wrote:
             | Bazel solves this exact problem (coming from its
             | macrosystem) by allowing you to ask for what I term the
             | "macroexpanded" BUILD definition using `bazel query
             | --output=build //some/pkg/or:target`. When bazel does this,
             | it also comments the file, macro,and line number the
             | expanded content came from for each block.
             | 
             | This gives us reuse without obscuring the real definition.
             | 
             | I automated this in my emacs to be able to "macroexpand"
             | the current buid file in a new buffer. It saves me a lot of
             | time.
        
           | withinboredom wrote:
           | > Being DRY usually makes decoding easier, not harder
           | 
           | "Usually" being the keyword and what the article is all about
           | IMHO. I work in a codebase so DRY that it takes digging
           | through dozens of files to figure out what one constant
           | string will be composed as. It would have been simpler to
           | simply write it out, ain't nobody going to figure out
           | OCM_CON_PACK + OCM_WK_MAN means at a glance.
        
             | djeastm wrote:
             | >I work in a codebase so DRY that it takes digging through
             | dozens of files to figure out what one constant string will
             | be composed as.
             | 
             | I don't know the codebase, but to my mind that level of
             | abstraction means it's a system-critical string that
             | justifies the work it takes to find.
        
               | RussianCow wrote:
               | Sorry, but this doesn't make sense. Why should system
               | critical things be more difficult to understand? Surely
               | you want to _reduce_ room for error, not increase it?
        
               | withinboredom wrote:
               | I mean, sure, I guess API urls could be system-critical.
               | But generally, I prefer to grep a codebase for a url
               | pattern and find the controller immediately. Instead, you
               | have to dig through layers of strings composed of other
               | strings and figure it out. Then at the end, you're
               | probably wrong.
        
             | jbverschoor wrote:
             | Function calls, the essence of DRY, are only readable if it
             | is well known and well understood what it does.
             | 
             | When code is serial, with comment blocks to point out
             | different sections, it is much easier to read, follow, and
             | debug.
             | 
             | This is also a little bit of a tooling problem
        
           | dsego wrote:
           | Visually parse.
        
           | Nevolihs wrote:
           | Does it? Every time I see DRY'd code, it usually makes the
           | project it's in more difficult to understand. It's harder to
           | understand where values come from, where values are changed,
           | what parts of the codebase affect what. And that's before
           | trying to figure out where to change something in the right
           | place, because it's often unclear what other parts of the
           | code are coupled to it through all the abstractions.
           | 
           | At a high level, at first glance, the code might look good
           | and it "makes sense". But once you want to understand what's
           | happening and why, you're jumping through five different
           | classes, two dozen methods and you still don't know for sure
           | until you run a test request against the API and see what
           | shows up where in the debugger. And you realize your initial
           | glimpse of understanding was just window dressing and
           | actually nothing makes sense unless you understand every
           | level of the abstractions being used.
           | 
           | It's suddenly a puzzle to understand another software
           | developer instead of software engineering.
        
             | hnick wrote:
             | An IDE can help a lot. Coming from Perl, everything you
             | said was true. I wanted everything in one file as much as
             | possible, and breaking tasks off into functions just meant
             | I had to jump around to try and rebuild the flow in my
             | head. I spent so much time inside the debugger since
             | reading the code would only go so far.
             | 
             | Now I work in C#, we have a lot of classes with a few
             | functions, a lot of helper functions. Doesn't matter since
             | it's so easy to use the tooling to build a mental picture -
             | let alone refactor it in an instant if that variable name
             | feels a bit off, or we think a function is not used (such
             | things were always a risky exercise in Perl).
             | 
             | We refactored one insurance based project to use generic
             | base classes extensively since all insurance shares some
             | attributes and features - this really helped cut down
             | complexity of changes and overall just reduced code on the
             | screen to sift through. I had a lot of fun doing this, I'm
             | a weirdo who almost likes deleting code more than writing
             | it. Once you hit the lowest level it is a little less
             | intuitive due to being generic but at the higher levels we
             | mostly work at, it's simpler, and rolling out a new product
             | we get a lot of stuff for free. They got a long way copy-
             | pasting the product logic (4 or 5 product lines) but at
             | this point it made sense to revisit, and I sneak a bit more
             | in each time I have a change to do.
        
           | cloverich wrote:
           | One area I find DRY particularly annoying is when people
           | overly abstract Typescript types. Instead of a plain
           | interface with a few properties, you end up with a bunch of
           | mushed together props like { thing: boolean } &
           | Pick<MyOtherObj, 'bar' | 'baz'} & Omit<BaseObj, 'stuff'>
           | instead of a few duplicated but easily readable interfaces:
           | 
           | interface MyProps { thing: boolean; bar: string; baz: string;
           | stuff: string; }
        
             | jddj wrote:
             | Am I crazy for almost exclusively just using _type_ and sum
             | types and no generics or interfaces and somehow being able
             | to express everything I need to express?
             | 
             | Kind of wondering what I'm missing now.
        
               | Aeolun wrote:
               | Hmm, you can do pretty nice things with generics to make
               | some things impossible (or at least fail on compile), but
               | I agree it's hardly readable. In some cases you need that
               | though.
        
         | blackoil wrote:
         | I am of opinion, code should be written to be readable. Rest of
         | the desirable properties are just side-effects.
        
           | gavmor wrote:
           | I think it's fair to say that between behavior and
           | maintainability, one is inflexible and the other hangs from
           | it in tension.
        
           | jay-barronville wrote:
           | Fully agree. I think this is something that takes some
           | time/experience to appreciate though. Junior engineers will
           | spend countless hours writing pages of code that align with
           | the "design patterns" or "best practices" of the day when
           | there's a simpler implementation of the code they're writing.
           | (I'm not saying this condescendingly--I was once a junior
           | engineer who did that too!)
        
             | coffeebeqn wrote:
             | It's impossible to know what "good" looks like when you're
             | new and haven't seen a few codebases of varying quality and
             | made some terrible mistakes
        
           | marcosdumay wrote:
           | Most commonly, code should optimized into being easy to
           | change.
           | 
           | That's almost entirely coincidental with being easy to read.
           | But even easiness to read is a side effect.
        
             | hnick wrote:
             | I agree with this. Easy to change often means good tests
             | too.
             | 
             | I worked in Perl. Yes it has a reputation for being hard to
             | read, but that was not the problem. Our scripting was
             | pretty basic and easy to read. It's the loose typing, the
             | runtime evals, the lack of strict function parameters, no
             | real IDE, "Only perl can parse Perl" - the fact you can
             | load a module from a network share at runtime, import it,
             | and call a function, based on a certain run flag - and so
             | on. Refactoring was always a mine field and there was a lot
             | I wanted to do in my old job but could not justify it due
             | to the risk.
        
           | drewcoo wrote:
           | "Side effects" are not the same as "less important traits."
           | 
           | Side effects are usually unrelated or unwanted.
        
         | jay-barronville wrote:
         | Readability is almost always (almost only because there are
         | some rare exceptions) the most important thing to me, even for
         | low-level systems software. I always ask myself, "If I don't
         | touch this code for a year and then come back to it, how long
         | will it take me to understand it again? How long will it take
         | someone who's never been exposed to this code to understand
         | it?"
         | 
         | Luckily, our compilers and interpreters have gotten so good and
         | advanced that, in 95%+ of cases, we need not make premature
         | "optimizations" (or introduce hierarchies of "design patterns")
         | that sacrifice readability for speed or code size.
        
           | arp242 wrote:
           | Was reading 1978 Elements of Programming Style a while ago.
           | It's mostly Fortran and PL/I. Some of it is outdated, but a
           | lot applies today as well. See e.g. https://en.wikipedia.org/
           | wiki/The_Elements_of_Programming_St...
           | 
           | They actually have a Fortran example of "optimized" code
           | that's quite difficult to follow, but allegedly faster
           | according to the comments. But they rewrote it to be more
           | readable and ... turns out that's actually faster!
           | 
           | So this already applied even on 197something hardware. Also
           | reminds me about this quote about early development of Unix
           | and C:
           | 
           |  _" Dennis Ritchie encouraged modularity by telling all and
           | sundry that function calls were really, really cheap in C.
           | Everybody started writing small functions and modularizing.
           | Years later we found out that function calls were still
           | expensive on the PDP-11, and VAX code was often spending 50%
           | of its time in the CALLS instruction. Dennis had lied to us!
           | But it was too late; we were all hooked..."_
           | 
           | And Knuth's "premature optimisation is the root of all evil"
           | quote is also decades old by now.
           | 
           | Kind of interesting we've been fighting this battle for over
           | 50 years now :-/
           | 
           | (It should go without saying there are exceptions, and cases
           | where you _do_ need to optimize the shit out of things, after
           | having proven that performance may be an issue. Also at scale
           | "5% faster" can mean "need 5% less servers", which can
           | translate to millions/dollars saved per year - "programmers
           | are more expensive than computers" is another maxim that
           | doesn't always hold true).
        
             | semi-extrinsic wrote:
             | > Dennis Ritchie encouraged modularity by telling all and
             | sundry that function calls were really, really cheap in C.
             | 
             | The old salty professor who taught numerical physics at my
             | uni insisted that function calls were slow and that it was
             | better to write everything in main. He gave all his
             | examples in Fortran 77. This was in the 2010s...
        
               | coliveira wrote:
               | In fact he is right. The advantage of writing modular
               | code, however, is that we can test the locations where
               | performance is needed and optimize later. With a big main
               | it becomes very hard to do anything complex.
        
               | Archelaos wrote:
               | This is why I liked it when the language I was coding in
               | supported inline expansion: I could keep my code modular
               | but nevertheless avoid the penality of function calls in
               | performance critical functions in the compiled code.
        
               | chipdart wrote:
               | > In fact he is right.
               | 
               | Was he, though? I mean, yeah having to push and pop a
               | call stack does indeed require more work than not having
               | to do that. However, compilers can and do inline and
               | optimize out function calls.
               | 
               | And what's the real performance impact of calling
               | functions a constant number of times outside of the hot
               | path? Is an untestable spaghetti salad of things better
               | than a few hypothetical push and pops?
               | 
               | There's wisdom behind Knuth's remarks on premature
               | optimization.
        
           | jghn wrote:
           | The one gotcha with optimizing for "readability" is that at
           | least to some extent it's a metric that is in the eye of the
           | beholder. Over the years I've seen far too many wars over
           | readability during code review when really people were
           | arguing about what seemed readable *to them*
        
             | smrq wrote:
             | This is the reason I refuse to use the word "clean" to
             | describe code anymore. It's completely subjective, and far
             | too many times I've seen two people claim that their
             | preferred way of doing things is better because it's
             | "clean", and the other's way is worse because it's "less
             | clean", no further justification added. It's absolutely
             | pointless.
        
               | jghn wrote:
               | There are a lot of topics in software development where
               | everyone can agree that X is correct. However, *defining*
               | X gets into subjective arguments. And yep, readability
               | and clean code are both in that category.
        
         | zooq_ai wrote:
         | aka "Engineering is about trade-offs"
        
         | Guvante wrote:
         | DRY is IMHO a maintenance thing.
         | 
         | If "I don't want to maintain three copies of this" is your
         | reaction unifying likely makes sense.
         | 
         | But that assumes the maintenance would be similar which is
         | obviously a big assumption.
        
           | coffeebeqn wrote:
           | DRY often gives you the wrong or a leaky abstraction and
           | creates dependencies between sometimes unrelated pieces of
           | code. It's got tradeoffs rather than being a silver bullet
           | for improving codebases.
           | 
           | Having 0% DRY is probably bad, having 100% DRY is probably
           | unhinged
        
             | kitkat_new wrote:
             | > sometimes unrelated pieces
             | 
             | you are using it wrong
             | 
             | https://news.ycombinator.com/item?id=40525064#40525690
        
         | insane_dreamer wrote:
         | > I'd prioritize readability over DRY.
         | 
         | Yes. Especially at the beginning when it's critical to ensure
         | that the logic is correct.
         | 
         | You can then go back and DRY it up while making sure your unit
         | tests (you did write those, right?) still pass.
         | 
         | PS: same applies to "fancy" snippets that save you a few lines;
         | write it the "long way" first and then make it fancy once
         | you're sure it runs the way it's supposed to
        
           | kitkat_new wrote:
           | > You can then go back and DRY it up while making sure your
           | unit tests (you did write those, right?) still pass.
           | 
           | not gonna happen once merged
        
         | hot_gril wrote:
         | I place copy-pastability somewhere into those priorities too :)
        
         | woodpanel wrote:
         | Agree and would add that software projects also run through
         | different phases in their lifespans with each phase having
         | their own objectives [1].
         | 
         | So while - as you say - best practices can be at odds with each
         | other - dev teams might be following both over time, just
         | prioritizing one in some phase while completely disregarding it
         | during another.
         | 
         | [1] E.g. the UI of the actual product might pivot multiple
         | times at phase 1 because the product has yet to find its niche
         | or core offering. While at a later stage the focus might be on
         | massive scaling, either in numbers of devs or rolling out the
         | product in new jurisdictions. Other phases might be a
         | maintenance one, when an "offshore" team is given ownership or
         | a sundown of an application.
        
         | p1necone wrote:
         | > Cohesion over extensibility.
         | 
         | Imo the very best approach is a codebase that's small enough
         | that you can just do chunky refactors every so often rather
         | than building in extensibility as a "thing". Not applicable to
         | all problem spaces (I'd hate to do this for UI code), but for a
         | lot of stuff it works really nicely.
         | 
         | For me this often looks like an external DSL/API that stays
         | relatively constant (but improving), with guts that are always
         | changing.
        
       | renegade-otter wrote:
       | I think the only hard and fast rule is to DRY the code that will
       | introduce a bug if you change it in one place and not the other.
       | And if it will, _at least_ do a fat comment in both places for
       | posterity.
       | 
       | Whenever I have to have a "mental model" of the code, I know I
       | screwed up.
        
         | senkora wrote:
         | +1. If I go with the comment option, then I'll sometimes write
         | a comment like "If you change this here, then you must change
         | it everywhere with this tag: UNIQUE-TAG".
         | 
         | This way, the reader can just do a global grep to find all the
         | places to change, and you don't have to list them in each place
         | and keep them in sync.
        
         | randomdata wrote:
         | A comment is a nice addition, but the very least is to ensure
         | that your test suite properly covers cases where changing one
         | and not the other will introduce a problem. This not only
         | ensures that both are changed, but that both are changed in the
         | way they need to be. A comment alone may prompt you to change
         | both (if you ever read it - I bet a lot of developers don't),
         | but you may not notice when you fail to change them in the same
         | way, which is no better than not changing one.
        
       | haswell wrote:
       | One of the #1 issues I've seen with DRY over the years seems to
       | stem from a misunderstanding of what it means.
       | 
       | DRY is not just about _code_ duplication, it's about
       | _information_ / _knowledge_ duplication, and code happens to be
       | one representation of information.
       | 
       | Hyper focusing on code duplication quickly gets into premature
       | optimization territory, and can result in DRYing things that
       | don't make sense. Focusing on information duplication leaves some
       | leeway for the code and helps identify which parts of the code
       | actually need DRY.
       | 
       | The difference is important, and later editions of the Pragmatic
       | Programmer call this out specifically. But the concept of DRY
       | often gets a bit twisted in my experience.
        
         | HanClinto wrote:
         | I feel like I hear de-duplication of information / knowledge
         | often referred to as "Single Source of Truth"
        
           | derefr wrote:
           | I think the best way to understand DRY is by thinking about
           | the practical problem it solves: you don't want footguns in
           | the codebase where you could change something in one place,
           | but forget to change the same thing in other places (or
           | forget to change the complementary logic/data in other
           | components.)
           | 
           | The goal of DRY as a refactoring, is first-and-foremost to
           | obviate such developer errors.
           | 
           | And therefore -- if you want to be conservative about
           | applying this "best practice" -- then you could do that by
           | just never thinking "DRY" until a developer _does in fact_
           | trip over some particular duplication in your codebase and
           | causes a problem.
        
             | rezonant wrote:
             | > you don't want footguns in the codebase where you could
             | change something in one place, but forget to change the
             | same thing in other places
             | 
             | This. Ironically the example on TFA is vulnerable to this
             | issue. Each of the deadline setting methods has a copy of
             | the validation ensuring that the date is in the future. If
             | it's discovered that we need to ensure deadlines are set no
             | later than the project deadline (since that wouldn't
             | generally make sense), it's awfully easy to only update one
             | and miss the others, especially after code has been added
             | and these implementations are no longer visually near each
             | other. I'm not saying that this means the code must be
             | DRY'ed, but it is a risk from the beginning of the project,
             | so one that needs to be weighed during initial
             | implementation.
        
           | hot_gril wrote:
           | The Two Generals Problem is mentioned a lot in databases and
           | networking, and you can take some liberties to extend it to
           | human orgs.
        
             | sham1 wrote:
             | > The Two Generals Problem is mentioned a lot in databases
             | and networking, and you can take some liberties to extend
             | it to human orgs.
             | 
             | In fact, the name probably already gives some hint as to
             | what sorts of human orgs this sort of principle would be
             | applicable in.
             | 
             | And, well, the vast majority of orgs require some sort of
             | coordination.
        
         | schneems wrote:
         | Someone "yes and"-ed a comment of mine awhile ago to teach me
         | DRY SPOT. Don't repeat yourself - Single Point of Truth.
         | 
         | I.e. what you said. Couple logic that needs to be coupled.
         | Decouple logic that shouldn't be coupled.
        
         | joe_fishfish wrote:
         | This is a more insightful comment than the comment at the top,
         | and also more useful than the blog post.
        
         | tetha wrote:
         | This is why some advice from Sandy Metz really stuck with me.
         | 
         | It is not a problem to /have/ the same code 2, 3 or even 4
         | times in a code base. In fact, sometimes just straight up copy-
         | paste driven development can be a valid development technique.
         | Initially that statement horrified me, but by now I understand
         | that just straight up copy-pasting some existing code can be
         | one of these techniques that require some discipline to not
         | overdo, but it's legit.
         | 
         | And in quite a few cases, these same pieces of code just start
         | developing in different directions and then they aren't the
         | same code anymore.
         | 
         | However, if you have to /change/ the same code in the same way
         | in multiple places, then you have a problem. If you have to fix
         | the same bug in multiple places in similar or same ways, or
         | have to introduce a feature in multiple places in similar way -
         | then you have a problem.
         | 
         | Once that happens, you should try to extract the common thing
         | into a central thing and fix that central thing once.
         | 
         | It feels weird to work like that at first, but I've found that
         | often it results in simpler code and pretty effective
         | abstractions, because it reacts to the actual change a code
         | base experiences.
        
           | nostrademons wrote:
           | The challenge is that if you're not careful, you can end up
           | copy-pasting the same bit of code hundreds of time before
           | realizing it has to be changed.
           | 
           | I once worked in a year-old startup of ~5 developers that
           | found it had written the same line of code (not even copy-
           | pasted, it was only one line of code so the devs had just
           | written it out) 110 times. A bug was then discovered in that
           | line of code, and it had to be fixed in 110 places, with no
           | guarantee that we'd even found all of them. This was a very
           | non-obvious instance of DRY, too, because it was only one
           | line of code and the devs believed it was so simple that it
           | couldn't possibly be wrong. But that's why you sometimes need
           | to be aware of what you're writing even on the token level.
           | 
           | That's why we have principles like "3 strikes and then you
           | refactor". 3 times fixing a bug isn't too onerous; even 4-6
           | is pretty manageable. Once you get to 20+, there starts to be
           | a strong disincentive to fixing the bug, and even if you want
           | to, you aren't sure you got every instance.
        
             | tetha wrote:
             | Oh yeah we've had those as well. I kinda feel two things
             | about these at the same time.
             | 
             | At a practical level, these situations sucked. Someone had
             | to search for the common expression, look at each instance,
             | decide to change it to the central place or not. They spent
             | 2-3 days on that. And then you realize that some people
             | were smart and employed DRY - if they needed that one
             | expression 2-3 times, they'd extracted one sub-expression
             | into a variable and suddenly there was no pattern to find
             | those anymore. Those were 2-4 fun weeks for the whole team.
             | 
             | But at the same time, I think people learned an important
             | concept there: To see if you are writing the same code, or
             | if you're referring to the same concept and need the same
             | source of truth, like the GP comment says. I'm pretty happy
             | with that development. Which is also why my described way
             | is just one tool in the toolbox.
             | 
             | Like, one of our code bases is an orchestration system and
             | it defines the name of oidc-clients used in the
             | infrastructure. These need to be the same across the
             | endpoints for the authentication provider, as well as the
             | endpoints consumed by the clients of the oidc provider -
             | the oauth flows won't work otherwise.
             | 
             | And suddenly it clicked for a bunch of the dudes on the
             | team why we should put the pedestrian act of jamming some
             | strings together to get that client-id into some function.
             | That way, we can refer to the concept or naming pattern and
             | ensure the client will be identical across all necessary
             | endpoints, over hoping that a million different string
             | joins all over the place result in the same string.
             | 
             | In such a case, early or eager DRY is the correct choice,
             | because this needs to be defined once and exactly once.
        
             | spion wrote:
             | This really makes me think we should be focusing on
             | cost/benefit, risk/reward, pros/cons at all times. If we
             | have a bug in these 5 copies, will it be too hard to fix in
             | all of them? No? What about these 10 copies? If that sounds
             | like its starting to get difficult, maybe now is the time.
        
               | danielmarkbruce wrote:
               | That means you have to think. Most people hate thinking.
               | Seriously.
        
               | laserlight wrote:
               | > If we have a bug in these 5 copies, will it be too hard
               | to fix in all of them?
               | 
               | Yes, it will be, because copy-pasted code is never the
               | same verbatim. First and foremost, name changes make it
               | almost impossible to identify different copies. Then,
               | there are different tweaks for each copy to make it
               | suitable for the context. I always DRY early, because
               | it's always free to copy-paste later.
        
               | spion wrote:
               | I could make the same argument for not using DRY. The
               | DRY-ed code is hard to change, programmers feel honour-
               | bound to keep using it and tweaking it by adding a
               | variety of parameters to more and more cases, and at the
               | end becomes impossible to understand or update, slowing
               | down development.
               | 
               | Now, what probably should've been 3 abstractions is one
               | incredibly convoluted "abstraction" that makes no sense,
               | and its 3x harder than 3 individual abstractions to
               | deduplicate and inline. It further pulls and invites
               | complexity, as its current size is implicit invitation to
               | include additional cases and places.
               | 
               | Furthermore, while without DRY fixing bugs may've been
               | tedious, now with DRY it may be almost impossible due to
               | high risk of breaking a lot of things that depend on that
               | code. (You might be lucky enough to be able to and have
               | written extensive tests with 100% edge case coverage for
               | it - if that's the case then you've postponed the moment
               | of pain somewhat)
               | 
               | Both can be true. It depends on the context whether
               | benefits exceed costs. Decisions should be made based on
               | a specific context and with thinking applied, not generic
               | rules.
        
               | kitkat_new wrote:
               | 5 copies are already extremely difficult.
               | 
               | It's not like they just jump into your eye when you edit
               | some code.
        
               | spion wrote:
               | It depends on the context. In some context, they might
               | actually jump out. In some context, even if they don't,
               | it might be fine, because the larger modules containing
               | the code already have excellent tests and are solid and
               | stable
        
             | withinboredom wrote:
             | This is why you shouldn't write one line of code, ever
             | again. /s
             | 
             | We've all been there though, at some point in our careers.
             | Possibly multiples of times (try changing thousands of
             | "echo" statements to call a logger because it was initially
             | meant to be a simple script that just kept growing).
             | 
             | It sucks but I've also been on the other side, where it was
             | DRY but 20% of the calls to the function now needed
             | different behavior. Finding all of those usages was just as
             | hard.
        
               | noisy_boy wrote:
               | > We've all been there though, at some point in our
               | careers. Possibly multiples of times (try changing
               | thousands of "echo" statements to call a logger because
               | it was initially meant to be a simple script that just
               | kept growing).
               | 
               | Been there - now unless it is a very simple / throwaway
               | code, I always start with logging setup from the start.
               | It also helps with print based debugging because you can
               | tune the output.
        
             | sodapopcan wrote:
             | Metz says she adds TODOs and comments that it has been
             | duped. It's one of those things that requires thought, and
             | she even says it's an advanced technique. How many times is
             | too many? I'm not sure, but I can safely say over 100 is
             | WAY too many. Probably 10 is too many. Heck, if you find
             | yourself updating the same code in four different places
             | over and over and over, it's time to abstract. The idea is
             | to let the code sit and let the abstraction reveal itself
             | _if there isn 't already an OBVIOUS one_. As mentioned by
             | the parent poster, you're looking out for these copies to
             | diverge. If four or five copied codepaths haven't diverged
             | after some time, there's a good chance that just from
             | working on it every day you will have realized the proper
             | way to abstract it.
             | 
             | You absolutely do have to be careful. But even so, it's
             | arguable that having to update something in 100 different
             | places is better than updating in one place and having it
             | affect 100 different paths where you only want 99 of them
             | (this is some hyperbole, of course).
        
               | tcfhgj wrote:
               | How do you monitor all code duplications in the code
               | base? Including ones that have been modified slightly (
               | such as optimizations, name changes, additional
               | statements in between, etc)
        
               | sodapopcan wrote:
               | Tests. AFAIC, this isn't something that should be long
               | living. If it is only duplicated in a couple of places
               | and remains unchanged for years, that's probably fine
               | too, because ya... no one is touching it. If one place
               | does need to change and tests still pass that should mean
               | that the other one didn't need to change and you've
               | reaped the benefit from not prematurely abstracting.
               | There are a lot of ways it could play out, though. Often
               | the duplication is very local and obvious. I think a lot
               | of people take "duplication is cheaper than the wrong
               | abstraction" WAY more seriously than its intended. It's
               | actionable way of saying "don't abstract early" as the
               | counter that is usually: "But then I'll have duplication
               | and DRY is the law." Like EVERY piece of programming
               | advice, though, it's not universal.
        
             | bigger_cheese wrote:
             | This happens with SQL a lot where people copy and paste
             | queries all over the place. Especially for reports, there's
             | always the case where some quick and dirty report was
             | thrown together in 20 mins ends up as something managers
             | can't live without.
             | 
             | Making changes quickly get onerous when the query (or
             | slight variation on it) is pasted into multiple places.
             | Nowadays my org has started to use Power BI so there is
             | also multiple dashboards that all need to be updated.
        
           | gmueckl wrote:
           | Conversely, trying too hard to DRY when requirements at call
           | sites start to diverge can lead to an unnecessary complex
           | single implementation of something where there could be two
           | very similar but still straightforward pieces of code.
        
           | wging wrote:
           | You're thinking of Sandi Metz:
           | https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction
        
         | bojanz wrote:
         | Couldn't agree more. There's a great decade-old blog post by
         | Mathias Verraes which illustrates this well, I keep coming back
         | to it: https://verraes.net/2014/08/dry-is-about-knowledge/
        
         | pineapple_sauce wrote:
         | How is applying DRY entering premature optimization territory
         | (maybe relative to LOC?)? I argue it is instead: premature
         | abstraction.
         | 
         | Optimization is specialization (which is the opposite of DRY):
         | to enable DRY you likely need to generalize the problem (i.e.
         | abstract) such that you remove duplication.
        
           | haswell wrote:
           | I've always seen "Premature Optimization" as an umbrella that
           | covers a variety of cross-cutting concerns, ranging from:
           | 
           | - Performance - Code structure / abstraction - Data structure
           | - Team organization / org structure
           | 
           | I'd argue that DRY (and a focus on abstractions more
           | generally) are optimizations of the codebase. Not all
           | optimizations are optimizing the same thing.
        
             | hot_gril wrote:
             | Yeah, it's like reminding people that code can change, so
             | it's ok to have known flaws day 1. Something forgotten too
             | often.
             | 
             | One thing that really goes against the usual programming
             | grain is DBMSes. We're taught to always decouple/abstract
             | things, but I'm convinced that it's impossible to abstract
             | away your DBMS in most applications. It's just too big of
             | an interface, and performance considerations leak right
             | through it. It's always one of the selling points of an
             | ORM, "you can switch databases later," and then nobody
             | actually switches.
        
         | BeetleB wrote:
         | Indeed - the acronym comes from _The Pragmatic Programmer_ ,
         | and the author defined it in this way. Every blog post I've
         | read criticizing/cautioning against DRY were not doing DRY as
         | originally defined.
         | 
         | DRY is almost always a good thing to do. Coupling superficially
         | similar code is definitely not a good thing to do.
        
           | haswell wrote:
           | Yeah, here's the quote from the later editions addressing
           | this:
           | 
           | > _Let's get something out of the way up-front. In the first
           | edition of this book we did a poor job of explaining just
           | what we meant by Don't Repeat Yourself. Many people took it
           | to refer to code only: they thought that DRY means "don't
           | copy-and-paste lines of source." That is part of DRY, but
           | it's a tiny and fairly trivial part._
           | 
           | > _DRY is about the duplication of knowledge, of intent. It's
           | about expressing the same thing in two different places,
           | possibly in two totally different ways._
           | 
           | > _Here's the acid test: when some single facet of the code
           | has to change, do you find yourself making that change in
           | multiple places, and in multiple different formats? Do you
           | have to change code and documentation, or a database schema
           | and a structure that holds it, or...? If so, your code isn't
           | DRY._
        
           | dllthomas wrote:
           | > Coupling superficially similar code is definitely not a
           | good thing to do.
           | 
           | I've taken to calling that activity (removing syntactic
           | redundancy that is only coincidental) "Huffman coding".
        
         | drewcoo wrote:
         | > misunderstanding of what it means
         | 
         | And in response, people will complain that they're being
         | dismissed with "you're doing it wrong!" Because that happens
         | with everything in programmer-land.
        
           | haswell wrote:
           | The easy response to someone feeling this way is to point
           | them to the origin of DRY: _The Pragmatic Programmer_.
           | 
           | In the book, the authors explicitly call out that many people
           | took the wrong idea from the original writing. They clarify
           | that DRY is not about code, it's about what they call
           | "knowledge", and that code is just one expression of it.
           | 
           | People can still disagree, but the original intent behind DRY
           | is very well articulated.
        
         | hot_gril wrote:
         | Yeah, applies to databases and documentation especially.
         | Databases have the ol' 3NF, you also want to avoid copying data
         | from one source of truth to another in a multi-service
         | environment, and sometimes I intentionally avoid writing docs
         | because I want the code or API spec (with its comments) to be
         | the only documentation.
        
       | causal wrote:
       | Yeah, premature DRY is a pet peeve of mine. Especially since the
       | "size" of the code necessary to trigger DRY is totally
       | subjective: some people apply DRY when they see similar blocks of
       | code, others are so averse to repetition they start abstracting
       | out native syntax.
        
       | pphysch wrote:
       | > Is the duplication truly redundant or will the functionality
       | need to evolve independently over time?
       | 
       | "Looks the same right now" != "Is the same all the time"
       | 
       | Bad abstraction is worse than no abstraction
        
       | rmnclmnt wrote:
       | It is especially hurtful when people apply DRY immediately on
       | some spaghetti code already mixing abstractions.
       | 
       | Then you find yourself untangling intertwined fatorized code on
       | top of leaky abstractions, losing hours/days and pulling your
       | hair out... (I'm bald already but I'm pretty sure I'm still
       | losing hair in these situations)
        
       | sharbloop wrote:
       | My rule: repeat yourself 3 times. On the 4th, re-factor.
        
         | seattle_spring wrote:
         | A good alternative to DRY is WET, or "Write Everything Twice."
         | Or, in your case, "Write Everything Thrice". Both better
         | alternatives than automatic, dogmatic DRY.
        
           | kitkat_new wrote:
           | they aren't better and lead to maintenance hell
        
       | wzdd wrote:
       | Seems like a strawman. The thing being repeated here is something
       | which raises if the datetime isn't in the future. So abstract
       | that out and you then get both methods calling
       | raiseIfDateTimeNotInFuture() which then also serves as
       | documentation.
       | 
       | (But yes, if the actual code is as simple as this example, you
       | may as well just repeat it.)
        
       | michaelcampbell wrote:
       | I mean, sure. I'm generally more WET than most of my colleagues,
       | but this...
       | 
       | > Applying DRY principles too rigidly leads to premature
       | abstractions that make future changes more complex than
       | necessary.
       | 
       | ... is just one of those things that sounds wise, but is just
       | basically a tautology. Use the best tool for the job, etc. No
       | kidding? Would never have thought of that on my own, thanks
       | sensei.
       | 
       | Seriously, the issue with the quoted statement is not that it's
       | new to anyone, it's that no one thinks they ARE applying DRY
       | principles "too rigidly". This is just chin beard stroking advice
       | for "everyone else".
        
         | AnimalMuppet wrote:
         | Well, then, here's some advice:
         | 
         | Learn when to DRY, and when not.
         | 
         | No, you probably don't know as well as you think you do. No,
         | you're not going to get there by grinding leetcode. No, you
         | aren't going to get there quickly, or without a lot of
         | interaction with more-experienced peers, or without being told
         | that your judgment is bad a few times. (And if you don't listen
         | - really listen - then you don't learn.)
         | 
         | Good judgment in these things takes time and experience. If you
         | have a year of experience and think you know, you're probably
         | wrong.
        
           | protomolecule wrote:
           | >takes time and experience
           | 
           | Or maybe asking yourself what end goal you're trying to
           | achieve by building an abstraction.
        
         | protomolecule wrote:
         | >it's that no one thinks they ARE applying DRY principles "too
         | rigidly"
         | 
         | But they should think twice if they are building abstractions
         | _only_ for the sake of DRY.
        
       | 0xbadcafebee wrote:
       | Like the article ends with, DRY goes hand in hand with YAGNI. The
       | point isn't to build a million abstractions; it's to find the
       | places where you have duplication and de-duplicate it, or where
       | you know there'll be duplication and abstract it, or to simply
       | rearchitect/redesign to avoid complexity and duplication. This
       | applies to code, data models, interfaces, etc.
       | 
       | The duplication is typically bad because it leads to
       | inconsistency which leads to bugs. If your code is highly
       | cohesive and loosely coupled, this is less likely [across
       | independent components].
       | 
       | And on this:
       | 
       | > When designing abstractions, do not prematurely couple
       | behaviors
       | 
       | Don't _ever_ couple behaviors, unless it 's within the same
       | component. Keep your code highly cohesive and loosely coupled.
       | Once it's complete, wall it off from the other components with a
       | loosely-coupled interface. Even if that means repeating yourself.
       | But don't let anyone make the mistake of thinking they both work
       | the same because they have similar-looking interfaces or
       | behaviors, or you will be stuck again in the morass of low
       | cohesion. This is probably one of the 3 biggest problems in
       | software design.
       | 
       | Libraries are a great help here, but libraries _must_ be both
       | backwards compatible, and not tightly coupled. Lack of backwards
       | compatibility is probably the 4th biggest problem...
        
       | pavlov wrote:
       | This DRY-sceptical viewpoint is a bit similar to database
       | denormalization.
       | 
       | Sure, in theory you want to store every bit of information only
       | once. But in practice it can make a real difference in smoothing
       | out the access pattern if you don't follow this normalization
       | religiously.
       | 
       | The same applies to code. If you have to jump through hoops to
       | avoid repeating yourself, it will also make it harder for someone
       | else reading the code to understand what's going on. A bit of
       | "code denormalization" can help the reader get to the point more
       | quickly.
        
       | chacham15 wrote:
       | I think it depends on _how_ you deduplicate your code. Creating a
       | DeadlineSettter as illustrated is definitely too much, but
       | creating a function:                 def
       | assert_datetime_in_future(datetime):         if datetime <=
       | datetime.now():           raise ValueError(             "Date
       | must be in the future")
       | 
       | and then calling that from both places seems fairly reasonable to
       | me.
        
         | 12_throw_away wrote:
         | Right? Creating a noun instead of a verb is the real anti-
         | pattern I see here. (Once you have a DeadlineSetter, it's a
         | slippery slope down to ClassInstanceFactoryConfigProxyManager,
         | etc.)
        
       | thrdbndndn wrote:
       | To the author: please do not use non-ascii quotes ("") in code.
        
         | theshrike79 wrote:
         | Some blog engines try to be too fancy and do it automatically
        
       | lcnPylGDnU4H9OF wrote:
       | A practical rule for the presented problem is "wait until you
       | have 3". The number being in reference to the amount of different
       | cases which need to be handled. You're not likely to catch
       | everything that will come up but you'll get enough to think of an
       | extensible abstraction if you don't realize that you already have
       | a workable one.
        
         | EugeneOZ wrote:
         | It really depends on what this code is doing. If it is dialog
         | window rendering - yes, not so important. If it's complicated
         | data validation - you better make it reusable and pure from the
         | beginning.
        
           | lcnPylGDnU4H9OF wrote:
           | I agree. The data validation example doesn't seem contrary to
           | the advice; you would generally have at least three data
           | types you need to handle in such a case.
           | 
           | The difference with the dialog window is that (presumably)
           | you don't know the different flavors of window you'll need to
           | render so adding an abstraction on top of the existing
           | rendering abstraction fits squarely in "premature
           | optimization".
        
       | dec0dedab0de wrote:
       | But don't stop telling new developers to be DRY, it's really just
       | a way to remind them they're allowed to make functions.
       | 
       | Just step in when they go too far
        
       | watters wrote:
       | This reads like a paraphrase of this widely circulated post from
       | 8 years ago...
       | 
       | https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction
        
         | dmeijboom wrote:
         | Thanks! I was looking for this blog post for a while now
        
       | dvh wrote:
       | DRY code (usually with lot of IF blocks to handle special cases,
       | or various oop lasagna) eventually turns into unmaintainable
       | nightmare where every trivial new feature can take hours to
       | implement and is very difficult, full of cussing, hair-pulling
       | kind of programming where every 5 minutes you think "we need to
       | rewrite everything from scratch, the system wasn't designed for
       | this". Every change breaks million different unrelated things
       | because of the complexity of extremely dry functions.
       | 
       | In WET code (write everything twice) everything looks primitive,
       | as if it was written by complete newbie, and every change needs
       | to be added at multiple places, but each change is trivial and
       | time to finish is predictable. I would go as far as calling the
       | code boring. The most difficult thing is to resist the temptation
       | to remove the duplicity.
        
         | ollien wrote:
         | > In WET code (write everything twice) everything looks
         | primitive, as if it was written by complete newbie, and every
         | change needs to be added at multiple places, but each change is
         | trivial and time to finish is predictable. I would go as far as
         | calling the code boring. The most difficult thing is to resist
         | the temptation to remove the duplicity.
         | 
         | This only scales so far. After some point, it's very easy to
         | run into cases where you meant to change something everywhere
         | but forgot/didn't know about others. Not to say everything
         | should be so compartmentalized as to restrict change, but there
         | is a balance to be had.
        
           | thfuran wrote:
           | Yes, what actually happens is that many code changes are
           | released half-baked because logic only got updated in 1 (or
           | 13) of the 14 places that needed to be updated, and the
           | cussing and hair pulling just starts later.
        
             | rrr_oh_man wrote:
             | Tests, baby
        
               | thfuran wrote:
               | Tests don't really help you when a newly discovered bug
               | affects logic copied in ten places and you're only aware
               | of two of them. You can add a regression test to the
               | places that you update, but not the others. And then if
               | there's another bug discovered in the duplicated code, a
               | different subset of the copies might get changed and have
               | tests added. Suddenly it looks like these different
               | versions of the repeated logic are intended to be
               | behaving differently for some unknown reason even though
               | the divergence is purely accidental.
        
           | gary_0 wrote:
           | Which is why you need a balance between WET and DRY. DAMP =
           | Don't Alter in Many Places.
        
             | kag0 wrote:
             | I've never heard this one before, but I love it.
             | Unfortunately we've also got "Don't Abstract Methods
             | Prematurely" and "Descriptive And Meaningful Phrases".
        
           | yakshaving_jgt wrote:
           | Or, use a sufficiently well designed type-checking compiler,
           | like GHC.
        
         | DSMan195276 wrote:
         | I've seen code like this, what eventually happens is that all
         | your 'copies' drift to be slightly different. Fixes get applied
         | to some but not all of them, people copy from old code vs. new
         | code, etc. And whenever you need to apply a fix you spend hours
         | trying to figure out where each copy is, what it is supposed to
         | be doing (since they're all different), and how the fix can be
         | applied to it. You inevitably don't find them all and repeat
         | the cycle.
        
           | YZF wrote:
           | This is also true. Some versions can be buggy and then a fix
           | might not get everywhere. My favorite example is C code bases
           | with a multitude of linked list implementations.
           | 
           | Like many things in software knowing when to do something and
           | when not to do something can be hard. Premature DRY as the
           | article mentions can lead to difficulty when eventually the
           | use case diverges. Re-implementing everything every time
           | everywhere is also silly. As mentioned in other places, I
           | like the rule of 3, if you have 3 examples the point strongly
           | a certain direction that's probably a good one to follow.
        
             | tcfhgj wrote:
             | Which tool do you use to manage the copies of code
             | segments, including possible modifications?
             | 
             | I imagine it's difficult to keep all of them in your head
        
             | DSMan195276 wrote:
             | I think the key is more to ask yourself what it is you're
             | abstracting away and whether the two things are actually
             | doing the same thing. Just because the code is "shaped" in
             | the same way (as in the article) doesn't mean it's actually
             | conveying the same idea. If they're not really the same
             | then the abstraction won't make sense and will just make
             | things messier down the road.
             | 
             | It's the same thing as naming constants, but with code. If
             | I have 3 `10` magic numbers in my code, I don't just
             | immediately abstract them into a `const ten = 10` because
             | they look the same, I abstract them into constants based on
             | their actual purpose.
        
         | Aeolun wrote:
         | > but each change is trivial and time to finish is predictable
         | 
         | Until your 'if' soup reaches all the locations in your
         | codebase, and now you have 10 different places with too many if
         | statements instead of one. Likely all touching slightly
         | different things, so you can't simply copy from one file to the
         | other any more.
        
         | brokencode wrote:
         | Why does everything need to be either DRY or WET? Like most
         | things in life, there are no absolutes in programming, only
         | tradeoffs.
         | 
         | Those may be good principles to think about when you are
         | starting out with programming, but the key is to gain
         | experience trying to solve problems in different ways.
         | 
         | Over time, you get better at making the right decision about
         | whether duplicative code should be refactored or left alone.
         | 
         | To be fair, we can't always make the "right" decision, but we
         | can at least try to make the best decision we can based on the
         | knowledge we have.
        
       | rjurney wrote:
       | This is especially true for a data scientist, where most code is
       | throwaway. If you make it all spectacular, you aren't getting
       | anything done. Data scientists' code should be "eventually good,"
       | that is to say it gets refactored as it approaches a production
       | environment. I talk about this in my last book, Agile Data
       | Science 2.0 (Amazon 4.1 stars 7 years after publishing).
       | 
       | https://www.amazon.com/Agile-Data-Science-2-0-Applications/d...
       | 
       | I will say that after 20 years of working as a software engineer,
       | data engineer, data scientist and ML engineer, I can write pretty
       | clean Python all the time but this isn't common.
        
       | stephc_int13 wrote:
       | The problem with all the "best practices" is that quite often
       | they are sensible within some context but can be a detrimental
       | tradeoff in a different one.
       | 
       | "it depends" is almost always the correct answer.
       | 
       | But what we see is young, inexperienced and zealous coders trying
       | too hard and implemented the so called best practices before they
       | understand them.
       | 
       | And I don't think there are too many shortcuts to replace
       | experience.
       | 
       | My advice for beginners and intermediate is to first stick to the
       | simplest solution that works, and don't be afraid to rewrite.
        
         | PaulStatezny wrote:
         | > And I don't think there are too many shortcuts to replace
         | experience.
         | 
         | But that's the point of these blogs: helping those without
         | experience. Should we leave them to flounder on their own until
         | they "figure it out" instead of trying to pass along wisdom?
         | 
         | There's evidence that the best approach is, yes, experience -
         | but _with Expert Feedback_. In practice, this looks like
         | pairing and informal apprenticeship with competent, seasoned
         | engineers.
         | 
         | I can confirm from my own experience how much you can learn
         | from working with engineers "further down the road".
        
       | hcarvalhoalves wrote:
       | The article builds a straw man though. The "bad example" is bad
       | because it introduces OOP for no reason at all.
       | 
       | What's wrong with:                   def set_deadline(deadline):
       | if deadline <= datetime.now():               raise
       | ValueError("Date must be in the future")
       | set_task_deadline = set_deadline         set_payment_deadline =
       | set_deadline
       | 
       | You don't need code duplication to avoid bad abstractions.
        
         | laserlight wrote:
         | Exactly. I was happy to see a code example, but facepalmed when
         | I actually read it.
        
         | spion wrote:
         | Its not about OOP but the probability that those two functions
         | will diverge. Linked elsewhere in the comments too, this
         | article (https://sandimetz.com/blog/2016/1/20/the-wrong-
         | abstraction) is probably better at articulating the point.
        
         | alibero wrote:
         | Later down the line, if you want to have separate behaviour for
         | task deadlines vs payment deadlines, you're going to have to go
         | through your codebase and look at every call to set_deadline
         | and figure out if it's being used to set a task deadline or
         | payment deadline. If you have an inkling that the deadlines
         | might need a different behaviour, the "good example" can save
         | you an annoying refactor in the future.
        
           | hcarvalhoalves wrote:
           | Don't make the symbol public, or call it _set_deadline, or
           | whatever is the idiom in Python. The point of this example is
           | ofc not having set_deadline be used, but the other symbols.
           | 
           | Again, you don't need to duplicate a function body just to
           | have semantic names.
        
       | zer00eyz wrote:
       | People talk about DRY and then happily type pip/gem/npm into
       | their terminal, and never look at 99 percent of what they just
       | downloaded...
       | 
       | Did we all forget leftpad? https://qz.com/646467/how-one-
       | programmer-broke-the-internet-...
        
         | Leherenn wrote:
         | Isn't leftpad the natural conclusion of DRY? Everything is a
         | unique, small, contained and tested library that other code can
         | depend on instead of reimplementing it? The ultimate one source
         | of truth, where if it breaks half the internet breaks.
        
           | zer00eyz wrote:
           | > Isn't leftpad the natural conclusion of DRY? Everything is
           | a unique, small, contained and tested library that other code
           | can depend on instead of reimplementing it?
           | 
           | There is nothing in dry that says "util" or "frameworks" or
           | "toolchains" are bad.
           | 
           | > The ultimate one source of truth, where if it breaks half
           | the internet breaks.
           | 
           | Dry says nothing about versioning, or vendoring or deleting
           | your code from the internet...
           | 
           | The reality is that leftpad wasnt used by that many things.
           | Its just that the things that did use it were all over the
           | dependency graph...
        
       | kelseydh wrote:
       | Fixing duplication is far easier than the wrong abstraction.
        
         | ziml77 wrote:
         | Yes. If you abstract without a specific need, you are likely to
         | end up with an abstraction that either wastes time because it's
         | never used or that you will later need to fight against because
         | the changes you need to make don't mesh well with it. At that
         | point you have to choose between a lengthy rework of the code
         | or awful hacks to bypass the abstraction.
        
       | wg0 wrote:
       | This DRY principle has ruined so many code bases merging so many
       | facets into one giant monster of complexity that then later has
       | to be specialised with flags and enums that I can't count how
       | many times I have seen such clever PRs.
       | 
       | IMHO - some of the clean code books have ruined the industry as
       | much as the virtues of microservice preachers have.
       | 
       | The second number goes to the Javascript tooling.
        
         | Nuzzerino wrote:
         | Can you honestly say with a straight face that those same code
         | authors would be any better if they hadn't read those books?
         | Sometimes the problem isn't the book but a lack of critical
         | thinking. Using a tool or or method because a book said to use
         | it and not because it was the time and place, is an obvious
         | sign of that to me.
         | 
         | And here's the kicker: If you were aware the individual was
         | taking inspiration from a book easily in such a way, what made
         | it difficult for them to take inspiration from you through a
         | dialogue? Hopefully your arguments were stronger than your HN
         | comment.
        
       | dailykoder wrote:
       | Just don't prematurely anything and write code that works. If you
       | know how it works you automatically get an intuition what can be
       | made better and where bottlenecks might be. Then you refactor it
       | or just do a plain rewrite.
       | 
       | It's really that simple. (There are always exceptions obviously)
        
       | globular-toast wrote:
       | Like any rule this can be taken too far. It happens all the time.
       | People like simple rules. They want everything to be like
       | assembling IKEA furniture: no thought required, just follow the
       | instructions. We all like it because it frees up the mind to
       | think about other things.
       | 
       | There are rules like "don't stick your fingers in the plug
       | socket". But, if you're an electrician, you can stick your
       | fingers in the plug socket because you've isolated that circuit.
       | DRY is similar. As a programmer, you can repeat yourself, but you
       | should be aware that it's thoroughly unwise unless you know you
       | have other protections in place, because you know _why_ such a
       | rule exists.
        
       | epr wrote:
       | The example is hilariously terrible. Firstly, this is the
       | currently required code:                   def
       | set_deadline(deadline):           if deadline <= datetime.now():
       | raise ValueError("Date must be in the future")
       | set_deadline(datetime(2024, 3, 12))
       | set_deadline(datetime(2024, 3, 18))
       | 
       | There simply is no trade-off to be made at this point. Perhaps
       | there will be eventually, but right now, there is one function
       | needed in two places. Turning two functions that already could be
       | one into a class is absurd.
       | 
       | Now, as far as teaching best practices goes, I also dislike this
       | post because it doesn't explicitly explain the pros and cons of
       | refactoring vs not refactoring in any detail. There is no
       | guidance whatsoever (ie: Martin Fowler's Rule of Three). This is
       | Google we're talking about, and newer developers could easily be
       | led astray by nonsense like this. Addressing the two extremes,
       | and getting into how solving this problem requires some nuance
       | and practical experience is much more productive.
        
         | alex_smart wrote:
         | Almost all programming tutorials and even books to a certain
         | extent suffer with the problem of terrible examples. Properly
         | motivating most design patterns requires context of a
         | sufficiently complex codebase that tutorials and books simply
         | do not have the space of getting into. This particular case is
         | especially bad, probably because they had the goal of having
         | the whole article fit in one page. ("You can download a
         | printer-friendly version to display in your office.")
         | 
         | > There is no guidance whatsoever (ie: Martin Fowler's Rule of
         | Three).
         | 
         | That is completely unfair imo. Although not properly motivated,
         | the advice is all there. "When designing abstractions, do not
         | prematurely couple behaviors that may evolve separately in the
         | longer term." "When in doubt, keep behaviors separate until
         | enough common patterns emerge over time that justify the
         | coupling."
         | 
         | Simplified maxims like "Rule of Three" do more harm than good.
         | Don't couple unrelated concerns is a much higher programming
         | virtue than DRY.
        
           | LouisSayers wrote:
           | > Properly motivating most design patterns requires context
           | of a sufficiently complex codebase
           | 
           | As someone that's made a best selling technical course, I
           | strongly disagree.
           | 
           | It's 100% laziness and/or disregard for the reader.
           | 
           | The reason examples are as bad as they are is that people
           | rush to get something published rather than put themselves in
           | the audience's position and make sure it's concise and makes
           | sense.
           | 
           | It's not like webpage space is expensive. There's plenty of
           | room to walk through a good example, it just requires a
           | little effort.
        
             | Bjartr wrote:
             | What does sales have to do with what you're claiming?
             | Please share the course and or examples of it being done
             | well without requiring that excessive context, so that
             | there's something to support your claim.
        
               | LouisSayers wrote:
               | Well if my course and teaching was crap I wouldn't get
               | good reviews and therefore many sales. I've spent $0 on
               | marketing.
               | 
               | https://www.udemy.com/neo4j-foundations/
               | 
               | There are many people who do teach and explain topics
               | well. Richard Feynman comes to mind.
               | 
               | I've found Abdul Bari on YouTube to also be an excellent
               | teacher around technical topics.
        
               | Aeolun wrote:
               | Not related to the topic at hand, but who buys these
               | courses? Going off the chapter titles it looks like it's
               | all basic 'read the documentation' kind of stuff (to me).
               | I could imagine it being useful to beginners, but not
               | anyone with a moderate amount of experience (they'd just
               | go to the Neo4j documentation).
               | 
               | On the other hand, what beginner starts with Neo4j and
               | Cypher? Is there really enough of them to justify a whole
               | course? Apparently there are, it just feels weird to me.
        
               | LouisSayers wrote:
               | You're right in that if you go through the docs you can
               | find all the info you might need.
               | 
               | It's really catered for beginners, people that have next
               | to no knowledge of graph databases or Neo4j and want to
               | get up to speed in just a few hours.
               | 
               | I imagine some people may not even be super technical,
               | but may want to learn just the basics of querying a DB at
               | work to get some basic info out of it.
               | 
               | Apart from lessons there are also exercises for people to
               | practice what they just learnt, and I do my best to point
               | out gotchas and keep it mildly entertaining with a gentle
               | progression in difficulty.
        
             | alex_smart wrote:
             | >It's not like webpage space is expensive.
             | 
             | It is not the webpage space. It is people's limited
             | attention spans and ability to focus. A complex example is
             | needed to properly motivate certain concepts, but too
             | complex an example also contains too many other details
             | that the reader gets bogged down/distracted from the main
             | concept being discussed.
             | 
             | At least that is my hypothesis for why almost all
             | programming books and tutorials have terrible examples. I
             | am happy to be proven wrong.
             | 
             | Coming back to the article, I looked at some of the
             | previous articles from the same series, and to me it feels
             | like a very conscious decision to only include 3-4 line
             | code examples.
        
             | kllrnohj wrote:
             | > It's not like webpage space is expensive. There's plenty
             | of room to walk through a good example, it just requires a
             | little effort.
             | 
             | Right at the top of the page:
             | 
             | > A version of this post originally appeared in Google
             | bathrooms worldwide as a Google Testing on the Toilet
             | episode. You can download a printer-friendly version to
             | display in your office.
             | 
             | So no, there isn't room for a longer example.
        
         | re-framer wrote:
         | Your example, deduplicating the two functions into one,
         | illustrates an interesting point, although I'd prefer still
         | having the two specialized functions there:
         | def set_deadline(deadline):           if deadline <=
         | datetime.now():             raise ValueError("Date must be in
         | the future")                  def
         | set_task_deadline(task_deadline):
         | set_deadline(task_deadline)              def
         | set_payment_deadline(payment_deadline):
         | set_deadline(payment_deadline)
         | set_task_deadline(datetime(2024, 3, 12))
         | set_payment_deadline(datetime(2024, 3, 18))
         | 
         | You lose absolutely nothing. If you later want to handle the
         | two cases differently, most IDEs allow you to inline the
         | set_deadline method in a single key stroke.
         | 
         | So the argument from the article...
         | 
         | > Applying DRY principles too rigidly leads to premature
         | abstractions that make future changes more complex than
         | necessary.
         | 
         | ...does not apply to this example.
         | 
         | There clearly _are_ kinds of DRY code that are less easy to
         | reverse. Maybe we should strive for DRY code that can be easily
         | transformed into WET (Write Everything Twice) code.
         | 
         | (Although I haven't worked with LISPs, macros seem to provide a
         | means of abstraction that can be easily undone without risk:
         | just macro-expand them)
         | 
         | In my experience, it can be much harder to transform WET code
         | into DRY code because you need to resolve all those little
         | inconsistencies between once-perfect copies.
        
           | epr wrote:
           | I can only assume the Google example would be part of a
           | script/cli program that is meant to crash with an error on a
           | bad parameter or similar. Perhaps the point is to catch the
           | exception for control flow?
           | 
           | My personal goal is to get things done in as few lines of
           | code as possible, without cramming a bunch on one line.
           | Instead of coming up with fancy names for things, I try to
           | call it by the simplest name to describe what it's currently
           | doing, which can be difficult and is subjective.
           | 
           | If we wanted to define a function which crashes like the
           | example, I would probably write this:                   def
           | throw_past_datetime(dt):             if dt <= datetime.now():
           | raise ValueError("Date must be in the future")
           | 
           | If the point is not to crash/throw for control flow reasons,
           | I'd write this in non-cli/script code instead of defining a
           | function:                   dt = datetime(2024, 5, 29)
           | if dt < datetine.now():             # Handle past date
           | gracefully?
           | 
           | If it needs to do more in the future, I can change it then.
        
           | alex_smart wrote:
           | >You lose absolutely nothing. If you later want to handle the
           | two cases differently, most IDEs allow you to inline the
           | set_deadline method in a single key stroke.
           | 
           | Problem with unintentional coupling isn't that you can't undo
           | it. It is that someday someone from some other team is going
           | to change the method to add behaviour they need for their own
           | use case that is different from your own and you won't even
           | notice until there is a regression.
        
             | kitkat_new wrote:
             | in this case (which shouldn't happen because it requires
             | that you merged things that don't belong together - see
             | accidental duplication), at least the one changing the
             | method has all information on his hands and doesn't have to
             | keep a potentially complex graph of copied code in his
             | mind.
        
         | Aeolun wrote:
         | I was going to say you were talking nonsense, but then realized
         | I'd replaced the original post in my mind, by this much nicer
         | post that someone else linked in this thread:
         | 
         | https://verraes.net/2014/08/dry-is-about-knowledge/
         | 
         | They essentially say the same thing, but one is better than the
         | other.
        
       | fiddlerwoaroof wrote:
       | I've always found that duplicating and editing over-DRY code is
       | easier than fixing code that's under-DRY. I strongly prefer
       | working with people that care about DRY code and accidentally go
       | too far than the reverse. Additionally, the worst problems I've
       | had in inherited code have been due to duplication and
       | insufficient abstractions leading to logical inconsistency.
        
       | jandrewrogers wrote:
       | The term "DRY" as commonly used conflates distinct situations and
       | objectives that should be handled differently in most cases.
       | 
       | There is the "single source of truth" problem where you need to
       | compute something exactly the same way at all points in the
       | software that need to compute that thing. In these cases you
       | really want a single library implementation so that the meaning
       | of that computation does not accidentally diverge over time since
       | there is a single implementation to maintain.
       | 
       | There is the "reuse behaviors in unrelated contexts" problem
       | where you want to create implementations of common useful
       | behaviors that can be abstracted over many use cases, often in
       | the context of data structures and algorithms. In these cases you
       | really want generics and metaprogramming to codegen a context-
       | specific implementation rather than sharing a single
       | implementation with a spaghetti mess of conditionals.
       | 
       | DRY works best when it fits neatly and exclusively into one of
       | these two categories. Cases that fit in neither category, such as
       | the practice of decomposing every non-trivial function into a
       | bunch of micro-functions that call each other, are virtually
       | always a maintenance nightmare for no obvious benefit. Cases that
       | fit into both categories, such as expansive metaprogramming
       | libraries, become difficult to maintain by virtue of the
       | combinatorial explosion of possible implementations that might be
       | generated across the allowable parameter space -- the cognitive
       | overhead grows exponentially for what is often a linear increase
       | in value.
        
         | a1369209993 wrote:
         | > such as the practice of decomposing every non-trivial
         | function into a bunch of micro-functions that call each other,
         | 
         | That has approximately nothing to do with DRY. At best it might
         | technically be a violation of DRY if (and only if) some of
         | those micro-functions are identical, but the correct way to fix
         | that is to recompose them into the non-trivial (and non-
         | repeated) functions they're more usefully expressed as. And
         | more often it's just a totally independent refucktoring that
         | makes the codbase worse entirely orthogonally to 'DRY-ness'.
        
         | skydhash wrote:
         | Common Lisp works well for the second case. But it seems that
         | some programmers are uncomfortable with the notion of code
         | generating code. And as you said, it does require discipline as
         | you need to focus the language's power. Other languages don't
         | let you solve the boilerplate problem so readily. Instead you
         | have a mess of utility functions or a huge class tree.
        
       | icoder wrote:
       | Reading a lot of this discussion I'm thinking whether DRY itself
       | is the problem or it's more about mixing different (but perhaps
       | comparable) things into one function (be it for the sake of
       | appearing DRY or otherwise).
        
       | Symmetry wrote:
       | I have a sticker on my laptop of a yin-yang with DRY and YAGNI
       | instead of the dots.
        
       | liampulles wrote:
       | My maxim: is "it" intrinsically the same, or coincidentally the
       | same?
       | 
       | Intrinsically the same means a rule, and so there should be 1
       | source of truth for it. Coincidentally the same means it has the
       | same shape but this just happens to be the case, and they should
       | be left separate to evolve independently.
       | 
       | Ultimately, it boils down to really thinking about the domain.
        
       | srvaroa wrote:
       | "Anytime you apply a rule too universally, it turns into an anti-
       | pattern".
       | 
       | Quote from Will Larson found in another HN post
       | (https://review.firstround.com/unexpected-anti-patterns-for-e...)
       | right after checking out this one.
        
       | halfcat wrote:
       | The visualizations in Dan Abramov's talk "The wet codebase" [1]
       | really burned this concept in for me.
       | 
       | Seeing what a premature, wrong abstraction looks like visually
       | was eye opening.
       | 
       | [1] https://youtu.be/17KCHwOwgms
        
       | ofrzeta wrote:
       | I know it's supposed to be catchy but "Don't Repeat Yourself" is
       | quite too dogmatic. A little redundancy can absolutely help
       | readablity. Obviously you don't want to repeat complicated code
       | blocks that you have to maintain twice.
        
       | LouisSayers wrote:
       | Can someone also write an article on how not to write code like
       | is in this article?
       | 
       | `DeadlineSetter` should not be a class, and besides that the
       | implementation makes zero sense. The whole thing should probably
       | just be a single if statement.
        
       | kag0 wrote:
       | In my experience DRY and many (any?) other coding principles are
       | only problematic when misused. They're typically misused because
       | the user doesn't understand the motivation or underlying value of
       | the principle in the first place.
       | 
       | I think the example in the article does a bit of that as well.
       | The example sets a deadline on a thing (a task or payment) by
       | validating the deadline against the current time, and then
       | presumably doing something else that isn't shown. The article
       | argues that in the future a task might have different validation
       | requirements than a payment, and they're only coincidentally the
       | same today; so it would be foolish to abstract the deadline
       | setting logic today. BUT, the reality is that the real
       | coincidence is that payments and tasks have the same set of
       | validations, not that the logic to validate a deadline is
       | coincidentally the same. In my opinion "good" code would be fine
       | to have separate set_task_deadline and set_payment_deadline
       | methods, but only one validate_deadline_is_in_future (or
       | whatever) method, alongside other validation methods which can be
       | called as appropriate by each set_x_deadline implementation.
       | 
       | Disclaimer: the code is so short and trivial that it doesn't
       | matter, I think we can all assume that this concept is
       | extrapolated onto a bigger problem.
        
       | vi2837 wrote:
       | I wonder why nobody mentioned it - there is one more advanced
       | principle AHA: (Avoid Hasty Abstractions)
       | https://kentcdodds.com/blog/aha-programming#aha- Overusing the
       | DRY principle can make software almost unsupportable.
        
       | wseqyrku wrote:
       | Related: I do believe starting off at the "second" level of
       | abstraction (as opposed to implementing the direct surface area
       | of the service) is not premature as it helps to better understand
       | the problem space, and on the implementation side, as soon as you
       | identify the building blocks, the rest would be really just
       | boilerplate. if you got time, rinse and repeat.
        
       | visil wrote:
       | Reminds me of this[1] great blogpost: "This abstraction adds
       | overhead. "Abstracting" the common operation has made it more
       | difficult to read, not less difficult to read. People for who
       | consider meta-programming some sort of Black Magic often make
       | this exact point: The mechanism for removing duplication adds
       | complexity itself. One view is that the overall effect is only a
       | win if the complexity added is small compared to the duplication
       | removed."
       | 
       | [1]: http://weblog.raganwald.com/2007/12/golf-is-good-program-
       | spo...
        
         | kitkat_new wrote:
         | maintainability is more important than readability alone
        
       | EasyMark wrote:
       | used once? don't worry, don't think about "what about the
       | possibility it's repeated in the future?"
       | 
       | used twice? okay, maybe I will, maybe I won't
       | 
       | used three+? "don't be lazy ya bum"
       | 
       | I like simple rules, and I don't care if someone wants to turn it
       | into a philosophical debate, I probably won't participate :)
        
       | asdfman123 wrote:
       | Upper management trying to drive software results by metrics is
       | like trying to win a war with metrics unrelated to battle
       | outcomes.
       | 
       | You must produce X number of tanks, your forces must fire Y
       | bullets, you should minimize the number of retreats.
       | 
       | If you try to manage with no understanding of what's happening on
       | the front lines (and upper management generally can't understand
       | the front lines unless they've worked there recently), you're not
       | going to win the war.
        
       | swiftcoder wrote:
       | This is a poorly-selected example, as the real problem here is
       | not the DRY validation, it's that the programmer is abstracting
       | the wrong thing.
       | 
       | Ending up with an awkward class name like `DeadlineSetter` is a
       | dead giveaway that your abstraction boundaries don't make sense -
       | if instead you abstract `Deadline`, and put the invariant check
       | in the constructor thereof, you solve both problems.
        
       | nevinera wrote:
       | DRY is _not a best practice_. Repetition is a "code smell" - it
       | often suggests a missing abstraction that would allow for code
       | reuse (what sort of abstraction depends on the language and
       | context), but "blindly-drying" is in my experience the _single
       | most frequent mistake_ made my mid-to-senior engineers.
       | 
       | My experience is mostly in Ruby though, so I'm not sure how well
       | it generalizes here :-)
        
         | hfe wrote:
         | My experiences are the same in C++ and Python. C++ in
         | particular can get way out of hand in service of DRY.
        
         | mmcnl wrote:
         | Premature DIY can lead to the wrong abstractions. Sometimes
         | code looks similar but actually isn't.
        
           | samtho wrote:
           | At my first big corporate jobs, I got to work on a codebase
           | that was nothing but premature DRY'd code, but I didn't know
           | it at the time. As someone who was self taught, and suffered
           | from imposter syndrome as many of us do/did in that
           | situation, I thought I was missing something huge until I was
           | talking to a senior developer and these strange design
           | decisions came up, to which he said something like
           | 
           | > Yeah, that was written by <ex-engineer> and he couldn't
           | abstract his way out of a paper bag
           | 
           | I guess the real lessons were the crappy decisions that
           | someone else made along the way.
        
         | colechristensen wrote:
         | Yeah I've had so many problems with understanding and working
         | with other people's code bases when the person was obsessed
         | with DRY.
         | 
         | You wrote that code 4 years ago with tons of abstractions
         | designed for some day someone not having to repeat
         | themselves... but it's been years and they've never been
         | useful. However I've had to dig through a dozen files to make
         | the change I needed to make which by all rights should have
         | been entirely contained in a few lines.
         | 
         | My most common reaction to a new codebase is "where the hell
         | does anything actually get done" because of silly over-
         | abstraction which aspires to, one day, save a developer five
         | minutes or three lines of copied code.
        
         | hughesjj wrote:
         | FWIW I completely agree in python, Java, typescript, and
         | golang. I've seen people just parrot dogma about DRY and SOLID
         | principals where their DRY'd code is completely not open to
         | extension etc
         | 
         | Premature dry'ing is the same as premature engineering. And
         | lest someone go 'oh so YAGNI is all you need'... no, sometimes
         | you are going to need it and it's better to at least make your
         | code easily moldable to 'it' now instead of later. Future
         | potential needs can absolutely drive design decisions
         | 
         | My whole point is that dogma is dumb. If we had steadfast easy
         | rules that applied in literally every situation, we could just
         | hand off our work to some mechanical turks and the role of
         | software engineer would be redundant. Today, that's not the
         | case, and it's literally our job to balance our wisdom and
         | experience against the current situation. And yes, we will
         | absolutely get it wrong from time to time, just hopefully a
         | lower percentage of occasions as we gain experience.
         | 
         | The only dogma I live by for code is 'boring is usually
         | better', and the only reason I stick by that is because it
         | implicitly calls out that it's not a real dogma in that it
         | doesn't apply in all cases.
         | 
         | (Okay, I definitely follow more principals than that, but don't
         | want to distract from the topic athand)
        
         | adrianmonk wrote:
         | > _" blindly-drying"_
         | 
         | Right. It's not an optimization problem!
         | 
         | Remember in school when you learned to turn a truth table into
         | a Karnaugh map and then use it to find the smallest equivalent
         | logic expression? Well, your code is not a Karnaugh map, is it?
        
         | m463 wrote:
         | It would be better to make a class for languages where DRY is
         | not a best practice, then create classes of languages where it
         | is a best practice or may be a best practice through multiple
         | inheritance. To keep things simple.
         | 
         | :)
        
       | philipwhiuk wrote:
       | I tend to follow "1, 2, many"
       | 
       | Duplication in one place, I'm often fine with, because you don't
       | yet know the level of abstraction needed with only two examples.
       | 
       | More than twice however and you should be able to see common
       | patterns across all three implementations and be able to isolate
       | it.
        
       | gsuuon wrote:
       | My rule of thumb is the third time I rewrite some code, I DRY it.
        
       | zamalek wrote:
       | There is an unpraised advantage of keeping code, uh wet?, for as
       | long as possible. When you do decide that a refactor is required,
       | you have real use cases to test your abstraction against. While I
       | am broadly in agreement with the article because of that, people
       | designing public binary APIs don't have the luxury of delaying
       | these choices.
        
       | BurningFrog wrote:
       | I think this is the best way to think about it:
       | 
       | Ask yourself, if some fact or functionality changes, in how many
       | places would the code have to change?
       | 
       | If it's more than 1, you have a design problem. Of course, the
       | solution does not at all have to be about DRYing.
        
       | Stratoscope wrote:
       | Sometimes it's best to be DRY right from the start.
       | 
       | Several years ago, I did some contract work for a company that
       | needed importers for airspace data and various other kinds of
       | data relevant to flying.
       | 
       | In the US, the Federal Aviation Administration (FAA) publishes
       | datasets for several kinds of airspace data. Two of them are
       | called "Class Airspace" and "Special Use Airspace".
       | 
       | The guy who wrote the original importers for these treated them
       | as completely separate and unrelated data. He used an internal
       | generic tool to convert the FAA data for each kind of airspace
       | into a format used within the company, and then wrote separate
       | C++ code, thousands of lines of code each.
       | 
       | Thing is, the data for these two kinds of airspace is mostly
       | identical. You could process it all with one common codebase,
       | with separate code for only the 10% of the data that is different
       | between the two formats.
       | 
       | When I asked him about this, he said, "I have this philosophy
       | that says if you only have two similar things, it's best to write
       | separate code for each. Once you get to a third, then you can
       | think about refactoring and making some common code."
       | 
       | That is a good philosophy! I have often followed it myself.
       | 
       | But in this case, it was obvious that the two data formats were
       | mostly the same, and there was never going to be a _third_ kind
       | of almost-identical airspace, only the two. So we had twice the
       | code we needed.
        
         | wnevets wrote:
         | > So we had twice the code we needed.
         | 
         | Was that necessarily a bad thing and something that must be
         | corrected for that code base?
         | 
         | I usually follow the same rule of thumb until I find myself
         | repeatedly updating both at the same time. If I can't update
         | one without updating the other then they must be the same thing
         | and its time to DRY.
         | 
         | Don't Repeat Yourself when updating code.
        
           | Stratoscope wrote:
           | Good points, thanks for bringing them up.
           | 
           | Yes, there was some ongoing maintenance of this code where
           | both versions had to be updated. The original author was not
           | a pilot and was unfamiliar with some of the nuances of FAA
           | airspace. One of the reasons they brought me in was that I am
           | a pilot and knew how the FAA's data should be interpreted.
           | 
           | In the end, not a huge deal, but it was annoying when I had
           | to make the same changes in two places.
        
             | thdc wrote:
             | Knowing to DRY there depended on business knowledge that
             | the original author did not have.
             | 
             | While they were wrong in this case, I would say it was a
             | reasonable move to not DRY based on the code pattern itself
             | at the time. And that's the big difference imo - DRYing
             | based strictly on the structure of code vs business
             | processes.
        
               | chii wrote:
               | But this implies that you have to guess when and where to
               | DRY, which basically implies that there's no good way but
               | via experience and domain knowledge!
               | 
               | That's not what people want to hear - they want a silver
               | bullet; a set of criteria for which DRY'ing could be
               | determined from the onset!
        
         | ebolyen wrote:
         | I don't know, that sounds like a complex kind of ingest which
         | could be arbitrarily subtle and diverge over time for legal and
         | bureaucratic reasons.
         | 
         | I would kind of appreciate having two formats, since what are
         | the odds they would change together? While there may never be a
         | 3rd format, a DRY importer would imply that the source
         | generating the data is also DRY.
        
           | TeMPOraL wrote:
           | In such case I think I'd go for an internal-DRYing + copy-on-
           | write approach. That is, two identical classes or entry
           | points, one for each format; internally, they'd share all the
           | common code. Over time, if something changes in one format
           | but not the other, that piece of code gets duplicated and
           | then changed, so the other format retains the original code,
           | which it now owns.
        
             | ebolyen wrote:
             | I like that approach.
        
               | Spivak wrote:
               | I've had the mantra "inheritance is only for code reuse"
               | and it's never steered me wrong.
        
               | jameshart wrote:
               | Inheritance is only good for code reuse, and it's a trick
               | you only get to use once for each piece of code, so if
               | you use it you need to be absolutely certain that the
               | taxonomy you're using it to leverage code across is the
               | right one.
               | 
               | All 'is-a so it gets this code' models can be trivially
               | modeled as 'has-a so it gets this code' patterns, which
               | _don't_ have that single-use constraint... so the
               | corollary to this rule tends towards 'never use
               | inheritance'.
        
               | Spivak wrote:
               | Single use? No way that's what multiple inheritance and
               | mixins are for. Inheritance being only for code reuse is
               | explicitly about _not_ creating a taxonomy. No more is-a
               | just,  "I need this code here." Hey this thing behaves
               | like a mapping inherit from the MutipleMapping and get
               | all the usual mapping methods for free. Hey this model
               | needs created/updated_at, inherit from ChangeTracking and
               | get those fields and helper methods for free.
               | 
               | Has-a doesn't make sense for _code_ like the literal text
               | reuse. It makes sense for composition and encapsulation.
               | 
               | Edit: I'm now realizing that Python has one of the only
               | sane multiple inheritance implementations. It's no wonder
               | the rest of y'all hate it.
        
               | zeroCalories wrote:
               | It seems like OP is describing a shared interface, not
               | necessarily inheritance.
        
             | jeremyjh wrote:
             | This is really good advice and a great way to think about
             | it.
        
             | chii wrote:
             | I believe this very method is very common in games - you
             | have similar logic for entities, but some have divergences
             | that could occur in unknown ways after playtesting or
             | future development.
             | 
             | Tho if done haphazardly by someone inexperienced, you might
             | end up with subtle divergences that might look like they're
             | meant to be copies, and debugging them in the future by
             | another developer (without the history or knowledge) can
             | get hard.
             | 
             | Then someone would wonder why there are these two very
             | similar pieces of code, and mistakenly try to DRY it in the
             | hopes of improving it, causing subtle mistakes to get
             | introduced...
        
               | cies wrote:
               | I prefer the FP approach of separating data and logic.
               | you could end up with a box of functions (logic) that can
               | be reused by the different "entities".
               | 
               | Last time i checked the FP world is slowly producing ECS
               | frameworks that are needed to make the game performant.
               | They used to be nearly C++ (or OO) exclusive.
        
             | chipdart wrote:
             | > In such case I think I'd go for an internal-DRYing +
             | copy-on-write approach.
             | 
             | I agree. The primary risk of presented by DRY is tight
             | coupling code which only bears similarities at a surface
             | level. Starting off by explicitly keeping the externa bits
             | separate sounds like a good way to avoid the worst
             | tradeoff.
             | 
             | Nevertheless I still prefer the Write Everything Twice
             | (WET) principle, which means mostly the same thing, but
             | following a clear guideline: postpone all de-duplication
             | efforts until it's either obvious there's shared code
             | (semantics and implementation) in >2 occurrences, and
             | always start by treating separate cases as independent
             | cases.
        
           | Stratoscope wrote:
           | Good point. This may be a case where domain knowledge is
           | helpful.
           | 
           | One of the reasons they brought me in on this project is that
           | besides knowing how to wrangle data, I'm also an experienced
           | pilot. So I had a good intuitive sense of the meaning and
           | purpose of the data.
           | 
           | The part of the data that was identical is the description of
           | the airspace boundaries. Pilots will recognize this as the
           | famous "upside down wedding cake". But it's not just simple
           | circles like a wedding cake. There are all kinds of cutouts
           | and special cases.
           | 
           | Stuff like "From point A, draw an arc to point B with its
           | center at point C. Then track the centerline of the San
           | Seriffe River using the following list of points. Finally,
           | from point D draw a straight line back to point A."
           | 
           | The FAA would be very reluctant to change this, for at least
           | two reasons:
           | 
           | 1. Who will provide us the budget to make these changes?
           | 
           | 2. Who will take the heat when we break every client of this
           | data?
        
             | ebolyen wrote:
             | I see, so it's a procedural language that is well
             | understood by those who fly (not just some semi-structured
             | data or ontology). This is a great example of the advantage
             | of domain experience. Thanks for sharing!
        
               | Stratoscope wrote:
               | > _a procedural language that is well understood by those
               | who fly_
               | 
               | That is a great way to describe it!
               | 
               | Of course it is all just rows in a CSV file, but yes, it
               | is a set of instructions for how to generate a map.
               | 
               | In fact the pilot's maps were being drawn long before the
               | computer era. Apparently the first FAA sectional chart
               | was published in 1930! So the data format was derived
               | from what must have been human-readable descriptions of
               | what to plot on the map using a compass and straightedge.
               | 
               | I just remembered a quirk of the Australian airspace
               | data. Sometimes they want you to draw a direct line from
               | point F to point G, but there were two different kinds of
               | straight lines. They may ask for a great circle, a
               | straight path on the surface of the Earth. Or a rhumb
               | line, which looks straight on a Mercator projection but
               | is a curved path on the Earth.
               | 
               | You would often have some of each in the very same
               | boundary description!
               | 
               | For anyone curious about this stuff, I recommend a visit
               | to your local municipal airport and stop by the pilot
               | shop to buy a sectional chart of your area.
        
               | tass wrote:
               | Paper charts are great (they're fairly cheap and printed
               | quite nicely in the USA at least) but you can get a good
               | look at these boundaries through online charts.
               | 
               | https://skyvector.com is a good way to view these.
        
               | Stratoscope wrote:
               | Thank you! I was trying to remember the name of that site
               | and it slipped my mind. Yes, SkyVector is great.
        
             | hamasho wrote:
             | I think if you know the domain well, it's not "premature"
             | at all.
        
           | bcrosby95 wrote:
           | I don't know. I've seen this approach for projects before go
           | bad - people didn't want to DRY because they might diverge.
           | Except they never did. Our 3rd+ scenarios we abstracted.
           | 
           | But what basically ended up happening was we had 2 codebases:
           | 1 for that non-DRY version, and then 1 for everything else.
           | The non-DRY version limped along and no one ever wanted to
           | work on it. The ways it did things were never updated. It was
           | rarely improved. It was kinda left to rot.
        
             | jononor wrote:
             | Why wasn't the original implementation swapped for the new
             | one? The unwillingness/inability to do that seems to be
             | most likely the core of the issues here?
        
               | bcrosby95 wrote:
               | The majority of our business was through the 1st
               | implementation. Because of that it was the base we used
               | to refactor into a more abstract solution for further
               | scenarios. It was never deemed "worth it" to transition
               | the 2nd non-DRY version. Why refactor an existing
               | implementation if its working well enough and we could
               | expand to new markets instead?
        
               | jononor wrote:
               | Yes, why do it? :p I mean, there are pros and cons -
               | costs and benefits. And I can see both scenarios where it
               | is better to spend the time on something else (that has
               | better chance of bringing in money), and cases where it
               | would be the right thing to do the cleanup (maybe
               | original is just about to fall apart, or the new has
               | straight up benefits to the business, or the act of doing
               | it will greatly improve testing/QA in a critical area,
               | etc).
               | 
               | Writing it DRY in the first place would also have costs,
               | including the alternative costs. Would it have been
               | better to take those there and then?
        
             | chipdart wrote:
             | > But what basically ended up happening was we had 2
             | codebases: 1 for that non-DRY version, and then 1 for
             | everything else. The non-DRY version limped along and no
             | one ever wanted to work on it. The ways it did things were
             | never updated. It was rarely improved. It was kinda left to
             | rot.
             | 
             | It sounds to me that you're trying to pin the blame of
             | failing to maintain software on not following DRY, which
             | makes no sense to me.
             | 
             | Advocating against mindlessly following DRY is not the same
             | as advocating for not maintaining your software. Also, DRY
             | does not magically earn you extra maintenance credits. In
             | fact, it sounds to me that the bit of the code you called
             | DRY ended up being easier to maintain because it wasn't
             | forced to pile on abstractions needed to support the non-
             | DRY code. If it was easy, you'd already have done it and
             | you wouldn't be complaining about the special-purpose code
             | you kept separated.
        
               | bcrosby95 wrote:
               | In my experience, once you copy code its bound to
               | diverge, intentional or not. Bugs become features and you
               | can never put the cat back in the bag without a
               | monumental amount of work.
               | 
               | Undoing an abstraction is way easier. Eventually, they
               | all turn bad anyways.
        
           | mannykannot wrote:
           | I vaguely recall Fred Brooks, in The Mythical Man-Month,
           | using a somewhat similar situation, but involving the various
           | US states' income tax rules and their relationship to the
           | federal tax, as an example in order to make some sort of
           | point (that point being 'know your data', IIRC.)
           | 
           | In a situation where there is a base model with specific
           | modifications - which is, I feel, how airspace regulation
           | mostly works - then I suspect that a DRY approach would make
           | it easier to inspect and test, so long as it stays that way.
        
         | steve_adams_86 wrote:
         | I find this is a case where different pipelines utilizing
         | common functions in different compositions can be a great
         | strategy. If something diverges and a function no longer makes
         | sense in a pipeline, that's not a big deal. Just pull it out
         | and replace it with something bespoke that does the right
         | thing.
         | 
         | I've had a lot of success with this in embedded settings where
         | data is piped into storage or OTA, and I want to format and
         | pack it/send it up consistently but I might want to treat the
         | data itself slightly differently.
        
           | hansvm wrote:
           | A related concept that IMO still aligns with DRY is that you
           | should only avoid seeming code duplication when things are
           | _semantically_ the same. No matter the mechanism (codegen,
           | generics, macros, inheritance, ...), if you can't give a
           | concept a meaningful [0] name then you usually shouldn't DRY
           | it up with any mechanism. Your example is a technique I also
           | use a lot, but the critical point is that you're choosing to
           | break out functionality which _is_ easy to name.
           | 
           | [0a] More generally, I like a concept of "total" functions --
           | those which have sensible outputs for all their inputs. It's
           | a bit of a tomayto/tomahto situation defining "all their
           | inputs" (e.g., I'm personally okay using a function name like
           | `unsafe_foo` and expecting a person to read the docs, and on
           | the other extreme some people want sensible answers to
           | anything the type system allows you to input), but the
           | desired end-state is that when the project's requirements
           | change you don't muck around with the ABI and implementation
           | of `count_or_maybe_sort_for_these_three_special_customers_or_
           | else_hit_the_db(...)`, or whatever much more generic and very
           | wrong name the method actually has; the individual components
           | are already correct, so you make the changes at the few
           | methods which are actually wrong given the new requirements.
           | 
           | [0b] Another way of thinking about it is whether the two
           | things should always change in tandem. For two largely
           | overlapping beaucratic data formats? Maybe; there's a comment
           | somewhere in this chain suggesting that they'll never go out
           | of sync, but I'm a bit paranoid of that sort of thing. For
           | the particular data structures that are currently shared by
           | those formats? Absolutely not; if one diverges then you can
           | build the new structure and link it in. The old structure is
           | still valid in its own right.
        
         | magicalhippo wrote:
         | We've got a large number of customer-specific file
         | integrations, and a lot of them are indeed very similar as the
         | customers have the same system on the other side. However
         | almost all the time there's some tweaking needed. Customer A
         | used field X for this but customer B used the field for that.
         | 
         | So if a new customer comes and need an integration to a system
         | we already support, even if we think they'll start out being
         | identical, we just copy the code.
         | 
         | Thing is, these things evolve. Suddenly we have to patch over
         | some process-related issues in the other system for customer A,
         | while customer B does not have that issue. Now we can fix A's
         | integration without worrying at all about affecting B.
         | 
         | Of course we write library and helper functions, and use those
         | actively throughout, so we only repeat the "top level" stuff.
        
         | klyrs wrote:
         | > Sometimes it's best to be DRY right from the start.
         | 
         | Never zealously adopt a programming practice that can be
         | summarized in a headline!
        
           | OJFord wrote:
           | Yeah, don't be DRY from the start, YAGNI.
        
           | barfbagginus wrote:
           | I feel like the "sometimes" suggests that in most cases we
           | should zealously not dry things, but in some cases we might
           | want to. Doesn't that make you curious what cases those might
           | be?
        
             | klyrs wrote:
             | > Doesn't that make you curious what cases those might be?
             | 
             | I've been programming for nigh on 40 years; I'm not
             | terribly curious because I've seen successes and failures
             | of pretty much every development paradigm firsthand. But
             | since you ask, my comment above was a self-referential
             | whimsicalism.
             | 
             | I DRY my code in phases. A rule of 3 is decent (e.g., if
             | you've copied a chunk of code three times, give it a think;
             | if you've copied it 9 times, give it a good hard stare).
        
         | PaulStatezny wrote:
         | > Sometimes it's best to be DRY right from the start.
         | 
         | 3 things matter most in real estate: Location, location,
         | location!
         | 
         | 3 things matter most in programming: Context, context, context!
         | 
         | DRY - like almost every other programming
         | tool/paradigm/principle - are very often misused by a lack of
         | the programmer's ability to discern correctly whether the
         | tool/paradigm/principle fits _in the specific context_.
         | 
         | It's not just a science. It's an art, too.
        
           | klibertp wrote:
           | > It's not just a science. It's an art, too.
           | 
           | It was... I will miss that until retirement, too, but the
           | artisan part of this craft has been gradually dying for over
           | a decade. I think this change started when "popular kids"
           | started confidently saying they wanted to work with computers
           | when they grew up. The effect is the proliferation of normies
           | throughout the trade, now many of them with 10+ years of
           | experience. The average developer's appreciation for the
           | elegant and the inspiring grew weaker, and the idea of
           | putting more work into a task than absolutely necessary
           | (like, for example, stopping for a moment to _consider the
           | context_ before deciding on a tool or technique to apply...)
           | lost all appeal. There 's a thin line between aggressively
           | pragmatic and ignorant, and the newer generations seem to
           | treat crossing that line as a non-issue as long as the ticket
           | can be presented as resolved. This mindset used to be
           | confined to cubicles and neckties, but now it's seemingly
           | everywhere...
           | 
           | Don't mind me; I just feel unusually old and grumpy today...
        
             | hoosieree wrote:
             | Come check out some of the array languages[1,2,3], or
             | perhaps the retro-computing comfy vibes of a system like
             | Decker[4]. Some of us still appreciate code as poetry.
             | 
             | [1]: https://mlochbaum.github.io/BQN/
             | 
             | [2]: https://www.uiua.org/
             | 
             | [3]: https://code.kx.com/q/learn/tour/
             | 
             | [4]: https://beyondloom.com/decker/tour.html
        
               | klibertp wrote:
               | Oh, I know! While I got discouraged trying to learn K (I
               | was a little too hung up on the notion of free software
               | back in the day...), I learned J and had a terrific time
               | interacting with the community. There were some bad
               | apples, but the basket labeled "Smalltalk" is filled with
               | many marvels. I enjoyed Factor while it was actively
               | developed, which led me deep into Forth-land - an
               | unforgettable experience. By the time I arrived on the
               | other side of s-expressions, Smug Lisp Weenies were out
               | and lots of friendly, curious, intelligent folk lived
               | there instead. More recently, I invested some time into
               | learning Raku - a beautifully eclectic, shockingly
               | expressive language whose development is severely
               | understaffed, underfunded, and underappreciated. I had a
               | great time in all those instances - I know there are
               | passionate people approaching programming creatively in
               | all kinds of shapes and forms, and it's indeed
               | heartening.
               | 
               | The problem is, when I go to work, I see exactly none of
               | those people among my coworkers. I feel like breaking
               | down on the spot and declaiming:
               | 
               | > I've seen things you people wouldn't believe... Attack
               | ships on fire off the shoulder of Orion... I watched
               | C-beams glitter in the dark near the Tannhauser Gate. All
               | those moments will be lost in time, like tears in rain...
               | Time to die.
               | 
               | I don't, mostly out of respect to Rutger Hauer. Anyway,
               | while in absolute terms people like that are probably
               | much more numerous now than 20 years ago, they feel much
               | more distant to me than ever before.
        
               | barfbagginus wrote:
               | Could you not just fire the people who are not like that
               | and hire the people who are? If not, I think you might
               | not have enough power in the organization! Seek more
               | power!
               | 
               | Also consider finding a job where you need to know
               | category theory to understand the system! That'll give
               | you coworkers who are magical! And a little insane!
               | 
               | Ps. The "C" in "C-beams" now stands for "Category
               | Theory"!
        
               | klibertp wrote:
               | I'm baffled. Which part of my comment triggered you so?
               | You come across as ballistic here. Can't you just "don't
               | mind" my comment? I specifically asked for this,
               | signaling that I don't feel too well right now...
        
             | lost_womble wrote:
             | Yeah, the younger generation must just be wired up
             | differently, it's all those popular kids, can't be anything
             | to do with the proliferation of scrum, agile, and crunch
             | that make them focused on doing all those tickets ;)
        
               | barfbagginus wrote:
               | Trying to educate a boomer (new official term for a
               | millennial) into not complaining about zoomers is like
               | trying to convince a zoomer into not dying inside when
               | they think they have committed an act of cringe.
               | 
               | It's best not to try! These are the cultural delusions
               | that drive us!
        
             | barfbagginus wrote:
             | If you're dealing with those kinds of people, make demands
             | that they begin studying category theory. I'm talking the
             | hard stuff - categories for the working mathematician,
             | toposes triples and theories, sketches of an elephant,
             | McCurdy's 2012 paper on graphical methods for Tanaka
             | reconstruction, and Roman's 2019+ work on coend calculus
             | and diagrammatic optics.
             | 
             | If they do it, if they actually learn categories, then you
             | need to shut the f** up and let them live. They now have
             | the power to do what they want. If they refuse to learn
             | categories then you get to laugh at them and tell them that
             | they are cringe, and fire them.
             | 
             | And if you don't have the power to just fire them for not
             | learning categories, then you very likely need to learn
             | categories yourself! And also gain the power to fire
             | people, lol! There is no rational point to ever complain
             | about a programmer, when you can just fire them instead.
        
         | hakunin wrote:
         | Did the repetition become obvious before or after you saw both
         | implementations? It's possible that if you dried it up right
         | away, you would've abstracted the wrong thing, and it's way
         | more obvious only in hindsight.
        
           | Stratoscope wrote:
           | This was some years ago, so my memory may be foggy - and I'm
           | not instrument rated!
           | 
           | As I recall, it was after I saw the two implementations. I
           | got curious and looked at the original FAA data and
           | specifications and saw how much the two kinds of airspace
           | have in common.
        
             | barfbagginus wrote:
             | How was the dried code to own and maintain before and
             | after? Was there pain before, and did it go away? Or did
             | the operation prove to be more of a nitpick/bike shed? How
             | long did you stick around with eyes on the code?
        
         | BurningFrog wrote:
         | I'm a fan of the "Rule of Three", but another great rule is to
         | not mindlessly follow rules.
        
           | heleninboodler wrote:
           | Call me crazy, but it's almost as if these should be
           | guidelines considered by a thinking person, with experience
           | to help inform them, rather than hard-and-fast rules that
           | must be applied to everything.
        
         | cjfd wrote:
         | This happens when people follow rules mindlessly. 3 is an
         | arbitrary number anyway. Even if 3 is the right number in most
         | cases there will be cases where abstracting after 2 cases is
         | best and others where abstracting after 4 cases is best, or any
         | other number, really.
        
         | jvans wrote:
         | This is just being hindsight oriented. The way it worked out it
         | was better to DRY from the start, but the person implementing
         | it didn't know how the chips would fall.
         | 
         | If you do what this guy did and are wrong, it looks like yes
         | obviously it was the same thing and you should have DRY right
         | from the start.
         | 
         | If you DRY from the start and are wrong, someone writes a blog
         | post about not DRYing from the start.
         | 
         | It's a bet on which mistake is worse, because you will be wrong
         | sometimes. DRYing from the start IMO is a worse mistake than
         | duplication.
        
         | tossandthrow wrote:
         | It is easy to come in after the fact and say this.
         | 
         | In reality: Had he DRYed it up from the beginning, you would
         | probably have complained about a codebase that needs to corner
         | cases into account deep in the code - the story had been turned
         | around.
         | 
         | Yes, it is annoying to get into an existing code base.
        
         | baja_blast wrote:
         | But you're refactoring an existing codebase so it's not
         | prematurely dry. You know exactly how these classes diverge.
        
         | gloryjulio wrote:
         | > Sometimes it's best to be DRY right from the start.
         | 
         | In your case I don't see how it couldn't be refactored later on
         | once you have the domain knowledge. In our company we always
         | have 2 types of projects planned. We can't always ship new
         | features under type 1. And refactoring is something we must do
         | under the 2nd type to reduce the tech debt. With 2 type of
         | projects in mind we always plan the capacities for both types
         | of projects
         | 
         | And that's how exactly how you should do dry. Once you see the
         | repeated pattern, you should refactor it.
         | 
         | I'm in the finance factor and we are seeing this kind of
         | 'similar but not the same' problem a lot. We are constantly in
         | the process onboarding new payment use cases while doing
         | refactors to abstract away the common patterns.
        
           | barfbagginus wrote:
           | In some cases I would prefer to have two separate clear yet
           | repetitive use cases, than to have, for example, a single
           | abstract use case, that gets injected with two different
           | factories at configuration time, depending on which sub case
           | you want.
           | 
           | In that case, reading and maintaining two simple use cases
           | might be less work than reading an abstract use case,
           | backtracking to the available factories, and then mentally
           | interpreting the injection and factory behavior.
           | 
           | Unless your abstractions really really make things just way
           | simpler, being explicit could be better.
           | 
           | Another place where this repetition tends to help readability
           | more than it hurts maintainability is in test cases. Often
           | abstracting things out with a little test fixture is helpful.
           | But then being obsessive about this ends up making tests
           | harder to maintain since there's all this long distance
           | coupling that you constantly have to maintain.
           | 
           | It seems that outside of test cases and use cases, we want to
           | be much more diligent about DRY and picking the right
           | abstractions - that makes logic in our use cases much simpler
           | and more coherent. While inside the use case or test case, a
           | little duplication of business logic is not so bad, and can
           | actually improve the narrative of the code.
        
       | hprotagonist wrote:
       | https://grugbrain.dev/#grug-on-dry
       | 
       |  _grug begin feel repeat /copy paste code with small variation is
       | better than many callback/closures passed arguments or elaborate
       | object model: too hard complex for too little benefit at times
       | 
       | hard balance here, repeat code always still make grug stare and
       | say "mmm" often, but experience show repeat code sometimes often
       | better than complex DRY solution_
       | 
       | something i have learned the hard way is that DRYing out too fast
       | paints you into architectural corners you don't even know are
       | there yet.
        
         | localfirst wrote:
         | read the whole article and wow!
         | 
         | grug tell facts
        
           | big_hacker wrote:
           | is free country sort of
        
       | ReleaseCandidat wrote:
       | That's an example for the wrong abstraction, not an example for
       | "no DRY".
       | 
       | Checking if a date is in the future does actually make sense, I
       | would not do it like that (that's more of a
       | `raise_if_not_in_future`), but whatever:                   def
       | check_if_in_future(date):            if date <= datetime.now():
       | raise ValueError(                 "Date must be in the future")
       | def set_task_deadline(task_deadline):
       | check_if_in_future(task_deadline)                     def
       | set_payment_deadline(payment_deadline):
       | check_if_in_future(payment_deadline)
        
       | resters wrote:
       | So refreshing to see this kind of wisdom in a concise blog post!
       | 
       | My take:
       | 
       | In beginners, over-emphasis on DRY is a mistake made because they
       | don't yet understand why DRY is considered a best practice.
       | 
       | In more senior developers, over-emphasis on DRY comes from a few
       | psychological desires... 1) to mitigate the uneasy feeling of not
       | knowing what direction the product will take, and 2) the warm
       | feeling that comes from finding a refactor that makes the code
       | more DRY.
       | 
       | What is overlooked is the cognitive overhead required to un-DRY
       | pieces of code when requirements change. Often the result is a
       | DRY but convoluted series of refactors that obscure the intention
       | of the code and (often) obscure system design intention that
       | would otherwise have been quite clear.
       | 
       | Sadly, many otherwise talented software engineers have the kind
       | of minds that prefer micro-level problem solving and are
       | challenged at big-picture reasoning. There is often actual
       | discomfort when too much big-picture reasoning or synthesis is
       | involved. I view this as more of an emotional than a cognitive
       | limitation, and something that is amplified by the conformist
       | culture found in most large organizations (and which many small
       | ones believe it is best to emulate).
       | 
       | Conformity with best practices is valued above real problem
       | solving. Worse still, there are often elaborate discussions of
       | PRs relating to minutia associated with DRYing up code for which
       | it wasn't necessary in the first place.
       | 
       | Sure, as a system matures there are opportunities to remove cruft
       | and DRY code where it is obviously helpful, but it is silly to
       | waste too much time on it until the true requirements of the
       | system are well understood.
        
       | bbwbsb wrote:
       | I prefer: do the thing when doing so reduces the expected cost of
       | (time-discounted) future outcomes by more than the expected
       | utility of the next best thing you can do now.
       | 
       | The problem with DRY occurs when it contravenes this principal -
       | when deduplication is too expensive and/or unlikely to decrease
       | the cost of future mutations enough to be worth it.
       | 
       | The proposed problem isn't a binary - that you should or
       | shouldn't make the assumption yet - but rather that the
       | assumption has a cost based on what you believe is likely to
       | occur in the future and the value produced by making the
       | assumption now needs to outweigh the cost.
        
       | dkarl wrote:
       | Reminds me of a conversation I had with a project manager. To
       | match the example, I'll recast it in terms of deadlines.
       | 
       | Project manager: Sam is working on a deadline validator. You made
       | a deadline validator last sprint right? Could Sam use yours?
       | 
       | Me: No, unfortunately not. My deadline validator enforces that
       | deadlines are in the future and are aligned with midnight UTC, to
       | ensure correct date calculations in the database. The deadline
       | validator Sam is working on does not enforce those restrictions.
       | Sam's deadline validator will be applied to user input for an
       | entirely different field, where deadlines don't have to be at
       | midnight and are just as often in the past as in the future. In
       | fact, Sam's validator only checks that a deadline string has the
       | expected format and is within twenty years of the present day. My
       | validator operates on timestamps sent as integers from another
       | service, not string values uploaded by users.
       | 
       | Project manager: So your deadline validator is not reusable at
       | all? That's unfortunate. Is there something we could have done
       | differently to avoid this redundant work?
        
         | Spivak wrote:
         | Developer, next time: No, I made a real-time database
         | constraint policy enforcement engine. Totally different thing.
        
         | Aeolun wrote:
         | > In fact, Sam's validator only checks that a deadline string
         | has the expected format and is within twenty years of the
         | present day.
         | 
         | You two have been talking about this longer than it took Sam to
         | implement by now ;)
        
         | kccqzy wrote:
         | This really hit close to home. Many years ago a project manager
         | said the same thing to me. Unfortunately in that case, Sam's
         | new deadline validator has much more simplified requirements
         | than the developer's. The developer's deadline validator takes
         | ten function arguments to customize its behavior in various
         | ways. One of the arguments, a stringly typed argument allows
         | the validator to match the fraction of the second against a
         | string. Sam thought providing the empty string "" would ask the
         | validator to accept all possible fractional values, but
         | unfortunately it requires the magic string "*" to accept all
         | possible fractional values and the empty string "" only allows
         | zero fractional values, i.e. the deadline must be an integral
         | number of seconds.
         | 
         | An outage happened because the system rejected every single
         | deadline that's not an integral number of seconds.
        
         | allenu wrote:
         | This is one of those things that's easier to respond to with
         | more experience. When I was a more green developer just
         | starting to work in a team, I'd probably have answered, "Yes, I
         | could've tried making the code more open-ended and extensible
         | for such a scenario."
         | 
         | Now, with decades of experience, I can answer, "No, because if
         | we designed all of our code to be reusable for any potential
         | use-case, we'd be adding in a ton of extra work that would
         | often not pay out later. It's acceptable that if we go with a
         | YAGNI principle that occasionally we'll have some code that
         | seems to be duplicated work, but that's far better than
         | constantly writing more code that we don't need as that will
         | increase our design complexity, leading to longer development
         | time and requiring additional testing."
        
       | TedDallas wrote:
       | DRY is more about support and maintenance than anything else.
       | 
       | I see a lot of attacks on DRY these days, and it boggles my mind.
       | Maybe it is being conflated with over-
       | engineering/paramterization/architecting. I don't know.
       | 
       | But I do know that having to fix the same bug twice in the same
       | code base is not a good look.
        
         | actionfromafar wrote:
         | It's not that. It's when you need to change how the function
         | behaves but for only one of the callers.
        
       | ftlio wrote:
       | I suck in the kitchen. If you asked me to make you a sandwich, I
       | would have to go to the cupboard or refrigerator a few times to
       | end up with all the right ingredients. Then I could at least
       | competently assemble the sandwich. My family also loves antipasto
       | salads, which are basically just like a sandwich without bread.
       | 
       | If you asked me to assemble 10 different sandwiches, and 1
       | antipasto salad, some of which I'm seeing for the firs time, I
       | would attempt to gather all the ingredients, but ultimately end
       | up going back and forth between the cupboard and refrigerator
       | still. I might even think, on one of those trips, hey, I don't
       | need the mayo anymore, so I can put it away, only to have to go
       | back and get it again for a later sandwich. The end result would
       | probably be all the ingredients for every sandwich on the counter
       | at the same time, as I should have done.
       | 
       | I'm pretty smart though. I'm good at Abstraction. So, I assume
       | I'm going to get another order from the family for a sizable
       | amount of sandwiches and some more antipasto salad. I name each
       | sandwich and salad type and then write down a list of ingredients
       | for each sandwich so I can cross-reference it to assemble a
       | master list of all required ingredients per sandwich when the
       | next order comes in. I can then go to the cupboard and
       | refrigerator once.
       | 
       | I then order each sandwich type by their shared ingredients, so
       | that I can apply ingredients only once until I'm done with that
       | ingredient (and then I could put it away, but I'm not a premature
       | optimizer). The only issue is that some ingredients require
       | slicing, like tomatoes, and tomatoes aren't sliced in the same
       | manner for the salad as the sandwiches, and my daughter can't
       | stand when the tomatoes and lettuce touch on her sandwich, and my
       | other daughter wants the cheese and the meat separate. I don't
       | want to overcomplicate the problem, but I don't want to Repeat
       | Myself either, since I know I can grab the tomatoes and slice
       | them all up in the same step, so I need to remember when I
       | assemble my list of ingredients per sandwich and salad that some
       | are exempt from the ordered application of ingredients and must
       | be handled by a single, separate script for assembly.
       | 
       | I run this process a few times, and it works, but I learn that it
       | takes me 35 minutes to do, and that there's now a hard
       | requirement on a frozen item involved with one of the sandwiches
       | that it not be out for more than 10 minutes, so now this
       | ingredient itself must be exempted from the step where I grab all
       | ingredients and my assembly instructions for the one sandwich
       | that involves this ingredient must be very clear that I will
       | still also need to grab that ingredient.
       | 
       | Then I learn/realize:
       | 
       | In-fact, 90% of the time I make a sandwich, or salad, I only make
       | one at a time.
       | 
       | OR
       | 
       | Nobody wants to order sandwiches by name, they just want to give
       | me a list of ingredients in the right order
       | 
       | OR
       | 
       | I am gradually making so many more sandwiches every day that my
       | kitchen counterspace cannot support getting all the ingredients
       | at once
       | 
       | OR
       | 
       | I only make the same sandwiches + one salad every day to the
       | exact same specification
        
       | booleandilemma wrote:
       | Software development is so varied that blanket statements like
       | this never work.
       | 
       |  _Never_.
        
       | redbell wrote:
       | In an unrelated note, this " _Google Blog_ " thing appears to
       | have [at least] three different domain names that redirect to the
       | same url: https://blog.google.com, https://blog.google and
       | https://googleblog.com, why is that?!
        
         | wnevets wrote:
         | History? The Google TLD is relatively new and would have been
         | created last
        
       | wumbo wrote:
       | Especially with AI. It's better to teach the AI many examples and
       | let it understand the implicit abstractions. Who needs to worry
       | about reusable higher order abstractions when the machine just
       | busts out the code you need in a single file?
        
       | andy_ppp wrote:
       | You can never make fast and hard rules about when to repeat
       | yourself and when not to, it probably takes a lifetime to know
       | when to do it correctly. I'm pretty certain programmers are not
       | going to be out of work anytime soon.
        
       | chubs wrote:
       | Back in my ruby on rails days, we used to have a saying: Don't
       | 'dry out' your code. I still find that to be a good saying to
       | bring up when you're trying to find a good balance between
       | repetitive code vs painfully generic code.
        
       | darylteo wrote:
       | While the examples may be quite facetious, they often are for
       | demonstrative purposes...
       | 
       | I myself have been burnt by Over-DRYed code - Over-DRYed code
       | tends to lead to God Functions, where 1 change has many
       | unintended consequences. Unintended side effects should be
       | considered a worse scenario over "tedious chore".
       | 
       | In any case, (at least in a API context) a robust test suite
       | focused on end user requirements SHOULD make the mythical "code
       | quality" metric irrelevant.
        
       | daxfohl wrote:
       | This is what I found hard about Haskell. It's so tempting to DRY
       | to such a phenomenal degree, but then the slightest requirement
       | change somewhere in the middle breaks some abstraction and
       | bubbles all the way out. It's almost like how a zip encoding of
       | two files that differ by one byte could be completely different.
        
       | noisy_boy wrote:
       | > When I asked him about this, he said, "I have this philosophy
       | that says if you only have two similar things, it's best to write
       | separate code for each. Once you get to a third, then you can
       | think about refactoring and making some common code."
       | 
       | There is also the angle of when eventually the third usecase
       | comes out, how much willingness/buy-in is going to be there to
       | make changes to the running code for the refactoring?
       | 
       | Quite often than not, nobody wants to take the risk for the sake
       | of introducing DRYness and you end up with three copies.
        
       | esprehn wrote:
       | This is one of my favorite principles, don't try to make the
       | codebase "too DRY" . I often see it cause stress and complexity
       | in platform or infra teams trying to support large communities of
       | developers.
       | 
       | It's right to be concerned that a bunch of teams might be a
       | wasting time implementing the same thing slightly differently, or
       | that you'll end up fixing the same bug across all these "copies".
       | 
       | Often that kind of duplication is good for the business though,
       | and the platform team doesn't have the insight to see all the
       | divergent requirements on the horizon. Letting the teams innovate
       | separately without having to coordinate changes in all these DRY-
       | ied up systems can be the best way to support them.
       | 
       | Or even more abstractly, zero codebase chaos is not the optimal
       | amount.
        
       | richrichie wrote:
       | Sedat wrote about this and other useful stuff.
       | 
       | https://www.manning.com/books/street-coder
        
       | heavyset_go wrote:
       | Rules of thumb are meant to be broken, they aren't laws.
        
       | poikroequ wrote:
       | I still practice DRY, but I try to not overdo it with unnecessary
       | abstractions. More recently I've been practicing SPOT (Single
       | Point of Truth). I interpret this in two ways. One, every piece
       | of data should have a location that reigns over all others. It's
       | okay to have duplicates of the data, such as caches, but any
       | copies of that data should be treated as ephemeral and possibly
       | inconsistent with the source of truth. Second, there is some
       | overlap with DRY where some logic that answers a question or
       | computes a result should not be duplicates. A specific function
       | or class which computes something important, should probably not
       | be duplicates, but implemented once and reused. A great example
       | is authentication, you most likely should not duplicate code that
       | checks whether a user is authorized to do something. In a sense,
       | the code which computes whether a user is authorized in itself
       | becomes a "source of truth".
       | 
       | There are still good reasons to DRY early on. Actions that need
       | to be performed synchronously, rather than acquiring the same
       | lock in several places, consolidate your code so you're so
       | there's at most a few places you acquire and release that lock.
       | Cache invalidation, having a single class for reading and writing
       | some piece of data makes it much easier to keep the cache
       | consistent.
        
       | lulznews wrote:
       | Just grug it out
        
       | poikroequ wrote:
       | Anecdotal but I've found it much easier to start DRY and then
       | later duplicate code that diverges a little bit from the
       | original. What is hard is working with a large code base with
       | lots of duplicate code. When you need to change one thing, but
       | there's multiple places you need to make that one change, I've
       | found it very difficult to track down all the places which need
       | to be changed.
       | 
       | When you can't DRY, or when it's simply not practical, at least
       | try to find a way of keeping track of duplicate code. Using enums
       | and global constants can help you by finding all the references
       | to those symbols and locate all the places in your code which
       | need to be updated or refactored.
        
       | asow92 wrote:
       | Ah, yes, Google testing blog. From the same company shipping
       | products recommending we eat "at least one small rock per day".
        
       | nfw2 wrote:
       | The main benefit of abstraction isn't to reduce keystrokes; it's
       | to break a program into comprehensible chunks of operation.
       | 
       | Routines that are conceptually identical should share an
       | abstraction. A concept might benefit from an abstraction even if
       | it is only used once. It is never too early to add more intuitive
       | abstractions to your code.
       | 
       | On the other hand, code that is only coincidentally similar in
       | execution should never be forced to share an abstraction.
        
       | vizualbod wrote:
       | Domain knowledge is just as important when programming as the
       | craft itself, this is why I have to have a great relationship
       | with subject matter expert and develop domain knowledge fast. So
       | abstractions can be a better fit
        
       | aitchnyu wrote:
       | Do we have (Python) codebase analyzers that detect subtle
       | duplicates (bane of WET) and overly complex functions called from
       | n places (bane of DRY)?
        
       | sureIy wrote:
       | People focus too much on DRY and not enough on modularisation.
       | 
       | If your functions do everything, DRYing them can be awkward and
       | ineffective. If you stop injecting your business logic into every
       | line and attempt to create "pure" functions, it's so much easier
       | to sprinkle the occasional `if(input.type2) pow(input, 2)` in the
       | business part.
       | 
       | If your codebase isn't at least 20% "utils", I don't want to
       | touch it. I regularly dip into code I wrote months/years back and
       | reuse it without much thought.
        
       | mherrmann wrote:
       | The example is terrible. It's understandable that OP wants to
       | keep it as short as possible. But it is made so simple that it
       | fails to convey the point. You would obviously not want to use
       | the DeadlineSetter class here. It doesn't even ever access its
       | "entity_type" field.
       | 
       | All code bases I have seen in the past 15 years have too little
       | DRY, not too much. Yes, every technique we use has pros and cons
       | and we need to decide in each case whether DRY is worth it. But I
       | worry that people will come away from the article (or even just
       | the headline) with the feeling that "ah, I don't need to DRY".
       | I've been in the situation too many times where somebody copy-
       | pasted and I later had to make it DRY to achieve consistent
       | behavior. Let's err on the side of DRY.
       | 
       | In the example, the right-hand side could either be left as-is.
       | Or it could extract a function:                 def
       | set_task_deadline(task_deadline):
       | _ensure_is_in_future(task_deadline)              def
       | set_payment_deadline(payment_deadline):
       | _ensure_is_in_future(payment_deadline)              def
       | _ensure_is_in_future(deadline):         if deadline <=
       | datetime.now():           raise ValueError("Date must be in the
       | future")
       | 
       | This is much better than the straw man example employed by the
       | author.
        
       | surfingdino wrote:
       | It sometimes takes time to discover the best approach to writing
       | code that handles similar inputs. I once worked on an ingestion
       | pipeline that was supposed to clean up data sent to us by a
       | person whose sole job was to sit in front of a Windows PC running
       | three pieces of software:
       | 
       | a) a terminal running a text mode app extracting data from a
       | mainframe
       | 
       | b) Excel
       | 
       | c) Outlook
       | 
       | Their sole job was to copy data from the terminal, paste it into
       | Excel on a daily basis, save it as a CSV file, and send it as an
       | attachment to the address monitored by a script that was
       | responsible to running data processing pipeline. Because of the
       | manual nature of the job and the way data was presented in the
       | terminal there were errors, which were really unpredictable. I
       | was not allowed to talk to the person doing this job or visit
       | them in their office so it took me three months to find out that
       | what was shown on the terminal screen was essentially a 80x25
       | version of a punchcard and that position of the fields mattered.
       | Sometimes the user would not copy the whole screen, sometimes
       | there would be an extra character added while pasting data into
       | Excel (not always a stray "v"), sometimes a gremlin character
       | would be added between the mainframe and the terminal (there must
       | have been a serial connection somewhere). Forget proper encoding,
       | JSON or XML, it was raw data, really raw. When I started working
       | on the pipeline it would break on every incorrectly formatted
       | record, when I finished the job and left it was only barfing on
       | 5% bad records and those that it could not process would be
       | neatly put aside an emailed to the support people responsible for
       | dealing with this client. There was a lot of repetition
       | initially, but I then discovered patterns (random offsets,
       | encoding errors, extra characters) and that allowed me to build a
       | set of generic classes to quickly implement problem-specific
       | handlers.
        
       | Gehinnn wrote:
       | I think DRY should be more "Don't repeat assumptions".
       | 
       | Or rather, don't assume the same thing in two different places,
       | especially not implicitly.
       | 
       | Avoiding code duplication mostly follows from that.
        
         | jstimpfle wrote:
         | That's barely possible because code has to coherently work on a
         | shared goal. But I agree in principle -- reduce duplication of
         | assumptions as much as possible.
         | 
         | One way that helps with that is creating abstractions. I don't
         | mean clever grandiose abstractions, those are extremely hard to
         | get right. I mean precisely those abstractions that factor
         | shared code.
         | 
         | But even before that, it's important to get the control flow
         | right to minimize doing the same thing multiple times in the
         | codebase. Because even when the implementation is in a central
         | place, calling it multiple times from different locations is
         | what you say, duplicating assumptions.
        
           | Gehinnn wrote:
           | I think it's quite often possible! Most of code acts as
           | implication. Given this piece of data (of this shape), I can
           | compute that. Or given I'm a valid object (constructor
           | completed successfully), I can do this and that.
           | 
           | However, very often, unstated assumptions sneak in.
           | 
           | For example, assuming to understand how to interpret a
           | certain string, which usually causes all kind of escaping
           | issues.
        
       | janaagaard wrote:
       | Don't use DRY but WET: Write Everything Twice. :o)
       | 
       | In practice: The second time you implement something, start out
       | by copy-pasting the first implementation. Once you're done with
       | the second version, figure out if and how to abstract the two
       | implementations.
        
         | Sammi wrote:
         | Basically never ever abstract something if you only have two
         | copies. Copy/paste and move on and be productive. It's usually
         | only if you have a third or fourth copy that you start to see
         | if there is any inherent abstraction to be gleamed.
        
       | DLion wrote:
       | I like a lot the "rule of three" when it comes to have to choose
       | when to DRY.
       | 
       | https://en.wikipedia.org/wiki/Rule_of_three_(computer_progra...
        
       | cess11 wrote:
       | I'm not particularly bothered by having to change several places.
       | The situations where I couldn't solve it with things like grep,
       | find, sed have been very rare, and this kind of solution crops up
       | every now and then.
       | 
       | One of the more common examples is generated code, where I prefer
       | to put that on the side and then copy in the files to the
       | appropriate place in the application and then update things like
       | package declarations in bulk. It makes it harder to overwrite
       | manually added comments or code by mistake.
       | 
       | Tools like ast-grep help too, when more sophisticated search-
       | replace is required.
       | 
       | So I agree, at least in less mature systems DRY is a bad idea
       | that causes a lock-in that will bite you at a later time. Often
       | it's much harder to tear apart a DRY abstraction than creating
       | one.
        
       | sambeau wrote:
       | My rules on code duplication:
       | 
       | - For code in different files you get 3
       | 
       | - For code in the same file you get half a dozen
       | 
       | - For code in the same function you get more like a dozen
       | 
       | - For code in a single repeated block you get as many as you want
        
       | HelloNurse wrote:
       | The example in the article is too short and incomplete to be
       | meaningful. In a real program there would be something that does
       | actual work for "tasks" and "payments" and deals with errors,
       | providing context for technical decisions instead of forcing the
       | choice of minimum complexity (i.e. two plain functions instead of
       | a class) as the only applicable design guideline.
        
       | msl09 wrote:
       | Cool tip! I learned that reading other people's code and seeing
       | how much they could get done with far fewer lines of code and
       | much faster than I cause they didn't over-engineer from the
       | start. I got the bad habit of making a big architecture from the
       | beginning because I read many books from academics talking about
       | beauty and elegance, while I admired people in the industry the
       | could get cool things done.
       | 
       | Now I have the experience that most of my projects I only
       | understand after months (or years!) of development, observing the
       | users and testing. Only after I have experience in the project I
       | can actually know what should be the focus of my engineering.
        
       | fl0ki wrote:
       | There can be lots of other factors that make a particular
       | refactor more or less desirable. Is the code actually that long
       | or not really, is it already complex or straightforward, was it
       | written well in the first place, etc. Without seeing the
       | particular code, people can jump to any conclusion or justify any
       | bias towards or against any particular refactor including
       | attempts at DRY.
       | 
       | My experience has been that the worst code was also the most
       | poorly tested, if at all. In many cases, you can't really test
       | the code without refactoring it, but you can't refactor it
       | without risking regressions due to lack of tests.
       | 
       | Breaking this cycle requires going back to requirements, whether
       | explicit or by painstakingly inferring every valid use case
       | supported by the intentions of the original code, even if its
       | defects meant it couldn't actually serve those use cases anyway
       | and so cannot even act as a reference implementation for those
       | use cases.
       | 
       | Once you've understood the intended behavior of the old code
       | enough, you now have a test suite to use for any future code.
       | This is usually the hard part [1], and it's going to seem that
       | way because once you have it, finding the simplest code that
       | passes all tests is just programming. Importantly, even if a
       | future maintainer disagrees with you on the best solution, at
       | least they can rewrite it without worrying about regressions
       | against any tested case.
       | 
       | Aside: Performance regressions are more difficult to detect but
       | preparing standard test workloads is a necessary part of that
       | too.
       | 
       | After you ship this rewritten solution you're going to get user
       | issue reports that you broke some edge case nobody has ever
       | explicitly considered before but someone had somehow come to rely
       | on. Now you're only adding a test and logic for that one edge
       | case, you know that no other case was broken by this change, and
       | that this case will never be broken again.
       | 
       | Now you have leverage over the complexity of the project instead
       | of giving it leverage over you. Now you're free to refactor any
       | which way you prefer, and can accurately judge the resulting code
       | entirely on its own merits. You know you're comparing two correct
       | solutions to the same problem, neither one is subtly hiding bugs
       | or missing edge cases. Your code reviewers will also find it much
       | easier to review the code because they can trust that it works
       | and focus the review on its other merits.
       | 
       | [1] You know if your problem domain is an exception better than I
       | do, like if you work on LLVM.
        
       | BenoitEssiambre wrote:
       | The question then becomes: when do you break out code or not?
       | Unfortunately (or fortunately if the art and craft or programming
       | fascinates you), the answer is not easy. It seems to have to do
       | with avoiding over-fitting or under-fitting the domain and
       | purpose, to do with getting the best fit in a Bayesian Occam's
       | Razor sense. Minimizing unnecessary code but also doing
       | "Dependency Length Minimization" of the parse tree of your
       | program so that it's maximally understandable and the
       | abstractions increase the potential of your program to correctly
       | interpolate into unknown future use cases. I reflect on some of
       | these points here: https://benoitessiambre.com/abstract.html .
       | It's about entropy minimization, calibration of uncertainty. It's
       | about evolving your code so that it tends toward an optimal
       | causal graph of your domain so that your abstractions can more
       | easily answer "what if" questions correctly. These things are all
       | related.
        
       | sltr wrote:
       | Jimmy Koppel wrote about this 6 years ago [1]. It's one of the
       | first exercises in his software design course [2].
       | 
       | Two identical pieces of code can have different specifications.
       | # x is an age of a person; the code checks if the person is past
       | the retirement age in the US       def is_of_retirement_age(x):
       | return x >= 65;            # x is an ASCII character which is
       | already known to be alphanumeric; this code checks if it's a
       | letter.       def is_ascii_letter(x):         return x >= 65;
       | 
       | [1] https://www.pathsensitive.com/2018/01/the-design-of-
       | software...
       | 
       | [2] https://www.mirdin.com
        
       | nightski wrote:
       | I don't think the distinction between DRY or don't DRY is
       | interesting at all. Instead what matters is how the abstraction
       | is achieved/performed. Good abstractions stand the test of time,
       | poor ones leak.
        
       | waffletower wrote:
       | This comes from within Google, which strongly embraces Go -- a
       | language famously impaired when it comes to abstraction
       | capabilities. This opinion has been voiced here before:
       | https://news.ycombinator.com/item?id=8316520
        
       | notjoemama wrote:
       | What this industry has taught me:
       | 
       | 1. DRY initially when your organization is afraid of refactoring
       | because they'll never let you touch it a second time if "it
       | works".
       | 
       | 2. DRY later when it's clear the code will not change into
       | genuinely separate branching workflows.
       | 
       | 3. DRY it initially when mid-level management uses duplication as
       | a metric to evaluate "good" engineers, especially when your
       | salary is impacted by that perception.
       | 
       | 1 and 3 have been a symptom of micro-management from the business
       | side. Only 2 is valid.
        
       | m000 wrote:
       | I would extend this advice to also explicitly cover excessive use
       | of functions:
       | 
       | A block of code that _would_ make sense as a standalone function
       | /method, _should not_ be made into a function /method if (a) it
       | is only repeated a few times, (b) it is not complex enough for
       | anyone to bother writing unit tests for the new function.
        
       ___________________________________________________________________
       (page generated 2024-05-31 23:02 UTC)