16 ms·
DRY Is a Trade-Off
- city41 6y agoHappy to see people questioning DRY. Hard to argue with some of these points. Dan Abramov also wrote on this fairly recently: https://overreacted.io/the-wet-codebase/ https://overreacted.io/the-wet-codebase/
- Osiris 6y agoThe way I like to work is first to write out all the code I need to make something work correctly, then I go back over the code to see if there's anything that could be simplified or split into separate functions, etc. I really like to see DRY code, but if you have to make a helper function that takes a bunch of parameters with a bunch of conditionals to something slightly different, you might be better off just sticking the specific logic you need in each place. The worst case of copy-pasta I saw in a codebase I came into was a function that was 1000 lines long, duplicated 3 times with < 10 lines of it different for each copy. That's a classic case for DRY to be applied.
- kazinator 6y agoHey, you mean "DRY IA TO"! Don't repeat common phrases like "is a" and "trade-off", damn it! Define an acronym and use that instead. DRY is literally impossible. If something has to be performed or evaluated two or more times, and you factor that out under a definition, you still have to invoke the definition multiple times. I.e. you are still repeating yourself, just using an abbreviation. What you are doing is called "compression". Classic data compression algorithms like LZ77 work by abbreviating. "LZ77 algorithms achieve compression by replacing repeated occurrences of data with references to a single copy of that data existing earlier in the uncompressed data stream. " - Wikipedia Outside of alcoholic drinks, that's the ultimate DRY. Thus, the argument against DRY is obvious: it's a form of compression, and excessive compression destroys readability: or else we would all be able to read source code that has been put through LZ77. Only mild compression improves readability. Mild compression improves readability largely because it's easier to see that two brief invocations of a definition are exactly the same, than to see that two repetitions of a code block are exactly the same. When we see that two code blocks are exactly the same, we don't have to understand them separately. Basically, brainless repetition and verbosity hinders readability, as does dense, thorough compression. One extreme might be represented by reams of Java boilerplate; the other by IOCCC entries.
- blackbear_ 6y agoYou definiteyly shouldn't strive to DRY for everything. The tricky part is to understand when duplicated code can be abstracted away and when it is duplicated "by chance", i.e. the code is the same but there is no real underlying abstraction. The latter tends to happen for business logic: suddenly requirements change and your beautiful "abstraction" falls like a castle of cards.
- shhsshs 6y agoSomeone else’s comment [1] I saved from an older post also about DRY I’ve usually heard this phenomenon called “incidental duplication,” and it’s something I find myself teaching junior engineers about quite often. There are a lot of situations where 3-5 lines of many methods follow basically the same pattern, and it can be aggravating to look at. “Don’t repeat yourself!” Right? So you try to extract that boilerplate into a method, and it’s fine until the very next change. Then you need to start passing options and configuration into your helper method... and before long your helper method is extremely difficult to reason about, because it’s actually handling a dozen cases that are superficially similar but full of important differences in the details. I encourage my devs to follow a rule of thumb: don’t extract repetitive code right away, try and build the feature you’re working on with the duplication in place first. Let the code go through a few evolutions and waves of change. Then one of two things are likely to happen: (1) you find that the code doesn’t look so repetitive anymore, or, (2) you hit a bug where you needed to make the same change to the boilerplate in six places and you missed one. In scenario 1, you can sigh and say “yeah it turned out to be incidental duplication, it’s not bothering me anymore.” In scenario 2, it’s probably time for a careful refactoring to pull out the bits that have proven to be identical (and, importantly, must be identical across all of the instances of the code). [1] https://news.ycombinator.com/reply?id=22022603&goto=item%3Fid%3D22022466%2322022603 https://news.ycombinator.com/reply?id=22022603&goto=item%3Fi...
- js8 6y agoIn imperative languages without macros, it might be difficult to abstract structural similarity, like a common if statement. In functional languages, where every statement is a function, this is often easier.
- jayd16 6y agoYou can write your method in a functional style in the popular imperative languages. This can stretch how DRY you can be a bit further.
- systemvoltage 6y ago> Then one of two things are likely to happen You forgot the third most common case - 90% of those repeated methods will never have a bug and now you've got a code base that will start smelling bad and no one is going to want to work on it. I agree with some of the things here, but allowing multiple places of repeated code unless there isn't a bug sounds like a terrible idea. Lot of these small methods will never have a bug and they'll continue to rot the codebase.
- danparsonson 6y ago"...putting common lines into functions, without careful thought about abstractions, is never a good idea..." (emphasis mine) I think this is the crucial part. DRY works fine and in fact arises naturally if the code is well factored to isolate areas of commonality - as the author points out though, this is very difficult to do and I think that's the core problem.
- lock-free 6y agoNot sure if anyone else shares this anecdote, but I've noticed the most DRY-hard programmers tend to be the most resistant to things like functional programming, monads, and other generic approaches which are the ultimate realization of DRY. And often the most inscrutable. Another anecdote on DRY: I'm currently refactoring two interrelated systems that share a single function, and very horrible bad things happen if the systems disagree on the return value of that function. However sharing the same code is more complex than duplicating it and this has a nontrivial impact on how the systems are distributed. So today I'm undoing the original work I did to make it DRY - turns out that sometimes, you need to copy/paste.
- BrianOnHN 6y agoIs it DRY if you use rsync instead of copy&paste?
- __jem 6y agoAbstractions are addicting for many developers, including myself. I switch between Go and Java. Go is the language I want my coworkers to use. I'd rather read "bad" Go code than "bad" Java code all day long. Bad Java can be truly excruciating to read and review, particularly due to the poor choice of abstractions. Whereas, Go mostly gets out of the way and may be written poorly but is straightforwardly written poorly. Still, there's a certain sense of aesthetic beauty that I just can't derive from Go, and why I kind of hate working in it. There's lots of things about Java and OO that I don't love, but reading a perfectly factored Java code base can be just beautiful. Mostly due to good choice of interfaces. Now, those code bases might be rare and not worth the lift of a million bad abstractions. I'd probably agree at this point, but still, I find it odd that most Go code bases just feel dirty and thrown together to me. Hacking stuff together in a mostly procedural language with good deployment story is probably the right way to write for-profit code, but I'm not sure I love it.
- BeetleB 6y agoPredictably, another article that doesn't know what DRY is, leading to slaying of strawmen. I believe DRY was coined in Programming Pearls, and probably none of the examples in the article are instances of DRY. DRY is about knowledge/requirements, not similar code. It is about ensuring that a given requirement is not duplicated in multiple places in the code. It is not about similar looking code, which often involves differing requirements but just happen to be coded similarly. The latter leads to coupling if you unify it into one piece of code.
- Ma8ee 6y agoYes, thank you! Exactly what I was trying to express in another comment. And if you do it the right way, none of the problems with DRY usually brought up will be relevant. The whole notion of looking at code and trying to spot similarities to find abstractions is completely backwards.
- deathanatos 6y agoI've referred to this in the past as "semantic duplication" (code that is the same by definition/requirement) vs. "syntactic duplication" (code that just happens to do the same thing today, but there is no requirement that requires both copies to remain the same).
- Ensorceled 6y agoI sort of agree with you. A better title/approach would have been "These aren't DRY, they're silly 'no common code' bigotry". I find the article resonates because I've seen all of these anti-patterns defended with "because DRY". I agree, it's not DRY; but so many people get stuck on the "no code duplication" part. I'm not sure if the "This isn't DRY" is the best fight or "Sometimes DRY is not the best". My favourite block of "DRY" code was a method that had a triple nested loop (for all object A, for all objects A.B, for all objects B.C) with a bunch of flags (like 15 different bools, ints, dates and arrays) that changed the ORM filters for A, for A->B, for B->C, and then changed what operations were done on A, B, C. Basically, at the end, the only similarity was the foreach part. The comment on the block of code had "Keep this loop together for DRY", as if they knew this was going wrong but not sure why. It ended up being 3 or 4 much simpler methods, based as you say, on requirements NOT code "shape".
- weavejester 6y agoClearly we need a good acronym for "duplication is better than the wrong abstraction".
- bluetwo 6y ago"Abstraction extraction is an expensive transaction."
- oftenwrong 6y agoI will humbly suggest "DOBA": Duplication Over Bad Abstraction. Not nearly as good as "DRY", but it is fun to say. I wonder how much of DRY's popularity is due to the catchy name. One can say "this needs to be DRYed up" (verb), or "this code is really DRY" (adjective), or "I appreciate the DRYness of this code" (noun). The opposite of DRY: "wet", of course. Being a homophone of an existing word that is both a noun and adjective, and that has an obvious antonym really lends itself to usage.
- gauravphoenix 6y agoSo is WET (write everything twice). The right answer typically lies somewhere between the two extremes.
- recursivedoubts 6y ago> Loss of locality Yes. I am trying to popularize the term Locality of Behavior (LoB) to capture this software concept: https://htmx.org/essays/locality-of-behaviour/ https://htmx.org/essays/locality-of-behaviour/ There are always trade-offs in development, and the older you get the more you value clarity of code over other potential concers, and locality of behaviour is a big part of that. I can't find the original source (it was somewhere on medium) but this graph struck me: https://ibb.co/qm4cLW7 https://ibb.co/qm4cLW7
- thisiszilff 6y agoI think this misses the point of DRY a little bit. DRY isn't about not copy pasting code, it's about ensuring that knowledge isn't repeated. If two parts of the system need to know the same thing (for example, who the currently logged in user is, or what elasticsearch instance to send queries to, etc.), then there should be a single way to "know" that fact. Put that way, DRY violations are repetitions of knowledge and make the system more complex because different parts know the same fact but in different ways and you need to maintain all of them, understand all of them, etc. etc. Code blocks that look to be syntactically the same are the lowest expression of "this might be the same piece of knowledge" insofar as they express knowledge about "how to do X", but the key is identifying the knowledge that is duplicated and working from there. Sometimes it comes out that the "duplication" is something like "this is a for loop iterating over the elements of this list in this field in this object" and that is the kind of code block that contains very little knowledge in terms of our system. But supposing that that list had a special structure (ie, maybe we've parsed text into tokens and have information about whitespace, punctuation, etc in that list) and we start to notice we're repeating code to iterate over elements of the list and ignore the whitespace, punctuation elements in it, then we've got a piece of knowledge worth DRYing out given that all the clients now need to know what whitespace & punctuation look like even when they'd like to filter them out. It's worth pointing out that DRYing out something isn't necessarily "abstracting", it is more like consolidating knowledge into one place.
- majormajor 6y agoI haven't seen this "don't repeat knowledge" take before, it's pretty interesting. I see why you don't want mutated various versions of the same information all over the place, but you still have dangers. Especially if you "overly reduce" your knowledge. If your common recipe is "do A, B, C, D, E" and you reduce that to just "do X," for instance. I've seen this often turn into "now, instead of the knowledge being repeated in several places, it's hidden in one place and only one person knows it." Everybody else just relies on the library doing its magic, and when someone needs to do something differently, they have this huge mountain to climb to figure out how to modify the code to also do "J" for certain cases without breaking everyone else.
- pydry 6y agoIt's not just DRY. Every axiomatic or semi-axiomatic principle of software development ends up being a trade off. Good code lies at a local minima where multiple competing concerns are all balanced against one another. At least, I've yet to see one which isn't.
- kens 6y agoI agree about the importance of tradeoffs. Looking at the historical perspective, though, the reason that every principle is a tradeoff is that the principles that are uniformly worse get discarded. For instance, structured programming (building code out of blocks with structured control flow rather than a pile of gotos) was victorious in the 1970s. Nowadays nobody considers the tradeoffs of using if-then-else versus gotos; structured code is the automatic choice. Self-modifying code was very popular in the 1950s (since it was the only way to get many things done), but essentially nobody uses it now. Modularity is another victorious principle of software development, winning out over big blobs of code with global variables. Using a stack for subroutine calls used to have tradeoffs, but now nobody would consider an alternative. Looking at the long perspective, there is real progress in software development (although slower than I'd hope).
- MauranKilom 6y ago> Self-modifying code was very popular in the 1950s (since it was the only way to get many things done), but essentially nobody uses it now. ...which proves that it is a tradeoff. Just one where the local minimum currently is at one end of the domain due to context (e.g. ROM size limitations) having evolved.
- Ma8ee 6y agoI see similar arguments come up over and over at HN, and I say they stem from a fundamental misunderstanding of DRY. What we must not repeat is not lines of code, but how we do certain things in the code. That is, it’s fine to repeat syntactically identical sections of code if their semantic meaning is different. But if the semantic meaning is the same, they must never be repeated because we must never have several definitions of the same thing in the same program. This is similar to the concept of normalisations in DBs.
- takeiteasyy 6y agoCould you give an example of syntactically identical sections of code with different semantic meanings?
- Ma8ee 6y agoOnly contrived ones without supplying quite a bit of context.
- hknapp 6y agoy = m * x + b can refer to the distance a car has traveled over time at a fixed speed with a starting point as well as the cost of ordering a certain quantity of an item with a fixed shipping cost that's my best attempt at an example
- bob1029 6y agoDuplication is usually a safe default course of action because you are not locking yourself in to any particular consensus of the problem domain. Obviously, too much of this will render a codebase a nightmare to maintain as bugfixes and feature enhancements have to be applied in multiple places. I have found that starting with duplication is by far the easiest and most flexible way to work through problem domains that are complex. Once you have a really good grasp of the modeling, then you can iterate and decide on normalization where appropriate. Thinking about this from an analytical perspective - If you build your application with duplication by default (i.e. define a domain model for each logical use case/scenario), then you will have an excellent analysis already in front of you regarding which business types should be normalized and which ones might be a little bit trickier to make common. Many times it is impossible to fully explore a problem domain until you have already written software against its entire extent.
- xnx 6y agoAnd often the process of de-duping repeated code later on isn't as bad as DRY purists make it out to be, especially since it can be done incrementally. Example: If you have similar functions in 7 places, you can consolidate them one by one. If you have 1 function used in 7 places, you have to consider all implications for all of those code paths.
- azundo 6y agoThis seems like an equivalent duality to me but in the case of seven independent functions you're much more likely to miss considering a case. If behavior changes for one you likely should be considering if that change applies to all the others.
- Ma8ee 6y agoBut when you set out to do that you first have to make sure they really are identical, and since everything from indentation to names often differ it might not be so automatic. And then you do find differences, and you have to figure out if they are there by accident because someone forgot to implement a fix or a change in some places or if the differences are intentional. On the other hand, if you realise your abstraction was bad, duplicating a function is always trivial.
- salmonellaeater 6y agoIt helps a lot to only extract common code that has a clear purpose. If I find myself naming a method "doThisThingThisOtherThingAndLogSomeStuff" then it's not a good refactor. On the other hand, "markStaleRows" or "enforceTreeInvariants" have clear purposes that help future engineers make intelligent decisions about how to use these functions and how they should evolve. I want to build in to the common code answers to the questions "should I call markStaleRows or roll my own code?" and "should I make this code change in the calling function or in markStaleRows?".
- Ma8ee 6y agoAnd if you have a function that is called markStaleRows, everyone better use it, because when I need to change how stale rows are marked, I will change this function and nothing else. I won’t go through the whole codebase trying to find if someone might have written some inline code somewhere else trying to do the same thing.
- layer8 6y agoA better formulation of DRY is SPOT — Single Point Of Truth. In the event that the logic is changed in one copy, should the other copy always be updated accordingly? If the answer is yes, combine them into a single copy, so that they don’t diverge and create ambiguous “sources of truth” in the future. Conversely, if it is likely that the logic in the two copies will need to diverge in the future, due to having a different context, then do not combine them, because they represent different “truths” that just currently happen to have the same form. Of course, the answer to that question can change over time, and one has to combine or duplicate accordingly. This also serves to document the intent that “yes, these two occurrences are expected to evolve identically”, or “no, these two occurrences are expected to evolve independently, even though they currently happen to look the same”. The article is correct though that there is a trade-off in terms of the complexity created by the abstraction, and in how important the “common truth” is. Sometimes a source comment pointing out the dependency is better than introducing a nontrivial abstraction. The book “A Philosophy of Software Design” argues that there are two sources of complexity in software: dependencies and obscurity. Combining two similar pieces of logic into one can reduce dependencies (of one having to be changed when the other is changed), but can increase obscurity due to the added abstraction. If the combining was done for the wrong reasons (the two occurrences actually need to evolve independently), then the dependencies are increased instead of reduced.
- camgunz 6y agoLove this, yeah. I've heard it phrased like "how do we answer questions" so like, "how do we answer questions about a job's status", "how do we answer questions about a user's bank account balance". Either way, once you have those kind of product requirements in place you can build to that spec, and then start iterating as you gain more knowledge.
- camgunz 6y agoThe problem isn't DRY, the problem is "helpers". Helpers are an anti-pattern, they don't fit in your architecture, they have no mental model, they're difficult (impossible) to name and organize, and they're extremely resistant to refactoring. Effectively they're spaghetti code. The example I always come back to is auth. If you're doing the same thing like "parse a cookie header, get the session, make a DB connection, look up the session info, etc. etc.", consider how you could architect the layers of your application using a mental model that people would find easy to reason about. That might be some OO, middleware, or even a macro, but the point is that it's thought about, designed, engineered, and documented. The reason helpers are more prevalent than thoughtful architecture is that humans are a lot better at prioritizing the short term "I improved it" fix from factoring into helpers over doing the long term work of architecture. If you want to change this, it starts with cultural values that prioritize long term sustainability.
- runald 6y agoWhat about the "helpers" found in the standard library? How do you feel about those things?
- camgunz 6y agoDo you have an example you’re thinking of? I don’t think standard libraries automatically have good architecture (see: PHP), but they do have a big influence on culture, which is interesting.
- runald 6y agoNo, I meant if you think helpers are anti-pattern that results into a spaghetti code, then you must also think that standard libraries are an anti-pattern mess. I don't think there's a standard library that doesn't have helpers, unless we don't agree on what a helper is?
- camgunz 6y agoHmm that could be it. Like, Python has json and it works like pickle before it. I don’t think that’s a helper and I like that it reinforces that serialization pattern. What I think of as helpers are like base64_auth_to_username_password. That’s factoring out like 2-3 lines of code that may be duplicated in a half dozen places, but in truth represents an incomplete abstraction, layer, or subsystem.
- indymike 6y agoA lot of programming maxims like "stay DRY" are rules of thumb, and often are dangerous, or at least lead to unexpected results when treated like laws of nature. I had a developer who drank functional flavored Koolaid and refactored any single line of code that appeared more than once in an application. Was about 38K lines of code. When he was done, it was still about 38K lines of code. Was it functionally pure? yes. Was it very difficult to debug? Yes... you had to step into sometimes five or six functions to get to a single line of logic.
- bluetwo 6y agoI agree. I also think that when building a prototype, flexibility is more important than stability. I allow myself to repeat code when I think two similar things will be different by the time requirements are better known. Later I'll usually re-write in a more stable fashion.
- chrisweekly 6y agoDRY -> AHA ("Avoid Hasty Abstractions")
- lmilcin 6y agoI am always wary of any extreme opinions. DRY is yet another tool. Skilled hands will be able to judge when to use it and when to keep a little duplication to preserve simplicity and legibility. I see frequently developers going to great lengths to avoid duplication of couple of lines of code. In the process, they create much more new code, use half a dozen patterns and make the code more difficult to understand. That's not the ultimate goal. The ultimate goal is always to keep the code produce more value and this usually is best served by simpler and more maintainable code.
- jakelazaroff 6y agoI feel like I post a link to this comment [1] every time the abstraction vs. DRY topic comes up, but it’s just such good advice. I consciously try to remember it whenever I’m programming. > Dependencies (coupling) is an important concern to address, but it's only 1 of 4 criteria that I consider and it's not the most important one. I try to optimize my code around reducing state, coupling, complexity and code, in that order. I'm willing to add increased coupling if it makes my code more stateless. I'm willing to make it more complex if it reduces coupling. And I'm willing to duplicate code if it makes the code less complex. Only if it doesn't increase state, coupling or complexity do I dedup code. > The reason I put stateless code as the highest priority is it's the easiest to reason about. Stateless logic functions the same whether run normally, in parallel or distributed. It's the easiest to test, since it requires very little setup code. And it's the easiest to scale up, since you just run another copy of it. Once you introduce state, your life gets significantly harder. > I think the reason that novice programmers optimize around code reduction is that it's the easiest of the 4 to spot. The other 3 are much more subtle and subjective and so will require greater experience to spot. But learning those priorities, in that order, has made me a significantly better developer. Dan Abramov also has a good talk about this [2]. [1] https://news.ycombinator.com/item?id=11042400 https://news.ycombinator.com/item?id=11042400 [2] https://overreacted.io/the-wet-codebase/ https://overreacted.io/the-wet-codebase/
- nmfisher 6y agoThat’s a really insightful comment, and also the reason why I think my code quality improved considerably after learning Clojure and F#. I discovered that immutability in functional programming made it far easier to reason about state - something goes in, and you know exactly what comes out. No verbose checking logic, no bugs because something wasn’t set that should have been. I’d personally switch coupling and complexity (though it really depends on your definition of the latter). Coupling is usually much less of a problem than you expect it to be, and is easier to identify/rectify in retrospect.
- jakelazaroff 6y agoWell, there's certainly a lot of overlap in the Venn diagram of coupling and complexity! The really insidious thing about coupling is that it can transcend module barriers — you can make changes that cause problems in places that seem to be entirely unrelated. Whereas e.g. cyclomatic complexity can be tough to tease apart, but it's at least limited to a single module that you can work on in isolation.
- ChuckMcM 6y agoI like the piece but the author might want to update it for distributed repositories where 'DRY' might mean you inherit a CVE from some other developer, and it also may mean that you've got code in your system that nobody who works for you knows how it works or why it was written the way it was written. Both situations are "sub optimal."
- minimuffins 6y agoI agree strongly with the main thrust of this article, but it doesn't support the "Loss of Locality" prong of the argument very well. > when reading through the original caller, it is less clear what the code does In my experience it is usually clearer, if the function has an even remotely appropriate name. And unless you are coding with notepad.exe, toggling between caller and callee is pretty trivial. > When you move this code to a function as a part of a straightforward DRY refactoring, this means that now a function is mutating its parameters. Whether that matters depends a lot on the language. And anyway it seems like the main problem in code like that is that the function is inappropriately mutating state outside of itself in a surprising way, which might be a problem separate from dryness. "Loss of Locality" does strike me as reasonable argument against code that is too dehydrated though. Maybe somebody else can make some better supporting claims for it?
- carapace 6y agoThe whole DRY concept irritates my inner curmudgeon because it is itself a lousy repetition of the (formal mathematical) concept of refactoring. When you refactor code it's just like factoring an algebraic equation. If you're just removing duplication without understanding how it affects the structure of the system then it's a kind of "cargo cult" programming (IMO.) Even when you know what you're doing. there's good refactoring[1] and bad refactoring[2]. [1] https://osdn.net/projects/joypy/scm/git/Thun/commits/aa43eb668492e03a5d2c6cc5010479f6e0cba220 https://osdn.net/projects/joypy/scm/git/Thun/commits/aa43eb6... This is a good clean bit of refactoring: it makes the branch symmetrical and lets you move code to a common post-branch step. [2] https://github.com/calroc/HulloWurld/blob/master/Hullo.html#L476-L488 https://github.com/calroc/HulloWurld/blob/master/Hullo.html#... This is a terrible function that, while it abstracts the core of the two following functions well, makes the system harder to understand. In other words, the three factored functions are less desirable than the original pair of functions despite their redundancy, because the original functions were easy to understand and the new factored helper funtion is inscrutable. Context counts.
- mumblemumble 6y agoReminds me of an excellent blog post (and talk) by Sandi Metz: https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction In it she also offers some great advice on what to do if you find yourself stuck with a bad abstraction.
- sedatk 6y agoI discuss this in my book "Street Coder" for beginner and mid-level programmers, in a section titled "Do Repeat Yourself". Being hardcore about not repeating yourself might create unnecessary dependencies and complicate your code structure. You can even cause irrelevant concerns to depend on each other causing it to become harder to maintain.
- throw1234651234 6y agoDRY and SRP are the air I breathe, so I need to defend it. "This means that when reading through the original caller, it is less clear what the code does." This is false - if you have a clear service and method name, with clear, properly-typed params, it SAVES time, because unless it's broken, it's a SHORT invocation that abstracts the details. "the function might have some surprising semantics. For example, mutating contents of local variables is sensible in code." Pure functions are the only "apply everywhere" part of the functional programming paradigm. Side effects are just as painful in .NET and Java as they are elsewhere. "Overgeneralized code" SRP let's you identify this fairly easily. As an example, I just tried to abstract away "Edit" and "SuggestEdits" in the same controller to stay DRY, but then realized it violates SRP. "Each modification of the "common" function now requires testing all of its callers. In some situations, this can be subtly non-trivial." Much less painful then knowing you have to change it in multiple places AND THEN checking the code. Just doesn't apply. "When each of those code segments were repeated, ownership and responsibility were trivial. Whoever owned the surrounding code also owned the repeated segment." The team owns the code. At worst, the lead owns the code. Bad excuse. Feels like a dramatic contrarian piece to me. As mentioned, SRP and DRY are the only two things that allow our team to keep churning without significant tech debt. That, and avoidance of magic strings. These 3 things are probably 80% of clean code. Edit: Oh, and keeping methods to 5 lines or less, except in genuinely extreme, rare cases. Of course that's theoretically under "SRP and abstraction", but you know.. Edit 2: The single source of truth comment is on point as well. There was a rule when Python was designed "There should be ONE, and ideally ONLY one way of doing things." This is extremely important to writing good code and on-boarding people onto your project.
- dj_mc_merlin 6y ago> Oh, and keeping methods to 5 lines or less, except in genuinely extreme, rare cases. This confuses me. Extreme, rare cases? Don't get me wrong, I code Lisp as much as anyone and love keeping my functions short and simple, but not going above 5 lines (esp. in Java or .NET) is masochistic. By that metric, an if in Java takes up 60% of a function's real-estate, a local variable 20%, and a try-catch is your maximum complexity.
- galaxyLogic 6y agoQuantity does not guarantee quality. But neither does reducing quantity
- seer 6y agoI recently got an epiphany reading the venerable SICP book. Best abstractions are not about wrapping everything in functions and calling it DRY. You create layers. If you have 2-3 problems that kinda look similar, you could wrap them up in a function, with some arguments for variation, and pat yourself on the back for DRYing it up. But after some time you get a 4th or a 5th occurrence of the same problem, all slightly different. Obviously you could create a mess of things by abstracting away what are different problems, but I don’t think “just don’t do abstractions until its really obvious” is a solution, to me it sounds more like giving up. And worse you can end up with piles of code that look kinda similar but with small differences here and there, really hard for new comers to parse and reason about. What I think is a better approach is to try to come up with abstraction “layers” - smaller pieces of blocks that would make the same problem trivial to solve. You got your 2-3 pieces of code that look kinda similar. OK do they have smaller parts that its dead obvious that are similar? Abstract away those, now you are left with parts you might be happy to leave as is, don’t need one big function for all of it. And sure this has pitfalls too, but a good way to test any abstraction is to see how leaky it is. Can I successfully understand and reason about whats going on by just reading the function names / descriptions / types? Does it compose? Yes? Awesome now you have a layer you can build your business logic on. No? Well either change it or get rid of the abstraction until you can come up with a better one.
- AndyPatterson 6y agoI’ve got one criticism in that the over generalisation remark shouldn’t really exist in well designed DRY code - if two function calls and one doesn’t even use most of the parameters then these aren’t covering then these should be separate functions
- mybrid 6y agoDRY is just a another way of saying the best practice of don't reinvent the wheel. The difference being reinventing the wheel is applied to third party packages and DRY to you own code. The friction from blindly applying both is the same, dependency. This is especially apparent in JavaScript where literally thousands of dependent libraries can be pulled in and any two version incompatiblies can break the build. Pulling in any dependency usually means pulling in orders of magnitude more lines of code then will be used. The inert code relative to one's use can be subject to version incompatiblies and security patches no different than any other code. It can also be subject to build differences such as numpy and pandas being non-native Python and native Python libraries exist for many limited use cases. Of course none of this is an endorsement of cut/copy/paste development. However, it is an indictment of cut/copy/paste enforcement of DRY and the more general don't reinvent the wheel.
- SergeAx 6y agoI checked out the author: he seems to be an expert, wrote some books. But this article is full of rookie mistakes and I can't figure out the purpose of writing it except clickbaiting. > The main problem with repeating a code chunk is that if a bug is found, there is more than one place where it needs to be fixed. No, the main problem is that reading repeated code adds cognitive load for reader. Also, when you copy-paste the code you should copy-paste its tests too, and that leads to CI/CD resource wasting. > The alternative to copy-pasting the code is usually to put it in a function (or procedure, or a subroutine, depending on the language), and call it. This means that when reading through the original caller, it is less clear what the code does. If the function and local variables all has clear naming and function does not mutate passed variables (which otherwise should be clearly implied by it's name) - you never need to step into it while debugging and just save a bit of time while reading the code. So, the takeaway here is: the code is written for humans, not computers. The idea of clean code is ease of reading and understanding. That's what DRY is for.
- waheoo 6y agoDRY is the cause of, and solution to, all software development problems.
- itoocode 6y agoWhenever I try to adopt DRY I tell this myself. - don't over engineer. - may be KISS (keep it simple stupid).
- einpoklum 6y agoWell, yes, it's true that there's a trade-off, but the "DRY principle" is justified by the other side of that trade-off. And that side becomes stronger with every repetition of your code. Also, some of the negatives of not-repeating-yourself are not general: * Often your repetitions themselves are localized. * Sometimes, you can generalize your code even _more_, to factor out the non-general parts, so that you end up with a generalization of the scheme/mechanism, applied with specific details. * You can limit your DRY to within your scope of ownership, i.e. have module-local / unit-local utility constructs. Then, only if you agree with others on the generalization, do yu surrender ownership. etc.
- himujjal 6y ago"There are no solutions, there are only trade-offs" - Some wise guy
- oxfordmale 6y agoIn Data Engineering I only use DRY when it absolutely make sense. Code connecting to a database tends to be boiler plate, so can be abstracted away. However, I never abstract away ETL transformations, even if the code is duplicated. Often ETL logic starts out very repetitive in its early days, only to be customised further and further over the lifetime of the code. Each business stake holders tend to ask for modifications, e.g. can you please ignore sales in Australia for now, as it is handled by a different team, etc...
- kgr 6y agoI'm known as a strong proponent for keeping code DRY (see: https://www.youtube.com/watch?v=S4LbUv5FsGQ https://www.youtube.com/watch?v=S4LbUv5FsGQ), but even I sometimes advocate just copying and modifying code. Sometimes two simple pieces of code are less work/maintenance/mental-burden than one overly generalized/parameterized/complicated piece of code. As the number of copies grows, then the balance of the trade off shifts.
- Aardappel 6y agoWant to see what happens falls too much in love with DRY? He invents an "automatic refactoring system" that automatically DRY-ifies a whole program: http://strlen.com/restructor/ http://strlen.com/restructor/ (not that great an idea in hind-sight, as the page explains). Interesting to see all the comments here that make it seem obvious that "fanatic DRY" is bad. Historically, there was a wave of OO love where a lot of people went crazy with wrapping every program element in deep layered class hierarchies. DRY (and OAOO, YAGNI etc) was maybe a backlash to that, and some go too far with it. Now we all seem in love with "leave the redundancy in the code", which is another backlash. What's the next backlash? Maybe we'll finally focus on how to balance these factors rather than seeing some extreme as the silver bullet?