11 ms·
DRY is an over-rated programming principle?
- stewx 4y agoOnly part of this I would quibble with: > Copying and pasting a few lines of code takes almost zero thought and no time. Find and replace are very good at finding repeating things later if we start to care Find-and-replace is unfortunately not really adequate for finding repeated code, in my experience. There must be much better tools out there.
- dimgl 4y agoOne of the dumbest blog posts I've ever read. Who is upvoting this stuff? If anything I see newer devs _not_ embracing DRY. Some of the codebases I've seen lately are hilariously bad, especially in frontend development.
- dakom 4y ago"Sometimes duplicating things, either code or data, can significantly simplifies a system. DRY isn't absolute." - John Carmack ref: https://twitter.com/id_aa_carmack/status/753745532619665408 https://twitter.com/id_aa_carmack/status/753745532619665408
- cwoolfe 4y agoTwo is company. Three is a crowd. If code is duplicated twice, I'm ok with that. If it's duplicated three times, then it's time to refactor.
- caramelcustard 4y agoBut wouldn't rejecting DRY (in a way) follow another over-rated programming principle called KISS? /s In all fairness, the best programming principle is: just like with software licenses, know what to use and when.
- xiphias2 4y ago,,To solve my sauce issue, maybe I could use an OOP style and have a PizzaOrderer class that can be subclassed for each pizza type, allowing each type to override sensible sauce/crust defaults.'' No, DRY doesn't mean that you should create classes just to prove your (invalid) point.
- progx 4y agoThe one and only important programming principle: use your brain. Not every problem is the same and not every pattern could be used to solve every problem.
- citrin_ru 4y agoThis true, but it is not very helpful advice for a novice programmer. DRY is popular because it is easy to follow. There are even tools which look for similar chunks of code in multiple places (and some companies put such checks in CI which IMO a bad idea).
- whiddershins 4y ago> Having the meaning of the first argument change because you passed an optional second argument is very odd. Is it, though? And yes, I would totally just make it so the function detects whether I am passing in an object or an array of objects and respond accordingly. I feel like I got this pattern from jQuery or something. Seems very normal for a good library.
- karol 4y agoYou should have called the blog post "DRY considered harmful" and go down in history.
- klaussilveira 4y ago"Duplication is far better than the wrong abstraction" - Sandi Metz https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction
- Bost 4y ago<whatever-done-correctly> is better than <wrong>
- valand 4y agoPeople forget that DRY means "Every piece of knowledge must have a single, unambiguous, authoritative representation within a system". The principle concerns the duplication of knowledge, not code. Author's reference to "accidental duplication" is caused by two similar code that represents different code that is when joined becomes the code that is ambiguous in meaning.
- darepublic 4y agoBy the title alone I would have to say I disagree
- retrocryptid 4y agoMight be worth it to redo the examples here in Lisp (with and without point free) and see what happens. Partial application might make the first example a little less ridiculous, for instance.
- mbostleman 4y agoTo me, DRY, like Single Responsibility, is practical to a point, after which it doesn't support its weight. It strikes me as being similar to normalization in relational databases and summed up by "Normalize till it hurts, denormalize till it works".
- klik99 4y agoI’ve had success with WET - write everything twice. If you copy paste something three times it’s a good candidate for abstraction. Ultimately these are just rules of thumb and none will fit all cases. I just found DRY to be too aggressive in practice.
- timtas 4y agoHere's how I apply DRY. As I build a thing, I just sling duplicate code like I'm getting paid by the line. Once I've created all or most instances of the duplicate code, and they're working, only then do I circle back refactor it to extract common concepts and abstractions. I've learned over the years that I don't really understand what I'm building until it's built. I need to step back and look at the patterns that have formed to spot the difference between firm concepts and trivial duplication. Doing this well takes experience. It involves predicting how your code is likely to change. One lesson from experience is to favor repetition over bad abstractions. I formed this opinion from living through the pain both types of anti-patterns. Duplication causes the need to find, change and test all instances. That sucks. It leave you open to bugs. But bad abstractions can require ripping the whole thing apart.
- id02009 4y agoI view strict DRY (and only DRY) adherence asa sign of less experienced devs. I was like this in my early days, almost religiously following this creating wrong abstractions. Thanks a lot for those examples.
- raxxorraxor 4y agoEspecially if there is a very common refactoring bug and you get a pepperoni pizza instead of a pizza with one of the most natural toppings like pineapple. But I tend to agree with the author. Sometimes verbosity is the lesser evil. No suggestion should become a dogma and whoever played some games of code golf knows that short code doesn't mean code that is easy to read. Extreme example of course. But I believe many start to optimize in this way just as a way to reduce the line count. Still, there is still room for some kind of factories or function templates (not in the c++ sense). I think a user is allowed to repeat himself but then again a user is just another arbitrary layer again. But if such helpers are to be implemented, I tend to like it in a place where the user is invoking said helpers and not on the level below that if that makes sense.
- ajuc 4y agoMy favorite pizza is pepperoni with pineapple and red beans.
- deleted 4y ago[deleted]
- zoomablemind 4y ago> ...Especially if there is a very common refactoring bug and you get a pepperoni pizza instead of a pizza with one of the most natural toppings like pineapple. Exactly! The DRY'ed example in the first section should read rather: def make_hawaiian_pizza(): make_pizza(["ham","pineapple"]) This demonstrates the omnipresent dangers of untested copy-paste. "Copy-paste, copy-paste, Will Robinson!!"
- andymcsherry 4y agoTwo instances isn't worth consolidating because it's hard to know whether the similarities are real or coincidental. Check out the rule of three: https://en.wikipedia.org/wiki/Rule_of_three_(computer_programming) https://en.wikipedia.org/wiki/Rule_of_three_(computer_progra...
- koshergweilo 4y agoleft_toppings = ["beef"] right_toppings = [] make_pizza([left_toppings, right_toppings]) # this will be a very funny pizza Holy cow I was not expecting a none pizza with left beef reference in code form
- gibolt 4y agoFor anyone curious, here is the background of this, via Hank Green: https://youtu.be/5yWTPtPYukg https://youtu.be/5yWTPtPYukg Be prepared for laughter
- linguistics__ 4y agoFor those wondering, it's a reference to this beauty: https://i.kym-cdn.com/photos/images/facebook/000/838/967/e39.png https://i.kym-cdn.com/photos/images/facebook/000/838/967/e39...
- throwaway787544 4y agothank you
- Nursie 4y ago> "Had we started out with two pizza types that have different crust/sauce/cheese, we never would have made this refactor. Instead of our code being architected around the concept of how pizzas are made in the abstract, its architecture is tightly coupled to the specific needs of these two pizzas that we happened to be dealing with. The chance that we will be putting this code back the way it was is extremely high." Maybe. But maybe you're falling prey to the other programmer trap - catering for conceivable situations that are just never going to happen, and making your codebase unnecessarily accommodating as a result. This is another great source of complexity, and quite often the source of unnecessary abstractions (which add to cognitive load) too. In my experience it's better to cope with half-and-half pizza toppings when they arise, rather than coding as if they're already needed. Because when they are needed, you'll probably find the requirement is actually to put them on a 3-tier wedding cake, or a car.
- ilitirit 4y agoPeople should reconsider writing articles like these. It's just a list of (3!) criticisms. You can still have your click-baity title, but why not write about when and how to use XYZ principle instead?
- wruza 4y agoWhat forced me most to use DRY in inappropriate ways is typing out blocks of the same code again and again. Then I realized that and began to maintain and use easily expandable snippets with fillable placeholders. It turned out that my mind had no objections against repetitive code at all, and the clarity of it has only increased, due to the lack of context switches and parametric entanglement.
- henrydark 4y agoThis is like a compiler inlining code for faster performance, with exactly the same reasons
- erik_seaberg 4y agoWe do this because, unlike developers, CPUs can’t learn anything. No reusable abstractions are possible without extra instructions (and cycles) that tell the CPU exactly when and how to reuse them, or new microcode firmware that ordinary users aren’t empowered to write.
- ehnto 4y agoGenuinely curious, what do you do for refactors? Create a new snippet and go replace all the required instances?
- wruza 4y agoYes, e.g. when I need to add a new line/block of code, I just search for a pattern and edit there. When refactoring demands heavy structural cross-module changes, I just don’t do it honestly. What’s dead is dead, but I may do a “guided” side by side rewrite. I don’t touch snippets unless there is a good reason to do that. They are my general templates, not per-project tools. In most of my code, the need for refactoring was mostly a consequence of building a too rigid high-tech structure which with time turned out to not fit the job anyway. Figured out I can avoid it by not building it, and antiDRY also plays a role in it (albeit mostly psychological).
- revskill 4y agoNo, the correct way is, write down some configuration, then with a little code to turn config into real pizza !
- mavu 4y ago> Instead of our code being architected around the concept of how pizzas are made in the abstract, its architecture is tightly coupled to the specific needs of these two pizzas that we happened to be dealing with. The chance that we will be putting this code back the way it was is extremely high. Mistake 1: Switch from DRY to premature optimization. > You might think that legit reasonable developers but would not actually do something like this and would instead go back to the existing invocations and modify them to get a nice solution, but I've seen this happen all over the place. Mistake 2: Assumption of incompetence to support your argument. > . As soon as we start the thought process of thinking how to avoid a copy paste and refactor instead, we are losing the complexity battle. Mistake 3: Strawman argument. DRY does NOT lead to over-complicating things. Overcomplicating things leads to overcomplicating things. Now, i wasted 5 minutes, so you can waste some more to reply to this comment, instead of completely ignoring this dumb random blog post.
- fartsucker69 4y agoI don't read coding opinion articles like OP but I like to check out comments. > DRY does NOT lead to over-complicating things. That is not true. I dive around foreign code bases a lot and dry-ness is actually a significant complicating factor in understanding code, because you're jumping around a lot (as in physically to different files or just a few screens away in the same file). As in, inherently every time it's used, not just in situations where it's used in a complicated way. This sounds dumb but it just simply is much harder to keep context about what's going on around if you can't refer back to it because it's on the same screen or one short mouse scroll above or below your current screen. That obviously doesn't mean you should leave copy pasted versions of the same code over your code base. But it's important to consider that refactorization of that code into something common that gets called from multiple places as something that you don't get for free, but that is an active trade off which you usually have to apply to prevent bugs (changing one code location and not the other) or simple code bloat. In practice this is very relevant when you suspect something might be repeated in the future, but you're not sure. Imo: Just don't factor it out into anything, leave it there, in place, in the code.
- MichaelGlass 4y ago
- fear91 4y agoDRY is better for performance (cache efficiency). It’s also less work for the compiler. Those might not be concerns of someone writing pizza CRUD in python.
- ukoki 4y agoDRY is not necesarily better for performance. Loop unrolling is extremely un-dry and often provides better performance. DRY can also lead to more branches which can lead to branch predictor misses which can impact performance. For example: the author's update to make_pizza to handle split pizzas introduces a branch where previously the code would have been branchless.
- fear91 4y agoThe “branch” looks like an easy cmove target. Moreover, while it might not have a branch in the func itself, you will have to have one somewhere higher in the control flow anyway. As for loop unrolling, I bet you a loop with unrolled calls to 2 different unDRY functions will be slower (and not only because of the most certainly present extra branches to select for them)
- Flankk 4y agoThe DRY example is better though. The payload is an object. When you have multiple objects of the same shape you have a class of objects. Menu items could be loaded from a JSON source. Separation of concerns and duplication is removed from the code.
- YouWhy 4y agoI disagree with the author's example as given. The example discusses a code boundary that is internal to a single atomic "module" - the preparation of a data structure that describes a pizza. Then the author says that bad things will happen if said code boundary is used from other modules. However, why would an extrenal module developer do that? It is common wisdom to recognize and avoid module-internal utility functions. Conversely, as long as the presented shortcut is internal to a module (=used only for a specific set of use cases well understood by anyone touching the code), and saves toil, it might actually be justified.
- jcelerier 4y ago> However, why would an extrenal user do that? Potential external users typically recognize and avoid module-internal utility functions. External users will go look the implementation of msvc's standard library and reverse engineer windows API to make things faster lol. No internal module function is ever safe.
- YouWhy 4y agoI agree that no internal module function is safe, but MSVCRT is used by literally millions of developers, some of which have very uncommon functional requirements, such as making their product work on a rare version of Windows. My empirical observation is that most developers are prone to the other extreme of not considering internals when they should.
- ihateolives 4y agoPersonal anecdata. I have been increasingly aware of my own mental patterns during development and I've noticed that often I've been sitting and mulling over refactoring to some sort of universal solution instead of getting on with the work and getting things done. There are instances when I could've finished the task twice as fast if I would've just went ahead and done it with repeating code instead of thinking of clever ways to DRY it. Therefore for my personal projects I'm now firm believer of quick iterative building. Just get the first iteration done, get it working and save improvements for later. It may create a bit more work for the future me but it decreases the mental load quite significantly. I'll take less mental load with clear objective (refactor this because this) over more mental load with unclear objectives (make universal solutions taking into account things that may or may not happen in the future) any day.
- kristopolous 4y agoMy principles are "be as stupid as possible" write it for someone stupider and comment it for someone even stupider and then maybe you'll have something maintainable. (Important note: stupid is not incompetent - it's a proxy for clarity, composability and rational structure without becoming formal, rigid, overly orthodox or academic about it)
- st-keller 4y agoCan it be that you‘re using „stupid“ exactly what is meant by „simple“ in the KISS principle? ;-)
- kristopolous 4y agoSure. Related. It's an art. Generally the less code, the cleaner the conceptual execution. I always strive to remove and reduce conceptually deceits Here's some code I wrote earlier, probably a good example https://github.com/kristopolous/music-explorer/blob/master/web/get_playlist.php https://github.com/kristopolous/music-explorer/blob/master/w... It's self contained, not very big, not trying to be fancy, as direct as possible. It's worth noting a few things: Some things are repeated when there's no reasonable way to refactor it in a way that simplify things. No framework. No view/model/controller/provider/orm separation. It's not doing much and it does it fine Stuff is composed but intentionally not abstracted Here's a frontend https://github.com/kristopolous/music-explorer/blob/master/web/scripts.js https://github.com/kristopolous/music-explorer/blob/master/w... Again, no react or angular or other framework. Just direct modern code. As far as what it looks like, it's a music player frontend to some sprawling project Example https://9ol.es/pl/ https://9ol.es/pl/
- vbezhenar 4y agoThere's no silver bullet. My opinion is that you should weight alternatives without repetition and with repetition and choose the most appropriate one. Also if in the future you feel that this common code is becoming more complex with options and switches, feel free to remove it by inlining, either completely or in a few places. Often it'll allow for better code or it'll allow to find out another way to extract common code. Basically I like the refactoring approach. You have a set of refactorings. Like extract method / inline method. The point is that every refactoring is two-way. And both ways are useful in different situations. To support this approach, sane IDE is a must and strictly typed language is preferable You should refactor your code without fear of breaking unrelated code. What I definitely think is overrated is "if it works - don't touch it" principle. It's lazy and in the end it creates much more work than if one would gradually improve something that works.
- reacweb 4y agoIMO, DRY is the second principle, the first one is KISS. It is preferable to repeat ourself if that contributes to simplicity and ease of maintenance. My third principle is that there is only three principles.
- arethuza 4y ago"There are two ways of constructing a software design: one way is to make it so simple that there are obviously no deficiencies, and the other is to make it so complicated that there are no obvious deficiencies.” Simplicity is hard.
- pech0rin 4y agoNot sure why this is on the frontpage. Not only are there a bunch of typos, a bunch of code doesn't actually work the way they said it does. Also gotta love hating on the 10x developer or whatever for saying you are wrong. EVERYTHING HAS TRADEOFFS. Every single thing has tradeoffs. Obviously you should not write terrible, brittle code. The reason DRY is important is because when you start duplicating code, having 30 different serialization methods littered throughout your code, 5 different ways of calculating the same value, etc etc you see why it really matters. Its a GUIDELINE used to as a general rule. And as guidelines and general rules go -- its useful for juniors and people who don't have the experience to see the best way to write the code. Its a good default, and like YAGNI, and 100 other programmer acronyms it has its ups and downs. Your pizza example is not "coincidental repetition" -- it is actual repetition -- you just abstracted it in a really poor way to make a strawman.
- misja111 4y agoThis. The clean code principles should be considered within the specific context of the situation. They are guidelines that are good to keep in mind, but no more than that. The article gets this wrong by considering DRY as some kind of dogma and then discovering some situations where it doesn't work well. And then of course some commenters here get it wrong by only looking at situations were it does work well. It's the same religious discussion again as FP vs OOP, static vs dynamic typing, no code vs full code etc. etc. The real answer to each of these is always 'it depends'.
- fuzzy2 4y ago> They are guidelines that are good to keep in mind, but no more than that. How great dev life could be if everyone saw it like that.
- fastball 4y agoYeah, this reads like a junior programmer that got told off for having very repetitive code and they're trying to get the internet to agree that they're in the right and DRY isn't all it's cracked up to be. From the about page it doesn't seem like this is accurate, but that's how it reads. The problem is that he made his case poorly and I definitely don't agree.
- Annatar 4y ago
- pharmakom 4y agoIn UI work I prefer copy-paste when there is no clear abstraction. So far this has paid off.
- charles_f 4y agoI see DRY as a smell, not a principle. If you see clones (same code in multiple places), then it's likely indicating that there is something that can be factorized. Now the question you should ask yourself before factorization is whether the duplication is coincidental (as the author shows) or if it's because the logic was copy pasted. Most of the time it's the second case, and duplicate code does make maintenance harder and riskier. One of my pet-peeves is clones in unit-tests. People tend to care less about code quality when it comes to unit-tests, and code gets copy-pasted all over the place. The result is usually an unmaintainable ball of mess, where the most subtle variation in the unit being tested requires you to apply the same change in 15 different places. In this situation, DRY is a very useful indicator that something is going wrong. Now the opposite of DRY is YSHRY - You Should Have Repeated Yourself. When you start adding 5 boolean parameters to a function to adapt it to all its calls, it's a smell that you thought you should have DRY, whereas YSHRY.
- norman784 4y agoIn my case when I was junior I tried to be very smart and try to DRY a lot, but I found that most of the times is better to write "dumb" code and repeat yourself if the complexity is not worth, also as you stated if your function is used in a lot of places for slightly different things is just so easy to break something without noticing and also harder to test. So I agree with you, as developer you should know when do duplicate code and when DRY, but overall try to maintain your code as simple as possible, that makes also easier to maintain.
- nicbou 4y agoThis works until someone updates code in one place, but not the other, and subtle bugs are introduced. DRY / single source of truth offers a certain protection against such bugs.
- philliphaydon 4y agoOnly experience teaches you where to apply DRY and where not to. Sometimes just because something looks the same or is similar does not mean it’s the same. Applying DRY just because it looks the same can have the unwanted consequence of changing in 1 place changing in another too when that’s not the desired affect. Then you add another parameter and conditional logic just because you don’t want 2 similar looking things.
- SilverBirch 4y ago> but I would assert that any change that doesn't modify the existing calls of make_pizza or make a totally separate function for split topping pizzas (not DRY) will be some level of bad. You make a make_pizza function that supports split toppings and you pull the guts out of the original make_pizza function that just calls the first make_pizza function with left_toppings=[toppings], right_toppings=[toppings]. You don't need to ruin your function signature with *args. The fundamental assertion is that you should be structuring your code such that it is reflective of reality, but reality is really bloody messy. The immediate response to this example is that Pizza is in fact Toast[1] and so you should actually have a make_toast function that handles all forms of toast. This is clearly ridiculous, and if you're building a system to make pizzas and you build your function in a way that extends as far as building nigiri sushi, you're an idiot. You have to take a reasonable judgement of what is the underlying structure that you want to reflect. It's not a coincidence Hawaiian and Pepperoni Pizzas are structure the same. [1]:https://cuberule.com/ https://cuberule.com/
- JacobSeated 4y agoExcept, once you are done, you probably never have to touch that code again, and creating a class does not really take long. Depending on the given problem, this is sometimes more time consuming, but still gets easier and faster with practice, and the benefit down the road can be tremendous. Associative arrays are bad even for such simple things imo, because it breaks autocompletion / code inspections, and your functions are then these blackboxes that are hard to understand without looking at the implementation (code). Sometimes this is also evident when it's just you working on the code; try leaving the code for a few months only to return at it, and waste time relearning how to use your own code, because it is not self-documenting, and you can also forget- or misspell an array key. Etc. This is not the case if you define data types as objects instead of using arrays. I learned this from trial and error myself, and I used to use associative arrays a lot for things – now I find myself using/creating objects more often, and I just love returning to this code later, and have it work without too much crapping around.
- oweiler 4y agoThere was rarely a point where I haven't regretted using an associative array instead of a class. Not only adds the class semantic meaning, you can also add constraints and methods to it.
- aaccount 4y agoWRONG. OOP is the most over rated
- account42 4y ago> The problem is that these two pizzas just happen to have the same crust, sauce and cheese. The problem with analogies is that they are often bad. Don't don't specify that you want tomato sauce and regular cheese when you order your pizza because that's the default.
- jokoon 4y agoThe best principle is KISS https://en.wikipedia.org/wiki/KISS_principle https://en.wikipedia.org/wiki/KISS_principle > "Keep it simple, silly", "keep it short and simple", "keep it short and sweet", "keep it simple and straightforward", "keep it small and simple", "keep it simple, soldier", "keep it simple, sailor", or "keep it sweet and simple". It's true that often, complexity is praised...
- beaker52 4y agoI once worked on a project that was basically a simple My Account application/area for a train ticket retailer. The backend itself held no data, but whoever built the backend had gone full service layer, with models and adapters to the upstream services that hold the data. The result was a backend that was a pain in the ass to change, necessitating whole trees of file changes to build features. So we started inlining everything. We just took it back to the request handlers. We started fetching, mutating and returning the data in the request handlers. Suddenly a change became modifying one function. Every endpoint was unique and didn't depend on anything else. Things became easy. Halfway through the migration, someone got our effort reviewed by a principal engineer who told me "it wasn't SOLID", and my contract wasn't renewed. It didn't dishearten me. Software design is meant to make change easier and proudly adding abstractions can be a bad thing.
- noisy_boy 4y ago> So we started inlining everything. We just took it back to the request handlers. We started fetching, mutating and returning the data in the request handlers. Suddenly a change became modifying one function. Every endpoint was unique and didn't depend on anything else. Things became easy. What is the big benefit you gained from doing that compared to calling, say, a service method call in the controller? costService.generateCost(newPrice); Is it really that difficult to go to the service method definition? With inlining at the controller level, in order to unit test generateCost, you'll now have to deal with authentication/authorization/request handling related infrastructure which has nothing to do with cost calculation.
- beaker52 4y agoThe most complicated code we had was for generating receipts, which used some functions which we kept separate, because it made sense. Auth was handled by a middleware. And then once we'd stripped out all the layers, 95% of the handlers looked like roughly like this: result = fetch(...); /* Maybe more fetches, maps or filters */ response.send(result); They didn't really _do_ anything. It was all just small tweaks to data someone else owned. The biggest challenge were upstream endpoints changing on us, making sure we were logging and passing things like correlationIds consistently. Moving to fat handlers, we unified those by having those things already set up and passed into the handlers. The focus was on devex, so a junior could easily modify/create an endpoint and not have to think about how to get it right. We made the pit of success as easy to fall into as possible by breaking the rules that weren't serving the project very well. It was a glorified proxy layer. There were benefits in treating it as such, rather than deluding ourselves into thinking we needed services, repositories, models and such. Just transform data from someone else's endpoints and focus on the frontend.
- frognumber 4y agoIn this case, the problem is with a bug creeping in: crust: "thyn", DRY is about avoiding this class of cut-and-paste bugs too. Or with changing a string to a token, as it should have been: crust: THIN The code isn't even correct. It's mixing JavaScript and Python. I'm also not sure why you'd declare functions for each type of pizza; that's data. I'm not sure about the context, but the right way is: def make_pizza(crust=THIN, toppings=[], cheese=REGULAR, sauce=TOMATO) and then in each call, to override. make_pepperoni_pizza() is bad code compared to make_pizza(toppings=[PEPPERONI]) All of the code in this post is horrible, and has easy solutions. I feel dumber for having read this post, and even dumber for having responded.
- jve 4y agoSpeaking about bugs, can you spot how he introduced a bug when going DRY? :) In case it gets fixed: https://i.imgur.com/ZR2XKA7.png https://i.imgur.com/ZR2XKA7.png
- bjohnson225 4y ago> I'm also not sure why you'd declare functions for each type of pizza; that's data. Yep, had the same thoughts reading the code. What you suggest even seems a purer implementation of the DRY principle, rather than what is proposed in the article which would result in copy and pasting the make_pepperoni_pizza() function as soon as you decide to sell a third type of pizza. Of course, the DRY principle used without considering other factors could produce bad results, but all the code in the article is bad for reasons unrelated to the principle it attempts to criticize.
- 4y ago
- thom 4y agoDRY and Once And Only Once isn’t about slavishly identifying similar code blocks. It’s about trying to arrange your code so that a single idea is expressed in a single place. The initial API here is actually quite nice - there’s a good separation of abstraction and specification, and I can see all the information about an individual pizza in one place. The idea of making a pizza and the recipe for each pizza exist once in their respective places. It’s true that a common pitfall is to prematurely create abstractions before having concrete examples of how they’d be used. But DRY is a _refactoring_. It’s something you do to an existing codebase to better clarify its design, not necessarily something to strive for ahead of time. Much better to extract abstractions from existing examples. I always remember the tale Ron Jeffries tells of Kent Beck actually _introducing_ duplication to allow both pieces of code to be refactored. Duplication can be an opportunity to refactor towards a clearer design, but it’s not a mechanistic thing to do without thinking.
- ivxvm 4y agoThe first code example doesn't make sense. There is no good reason to write code like that which hardcodes payloads for different types of pizza. It's a realm of data. Realistically, those payloads will either be constructed by user in the UI, or they will be provided in json file as predefined variants.
- gjvc 4y agowhat about early returns?
- qubyte 4y agoProbably the most common comment from me in code reviews is about code being prematurely DRY’d out. Fortunately, I’ve found that mentioning the Rule of Three is usually enough to correct it, and it tends to stick in the mind. Waiting that little bit longer, more often than not there’ll be no third instance of some pattern and no need to abstract it. When a pattern does emerge, it’s a clearer one. Either way it’s less work.
- xiphias2 4y agoFor an excellent code base to see DRY in action, look at tinygrad: https://github.com/geohot/tinygrad https://github.com/geohot/tinygrad I believe it has a potential to be a great alternative to pytorch. I love watching GeoHot's Twitch streams as he goes to the extreme to simplify the codebase, and the end result is amazing.
- progx 4y ago"I figured I'd kick off my new blog with the most click baity thing I could think of." Then write: "Why i prefer tabs over spaces!" let the flamewars begin
- mcv 4y ago"When to use tabs, and when to use spaces."
- kaon123 4y agoI am working in a code base right now that was literally ruined because of #3. It's full of extremely difficult to follow and test higher order functions that are completely unnecessary. A feature request did come for a "half/half" pizza and we're spending our days trying to disentangle the higher order functions. The developer who wrote this thought himself the programmer genius and wanted to make a pattern out of everything. He did not accept criticism because "DRY is a holy principle". And that is why a post like this is important. Because next time I have someone like him in my team I can point him to this post. Argument by Authority may be a fallacy, but it is significantly more persuasive than other arguments. And yes, you can respond to this with "why did you hire this guy in the first place, or why did you not fire him?". Well I do not make all the decisions. Not every teammate is perfect. Such is reality. Particularly in such a young industry as software development which (compared to, say, electrical engineering) is still searching a common understanding of ubiquitous best practises.
- bryanrasmussen 4y ago>Argument by Authority may be a fallacy, but it is significantly more persuasive than other arguments. I looked at https://gordonc.bearblog.dev/ https://gordonc.bearblog.dev/ - I don't know why I would think this guy was any more of an authority on what was important than I am. So I'm not sure if anyone who thinks they're a programming genius would even care.
- kaon123 4y agoGood point. In my experience the chances of persuading the culprit are limited. However an article written by a third person is effective in helping persuade other team mates, POs, BAs and line managers. Ah yes, the terrifying politics of a team with internal disagreement.
- sshine 4y agoMaybe it’s rather an “argument by effort” — someone bothered to elaborate this into a blog post, and the recipient didn’t. It’s like finding a third person on the internet to agree with you in a one-on-one, without exposing the person you disagree with to judgement of an actual third person.
- dvh 4y agoI have reached similar conclusion. DRY projects tend to snowball over the years into mess where every minor change is insanely difficult, breaks everything and code is hard to read, bugs are difficult to solve, diffs are difficult. WET code (opposite of DRY code, often starts as copy pasted) has more code, more typing, but the diffs are simple, bugs are simple (often you simply forgot to copy piece of code into 7 different places which is easy to solve). After many iterations, what started as very similar "classes" is now completely different. One look at WET class and you know what it does, you change one line and you're done, maybe you need to copy it to 2-3 other files, maybe you don't. In comparison, you'll stare at DRY class for 2 hours and realize you need to refactor absolutely everything, it will break half of the codebase and diffs are insanely complicated. I've recently wrote 2 similar projects, one wet one dry and wet one is simpler, easier to maintain, and more enjoyable to work on. Dry is root of all evil.
- drKarl 4y agoIf you have the same code copied to several places it makes it much harder to maintain. If there you find a bug in that code block then you have to fix it in several other places, and if you forget some, a bug that you thought you had already fixed might arise again.
- ozim 4y agoHow often do you really write the same piece of business logic code in multiple places? I think discussion is that mostly it really is different code that only superficially looks the same. I don't like pizza example. But I have seen more issues because people were trying to cram code that looks the same in one function than some bug needed to be fixed multiple times because code was duplicated. You also have layers of code and DRY best applies to things like "SaveStuffToDatabase" or framework code. Where a lot of business code can still be better off duplicated because usage will evolve in different ways like: CreateNew vs EditExisting - there is a lot of business cases where when creating new entity you want to set some values that should never be available in Editing - but saving to database should be just saving to database...
- fuzzy2 4y agoI like DRY. Do Repeat Yourself. You can always refactor later, once the requirement/change is fully implemented.
- icedchai 4y agoRepeating yourself 3 or 4 times is okay. After that, it is probably worth cleaning up.
- fuzzy2 4y agoHm, I don’t think it’s about the number of repetitions. Even two can be too much if it’s the same logic, not just coincidentally. The same logic may not always be a code clone either. Maybe you need to generalize the code to remove the repetition. The reverse is also true: If it’s not the same logic, it should not be deduplicated. Even if it is a code clone now. It will probably lead to unintended bugs down the line when that code changes.
- sph 4y agoThe problem with DRY is that it doesn't tell you how many repetition is too many. Here's my advice: don't refactor when you repeat yourself twice, refactor when repeat yourself _three_ times. Having one chance to copy-paste before DRY'ing your code has been one of my most treasured coding tricks, it'll save you so much time and premature refactoring.
- ramraj07 4y agoAgreed. Sometimes even three times isn’t enough. You’re being paid obscene amounts to make these judgement calls anyway. If all your API does is return two pizza description jsons then keep it that way. If your client is a pizza delivery company and your api is supposed to allow definition and customization of pizza recipes, then you better take your ass to DRY town. Don’t blame the principle when you can’t understand what it is that you’re abstracting. I have time and again gained enormous benefit by pursuing DRY principle to its absolute core. 20x speed and code complexity optimizations, making entire teams obsolete, etc. The most important point is to make sure that your abstractions absolutely match the fundamental principles of the concept you’re trying to represent. No matter how verbose you think it’s getting it’s totally worth it if this is your bread and butter.
- samrocksc 4y agoDo what's best for the situation, WET/DRY principles both have valid application use cases. They should be used to leverage the most advantageous effect for your usage.
- NikhilVerma 4y agoI had a general guidelines in my previous company which I follow to this day to great results. If a piece of code is duplicated thrice, it's ok. If a piece code is duplicated four times, then you must extract it.
- usrusr 4y agoSolve every problem once. Two times repeated can be much worse than ten times repeated if one is a dizzying mess (e.g. to handhold some tragic third party API) and the other is a trivial sequence of instructions you'd understand even if dementia forced to read with a finger on the current line. That code wouldn't qualify as a problem so it isn't affected by the rule (but you might still de-duplify if you know that if it changes it should change uniformly)
- eternityforest 4y agoI'm at most a 2x developer and?definitely have solutions in mind. For one thing, pizza recipes are either dynamically built by the customer or they are just fixed recipes. Having individual functions for different predefined pizzas is not really dry enough for me. I would have a pizzas.yaml file, and a get_pizza_recipe("id"). Maybe I'd even read pizza data from an excel spreadsheet directly for ease of editing and sharing by management if needed.
- kelexj 4y ago[flagged]
- jimjimjim 4y agojust treat things as suggestions. stop turning things into unmovable laws. dry - if you are constantly writing the 90% of something multiple times, maybe look to see if you can genericitize it.
- gls2ro 4y agoI actually think that the example provided in the article can be solved easy - without adding too much complexity with OOP as the author explains in the third point. Yes, I actually think OOP is not bad :) But I disagree with their conclusion: That the goal is to send a post with a single JSON object and marking this task as: > That is a very, very simple thing to do That is a very simple thing to do if you think that you will write this code once and never have to change it to fit some new requirements. But probably there will be changes either from your own business or because the API will change thus the task becomes: << How can I implement sending a post request that will follow a body request format and create a code that is simple to understand and _easy to change_ >> And as right now when you write this code you cannot know what kind of change will come in the future the best way to move forward is to write small functions with very few conditions and open to extensions. Thus repeating that code there is not a good solution. What if the API will request to add any new key in the payload? And those two methods (def make_hawaiian_pizza and make_pepperoni_pizza) are not in the same file and the one doing the implementation is not the current author to remember "ahh the code is duplicated so I have to change it in multiple places"? Anyhow there are cases when duplication is good, but when composing the payload for a request is not one of them :) IMHO. Let me add to think one more thought: the structure of code tends to be duplicated in the future. So choose not to DRY having in mind that people who will write code after you will tend to make the same choice. They will look at what you wrote and then follow a similar structure. So don't DRY but make sure you do this in a place where you will be ok with other people increasing the number of duplicate code.
- drKarl 4y agoIf you have the same code copied to several places it makes it much harder to maintain. If there you find a bug in that code block then you have to fix it in several other places, and if you forget some, a bug that you thought you had already fixed might arise again.
- contravariant 4y agoThe code in the second example is horrible and for some reason used incorrectly by the author. My usual approach would be something like: def make_pizza(left_toppings, right_toppings=None): if right_toppings is None: right_toppings = left_toppings ... through really it should probably be def make_pizza(toppings=None, **kwargs): if toppings is not None: kwargs['toppings_left'] = toppings kwargs['toppings_right'] = toppings return requests.post(PIZZA_URL, kwargs) If this logic should even be handled in the application itself at all (not sure why you'd choose to make a breaking change to the API rather than extending the API, though changing the make_pizza function to keep the code working after an API change is the correct response). I'm also not sure why the author chose to make the function capable of handling an arbitrary number of arguments, or why after doing so he chose to incorrectly invoke it on a list.
- tmnvix 4y agoI think DRY is one of the first pieces of advice that many programmers come across. Taking it to heart as a beginner it has the advantage of encouraging you to sometimes stop and give a bit more thought to what you are actually doing. Is this essentially the same as what I'm doing over there? How is it different? Why is it different? Etc... As you gather experience you can recognise these patterns more easily and develop a stronger intuition for when you should pursue a DRY approach or just leave things as they are. You might even choose to make two things even more similar so that they feel more familiar (i.e. choosing to be even less DRY!)
- chpmrc 4y agoI feel like most of these articles would lose their click bait appeal if the title always included "when done wrong" at the end.
- albertTJames 4y agoI must say, that mediocre article lead to very interesting comments.
- undoware 4y agoMisses the most important reason. Repetition creates symmetrical cases. As they say in German, 'einmal ist keinmal' -- once is never. Overly DRY code is incredibly non-educational. Why? Because you learn by comparing and contrasting -- if you can't compare, you can't contrast, and therefore, you cannot learn.
- dragonwriter 4y ago> I suspect any developer reading this is aware of the DRY principle because it is just so ubiquitous. If not though, you just need to know that it stands for "Don't Repeat Yourself" and is generally invoked when advising people to not copy and paste snippets of code all over the place and instead consolidate logic into a central place. Well, no. What you actually need to know is the next layer out: DRY stands for Don't Repeat Yourself, sure, but Don’t Repeat Yourself isn't the rule, it's a short phrase that is supposed to be a memory cue for the principle “Every piece of knowledge must have a single, unambiguous, authoritative representation within a system”. If all you know is “Don’t Repeat Yourself”, you don't know the principle, and you can neither apply nor critique it. #1 is simply applying the memory cue as if it were the principle. Yeah, don't do that. #2 is, well, no, you only refactor to extract a bit of knowledge to a common place where it is immediately reused: there is no presumption of reusability, it is demonstrated. The specific example they use of how this might be done wrong is...so bad. Starting with: the example code that they suggest is bad design but works does not work. make_pizza([left_topping, right_topping]) gives args a length of 1, not 2, but their function definition relies on it having length 2, and using that to distinguish from the simple case.
- gcassie 4y agoThanks - I fixed this mistake. I originally was "solving" the problem by checking if the arg passed was a list of lists or a list of strings but I thought I'd get flamed even more for being a terrible developer with that solution. When you go read proper definitions of DRY they have lots of nuance that speak to many of my criticisms. But the reality is most developers are not encoding that nuance and using it as a fairly blunt instrument. I can't really prove it but at least some people in the comments seem to agree. So I guess I could say "DRY is misunderstood" - but if it's so easily misunderstood then maybe that's a shortcoming in and of itself?
- Swiffy0 4y agoWtf. If your use-case is that the user can select the crust, sauce, cheese and toppings for a pizza, just pass that shit to the make_pizza function with the help of enums and arrays. If you want to have predefined pizzas, you'd simply make a dictionary of pizza templates with all the options that the make_pizza function needs and/or if you wanna be fancy, you'd make a separate make_pizza_from_template function, but definitely not a make_pepperoni_pizza function, because that's just encoding data as a code in a silly way that arguably not even a factory pattern. No solution will be able to cater to requirements that don't exist at the time of developing this pizza-application. You build it according to the requirements that exist and that is enough. It's not your fault if nobody cared to mention that the user should be able to arbitrarily subdivide the pizza and select options sepatately for each subdivision - that's a feature update and it's OK if the original program hadn't though of that. Just like you wouldn't scaffold ecommerce capabilities into a webpage "just in case", if there had been zero mention of such a need.
- HelloNurse 4y agoIt's a confused abstraction level: there must be a database of pizza "templates" (consisting of named menu items and of the lower level of pricing rules and admissible choices of crust, topping, etc.); it must be separate from generic pizza processing because it is subject to change over time; and conversely pizza processing must work for any configuration of that database, without special cases. Mixing pizza database identifiers into generic pizza processing (e.g. make_ham_pizza) is wrong even without repetitions.
- ess3 4y ago> It's not your fault if nobody cared to mention that the user should be able to arbitrarily subdivide the pizza and select options sepatately for each subdivision - that's a feature update and it's OK if the original program hadn't though of that. I would argue it’s part of your job most of the time to challenge whatever needs are presented and ask questions about the long-term vision to find a good middle ground of future proofing vs over-engineering. That is of course one of the hardest things to get right.
- 4y ago
- strken 4y agoSurely you'd remove repetition by doing this instead: hawaiian_pizza = { crust: "thin", sauce: "tomato", cheese: "regular", toppings: ["ham", "pineapple"] } pepperoni_pizza = { crust: "thin", sauce: "tomato", cheese: "regular", toppings: ["pepperoni"] } def make_pizza(pizza): requests.post(PIZZA_URL, pizza) This isn't better just because it's DRY, it also keeps the data separate from code, which makes it usable elsewhere. Defining fifty different types of pizza inline inside functions is a strange choice, because it tightly couples your pizza definitions to your pizza-making. What if you want to answer a question like "how many thin crust pizzas do we sell?"
- n4r9 4y agoEven better IMO (although devolving into pseudo code): pizza_base = { crust: "thin", sauce: "tomato", cheese: "regular", toppings: [] } hawaiian_pizza = pizza_base { toppings: ["ham", "pineapple"] } pepperoni_pizza = pizza_base { toppings: ["pepperoni"] } def make_pizza(pizza): requests.post(PIZZA_URL, pizza)
- kitkat_new 4y agothis could(!) be coincidental code duplication
- dgb23 4y agoDon't do this! OP's version is better! It might be "fine", but you don't gain anything here while introducing both indirection and coupling. DRY is _not_ about data repetition. Data repetition is fine. Alice and Bob having the same birthday is coincidental. And even if they are actually twins, you rather say that they are twins separately. In your example you are just preserving keystrokes, but you don't say anything of value with 'pizza_base'. You haven't shown that 'pizza_base' is worth keeping track of or even mentioning. A pepperoni_pizza with thick crust or extra cheese is still a pepperoni_pizza. A hawaiian_pizza's sauce being tomato doesn't relate to a pepperoni_pizza's sauce. When coding data, just be explicit, verbose and keep it simple. Our text editors, IDEs and database APIs have affordances to change data in bulk. Those things are orders of magnitude easier if your data is simple, dumb and not complected.
- wolframhempel 4y agoI think the underlying meta principle here is: Don't be dogmatic in your following of principles. Make sensible choices for the use case at hand which might be informed by the spirit of principles, but don't treat them like some biblical commandment that has to be applied at all time.
- augustk 4y agoThe apprentice doesn't know about it. The journeyman uses it dogmatically. The master uses it thoughtfully.
- bsza 4y agoThe very first "refactor" is broken, the last line should be `make_pizza(["ham", "pineapple"])`. Which is actually very easy to notice if you actively watch out for repetitions. Ironically the author just demonstrated why DRY is a great principle.
- est 4y agothere isn't a one-size-fit-all DRY. If there's much gain by DRY, then refactor it with a factory or something. Otherwise, keep it intuitive and stupid.
- mooktakim 4y agoNew devs start with doing DRY everywhere. Over time they learn to be more thoughtful. Sometimes duplication is good. In my experience the priority should always be dev readability. If duplication helps you read the code better (as its not hidden away), thats fine.
- mjburgess 4y agoAs with any such article, it comes down to competence mistaken as principle. Principles are never, here, to blame. A cultish (inexperienced) belief in the sanctity of a principle is to blame. Excellence in programming is trading principles off each other based on the design constraints and expected changes. DRY trades off everything else in different ways depending on the language and problem. DRY in python should, often stop when you ask "should this be a metaclasss" but before "should this be a decorator", that's different than in C
- Joel_Mckay 4y agoAPI/framework complexity can often be minimized within reason, but given the code-template nature of pattern components it is unreasonable to expect optimization without incurring tightly coupled code/structures. For example, a project using a framework may only require a developer look at 4 small files to understand the functionality of a resource, and it acts as inline documentation to others on how to quickly contribute new features. In a way, through explicit separation of resources the “similar” code tends to differentiate rather quickly as use-cases rarely share the exact same context throughout the entire life-cycle of a program. The worst maintenance teams of popular projects permute an API definition every 6 months, and break existing production code in downstream works. You know, ironically still building that bug infested Ivory Tower everyone assumed they could avoid with grossly oversimplified acronyms ( https://en.wikipedia.org/wiki/Ivory_tower https://en.wikipedia.org/wiki/Ivory_tower ). ;-)
- menaerus 4y ago> Now we are talking about all kinds of fancy programming stuff to try to solve problems that only exist because we don't want to repeat the same 6 line snippet in a handful of different places because DRY tells us that's bad. Oooh yeaah ... I have had that exact argument in the recent code review where patch literally modified _hundreds_ of LoC across many different files just to avoid duplicating a simple 10-liner at a single place? Yeah, you read that right. A developer basically rewrote half of the existing code architecture and applied "best" OOP practices. Intention wasn't a bad one but it is unnecessary to say how incomprehensible code would have become if that patch went in in its original form. It was hard to argue against it and what would have been a 10-minute work it became a 2 or 3 week long discussion. And that is just ... bad.
- kstenerud 4y agoDRY absolutely can get this ugly and messy (I've seen it many times), but I believe that this is largely an experience problem. You have many tiers of DRY knowledge: - Have heard of it and it sounds like a good idea - Let's DRY everywhere! - OK, maybe don't DRY everywhere... - There are multiple ways to implement DRY, and it all depends on circumstance Taking the article example, a better approach would be to DRY the data first (after discovering that in your organization the most common pizza is thin crust with tomato sauce and regular cheese): val STANDARD_PIZZA = { crust: "thin", sauce: "tomato", cheese: "regular", } val TOPPINGS_PEPPERONI = ["pepperoni"] val TOPPINGS_HAWAIIAN = ["pepperoni", "pineapple"] def make_pizza(design): requests.post(PIZZA_URL, design) def make_standard_pizza(toppings): make_pizza(STANDARD_PIZZA + {toppings: toppings}) Now it's easy to use with no repetition: make_standard_pizza(TOPPINGS_PEPPERONI) make_standard_pizza(TOPPINGS_HAWAIIAN) make_standard_pizza(["pepperoni", "ground beef", "olives", "feta cheese"]) You can easily add to it: val TOPPINGS_VEGETARIAN = ["green peppers", "tomato", "spinach"] Then when you need to expand for half-and-half: def make_standard_half_and_half(left_toppings, right_toppings): make_pizza(STANDARD_PIZZA + {left_toppings: left_toppings, right_toppings: right_toppings}) make_standard_half_and_half(TOPPINGS_HAWAIIAN, TOPPINGS_VEGETARIAN) This gives you both low level and high level (convenience) interfaces to pizza generation, with none of the silly class complexity or function explosion.
- goodpoint 4y agoThat's quite ugly.
- MaxMoney 4y agoI wouldn't recommend naming your function with a suffix that relates to a model. You are better off doing: def generate_payload(crust, sauce, cheese, toppings):
- Ideabile 4y agoThanks for this article, I was just talking with my colleagues about it. And didn't find something simple to share with them, so this was just what I needed. I think DRY is a good thing in some cases, but you should careful consider when something is worth to DRY and when rather WET gives you the best tradeoff for isolation. My metrics to decide is to stick in favour of the Single Responsibility Principle. If DRY means compromising it, most likely is not worth it.
- locallost 4y agoDespite the backlash in the comments, I have to say I agree with the article. I realized eventually that it's our job to produce solutions, and not write code. Engineering (at least for software) is about making a computer do something novel, or if not that then making easy to adapt. Creating code that is nice and elegant, DRY, is about engineering code, but not engineering a solution. If it helps make things easier then sure, but despite the simplicity of the examples I think they demonstrate what everyone has seen -- this one nice to have function or whatever turning really ugly from trying to handle all of the edge cases. At that point it becomes nice to not have, but it's too late. Also, I liked the comment from ihateolives somewhere in the thread a lot.
- scanr 4y agoPremature generalisation is the second cousin of all evil. The problem with DRY is that the cost of it being wrong in the future often isn’t accounted for. Copy, paste, search, replace is underrated. That said, it’s a balancing act. The right generalisations are great.
- mmis1000 4y agoSearch, replace it with a single call to unified method when there are more than enough repetitions in the code. Probably people just call it 'Refactor'. The requirement changes and you refactor it to meet requirement. And that's all you need to do to avoid premature generalisation.
- nextlevelwizard 4y agoReading the code I get a feeling author doesn't quite know how to write code.
- skatanski 4y agoAs anything it is just a tool in our toolbelt and should be used carefully. If our system contains same user validation in 2 places, changing it in 1 place may lead to issues, which are difficult to discover. However forcefully implementing DRY everywhere can lead to coupling and lack of separation between modules and influence deployments, and work of different teams. Its more difficult to build context of the implementation, if one needs to jump from file to file. There’s a balance to when to use it or not.
- deleted 4y ago[deleted]
- Bost 4y ago> same user validation in 2 places, changing it in 1 place may lead to issues, which are difficult to discover. Disagree. Quicker discovering issues difficult to discover is actually a good thing. > Its more difficult to build context of the implementation, if one needs to jump from file to file. Agree. In general, I personally look at DRY as "It takes time to implement and/or understand it, but when it's done then it works and it will last." "It takes time" is something your boss won't like, but that's (mostly) not an issue if working on open source SW. No boss -> no pressure -> higher quality.
- usrbinbash 4y ago> "All these ideas are great. But remember that the fundamental goal here, is to send a POST request with a single JSON object." This. A million times this. IMO, the single most important principle is still "Keep It simple when you can, make it complex when you have to." A system that is simple can be grokked quickly, meaning it can be debugged quickly, modified quickly, new developers can be onboarded quickly,... Yes complex systems have to exist. Some tasks are complex, and require complex solutions. BUT: Complexity should come into play when it is necessary. It is perfectly okay to design simple solutions for simple tasks. Yes, sometimes this means ignoring things like DRY.
- Aeolun 4y agoI dunno, the only thing I came away with here is that the author either doesn’t know how to write decent code, or deliberately obfuscates the problem by writing obtuse stuff.
- idealmedtech 4y agoThe biggest issue with DRY is commitment to a bad abstraction just for the sake of not copy-pasting some code. Abstractions should be liquid while you're figuring out the best way to model your problem, and DRY can often be a culprit in having a model that's a bit too rigid. Obviously YMMV.
- jmartrican 4y agoTo help figure out when to DRY or leave wet i like to use the rule of 3. Where a piece of code needs to be repeated 3 times before it is DRYd up. You can adjust this number from 3 to whatever you would like. What I really like about rule of 3, vs rule of 2, is that it allows more time to go by that may lead to the two pieces of code no longer being identical as requirements change. Which would either remove the need for the abstraction or allow for a more accurate abstraction.
- kitkat_new 4y ago> Which would either remove the need for the abstraction or allow for a more accurate abstraction. or indicate a bug or nothing, because the differences aren't in logic, but e.g. in variable names. ^ perhaps voluntarily or, because happen to not find the duplication, or not search it in the first place. The more time passes by the higher the risk.
- submeta 4y agoHave you ever seen the complete opposite? non-DRY all over the place? No variables or constants defined, same values manually inserted all over the code? Or same code with slight differences duplicated all over the place. - When you see that, you'll realize how useful the DRY principle is.
- lakomen 4y agoDRY has the same idea as modules. Write once and re-use it. This article is the result of someone exploring a new idea and cussing it out because he has to change his ways. I've been there countless times. DRY is not overrated. DRY is a time saver in the long run. Why is this even on top of the 1st page, how new are you to development anyway? Sorry but I'm really getting pissed off by people wasting my time with useless articles lately. It's getting out of hand.
- k__ 4y agoI fondly remember the Ember.js docs. They were awesome, but all the code examples where DRY to the max, it was quite funny.
- mrcartmeneses 4y agoIf I’m not mistaken the original point of dry is to not repeat data/state, rather than not repeating code. Being dry about state is actually really useful (essential!), where state can be derived it should be, rather than stored as a new variable. Being dry about code is often less useful in my day to day coding, but I’m not a library designer I just make apps /2c
- liampulles 4y agoI've tended to approach refactoring common functionality based on whether two pieces of code are either "coincidentally" the same or "intrinsically" the same. If code is coincidentally the same, then you should leave it alone - the two pieces of code are likely to evolve independently and trying to make a common function/class handle two separate usecases is likely to lead to complex, ugly code. Conversely, if the two pieces of code are intrinsically the same then you SHOULD pull them out into something common. If you don't, you risk the implementations drifting and getting inconsistent behaviour over time. Determining which is which is a matter of interrogating your domain and business logic, which is the essential function of our job as developers/engineers.
- douglaswlance 4y agoJust set intelligent defaults with the ability to override with customizations. Bam. It's DRY and you don't lose flexibility.
- harryvederci 4y agoSame thing without the clickbait title: https://ahungry.com/blog/2020-11-17-Write-Code-Like-You-Write-a-Recipe.html https://ahungry.com/blog/2020-11-17-Write-Code-Like-You-Writ...
- mjw_byrne 4y agoIronically, the second example has a copy-and-paste bug: def make_pepperoni_pizza(): make_pizza(["pepperoni"]) def make_hawaiian_pizza(): make_pizza(["pepperoni"])
- deleted 4y ago[deleted]
- neilwilson 4y agoPerhaps the problem is that we've stopped teaching people about coupling and cohesion, along with the mechanism of stepwise refinement. We abstract to functions to reduce cognitive load and to allow scope rules and information hiding within the language to prevent local variables becoming pseudo globals. The whole premise of the 'goto consider harmful' structured programming movement was to allow us to replace control structures with a single black box consisting of input-process-output, which aided in reasoning. The premise was to construct the program from cohesive functions that are lightly coupled. When did we move away from that?
- spacemanmatt 4y agoThis article is just a rant against what the author sees as popular-but-wrong justifications for DRY, while failing to mention any of the good reasons. -1
- jleyank 4y agoTl;dr with the comments but I didn’t see “debugging” or “maintenance” showing up. Collecting things, which I assume is an aspect of DRY, makes it less risky - the change or fix can be applied once rather than hoping all of the instances in the code were addressed (correctly). Developer time is precious no matter how many of them you have. Given a sensible design, you can always tune hotspots. Can’t speed up debugging and brittle or unclear code means you’ll be doing more of it. And if it’s “just going to be used once” who really care how it’s written other then “quickly and correctly”? And sadly, too many things aren’t just used once.
- DonHopkins 4y agoPizza Cost Optimization Dark Pattern Programming Example: Here is the pizza cost optimizer from Pizzatool, written in object oriented NeWS PostScript, which checks all of the pre-defined base pizza styles and selects the "best" combination of style + extra toppings, ostensibly to save the user some money. It's actually a dark pattern, because it's biased towards selecting higher level pizzas instead of the least expensive pizza. But at least the dark pattern is documented: "Figure out the cost of the pizza, were we to order it as this style, and remember the style as the best match if it pleases us. The definition of pleasing us is biased towards matching higher level complex pizza styles, rather than economical lower level pizzas with extra toppings. This is the kick-back to Tony&Alba's for all that free beer." The Story of Sun Microsystems PizzaTool How I accidentally ordered my first pizza over the internet: https://medium.com/@donhopkins/the-story-of-sun-microsystems-pizzatool-2a7992b4c797 https://medium.com/@donhopkins/the-story-of-sun-microsystems... Tony and Alba's Pizza and Pasta, Mountain View: https://www.yelp.com/biz/tony-and-albas-pizza-and-pasta-mountain-view https://www.yelp.com/biz/tony-and-albas-pizza-and-pasta-moun... PizzaTool Source Code: https://www.donhopkins.com/home/archive/NeWS/pizzatool.txt https://www.donhopkins.com/home/archive/NeWS/pizzatool.txt % Calculate the cost of this pizza. % /updatecost { % - => - 10 dict begin % localdict /TheBest /defaultstyle ClassStyle send def /TheStyle null def /TheTopping null def /TheBestCost 99 def /TheBestExtras 0 def % For each and every pizza style in the universe: /styles ClassStyle send { % forall: % style /TheStyle exch def % % Ask this style for its list of standard toppings. /TheToppings /toppings TheStyle send def % Is every topping from this style on our pizza? true % true TheToppings { % forall: % true topping Toppings exch arraycontains? not { % if: % true % Oops, this topping's not on the pizza. No dice. pop false exit % false } if % true } forall % true|false { % if: all the toppings of the style were on our pizza: % % Make an array of our pizza toppings that aren't in the style. /ExtraToppings [ Toppings { % ... topping % Is this topping included in the style? Then toss it. TheToppings 1 index arraycontains? { % ... topping pop % ... } if } forall ] store % % Figure out the cost of the pizza, % were we to order it as this style, % and remember the style as the best match if it pleases us. % The definition of pleasing us is biased towards matching % higher level complex pizza styles, rather than economical % lower level pizzas with extra toppings. % This is the kick-back to Tony&Alba's for all that free beer. PizzaSize /pizzasizeindex self send % sizeindex ExtraToppings length % sizeindex extras /extraprice TheStyle send % $ dup % $ $ ExtraToppings length % $ extras /extras TheStyle send sub % $ $ extras' 1 le { .9 mul } if % $ biased$ TheBestCost le { % ifelse: % $ % Hey this is the best match so far, let's not forget it! /TheBestCost exch store % /TheBest TheStyle store /TheBestExtras ExtraToppings length /extras TheBest send sub store } { pop } ifelse % } if % } forall % % Set the window footers of the pizza topping panel. % The left footer displays the name of the pizza style, % and the right footer displays a message % telling the user to choose more toppings, % or the number of extra toppings, % or nothing at all. TheBestExtras dup 0 lt { % ifelse: % extras neg dup 1 eq { () } { (s) } ifelse % extras (plural?) exch (Choose % more topping%!) sprintf % (message) } { % else: % extras dup 0 ne { % ifelse: dup 1 eq { () } { (s) } ifelse % extras (plural?) exch (With % extra topping%.) sprintf % (message) } { % else: % extras pop nullstring % () } ifelse } ifelse % (left footer) /name TheBest send exch % (left) (right) /setfooter ToppingWindow send % % Remember the price of this pizza in dollars rounded to cents, % and calculate its string value. TheBestCost % $ Fraction mul 100 mul round 100 div /Price 1 index store dup 100 mul round cvi 100 mod % $ cents exch floor cvi % cents dollars 1 index 10 lt { (%.0%) } { (%.%) } ifelse % cents dollars fmt sprintf % (price) % Set the value of the costfield and totalfield labels to % the price string. dup /setvalue costfield send /setvalue totalfield send % % Set the value of the stylevalue label to the name of the best style, % and set the stylemenu value to the index of that name in the list of % pizza styles. (The stylemenu is an exclusive settings menu.) /name TheBest send % name dup /setvalue stylevalue send PizzaStyleNames exch arrayindex { % index [exch] /setvalue stylemenu send % } if % % Remember the best match pizza style. /Style TheBest store end % localdict } def
- layer8 4y agoA better formulation of DRY is SPOT (Single Point Of Truth). Definitions (code, data) that represent the same “truth”, i.e. when one changes all have to change to represent a consistent truth, should be reduced to a single definition. For example, if there is a rule that pizzas need at least one topping, there should only be a single place where that condition is expressed, so that when the rule changes, it isn’t just changed in one place but not the others. Another example is when fixing a bug, you don’t want to have to fix it in multiple places (or, more likely, neglect to fix it in the other places).
- 12thwonder 4y agoI like this. It is very hard to find out if the definition already exists or not in the codebase. This can lead to multiple definitions of the same thing or the truth. anyone has a good way to deal with this?
- waynesonfire 4y agoIt's interesting to note that this principal doesn't just need to apply at a low level, e.g. code. It continues to add value when designing application architecture. Or, can be used to help refine features.
- layer8 4y agoIf the codebase isn’t a total mess, one should be able to guess which components or code paths have to deal with a given truth by virtue of their purpose/function. Then one can investigate the code paths in question to find out where exactly the existing code is dealing with the respective thing. It should be an automatic thought when implementing some logic to think about which other parts of the system need to be consistent with that logic, and then try to couple them in a way that will prevent them from inadvertently diverging and becoming inconsistent in the future. In terms of software design, a more general way to think about this is that stuff that (necessarily) changes together (is strongly coupled) should be placed together (have high cohesion).
- Jabihjo 4y agoUnfortunately the issue of lacking a single point of truth is exacerbated the more people who work on a project. I believe the issue in spreading around logic comes from not knowing the original intention, and asking the original authors is, IMO, the best way to fix something or add new features. Obviously knowing the original authors is not always possible, so I try to follow existing patterns.
- captainmuon 4y agoEvery time I read an article like this, "why <often cited best practice> is overrated", I think, yeah you are right in theory. But most places I have worked, these best practices were not overused, but underused. If you have the problem that your coworkers create unneccessary abstractions, I envy you, because I have so often had the opposite problem. Maybe this is not the case if you work in a great software development team. But if you work somewhere where they do software development on the side (science, hardware, etc..) it is the main issue. People not able to factor out functions or structure their code in a readable way. Variables are called v1, v2, v3. Unit testing seen as a waste of time. CI seen as a fun toy. They lack the experience to even notice the difference. Maybe I'm becoming a curmudgeon, but I think many people would be well served by just googling "<my programming language> best practices", learning the acronyms like DRY, and just following them. And when you have gained some experience, sure, then you should question the wisdom and not follow it blindly.
- saltsucker 4y agoI am a CS graduate working at a top HW company and you could not have stated it more perfectly. The code I have to work with given to me written by HW engineers is pretty brutal. Talking 1k+ lines of code with 5+ nested conditions on the reg. Ofc, no unit tests. Blows my mind.
- QuadmasterXLII 4y agoIn my workspace, the issue isn’t that junior developers consistently DRY too much or too little, instead they make dramatic mistakes in both directions. However, the code that repeats itself unnecessarily is way, way easier to fix than the code that tangles itself up like the left pineapple example.
- davewritescode 4y agoYour problem isn’t DRY, your problem is that you don’t have people advocating for very basic best practices. You can’t adopt DRY or SPOT or anything else if you aren’t free to refactor and you aren’t free to refactor without some tests.
- codesnik 4y ago
- rhdunn 4y agoThe general approach is to refactor/generalize when creating a third version of something. With the first thing, you don't know if it needs any common functionality or what an appropriate abstraction will look like. With the second thing, you have some similarities but not enough information to know where the abstractions should be -- here, repeating yourself is OK. With the third thing, you should have enough information to work out where generalizations should be. Even then, only generalize what you need to at the time. Going overboard can add unnecessary complexity, so it is generally a good idea to be conservative in what you generalize. As you add more things, you can refine and evolve the system as needed. At this time you should have a better understanding of the system and what parts can be shared and generalized.
- plibither8 4y agoOne article advocating against unnecessary abstractions that I really appreciate and highly recommend is Dan Abramov's: https://overreacted.io/goodbye-clean-code/ https://overreacted.io/goodbye-clean-code/
- papito 4y agoThat's just justification for unnecessarily using microservices, which basically leads to 75% of your code being redundant plumbing. Microservices (correction: distributed systems) purposefully throw away DRY, small cohesive teams, developer ergonomics, and streamlined debugging, in favor of solving hard problems at scale, which most companies simply don't have.
- st-keller 4y ago> It's also probably one of the simplest principles to understand. Turn‘s out - no it isn‘t! I think DRY and KISS are probably the most important and most misunderstood principles by far. Why? Because they seem trivial at first sight, but really are not. Not every repetition should be DRYed (dry those which pose a risk to integrity) and „simple“ is not the same as „easy“ or „familiar“!
- crabbygrabby 4y agoThere's nothing wrong with DRY. It's a concept not a law. I once saw some VB6 code that was 30k lines of copy pasted if/then statements. DRY would have reduced this to about 500 lines of highly readable code. Are there cases where you by design don't want to follow DRY yea... There are... But it's not a useless principal
- catchclose8919 4y agoTL/DR: It's a naive "sour grapes" type argument that doesn't take any kind of costs/tradeoffs into consideration... Proper DRY at scale requires types that make sense and are easy to think about (you have to invent and document them even in dynamic langs ...that's why Typescript's a thing and so sucessful). You can't have DRY that doesn't slow you down and cause bugs without proper f types! Eg. a sane solution to the authors' problem when the requirement for split pizza came would be: - rename make_pizza(toppings: dict) to make_pizza_part(topings: dict) - implement a new make_pizza(topping: list[dict]) calling make_pizza_part(toppings: dict) - and here you've change the type (important!), so you'll not miss any unchanged all calls to it, your tools will yell at you (ideally at build/compile/commit), or at worst at runtime but with an easy to interpret even from logs error DRY is fine if done in the context of proper software engineering practices and tools. Now being non-sloppy and following solid practices has a cost, and you might want to avoid it sometimes - in those cases do less DRY rather than crappy DRY! (Whole languages are built around the OP's philosophy, eg. Go, but they are explicitly engineered to lower cost and defects in large corporate orgs! Randomly choosing to follow this in a project with 1-3 people of adequate skills and limited scope will just unnecessarily make that project have 4x the code, 4x the bugs, and 4x the cost for zero benefit.)
- ChicagoDave 4y agoA simple lesson on functional intent, variants and invariants, doesn’t need to be some overarching development management principle like DRY. Let’s just teach people how to write clean, readable, loosely-coupled code.
- greenthrow 4y agoThis post is not very good. DRY is a really solid principle (pun intended.) If you have to define the payload schema for an API yourself (i.e. the API provider doesn't supply a library for you) then you really should define that in one and only one place. It doesn't matter thst today you only want a "handful" of hard coded JSON dicts. That path quickly leads to so many headaches and run time errors. Implementing the API schema in one and only one place means you limit the sources of bugs for all code that deals with it. You only have to test that functionality in the one place it is defined, if it changes you don't jave to chase down hard coded JSON all over your codebase, etc This post basically amounts to "I want to write lazy bad code and DRY tells me not to."
- Pr0ject217 4y agoI'm interested to know more about what you mean in this context by "define the payload schema for the API yourself". Can you provide an example?
- ZeroGravitas 4y agoI think you could make a decent case for DRY being the only principle. If there's something getting in the way of DRY it's probably the biggest problem you have. However, that doesn't mean you have the ability or control to fix it completely in the short term, but the arc of history bends towards DRY I think. In this specific article, the first example seems better the DRY way to me. The author seems to suggest that rewriting it later is worse than repeating the logic everywhere, which sounds very fragile. If you can't confidently rewrite an API later than you're doomed to repeat yourself until it all collapses. You could make a reasonable argument that the developer who fulfils the short term requirements and has found a new job by the time it all collapses would have a more lucrative career, but I doubt you could argue it's better software.
- civilized 4y agoWe could move away from prescriptive "programming principles" and towards ideas that empower people to use their own judgment. Instead of "Don't Repeat Yourself", it could be "You Don't Have to Repeat Yourself". Now I know what I'm getting myself into here. Most people hate making their own choices and love to blindly follow simple prescriptive rules which are known by Experts to produce Good Results. But when the religious approach isn't working for you, maybe it's time to stop making your occupation a religion.
- timcavel 4y ago
- astura 4y agoUhhhh... Anyone who says DRY is overrated has never worked on someone else's codebase that didn't follow attempt to reduce repetition. What a waste of a click.
- fareesh 4y agoDRY requires some judgment If you have two features that have N parts in common, and you are certain that they will never diverge for feature-specific customization or special cases, then DRY is probably a good idea If they may diverge at some point, then structuring the code in anticipation of that divergence is a good idea, else you end up with a messy DRY implementation that inevitably has to fork
- hernantz 4y agoDRY often leads to too much abstraction that while correct from the theory of software engineering, it increases complexity and maintainability costs. That being said, I always allow myself to repeat code until a good enough patter emerges from that repetition, and then I refactor. Having the same code twice is not always sufficient to reveal what is the right refactor to do, if any.
- osigurdson 4y agoThe "single responsibility principle" has caused a lot of damage as well. SOLID in general is somewhat dubious.
- wizofaus 4y agoSurely I'm not the only one to notice the first example refactoring is wrong, in a strangely ironic fashion too...(but too lazy to contact author)
- AtNightWeCode 4y agoDRY is mostly a good thing. What complicates stuff at companies is often coupling and dependencies though. Sometimes it is way faster and better to do a bit of copying just for the sake of removing coupling. This is largely due to the short comings of programming langs, packaging tools, CI/CD and source control.
- megraf 4y agoI tend to start DRYing up my code after five usages. I think it's a balance that has kept me away from the unnecessary complexity, although the Gateway drug is real- I catch myself wanting to DRY early now and then
- ralmidani 4y agoWhen I hear “DRY”, I think of Django with its Models, ModelForms, etc., not enterprise Java where every piece of functionality is hidden behind 15 levels of indirection.
- throwaway787544 4y agoIf I can be a little too honest here, I will admit that I just don't like making programs that are too simple. I can make an extremely simple program... But it doesn't feel good. I know there will be limitations to that simplicity, and I want to make functions that do things, and combine those functions, and let them override things, and pass state, transform state, etc. Making it complex just feels better. I actually stand outside my body and watch myself make it more complex, and think, "Ugh, this is more complex than it needs to be, I should make this simpler. But I don't want to." I continue and hope that a refactor will make it less embarrassingly complicated.
- trey-jones 4y agoThis article is fine, though the headline is misleading. In the end, the author still seems to believe that DRY should be the rule, not the exception, and I agree with that.
- Pr0ject217 4y agoWhat about something like this (in JS)? // Option 1 const Pizzas = { Hawaiian: { type: 'hawaiian', crust: 'thin', sauce: 'tomato', cheese: 'regular', toppings: ['ham', 'pineapple'], }, Pepperoni: { type: 'pepperoni', crust: 'thin', sauce: 'tomato', cheese: 'regular', toppings: ['pepperoni'], }, }; // Option 2 // Pizzas could be returned from an API, so that the pizza types are configurable outside of the code const response = [ { type: 'hawaiian', crust: 'thin', sauce: 'tomato', cheese: 'regular', toppings: ['ham', 'pineapple'], }, { type: 'pepperoni', crust: 'thin', sauce: 'tomato', cheese: 'regular', toppings: ['pepperoni'], }, ]; const makePizza = (pizza) => requests.post(PIZZA_URL, pizza); // Then, for Option 1 makePizza(Pizzas.Hawaiian); makePizza(Pizzas.Pepperoni); // Or, for Option 2 (e.g. user selected via a menu) makePizza(selectedPizza);
- sjducb 4y agoThat's very clever, but the OP's point is that you shouldn't try to be clever. Your life will be easier if the code is simple. When I read the initial example I can understand it in the time taken to read the code. With your example I had to think for about 1-2 min before it made sense. If the codebase is full of clever stuff then I have to spend hours understanding all of the clever things before I can make changes. If everything is simple then it's easy to change. If you want to see where overengineering leads you then take a look at this project. https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpriseEdition https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpris... It is satire but I have absolutely worked in places that write code like that. Good programmers know that it's 10x times harder to read code than write it, so they deliberately keep it simple so that they can read it later.
- Pr0ject217 4y agoThank you for your response. Would you mind clarifying what is clever about it? Thank you very much.
- gwbas1c 4y ago> All these ideas are great. But remember that the fundamental goal here, is to send a POST request with a single JSON object. That is a very, very simple thing to do. Now we are talking about all kinds of fancy programming stuff to try to solve problems that only exist because we don't want to repeat the same 6 line snippet in a handful of different places because DRY tells us that's bad. Yes, that is a very common beginner mistake. Sometimes a little copy-pasta is needed to avoid over-complicating what needs to be simple. Where DRY is really important are things like: - Hey, you seem to be using "foo" and "bar" all over the place. Put those strings in constants. - Hey, you're using magic numbers all over the place. Use an enum (or constants depending on your language / situation.) - Wow, you copied and pasted that logic all over the place. Now when we need to make a change we have to make it in 20 spots. That should be encapsulated in a function / method / object - (And to get closer to home) Even though you just want to "send a POST request with a single JSON object," we have a common session management pattern and error handling pattern in our application to deal with this API. That particular pattern should be encapsulated so you aren't repeating it for every #%$#@ API request.
- amw-zero 4y agoThe article is actually good. The criticisms of DRY here are valid! For criticism 1, I had a coworker once say something that resonated with me: "Just because two things are the same right now doesn't mean they _should_ be the same." So that criticism is totally valid - DRY has to be applied only when things _should_ be the same, and that can actually be hard to identify. That being said, of course the title of the article is bad and not accurate. DRY is essential. I don't think there's many people that actually argue against it. If you have a piece of business logic that's essential to the business and it influences other pieces of logic, they all have to refer to the same definition. Repeating it is bad for everyone - users will see inconsistent behavior, and devs will have to "remember" (read: never actually remember) to update important logic in multiple places. Important things should have a single source of truth. That seems inarguable to me. It can be hard to find a design that actually achieves that. That's not DRY's fault.
- HelloNurse 4y agoNo, this article isn't good because it discusses alternative options within the boundaries of seriously wrong premises (write nonsensical hardcoded recipes "right"), and unsurprisingly all the options are bad.
- V-2 4y agoValid points, but it doesn't make DRY overrated. Just abused / taken too far if one is overeager. That's true of any principle if you're being dogmatic about it and start treating it as a goal in and of itself (rather than as means towards a goal). This, to me, fits the typical "X considered harmful" (headline) / "X considered harmful when done badly" (actual content) template.
- agentultra 4y agoIt's worth knowing when it's useful to apply. Needlessly duplicating code also creates another kind of complexity: large surface areas of change that need to be updated in tandem. If you get changes where you have to change multiple places at once in the same way it's a good sign you need to do some refactoring before someone accidentally introduces an error into the program.
- jjice 4y agoA Philosophy of Software Design by John Ousterhout goes into some of these ideas a bit. To me, the book takes an approach that is somewhat contradictory to Clean Code (a bible to many), but in a rational and well explained way. Lots of talk about over abstraction which can end up complicating code reading in the long run. An idea I've seen a lot here on HN is that DRY is good with a baseline number of reuse. If we see the same pattern twice, maybe it's not a good abstraction since we haven't seen it grow yet. If we see that same pattern 15 times, I think we know an abstraction is handy here.
- towaway15463 4y agoI believe that’s called WET (Write Everything Twice). It’s a useful reminder to not be dogmatic about DRY.
- jkingsbery 4y ago> Now we are talking about all kinds of fancy programming stuff to try to solve problems that only exist because we don't want to repeat the same 6 line snippet in a handful of different places because DRY tells us that's bad. Introducing unnecessary complexity is, by definition, unnecessary. But we shouldn't be introducing complexity because DRY tells us. We should introduce complexity - some, but not more than needed - because some day a developer will know to update 2 of these 6 line snippets, but won't know about the third.
- swader999 4y agoI'd argue that over using inheritance has more dire effects than going full hog on dry.
- kybernetyk 4y agoI prefer YAGNI. http://c2.com/xp/YouArentGonnaNeedIt.html http://c2.com/xp/YouArentGonnaNeedIt.html
- recroad 4y ago> DRY creates a presumption of reusability I stopped reading there. DRY creates maintainability, not necessarily reusability.
- redleggedfrog 4y agoUh, no. Like your about to have to endure a discussion at the Lead Developers desk no. "Copying and pasting a few lines of code takes almost zero thought and no time"...and you're fucked. Pardon my French but it's warranted. Been there, done that, now know better. Firstly software development is a thinky sport and the moment you're cut-n-pasting code while not thinking you're exhibiting risky behavior. Here come the bugs. Guess what happens next: senior devs are busy, simple bugfix is assigned to junior or farmed out to contractor. They fix just one of the cut-n-pasted routines and call it a day. Then you play a few iterations of the PR to test failure game or you ship a bug. I see this all the time. I saw it yesterday. After it fails the tests enough and I get the PR I have them DRY that code up. This is especially important if you've inherited a crappy code-base with lots of duplicate code. We have a rule that if you touch it you DRY it. Never had that rule not serve us well. Getting people outside the core team to stick to it is work, but that's a different oroblem.
- RajT88 4y agoNow. I am not a 10x developer. But this concern here with writing a make_pizza() function: > The problem is that these two pizzas just happen to have the same crust, sauce and cheese. Had we started out with two pizza types that have different crust/sauce/cheese, we never would have made this refactor. You can solve for this by adding parameters with default values for all of those things. Use the defaults for the most common use cases, but of course for pizzas with different crusts and such, you can override the default. Not all languages support default parameter values, it's true. And of course, there is a level of complexity at which this breaks down.
- Clubber 4y agoDRY is about code, not data. If you see two methods do the same thing somewhere in their code blocks, separate the duplicate logic into a separate method, then call that method when you need to execute that logic. The reason for this is maintainability. Say that logic needs to be changed. If you didn't break it out into a callable method, you'd have to find all the places you use that logic and change it. If it is a callable method, you only have to change the logic in one place, thus DRY. https://en.wikipedia.org/wiki/Don%27t_repeat_yourself https://en.wikipedia.org/wiki/Don%27t_repeat_yourself Here is an overly simplistic example: If you see this scattered around your code: var formattedName = firstName + " " + lastName; Create this method and call it when you need it: string FormatName(string firstName, string lastName) { return firstName + " " + lastName; } var formattedName = FormatName(firstName, lastName); When business decides it wants to change name formatting from "firstName lastName" to "lastName, firstName", you only have to change the logic in the FormatName method because you "didn't repeat yourself."
- dragonwriter 4y ago> DRY is about code, not data. No, it's about representation of information in a system, as your own Wikipedia link states right up at the top of the second paragraph. Code and data are both forms in which information may be represented ina a system.
- Clubber 4y ago>No, it's about representation of information in a system, as your own Wikipedia link states right up at the top of the second paragraph. That's a little too broad to be useful, IMO. I learned the DRY principle before that book came out, and it was strictly about not repeating yourself in code. You can apply it to other things, but it was originally code. Normal form is a good example of applying DRY to RDBMS systems/schemas. "Single source of truth," is a good example of applying DRY to separate database systems. None of those were considered part of the DRY principle when I was starting out. >They apply it quite broadly to include "database schemas, test plans, the build system, even documentation". The article even hints those particular authors expanded on it beyond its original intent. They certainly didn't invent it.
- peter_retief 4y agoI have wondered if we sometimes sacrifice readability for DRY.
- ParetoOptimal 4y agoWhat does everyone think about this argument for functional languages especially with regards to parametricity in languages like Haskell?
- the__alchemist 4y agoThis article's conclusion and headline is shallow. The example is a good one about where (depending on context), adding abstraction to reduce repetition might add complexity. Great point! Repetition is fine in reasons like that. It does not invalidate DRY concerns in general! For example, an important reason to avoid repetition is that it adds maintenance inertia. Where a setup that a single-location change is easy to experiment with and improve, one that's repeated several places becomes tougher to change. Ie, friction. I would argue this is also adding complexity - what the author seeks to avoid. You could imagine contexts for the pizza example where the repetition doesn't make sense, and a refactor could make things easier. You can't tell alone from the snippet. From the headline and concluding paragraph, it feels like a straw-man. eg overrated, and: > "Well, obviously I'm not saying we should throw DRY completely out the window. I'm not sure it would actually be possible to write code that "never doesn't repeat itself". But I do think we should tone down knee jerk reactions to PRs that contain several repetitions of a block of code. There are at least a few cases where that might be the exact right thing to do."
- djhaskin987 4y agoI often find that DRY is in conflict with Conway's law[1]. It's almost always better to let Conway's law win. If I help write a build script for one team, I often copy and paste it into another's git repo to instead of trying to share it. It's often way better than factoring it poorly[2] or getting the two teams to coordinate on changes they need to make to it. Best to let the two copies diverge in that case. 1: https://www.wingolog.org/archives/2015/11/09/embracing-conways-law https://www.wingolog.org/archives/2015/11/09/embracing-conwa... 2: https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction
- atx42 4y agoI'll claim the only universal truth in programming as in anything is: "moderation", and it's corollary, "there is no silver bullet". They are all rules of thumb, and knowing when to apply them is the most important aspect of rules. In this case, unit tests are a very reasonable place to "repeat yourself", so you don't have to figure out what it's actually doing. Seeing the code all in place makes life easier.
- pkrumins 4y agoThe only programming principle you should be using is getting things done as fast as possible and shipping to production.
- walterburns 4y agoDRY is about readability. If it makes the system more readable, DRY. If not, don't.
- walterburns 4y agoDRY is about readability. If DRY makes the system more readable, do it. If not, don't.
- gspencley 4y agoTranslation: I took DRY in isolation, never learned larger architectural concepts like SOLID, things that change together live together, loose coupling. Didn't really study up on code smells and their solutions. Didn't learn design patterns and the problems they are designed to solve. Then I ran into trouble and now I blame DRY. The point of DRY is that when you need to change something, you should only have to change it once (a common code smell that comes about when not employing DRY is "Shotgun Surgery"). If the rest of your architecture is broken, DRY is not going to magically save it. That should be obvious.
- dan_83 4y agoLooks like a perfect case for a builder pattern, that way you can support sensible defaults in just about every language. default_pizza() .with_crust(Crust::Cheesy) .with_sauce(Sauce::Garlic) .add_topping(Topping::ExtraCheese) .cook() I'm not going to weigh in on the DRY stuff because it's being discussed to death. I just liked thinking about how I would approach this problem.
- dkottow 4y agoDRY is mostly fine in code, but does not hold for data. There, inmutability and audit trails win over DRY, in my opinion.
- goto11 4y agoA single source of truth is even more critical for data. It is the principle behind normalization.
- whatsakandr 4y agoHe doesn't address the problem of in a large project, sometimes it's better to copy and paste a function to avoid the extra library dependency. DRY and over coupling are two forces that must be balanced by the engineer. Dry is more important at small scales, over coupling is more important at large scales.
- atx42 4y agoOn the other hand, I find most frameworks don't allow for DRY. The typical case is making a 40 char DB field, and then having to code a check in the payload to ensure field is 40 chars. I've wondered if any system has achieved such enlightenment.
- iwwr 4y agoWrite code that is easy to delete: https://programmingisterrible.com/post/139222674273/how-to-write-disposable-code-in-large-systems https://programmingisterrible.com/post/139222674273/how-to-w...
- jakobov 4y agoAll things in porportion
- kache_ 4y agojust build it
- goto11 4y agoSpeaking from many painful experiences, DRY is underrated. Duplicate code is a major liability, and due to the natural entropy of code, duplicate sections will slowly drift apart over time. Yes, sometimes you may discover that you prematurely DRY'ed the code, and it was just accidentally similar. Easy, you just un-DRY the code. This is a trivial operation. In an IDE it might be a single keyboard shortcut. Going the other way is a difficult and error prone process.
- theptip 4y agoMy personal rule of thumb for this type of situation is to use the counting scheme “one, two, many” and to try to defer commonizing before you get to “many” instances of the repeating pattern. It’s really easy to make assumptions about what you are going to need later that turn out to be completely unfounded (or even - years later there is no “many”, just the one or two usages you already have). And I think folks shouldn’t freak out over a little bit of duplication, as long as it doesn’t get out of hand in the codebase, and you make sure to come back to refactor later when you do have many common usecases.
- ch_sm 4y agoAt our first start up, we always used to say "RY before you DRY", so: you have to repeat yourself first, consciously, before you can start with abstractions, because bad abstractions are worse than no abstraction
- racl101 4y agoIt's not the end-all, be-all but it is a generally good principle. But yeah, 100% dry code that is also practical to maintain is also a fucking myth and not grounded in reality.
- dkersten 4y agoNo, it’s a pretty good principle, but just like everything in programming, there are no hard rules. Every principle has times when they are appropriate and when they aren’t and it’s our job to find what is most appropriate for the situation at hand. It’s still worth striving towards DRY, but that doesn’t mean that there aren’t many cases where it doesn’t improve the code. The examples are also pretty contrived, there’s hardly any duplication there and the duplication is very simple and unlikely to change much. DRY is beneficial when the repeated code is complex and will likely need to be changed in the future (eg to fix bugs or to be extended), where repeating the code will be a source of error since every change would then need to be applied to each instance and forgetting one is a problem. The example is trivial enough that I wouldn’t bother refactoring it until it became complex enough to be a problem. A good principle is to not apply principles too quickly/soon but only when not doing so would introduce complexity or cognitive overhead. YAGNI, basically.
- wellbehaved 4y agoLike all principles, if you don't understand it, then you're going to misuse it, and then some people will wrongly blame it for being "over-rated" rather than blaming their own understanding of it.
- 8note 4y agoNotably, this already starts dry. A less dry one would look like def make_hawaiian_pizza(): payload = { hwaiianCrust: "thin", redSauce: "tomato", cheese: "regular", ham: true, pineapple: true, toppings: ["ham"] } requests.post(PIZZA_URL, payload) def make_pepperoni_pizza(): payload = { pepperoniCrust: "thin", crust: "thick", sauce: "tomato", cheese: "regular", toppings: ["pepperoni"] } requests.post(PIZZA_URL, payload)
- JaceLightning 4y agoGuy got upset during a code review and wrote a blog post about how he's right.
- sparrc 4y agoI agree with the principle of this blogpost, though IMHO this was covered better and with better examples here: https://lbrito1.github.io/blog/2017/03/dont-obsess-over-code-dry.html https://lbrito1.github.io/blog/2017/03/dont-obsess-over-code... I also think the title of "don't obsess" covers the intent better. In other words, it's perfectly OK to write DRY code, but don't obsess over making all code DRY all the time at the expense of readability.
- blarg1 4y agoSometimes having repeated sections makes it easier to understand the code, and later figure out how to merge the logic to remove them. Separating it out into a function can obfuscate things making it harder to do that.
- rhacker 4y agoAs usual, when someone is trying to show how certain code idioms are bad (or good in some cases), the entire example is bad which makes it hard to even care about the entire point of the article. As some have pointed out - make_pizza shouldn't be anywhere in the code. There should be a mongo collection or rel table that has a bunch of typical pizzas and a way to make custom ordered pizzas, typically through a UI. The more complicated thing is the data structure that represents any pizza (like 50/50, 10/90, 10/80/10, toppings per section, etc...) And a mongo collection for "typical" pizzas would fit that pretty well. And beyond that custom ordered pizzas. All that being said even the above is over engineered. Typically this over-engineering is a result of allowing end-users to create a pizza. I'm super old school and still call in my orders on the phone. The difference being that end-user UIs that let you make a pizza need to be over-engineered while call in orders is just a bunch of notes and a total "additional toppings" count.
- 49531 4y agoSomething I have come to feel myself but haven't found a good way to articulate is asking "is what I am doing favoring authorship over maintenance?". I find that a lot of times the way DRY or other programming principles are used tend to be done to optimize authorship. This optimization sometimes happens at the expense of maintainability. Anticipating maintenance is tricky; I had a scenario where a developer on my team created a utility function to abstract away some code that was being repeated multiple times in the same file. As an author that made sense because he was writing the same code over and over again, but down the line when we wanted a specific instance of this copied code to work in a slightly different way we ended up making the utility function handle the edge case. Over time this utility became extremely hard to work with, because you weren't always sure if you made a change it wouldn't create a regression in other places it was used. When we sat down and asked ourselves "Is this utility assisting in authorship at the expense of maintenance", the answer was clear. We removed it and put back the repetitive code. We felt good about it because in reality, 90% of the time we were interacting with this code we were doing it in maintenance mode, tweaks and small updates. When in maintenance mode I don't feel the strain of a specific part of my code being repeated, I'm only looking at a small subset of the code. Sure, if I need to author a new case in this code it might be a bit more wordy, but I think the tradeoff is worth it. I am sure there are perhaps better ways to abstract things, or that we were doing DRY wrong, and our utility function could have been smarter, but I've seen this same thing play out over and over again and usually trying to make my abstraction better hasn't helped.
- beyondthebrush 4y agoI tend to agree with your premise, though I wouldn't say the two are mutually exclusive. In fact, I imagine favoring authorship would more often trend to maintainable code than not, depending on what is being optimized for (writing less code, etc.) I think in the case you described, instead of handling edge cases within that function, it might have been better to create an entirely new function to be called in those cases. You could then go a step further and identify shared logic, extract those and call them separately. At least that's what I tend to do when I find myself having to branch logic, especially established logic. Obviously I'm assuming a lot of the details here, and most likely what y'all ended up doing was the best right thing for your project/team.
- kazinator 4y agoHere is my take: if you run your code through gzip (or similar) and it gets any smaller, you've repeated yourself somewhere. I'd rather read the gunzipped code.
- bstar77 4y agoGenerally, I never think in terms of writing "DRY" code. I think the presumption of re-usability is a primary reason. I architected a React.js framework that needed to exist in our existing portal environment and play well with all of the other frameworks and scripts. My solution was tightly coupling bundles of code for widgets deployed on my platform. I have conventions all devs need to follow and it does result in not very dry code. The benefit is that everyone can work independently and not affect each other. Testing is easier to do as there are less logic paths. Performance is still optimized with code-splitting, so the extra code really doesn't affect performance. Whenever people try to create a DRY one-size-fits-all solution, I find them very inflexible and prone to breaking. Add to that, they are generally poorly documented, so making changes can be very stressful.
- tomohawk 4y agoA little copying is better than a little dependency - Rob Pike DRY code is a good value, but it is not an all important value. It's one of many values, and must be kept in balance.
- tiberriver256 4y agoI think Kent C. Dodds gets to the goal behind the DRY principle fairly well here: https://kentcdodds.com/blog/aha-programming https://kentcdodds.com/blog/aha-programming
- azov 4y agoThis is like saying "database normal forms are overrated - they make my SQL more complex and harder to read!" Well, yes, they do. They also make your database slower. This doesn't mean they are overrated - this means they are tradeoffs. Like everything engineering. With DB normal forms you buy integrity (i.e. keeping the data consistent as it changes) and you pay with performance and schema complexity. Usually this is a sensible tradeoff because integrity is more important. But, for example, if your data never changes - you will be paying for nothing. Or, perhaps, you can't afford the performance price and you have some other way to ensure integrity. Then you de-normalize. DRY is similar. As mentioned in the sibling thread, it's not about mechanically avoiding repeated code - just like normal forms are not about never having the same value in two different rows. It's about maintining logical integrity of your code as it changes. PI=3.14159265359? Probably safe to copy around. An implementation of some use case? Probably not. I'd say following DRY/STEP principle is a sensible default. If a reviewer asks you why your code is not DRY - you should be able to articulate a reason.
- fleddr 4y agoThe key skill is to distinguish between harmful repetition, harmless repetition and beneficial repetition. Say you have two templates (web pages). They are conceptually independent and serve two different business purposes. Yet in terms of their structure/content/whichever, they have about 20% in common. Somebody obsessed with DRY would now elevate that 20% into some reusable module, after which both templates use it and the repetition is gone. Feels clean. In reality, you didn't solve a real problem whilst you created a new one. Now individuals/teams cannot independently edit these templates as they need to understand and check the dependency tree. It no longer is simple, there's no piece of mind. Next, inevitably somebody is going to request changes impacting that 20% and before you know it and after cutting lots of corners, you end up with this freak component that changes output based on some flag. It's taken me 20 years to come to this conclusion: the negative effects of (too much) DRY (it increases complexity) show up in every single project and make code harder to understand and change. Meanwhile, the negative effects of allowing (some) repetition are mostly theoretical and more often than not a benefit, not a negative. I mean it. This is coming from an ex-DRY fan boy. The DRY principle makes us eager to connect dots that really aren't connected and shouldn't be connected.
- HeavyStorm 4y agoNot as overrated as this discussion is bike shedding.
- Fire-Dragon-DoL 4y agoThe big deal is that when a boundary exists, DRY should be "ignored" (cannot word this decently). For example, let's say there is an application where a user can purchase items and also give reviews for such purchased items, the user reviewing and the user buying have in common just the ID, while in the review boundary the relevant information is probably the user nickname, while in the purchase boundary the relevant information is the payment system, or the state of the checkout ("purchase" is probably not a single boundary). In that case, data could be duplicated to ensure the boundaries are decoupled. This is of course at the data level, but usually it translates to "there is a user model that has many orders and many reviews", because of DRY, no two user models could exist, there you have the boundary violation though. Sorry, this is a bit of a ramble, it's a long discussion.
- michaelsalim 4y agoFor me, a good way to evaluate whether you should combine them is to ask this question: If I need to change it on one location, do I need to do the same on all the location? If yes then it's a good candidate to abstract the code.
- sjducb 4y agoI think there are three levels of understanding this topic: 1) Repeat yourself everywhere because you're a noob and don't know how to DRY 2) DRY everything because it's easier to maintain, learn all sorts of clever tricks to make DRY work 3) Realise that sometimes it's easier to DRY and sometimes it's easier to write repetitive code. The problem is that if someone at level 3 (like the author) talks to someone at level 2 then the level 2 developer thinks that they're talking to a level 1.
- deleted 4y ago[deleted]
- jarek83 4y agoIt looks to me that the author has not faced a problem with DRY, but a junior developer and/or YOLO approach to architecture design. In many cases thinking about possible future features/extension was one of the most limiting, complexing and at the end irrelevant approach out there, because it usually turned out that the future was different than it had been imagined. On the opposite side, when you take into account just what you know at the time of writing, led to easier adjusts, because it was way more clear to understand. DRY is a great technique to organize your code, you just need to think about the structure first. In this article, the problem was that he wanted to bend 2 pizzas method into many other different types of pizzas, so his pizza architecture changed so vastly that it was not possible to describe it with just the initial idea of pizza.
- drbojingle 4y agoThe problem with dry is no one tells when when to not use it. It's great when you have 5 instances of the same string, much like oop is great when all your objects are animals that fit into a neat little category. It's not so great when you're drying code across feature boundaries. Features tend to diverge over time rather than converge so what's dry one day is garbage the next. You refactor the code to be dry so 3 features are now one function and someone comes along asking for an amendment to one of those 3 features which adds an edge case to your function. Now you've broken another feature because you didnt check if your new edge case would change other features but quality control didn't check the other features cause no one asked for that feature to be changed and how do they know that it's all one function under the hood. Now you have bugs in production and no one to cover your ass. Time goes on and you add more edge cases and now you have one function that does many things with special edge cases throughput it. You cant separate one feature without interacting with another, almost like you're interacting with strands of spagetti and you can't help but pick up a bunch when you only really wanted one noodle. Tldr if you're making code dry and you insist that you make code dry across feature boundaries then for the love of god make unit tests for those functions. Or keep your functions dry and your features wet.