Hacker Read top | best | new | newcomments | leaders | about | bookmarklet login

It's not DRY, but then again, the chances of not overwriting, or more likely adding something to that method are pretty small when your project becomes more than an illustration of a principle.

For example, what if you want to add custom animations any time your `Character` takes an action? Suddenly, all that boilerplate you "abstracted" away through mix-ins is back, with a vengeance. How about if your physics for a `Projectile` are different than for a `Pickup`? What if your character suddenly picks up a Sling of Thrown Voices, and needs to apply conversation snippets to its projectiles?

In simple examples, mixins are great and reduce a lot of boilerplate, but in reality they are rarely so clean.



sort by: page size:

DRY isn't something you should blanket apply. There are cases where you do want to repeat yourself, e.g. to improve readability (avoiding metaprogramming, which is hard to reason about and will break your IDE) or because two things aren't actually semantically related (trying to force an abstraction means you need to undo it later anyways when the implementations diverge).

DRY is good but like any abstraction it has tradeoffs. It might give you a warm feeling to do the refactor, but you might end up with code that's harder to understand and harder to replace.

DRY is mostly bad. You couple a lot of things together by making them all share the same code and then a small change to that code breaks various unrelated components.

I used to care about DRY to a _very_ great degree [0], and I still do. But I've come to realize DRY is much better to apply for high level _concepts_, ideas, and architectures, not unexported helper code.

It's unhelpful to try to apply it to little snippets of code and helpers. It's much less expensive to copy a snippet that's needed and duplicate it 1-2 times before starting to worry about factoring it out. By the time it's repeated 3+ times, you'll have a much better idea how to structure it.

[0] https://github.com/shurcooL/Conception#motivation


The 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.


DRY is hard to do right, esp. for inexperienced coders. They see three lines of code in more than one place and extract it into a single procedure, without knowing if those three lines of code present a useful abstraction or they are just necessary boilerplate. Soon the codebase become a rube goldberg device with stuff piping in and out of these buckets of DRY code and run-time switches to activate or deactivate behaviors. Instead, you want helpers and components and tools to be reused but only when you notice a pattern, not before.

The problem is with being either dogmatic or thoughtless in either direction. I've seen what you're talking about: people combine code religiously because of DRY, leading to insane pyramids of abstractions that are impossible to modify.

However, I've also seen people copy and paste everything they ever need. When that happens, those offshoots gradually evolve independently from one another, and introducing a proper abstraction becomes a huge slog. I've spent hours reading through git blame trying to piece together a phylogenetic tree of the various copies of the same code so we can ensure that the new abstraction contains all relevant features and bug fixes. I wish those developers had thought more carefully about DRY.

I think the best balance is to use these catch phrases as principles to guide your decision making, while being willing to make exceptions when they don't apply. If DRY makes you think for a second before copying a piece of code, it's done its job, even if you decide that this situation really does call for a copy.


Agreed that fanatical adherence to DRY is a mistake. I did this when I was first starting out: any code that looked similar at all would be factored out into a new hyper-specific function, regardless of how unrelated the original pieces of code were. But this introduces extreme coupling between components that have no right being coupled, and makes evolving the code a nightmare (suddenly all your DRY-borne functions grow immense lists of configuration parameters to account for unforeseeable differences).

At the same time, let's not take this dogma too far in the other direction. Repeating oneself is still undesirable, but DRY-related refactoring should focus on behavior and intent, not on how the code looks. And abstractions are still good (they're how we get anything done at all!), but leaky abstractions should be avoided, indirection-via-abstraction should be minimized, and we should be vigilant against extreme and overeager overapplication of abstraction (insert your blub joke here).


For the same reason, I'm not so absolutist about DRY. Having the most elegant codebase also often means the codebase that's hardest to work on, and it's often better to clean things up afterwards once you know how things will be structured.

DRY is a good principle when you're aiming for effective product development, but that doesn't mean it's the most fun. I often find myself reinventing the wheel in my private projects, not because I couldn't easily find a library that already does what I want, but because I enjoy that sort of "DIY-Programming". I want to think about all the messy details because it's one of the things that makes programming fun for me.

Maybe this depends a lot on the individual. Some might find my way of enjoying code very boring, as I'm just messing with low-level stuff and not getting that much done. To me it would feel very exhausting to be writing glue-code all the time without messing with the internals of the systems I'm working with and recreating them myself every once in a while.

As a general advise to new developers: Find out what you enjoy about coding and always keep that in mind, be it while looking for a job or starting a new private project.


