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.
Invoking the same function from two places in the code with vaguely similar parameters is lot repeating yourself, dude.
Sometimes I wonder if the amount of sanity in the industry is finite and adding more people just makes us all crazier.
Which is exactly why static analysis tools that force you to do something need to be shot. Static analysis tools that inform you about a possible duplicate are totally fine. Give me an option to disable that particular instance.
Co-incidentally, micro-services do away with such problems in many cases due to the fact that code is "separate" and thus analyzers and sticklers don't find the "duplicates" and you can write beautifully simple code. Unfortunately it has the opposite problem then of leading to things like this Netflix architecture https://res.infoq.com/presentations/netflix-chaos-microservi... but for something simple like a personal blog (yes I exaggerate - slightly)
In the end I think the only solution is to have the right people and stay small enough to keep the right culture. That probably goes against all your metrics and growth goals of the company of course.
If all we do is rail against the "new" DRY and forget the original one, then we are at a net loss.
Indeed - I didn't bother reading the rest of the article.
But the actual DRY definition is a little more nuanced.
> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system
And this is what OP is referring to. It's the little abstractions that become big abstractions in the name of DRY that can over complicate code bases.
When it comes to heuristics intention doesn't matter. If the end result of DRY is that most people over-apply it then it is a bad heuristic.
Sorry, not sure how this is my redefinition, and I would not by default agree to this. If you have code that does the exact same thing, but they are for separate requirements (which does happen), then I would not recommend refactoring to one function.[1]
If they are for the same requirement, I would.
> > Every piece of knowledge must have a single, unambiguous, authoritative representation within a system
> And this is what OP is referring to.
Sorry, but my original comment is that this is not what the OP is referring to. If you abstract into something with a lot of booleans, chances are that function is now related to multiple pieces of knowledge.
[1] I may still do it, but with the understanding that I may need to undo it when one of the requirements changes.
That way you're not looking for abstraction layers to do avoid lines of code. You are looking to make sure you don't have subtle bugs that only happen in some code paths. You also only have one place to update as the business rules change.
It's the same as dry but avoids confusion.
> almost every instance of DRY people complain about is not at all what is advocated in the book.
It's futile to bang the drum insisting that people follow the original meaning of what the creators intended. Look at what happened to Agile. Completely perverted of its original meaning. You can't argue the case for Agile anymore just by saying that the creators of the Agile Manifesto meant for it to be something completely different, because what other term can you use to describe the micromanaging process-heavy framework has currently taken its place?
DRY may have been introduced with a caveat to not conflate two pieces of code that are only incidentally similar, but people have completely disregarded it and used it to propagate code monstrosities. It's not a strawman argument, we need a term to describe situations where functions end up using four boolean flags.
I phrase it as "duplication is a tool". Which is to say if something is actually definitely for sure the same requirement, you can de-duplicate it to have the compiler/tooling enforce and uphold that design constraint. This is good!
Many "duplicates" are really only similar, temporarily, and by coincidence. In those cases, it's not really "duplication" and keeping it separate is almost certainly a better choice.
Counterpoint: Every article that defends DRY will be a No True Scotsman argument.
Here we have an objective, original, unambiguous definition of DRY. So no, this is not a No True Scotsman fallacy.
That doesn't go over well, with today's crowd. Not considered "cool."
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.
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.
However, it's also a massive pain in the Automated System Structure to keep them updated properly.
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.
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.
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.
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.
Totally agree with this interpretation and why I wrote a recent article about it: https://bower.sh/anti-pattern
Sometimes. Other times it means you need to jump around in a file (or jump between files, even) to understand what's going on.
Good code rhymes! It's better to err on the side of less abstraction and create patterns that will become familiar to the next person working with your code.
Reading code is harder than writing it and most of the time coding is about communicating with humans or "ghosts", i.e. people you might never meet, people who worked on the code in a different context or "era".
https://sonnet.io/posts/code-sober-debug-drunk/
https://sonnet.io/posts/emotive-conjugation/#:~:text=Ghost%2...
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]
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.
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.
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.
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.)
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.
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.
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.
"They did it by making the single worst strategic mistake that any software company can make: They decided to rewrite the code from scratch."
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.
In procedural programming it might just mean indirection. Or silly metaphors.
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.
With the corollary that "hard-coded data" is still data, not code.
Tinker with these examples to improve your understanding of underlying factors of the principle.
Look for examples that might contradict the principle and understand why.
Let us take DRY for example:
If I make a change in one place, and I have to know about 4 other far away places to make the same change, will that cause problems?
What if there two duplicate lines of code in the same file, right below each other?
What if it's a 3000 line file that's duplicated? How about a 4 line function?
What if changing it in one place doesn't mean you have to change the other place?
Much like a craftsman uses various stencils and measures to guide their craft, it's not simply using the stencil that is necessary but applying that stencil with skill and judgment, and when working with others, sharing those stencils kindly.
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.
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".
* Give this chunk of code a name
* Clearly document, in types and names, the inputs and outputs at its boundaries, without having to discover this through a breakpoint
Does the clarity of adding that documentation outweigh the indirection?
A great example is a set of 4-5 if-conditions. Looking at them might be unavoidable arcane-looking complexity with regex's or who knows. Now instead it's called:
if ($this->orderIsValid($order))
Isn't that nicer in most cases for the person reading it, who's trying to understand what the larger function does? Yes, even if it's only used in that one spot.A lot of this is subjective so I'm not going to pretend to have written the programmer's stone tablet of rules but that's my strategy.
When I'm writing code I care a lot about, I don't have to worry about DRY stuff, because I can't really write the code without figuring out the right abstractions. It starts DRY.
But when I'm cranking out reporting code or boilerplate of some kind and I just want to finish the job, my work starts out dripping wet copypasta. I test it. I then do some squeezing -- refactoring -- to unify some of the abstractions and delete the almost-dup code. I try to DRY it up to a reasonable level. But, I confess, I don't subject the abstractions to as much scrutiny as the code I care about. My successors probably hate me for that. But...
Eg with a bunch of one-line functions, you then need to call them, or they call each other, and then the complexity is in the call graph rather than laid out in a single function.
Code needs to be as complex as the problem it is solving, the challenge is to avoid complexity beyond that, and ideally have the code be 'transparent' to the problem, ie it's easy to see the problem and how it's being solved from the code.
As an aside, didn't 'fat controller' stop being a thing in the early 2010s, and the problem changed to 'god models'.
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.