[HN Gopher] Repeat yourself, do more than one thing, and rewrite... ___________________________________________________________________ Repeat yourself, do more than one thing, and rewrite everything (2018) Author : bshanks Score : 144 points Date : 2023-03-14 13:25 UTC (9 hours ago) (HTM) web link (programmingisterrible.com) (TXT) w3m dump (programmingisterrible.com) | strict9 wrote: | > _"Don't Repeat Yourself" often gets interpreted as "Don't Copy | Paste" or to avoid repeating code within the codebase_ | | When I think of the most difficult to understand code I've come | across it was probably written by someone who lives and breathes | that interpretation of DRY. | | But it doesn't end with code comprehension. Extreme abstraction | and countless files and components also lead to buggy and | difficult to maintain code. | | It's easy to lose understanding of branches and business flow | when abstraction exists in the extreme. | qudat wrote: | > When I think of the most difficult to understand code I've | come across it was probably written by someone who lives and | breathes that interpretation of DRY. | | Totally agree with this interpretation and why I wrote a recent | article about it: https://bower.sh/anti-pattern | strict9 wrote: | Great article, particularly agree with the tests part. Sanity | check tests (function doesn't throw an exception in common | path) are essential but following all possible paths and | verifying all outputs is the road to diminishing returns and | subpar output. | Spivak wrote: | I call these frankenframeworks. The constant drive to DRY and | reach the supposed nirvana of code being a DSL of pure business | logic leads to more and more implementation details being | shoved under the rug to deeper and deeper layers. But for some | reason there's no foresight that any non-trivial change | requires changing more than just the business logic and so you | have to resort to bolting on config options, weird hooks, | mixins, "concerns", and global state for no reason other than | it's all you can do to reach down the layers. | andorov wrote: | DRY needs to be balanced with SRP (Single Responsibility | Principle). You can legitimately have two functions that are | exactly the same but they should not be DRY'd up if they are | actually serving different purposes. | | The use cases will likely diverge in the future, and if the | functions are DRY'd making changes will make introducing bugs | from the calling code that you're not working on easy. | Eventually the single function will likely have a lot of | conditions in it, which is a red flag for this situation. | ramesh31 wrote: | >You can legitimately have two functions that are exactly the | same but they should not be DRY'd up if they are actually | serving different purposes. | | I use what I've come to call the "Rule of Three" here. | | The first time, don't even consider any kind of abstraction. | Just do the thing. | | The second time, don't abstract yet. Repeat yourself, but do | it in a way that will scale and make abstraction possible, | while mentally noting how you _might_ abstract it. | | The third time, abstract it. | | Adhering to this, the vast majority of code will never reach | that third stage, thus taming the complexity beast. | getoffmycase wrote: | Thanks for putting the process of what I did twice in the | last two days into clear, coherent words and logic. | pydry wrote: | I usually phrase this as balancing against loose coupling. | | I've had bad experiences with the single responsibility | principle. It sounds kind of right, but in practice | "responsibility" is too vague and often surprisingly hard to | agree on what is e.g. one responsibility vs. three | responsibilities. | | By contrast, loose coupling is more objective and can (at | least in theory) be measured. | unethical_ban wrote: | Back when I took a Comp sci course 16 years ago (shit) I was | taught that a single function should try to fit within a | screen. The idea is that breaking a task up into digestible | steps would both harbor more readable code, self-documentation | and code reuse. | mcphage wrote: | > The idea is that breaking a task up into digestible steps | would both harbor more readable code, self-documentation and | code reuse. | | Sometimes. Other times it means you need to jump around in a | file (or jump between files, even) to understand what's going | on. | wizofaus wrote: | Which is fine - any software of non-trivial complexity is | going to require looking at the behaviour of multiple | execution units, which may well be in different files. But | if they're named sensibly and the dependencies are clearly | maintained, the typical programmer is going to have a much | easier time of "understand[ing] what's going on" than a | single huge largely-unstructured blob of code. The moment | you can't quickly see where a block starts and ends | (without using IDE shortcuts) then the code has a | readability issue. | MrPatan wrote: | Good idea, wrong metric. What should fit in a screen is the | concept. If you take a big, hairy, complicated thing and just | chop it into several functions that fit on a screen you have | gained nothing. | saulpw wrote: | If you name the functions reasonably, and the functions | don't interact except through arguments and return values, | you have absolutely gained something. | commandersaki wrote: | Probably a good case for inclining: http://number- | none.com/blow/blog/programming/2014/09/26/carm... | thfuran wrote: | Inlining is the compiler's job, not mine. | rzzzt wrote: | That's when you rotate the display to vertical orientation. | justinator wrote: | "big, hairy, complicated thing" is a code smell and you | would consider doing something about it, before it becomes | truly a monster. Breaking it up into its constituent parts | may be the way to go, if only because my brain's CPU cache | is about 8 bytes of faulty memory. | vlunkr wrote: | I've dealt with code like that, and often wrote it. It's | incredibly hard to get right IMO. It's an intuition that you | develop over years of trial and error, not an exact science or | set of rules. | a_c wrote: | Your code is useless if no one is using it. If a tree falls in a | forest and no one hears about it, it made absolutely no sound. | Everything comes after that. | | Many programmers put cart before the horse by subscribing to | tidbits of "best practice". Having people using it, you can think | about making it right and fast, making the making of it right and | fast. Then make the right people making it right and fast. And | turtle all the way done from here. | avgDev wrote: | I found myself stuck in analysis paralysis and fear of not | creating a perfect app. I became my worst enemy. My app was | praised by VP, managers and staff using it, yet I saw it as a | pile of garbage. | | Then I realized it doesn't have to be perfectly DRY, it could | technically just be spaghetti. It isn't spaghetti but some things | could be improved. While, better designed apps are easier to work | with sometimes there are situation where it is impossible to | create a formal design document, so you just need to 'send it'. | | The next iteration will improve many things, but if were to do | those things initially the app would be in development for years, | and now it is running a business. | kitsunesoba wrote: | With time I've progressively become less concerned with staying | DRY, which perhaps counterintuitively has made it easier to | avoid spaghetti problems. It's easier to keep things clean with | a handful of near-duplicates that are tailored to the needs of | their call sites than it is with a single trying to do | everything. | | It's a bit more work to keep behavior consistent across | duplicates but I'll take it if it means less untangling work | for myself in the future. | programmarchy wrote: | I've found the same, and have leaned more into patterns and | proximity as guides. Find good patterns that can be repeated | easily and predictably. Also, keep related code close | together so it's easy to find and copy somewhere else. Often | times there are higher level abstractions that emerge which | can then be "dry"ed out, but trying to do that too early | creates more problems than it solves. | toast0 wrote: | Garbage that shipped and has customers and a purpose (and maybe | makes money, if your company is interested in that) is called | Legacy. Perfect code that never shipped doesn't have a name. | | Worst case, your garbage code gets you 6-12 months with | customers and it has to be thrown away. No big deal, you said | it was garbage _and_ now you 've got 6-12 months of actual | knowledge of what your customers need and want, instead of what | you thought they would need. You can make new legacy garbage | that's much better than the first version now. | BeetleB wrote: | I've said it before and I'll say it again - I should encapsulate | it as a law. | | BeetleB's Law of DRY: Every article that complains about DRY will | be a strawman argument. | | The DRY acronym came from _The Pragmatic Programmer_ , and almost | every instance of DRY people complain about is _not at all_ what | is advocated in the book. There are different ways of | interpreting what he wrote, but my version is: "If you have two | separate requirements that are very similar, keep them separate | in code. If your duplicated code is _one_ requirement, then DRY | it into one location in your code. " | | So this: | | > Following "Don't Repeat Yourself" might lead you to a function | with four boolean flags, and a matrix of behaviours to carefully | navigate when changing the code. | | Is not DRY. In fact, having boolean arguments is almost a | guarantee that you've violated DRY. | | Another way to know you've violated DRY: If one requirement | changes, do I need to add if conditions to the DRY'd function to | ensure some _other_ requirement doesn 't break? If yes, you're in | violation. | | Never tie in multiple requirements into one function. Or rather, | do it but don't call it an application of DRY. | duxup wrote: | Yeah I'm pretty burnt out on the DRY articles. It's so easy to | misrepresent something as an absolute and talk about how it is | wrong. As for DRY, it also means they don't understand / or | aren't honest about what DRY is about and I immediately am | skeptical about the author. | BeetleB wrote: | > As for DRY, it also means they don't understand / or aren't | honest about what DRY is about and I immediately am skeptical | about the author. | | Indeed - I didn't bother reading the rest of the article. | noodle wrote: | I personally agree with your point of view on what DRY is. | But,I don't think that's what's being taught or talked about | anymore. When people say DRY they usually mean an abstraction | layer that merges two similar concepts into one, and the DRY | you talk about is just standard operating procedures. Static | analysis tools will ding you on DRY rules if you have code that | looks pretty similar, urging you to refactor them to be the | same. Etc.. | TeeMassive wrote: | If most people "straw man" your position then it's not because | they're straw manning your position but because they | misunderstand your point because it is not clear enough. | lightbendover wrote: | I don't see how you can throw away the modern definition of DRY | and put critiques of it (the modern definition) to bed just by | pointing out the historical origination. They're completely | different topics and the discussions thus need to be insular. | BeetleB wrote: | Then start your critique of DRY by pointing out there are | multiple definitions of it, and that you are referring to one | definition. | Jtsummers wrote: | They can't be meaningfully separated because they're using | the same phrase with almost, but not exactly, the same | meaning. One version having a sanity check (don't repeat the | actual same logic/information, at least not excessively; | usually it's paired with the "Rule of Three" which is three | repetitions then look for a refactor) and the other not | (don't repeat anything that happens to look alike and don't | actually think, follow this rule like it's written in stone). | If these aren't distinguished, then you end up with everyone | talking past each other both thinking the other is an idiot | (rightly from both perspectives). | | People arguing against the latter are making a sane and | reasonable argument. And people arguing for the former are | making a sane and reasonable argument. But if, in the same | discussion, both senses are meant without qualification or | clarification then only confusion will be found. | strken wrote: | If your catchphrase is Don't Repeat Yourself and some people | take it to mean they shouldn't repeat themselves, the fault is | entirely with the catchphrase you used. | | If a third set of people then take it upon themselves to tell | everyone that Actually You Should Repeat Yourself Sometimes, | this is not them attacking a strawman, it's an attempt to clear | up the confusion caused by the phrase/acronym DRY. | | The original definition of DRY was "Every piece of knowledge | must have a single, unambiguous, authoritative representation | within a system." _That 's not what Don't Repeat Yourself | means_, if read literally. Because DRY sounds like it applies | to code instead of to knowledge, _of course_ it 's widely | misinterpreted! If they'd called it the Fight Unnecessary | Copying of Knowledge principle nobody would be having this | argument and we'd all get to save ourselves a lot of time. | klysm wrote: | Doesn't matter what it originally meant, people take it to mean | you shouldn't have repeated code and that's the DRY we all live | with which results in bad abstractions. I don't think it's a | straw man at all, unless you use your specific definition of | DRY which isn't very useful | BeetleB wrote: | People are welcome to co-opt the acronym and give it another | meaning. The issue is that the original DRY is a _damn good_ | principle, and it is more important to give it a name and | propagate that knowledge. | | If all we do is rail against the "new" DRY and forget the | original one, then we are at a net loss. | wizofaus wrote: | I'm with you - the violations of DRY I still see regularly | are clear cases of copying and pasting _exactly_ the same | logic (or magic literal value) when there was no reason not | to put it in a helper function or named constant that could | be referred to in both places. A code review I did | yesterday had that - there were 5 or 6 lines of code that, | starting with a particular regex, did some data massaging. | It was determined in some cases a different reg-ex was | needed for a second pass over the data, and so the | submitter had simply copied the 5-6 lines of code and just | changed the reg-ex used. I see that sort of thing 20 or 30 | times more often than code that gets itself into knots | because of excessive abstraction trying to avoid code | repetition. | kqr wrote: | To be fair, in your example it sounds like the repetition | is very local and easily recognised for what it is. Not | ideal, but hardly a poster child for when DRY is | impactful. | | If the change was otherwise good, I would remark on the | repetition as "here's how I would write it differently" | and not "go back and fix it now". | | The first time someone changes four of the cases the same | way but misses the fifth, though. That sounds like a good | time to refactor. | isleyaardvark wrote: | I love the principle but do we want to save the principle | or save the acronym? At this point IMO it's a lost cause. I | wish someone with a following would make a retronym of TIE | or SYNC to express it. | | I remember when The Pragmatic Program 20th Anniversary came | out, the authors, in interviews and the new edition itself, | described DRY as "the most misunderstood" concept in the | book. If for 20 years that is the most misunderstood | concept, then maybe the name is not the best. | xahrepap wrote: | I propose the following: DRBL - Don't Repeat Business | Logic | | It's funny, because "dribble" or "drool" is the opposite | of being dry ;) | | Although it doesn't have as fun of a pronunciation in | English, DRBR is probably a better acronym? Don't Repeat | Business Requirements | mrkeen wrote: | I feel exactly the same way about DI. | | Structuring code so that abstractions don't depend upon | implementation details is in my top 3 principles of all | time (along with pure functions and good typing). | | DI frameworks a la Spring and Guice just annoy me. | rched wrote: | The problem is with the entire concept of development | "principles". They are a bad way to propagate knowledge. I | suspect more people have an incorrect understanding of DRY | than not. Seems like a net loss to me. | | We should ditch these principles altogether and focus on | teaching a deeper understanding of these concepts that | captures the nuances. | BeetleB wrote: | I don't think you can move away from principles in | general. The reality is that most SW design is | subjective. Not reusing code is a _generally_ good | principle. It 's just that the misapplication of DRY is | following one good principle but violating another one | (requirements should be decoupled). | | In any case, the reason I go on the anti-rant rant each | time is because when I use DRY appropriately, I don't | want some idiot flagging me in a code review saying | "Don't do this. DRY is bad. Here are N blog posts | explaining why" - when none of the blog posts are | complaining about what I am doing. | rched wrote: | I don't think you can separate the principle from its | misapplication. It's misapplied because it tries to stuff | useful knowledge into a memorable phrase and the nuance | is lost. | | Flagging code in code review is another great example of | harmful behaviour principles encourage. I've stopped | referencing principles altogether in code review and I | encourage others to do the same. Instead I focus on | trying to explain the specific impact the code will have | on our specific codebase. | danielovichdk wrote: | I think it's very useful | smac__ wrote: | Here, only apply Don't Repeat Yourself to data not code. Making | it mean each piece of knowledge should have a single | authoritative reference. E.G. Avoid (if possible) places where | state is synced. | pphysch wrote: | That's a great way to summarize it. | | With the corollary that "hard-coded data" is still data, not | code. | mmcclure wrote: | > If a replacement isn't doing something useful after three | months, odds are it will never do anything useful. | | This is painful to read, but unfortunately rings true. | | As an aside, when I saw the domain name/year I thought I'd find | an update to one of my favorite programming rants of all time, | "programming sucks."[1] | | [1] https://www.stilldrinking.org/programming-sucks | jonfw wrote: | This is oddly comforting to read | BoxOfRain wrote: | That rant gets better every time I read it. | Clent wrote: | Ignore advice on what not to do. | | Listen to advice on how to accomplish tasks. | | A carpenter does not study how not to hang a door. | | Likewise, don't listen to advice on how not to write code. | ParetoOptimal wrote: | I don't think it's quite the same though... or at least I can | make an argument for learning about ways not to do programming | tasks because it generalizes. | | There are patterns between ways not to do programming related | things, e.g. use the single responsibility principle, use pure | functions. | | There are also so many ways to accomplish programming tasks, | it's useful to be able to filter down that multitude of ways or | notice "this stack overflow post has 5 bad patterns, maybe I | shouldn't use it". | [deleted] | jrochkind1 wrote: | I don't disagree with a single thing in the OP. | foundart wrote: | Lots of good ideas here, especially that duplication is better | than the wrong abstraction. | tpmoney wrote: | One of the best pieces of advice I got really early in my | career was write something 3 times before you decide to | abstract it. Until you've done that, you just don't know what | parts you can really abstract and you're likely wasting time. | Pretty much every time I've ignored that advice I've regretted | it. | stametseater wrote: | Same. Writing something more than once gives you more than | one perspective on the problem space. And with a better | understanding of the problem space, you're more likely to | find an optimal solution. | giraffe_lady wrote: | aka WET: Write Everything Twice. | jimmaswell wrote: | > Never rewrite your code from scratch, ever! | | Is this really a common sentiment? | | When it comes to rewriting _others '_ code, it's prudent to keep | in mind that it's naturally harder to understand code written by | someone else. Just because you're confused in the first five | minutes of looking at something doesn't mean it's an unsalvagable | spaghetti. It's too easy to underestimate the time and cost of a | rewrite and confuse your lack of knowledge for a fault in the | codebase. Of course sometimes a rewrite is still appropriate | after that consideration. | | If it's your own code then you probably have a better judgement | than anyone whether it's in need of a rewrite. | | Doesn't everybody tend to rewrite major components of something | in its early states? Though I find as I gain experience over the | years I have to rewrite/"draft" code less and less. | kitsunesoba wrote: | Some of the most significant jumps in quality I've seen have | been in total rewrites of my projects, at least when the | project in question was at least moderately complex. | | There even used to be a project that I'd rewrite every so often | (but never publish) just to see how much better each iteration | was. That fell to the wayside because I got busy, but I should | probably pick it back up at some point. | rzzzt wrote: | Do you keep the project runnable at all times when you | rewrite? My issue is twofold: | | 1) You can not do anything with the pieces until they are | back together | | 2) If everything goes well, you get to see the exact same | behavior as before. It can be faster, easier to modify or add | stuff, perhaps even more elegant on the inside, but it will | still be the same application. | kitsunesoba wrote: | For #1, yeah I do. If I'm working on more | routine/boilerplatey parts I might go a little longer | without running, but it's pretty important to me to verify | that each bit is working as expected before moving on. It's | easy to wind up in a mess if I'm operating on the | assumption that what's been written so far all works. | | For #2, yeah that's true, but for me less visible | improvements are gratifying, because not only is the thing | being rewritten being improved, but I can also apply | learnings to other projects that make rewriting them less | necessary. Also, it just bugs me when there's reasonable | obtainable improvements in optimization, flexibility, etc | that I've left on the table... feels like I left the job | half-done which isn't a great feeling. | colonCapitalDee wrote: | Not the person you originally asked, but I have the same | rewriting habit. | | Automated tests are essential for rewriting code. If I want | to rewrite untested code I either add tests or don't | rewrite it. The rewritten code also needs to be tested. | | Whenever possible, split up big, untestable rewrites into a | series of smaller, testable rewrites. Let's say I want to | rewrite component A with subcomponents B and C to use some | new library Foo. I might first rewrite B to use Foo, then | rewrite A (and A's tests!) to work with rewritten B. Then I | rewrite C to use Foo, then rewrite A again to work with | rewritten C. Then I finally go rewrite A to use Foo. This | is more coding then doing the whole thing in one go (I | rewrote A 3 times!), but it's a net time saver because when | I make a mistake I can quickly find and fix it. | | When I'm on the clock I rewrite code for practical reasons | (maybe the current structure of the code can't support some | new requirement, or technical debt has gotten high enough | to make maintenance difficult, or whatever). This is rare- | ish. Rewriting code, especially production code, is risky | and time consuming and just generally not worth it. | | Most of the time when I rewrite code I'm rewriting my own | code, and I'm doing it for personal reasons. I like good | code, I like reading it and I like writing it. To me code | has aesthetic qualities, code can be beautiful and elegant. | It's also educational. Imagine a writer that never edits | their work; they're probably not a very good writer. It's | also fun! I'm already familiar with the problem domain, so | I can devote my entire focus to solving the problem instead | of splitting my attention between solving the problem and | figuring out wtf is going on. | joevandyk wrote: | https://www.joelonsoftware.com/2000/04/06/things-you-should-... | | "They did it by making the single worst strategic mistake that | any software company can make: They decided to rewrite the code | from scratch." | sundarurfriend wrote: | If you're not a programmer and are just stumbling around trying | to code, ideas like DRY and abstractions and modularity are super | useful. I work with scientists/PhD students helping with their | code from time to time, and it's easy to forget how much basics | we take for granted. | | If you're a career programmer or want to be one, then yes, it's | better to try things out and figure out from experience _why_ | these principles exist. Then, you can break the rules, because | you understand their purpose and limitations now. | tabtab wrote: | "Always do X" and "Never do Y" are almost always bad advice. Live | by rules of thumb but don't become a zealot or rigid purist. Some | duplication is acceptable, but lots is probably a sign that | something is factored poorly or the wrong tool for the job. | | Rules of thumb include but are not limited to: KISS, YAGNI, and | DRY. | | Another good rule of thumb is make things easy to figure out for | future maintainers who you have yet to meet and may never. | Programming is communicating with a future human, not just a | machine. It's about people. (Insert Soylent Green jokes here.) | | At least in ordinary CRUD, I find that simple, re-composable | mini-components get me far more reuse than big swiss-army-knife- | like components. Small components that can be copied, tweaked, | remixed, or ignored with ease are more flexible. | | Also, communicating via strings and string maps (dictionaries) | makes them easier to mix and match than complex data | structures/classes. String maps are relatively simple yet | flexible for structure passing. You lose a little compile-time- | type-checking by going string-centric, but there are work- | arounds, such as optional named parameters that switch on type | scrubbing when needed. (I love optional named parameters. Every | language should have them.) | bokohut wrote: | I found the article to be sound in the fact of modularity and | building upon what works as the old adage states "if it ain't | broke don't fix it" holds true however there is always room for | technology improvements when one monitors and measures the entire | lifecycle of a transaction system. The world's systems exist in | the way that they do today because someone took a risk on a | design to work and the uptake of what "works" only spreads as the | acceptance of said design is proven. I have wasted most of my | adult life rewriting the same system in entirety five times and | am now in the process of rewriting it again for the sixth however | now I am applying it to a different industry. The design was | proven over several decades in the critical uptime high | transaction volume payments industry and now that same design is | being generalized into other industries. The other industries | applications may not have the same transaction volume | requirements as the financial industries designs however | refactoring what works ensures the critical availability portion | as well as the scaling flexibility to meet potential high | transaction volume should any other applied industries demand | that same growth requirement. | agentultra wrote: | I think there needs to be a version of this for pure FP languages | like Haskell or even OCaml or F#; almost none of these maxims and | aphorisms seem to apply. | | Abstraction? It has a completely different meaning in this | context. Our business is abstraction: creating precise | definitions and semantic meaning where none existed before. It is | much easier to create abstractions and sufficiently demonstrate | their laws hold. So much so that we often design our programs and | libraries with abstractions first. | | This forces our programs to deal with the side-effects of | interacting with the world outside of our programs' memory-space | at the very edges of our program. We can prove a great deal of | our code is correct by construction which lets us focus our | testing efforts on a much smaller portion of our programs. | | However even in non-FP languages I think a lot of these problems | do go away if you use the above definition of _abstraction_ and | spend a bit more time thinking about the problem up front before | writing code. Not too much, mind you, because the enemy of a good | plan is a perfect one; however enough that you know what the | essential properties and laws are at least tends to help and | reduce the amount of code you need to consider and write. | avgcorrection wrote: | FP has a better definition of abstraction where it is closer to | the sense of "simplify". | | In procedural programming it might just mean _indirection_. Or | silly metaphors. ___________________________________________________________________ (page generated 2023-03-14 23:02 UTC)