Lately I've been ripping out crappy abstractions that were all about creating DRY code but instead made the code incredibly hard to follow. Even though the resultant code has a bit of duplication, its much easier to read, reason about, test and maintain. The redundant parts are typically in areas that are unlikely to change. I might try to come up with a really clean abstraction later in order to re-DRY the code, but lately my experience is that a shitty abstraction created entirely for the purpose of DRY'ing code is vastly worse than just allowing a bit of code duplication. DRY only for the sake of DRY is terrible.

Your points about garbage code are interesting, and I think there's much to be said for that strategy.

Regarding your first sentence, you should be frequently spinning off chunks of code, but you should be careful that you're not adding coupling where it doesn't exist in the domain.

Again, I like the knowledge formulation of DRY.

"This is the way we render a FOO" is one piece of knowledge, and shouldn't be repeated every time you try to render a FOO.

"There should be a FOO rendered here" is one piece of knowledge, and shouldn't be repeated at multiple layers in your stack.

Now... what if we've been good about the above, but there's some similar code in "how we render a FOO" and "how we render a BAR"?

If the code is similar because we want a consistent style across FOO and BAR, then "we want such-and-such a style" is a piece of knowledge, and it should be represented in one place!

If the code is similar, but only coincidentally, and either might change in any direction tomorrow, then there's a question: "Is it a coherent abstraction?" We want abstractions such that we can give them a clear name, such that when we make a change to how we render only FOO or BAR we'll be obviously moving to a new abstraction and won't be tempted to change the function, and such that we know to reach for this function when it's applicable in a new context.

Alternatively, is it just a gathering of a particular grab bag of functionality? If you're pulling that out, you're not improving your code - you're compressing it.


I basically agree, but doesn't this just mean, if I'm consolidating non-DRY code, that I'm now the one using DRY consciously, and the next dev will be cursed with all of my newly introduced DRY abstractions?

Agree completely.

I've dealt with too many code bases that didn't follow this advice. It's extremely expensive to cut down capabilities from code because they are overly coupled.

DRY tends to create things like utility classes and deep dependency trees. For example, I saw a non-ui code base that pulled in JavaFX to use their pair class.


Any pointers on how to do DRY without making the codebase too constrained ?

Like the article ends with, DRY goes hand in hand with YAGNI. The point isn't to build a million abstractions; it's to find the places where you have duplication and de-duplicate it, or where you know there'll be duplication and abstract it, or to simply rearchitect/redesign to avoid complexity and duplication. This applies to code, data models, interfaces, etc.

The duplication is typically bad because it leads to inconsistency which leads to bugs. If your code is highly cohesive and loosely coupled, this is less likely [across independent components].

And on this:

> When designing abstractions, do not prematurely couple behaviors

Don't ever couple behaviors, unless it's within the same component. Keep your code highly cohesive and loosely coupled. Once it's complete, wall it off from the other components with a loosely-coupled interface. Even if that means repeating yourself. But don't let anyone make the mistake of thinking they both work the same because they have similar-looking interfaces or behaviors, or you will be stuck again in the morass of low cohesion. This is probably one of the 3 biggest problems in software design.

Libraries are a great help here, but libraries must be both backwards compatible, and not tightly coupled. Lack of backwards compatibility is probably the 4th biggest problem...


I have tried this path of not trying to DRY everything, but has regretted and refactored to a more DRY approach eventually. The cost of remembering to fix/alter the logic everywhere is more than trying to keep it DRY. More often a method or a module is enough, nothing fancy.

The only place where I have accepted that DRY is not worth it is, unit tests. I used to extract any common behavior in a shared test, but each object will eventually evolve in its own way that the effort to make it DRY will be useless.


I should have been a bit more precise. There are essentialy two ways to approach DRY in OO. One is to inherit implementation, which isn't the best way to go about things since it leads to more brittle code. The other way is to make sensible use of polymorphism and composition.

There are aspects of DRY that are worth pursuing (like reducing the number of places you need to get things right) and some that aren't worth pursuing (thinking you reduce the amount of work by re-using code heavily). For code to be reusable you have to be sure that developers only have to care about its interface. Implementation inheritance often doesn't do that. I've worked on projects with people who were obsessed with DRY and made heavy use of implementation inheritance, and the projects inevitably needed extra work to untangle a brittle mess with lots of surprising behaviors.


I can't disagree more. DRY forces you to create pure reusable code, and split your code into small pieces. When I read such code I need to understand just a few pieces.
next

Legal | privacy