0

I've only ever been working in higher level languages such as C# and JavaScript.

A couple of moments ago someone asked this question on SO.

Now I'm curious to know, for some of the lower level languages or specifically the ones I haven't encountered, any other for that matter...

What would be a valid situation to not apply the DRY principle / guideline?

I realize that thing like unit tests are an example of this, and the link shared by @mkk is an excellent example of DRY-over engineering, but in discussion with one of my colleague we where attempting to think of a few other just out of a matter of interest.

  • I can think of one example where some amount of repetition may be fine: tests. – 9000 Apr 28 '15 at 15:42
  • 3
    I think this link might give you a little bit light on a topic tomdalling.com/blog/software-design/fizzbuzz-in-too-much-detail – mkk Apr 28 '15 at 15:43
  • 1
    DRY isn't a mandate; it is a guideline. – Robert Harvey Apr 28 '15 at 19:32
  • 2
    @RobertHarvey, I do realize that... but I feel that my question is still valid. Guidelines are there for a reason, to guide and in general they would be considered best practice would they not? Thus my question, when is this guideline not valid? – Rohan Büchner Apr 28 '15 at 21:25
  • 1
    When following it detracts from your purpose. A good example is writing code for Unit Tests. Following DRY there is not only difficult, but generally undesirable, since each test should be completely isolated from the others. – Robert Harvey Apr 28 '15 at 21:30
5

There's always a time when you simply have to break a rule, even if everything you know as a coder says there must be another way.

Good examples of when DRY may not be the best strategy to use right off the mark include:

  • Code that, for all you know, is a one-off. I generally have a "three-strikes" rule for refactoring; "make it work, make it clean, make it SOLID". If I'm writing a line of code to do something, and I have another line of code somewhere else that would do that thing with a tweak here and there, then I hit Ctrl-C, Ctrl-V and don't give it a second thought. Why? Because I'm not getting paid for pretty code, I get paid for working code. Easy-to-maintain, reusable code is for my benefit, and it's only a benefit if I expect to go in and make changes. If I don't, then until I'm proven wrong, ivory-tower development rules just get in the way of a working program (and my paycheck). HOWEVER, once I am proven wrong, by having to go back in that code file and make a change, then I have to be disciplined enough to go back in and clean up, paying off the "technical debt" early instead of compounding it with more spaghetti.

  • Testing code. This is a significant subset of the above point. If you are writing a test for a variation on existing behavior, and you have a test exercising pre-existing behavior, then copy the test. Tests are typically "write once, run many", with very little maintenance needed once the tests go green, so they're the perfect example of a piece of code you'll likely never see again. Of course, you still have to be disciplined enough that the first time a change is required to both tests (or the fourth of fifth time you copy the same test to make another), you look for ways to ensure further changes happen in one place, not two (or six or ten or...).

  • Implementations of an interface or abstract base class. Even though you might think of these as being the primary way to avoid repeating yourself, the truth is that setting up multiple classes as implementations of some base type involves a lot of very similar-looking boilerplate, not all of which you can avoid. This is especially true if the hierarchy follows a Template Method pattern where a big driving function in a base type depends on specifics in derived types; if you have ten classes implementing this base type, odds are good at least two of them will end up with the same implementation for one of the stubbed methods. Not enough to justify pulling that implementation up into the base, but it's repeated code. Interfaces, which can't have any code, suffer from duplicate implementations almost by definition.

There are other examples, but these are a few of the ones I typically encounter. In short, DRY is not a hard and fast rule, it's just one of those things you keep in mind, and when you do repeat yourself, it's a "code smell", indicating you might be incurring technical debt or just generally screwing up. When your code "smells", it's worth a second look, but a smell is not always a genuine problem, and even if it is it may not be one worth solving (or even one that is possible to solve).

  • 4
    Actually, the "copying testing code is fine" has bitten me too often to agree to this part of your answer. Things got a whole lot better since I keep my testing code DRY, too. – Doc Brown Apr 28 '15 at 16:22
  • 2
    I've been bitten a couple times. I might want to amend that to say that making two tests from one is typically OK, but when that grows into five or six you have to look at it the same way you would production code and say "this is too much, it needs a refactor". – KeithS Apr 28 '15 at 17:23
  • @KeithS, thanks for the thorough answer, I actually have come across a few of these scenarios (Knowingly, or I guess unknowingly at the time of asking). Ultimately I think that if a pattern repeats itself often enough, then it'll be worth looking into a cleanup, but as always is a weigh up of time/cost vs benefit at the end of the day – Rohan Büchner Apr 28 '15 at 23:00
  • I would add "prototype code". When I am throwing together a first cut to make sure I understand the problem, I typically violate DRY all over the place. Then when I have a roughly working version, I polish in many ways, including removing all the repetition. I find it faster to work this way, as I don't waste a lot of time on code that ends up in the bit-bucket. – Gort the Robot Apr 29 '15 at 0:40
  • @StevenBurnap - I actually disagree here; I follow the same mentality for POCs as for code specifically intended to enter production. This is because POCs and other "prototype code" get folded into production code all the time; there's even a term for it, "protoduction". Management's mentality is, the POC solves the problem, so the "least-work" solution to that problem in the production system is to bolt the prototype code onto the production system the fastest way possible. The better your prototype code, the more willing you will be to go along with that and the happier everyone will be. – KeithS May 4 '15 at 15:43
4

SQL Server. Scalar user-defined functions in a T-SQL query prevent a parallel execution plan and can also have other negative effects on performance.

Hugo Kornelis explains it nicely here.

...it’s easy to see why using functions to encapsulate and reuse common computations is considered a best practice. But SQL Server isn’t a traditional programming language. It’s a declarative language, with an optimizer that has been designed to optimize the execution order within queries to make sure that the results are returned as fast as possible

  • Yeah. I find myself disgusted with the level of repetition I keep being forced to make in SQL code and to a lesser degree in code that works with the database. – Loren Pechtel Apr 29 '15 at 0:13
2

DRY exists for a reason, because the more places that the same code exists, the more chances there are of bugs arising from one of those places not being changed when the system needs to change. It is also, obviously enough, easier to test if you only have any given part of your code in one place. Consequently, in most cases where I need to do something more than once I will break it out into it's own function or method.

One place where I think repetition is alright, or even helpful, is when you have the same code working with different intents - so you might have a function or method that shows very similar behaviour, but in one place it is part of the system for retrieving a user from an authentication system and in another it is retrieving some data from a store. Even if the code is identical this can exist in different places because it is part of different flows and with these divergent intentions it is very likely that they will diverge at some point in functionality. If I was sharing the code at that point, I would have to make things more complex and less readable.

That said, if I have the same or very similar code in multiple places that starts to smell WET to me and I will certainly get to thinking about whether I could be abstracting the relevant behaviour in a way that would facilitate sharing.

2

When runtime efficiency matters, repeating yourself (e.g. unrolling loops) might be beneficial because some work that should be made at runtime is done earlier. Ideally, such things should be done by a compiler.

Also, when you work with a language that does not offer the kind of genericity that would allow you to avoid repeating yourself, you might need to duplicate things.

Apart from that, stick with the two rules of DRY:

  1. Do not repeat yourself
  2. See 1.
1

The problem with lower level languages is not that it "may be a good idea" not to stay DRY when using the specific language. The problem is that it can be hard to impossible to stay DRY if your language lacks certain means of abstraction.

For example, if your language does not provide any kind of higher order functions (or at least pointer-to-functions), try to implement something like a generic, reusable root finder, for example, a generic Newton solver. If your language does neither support generics nor an "common object base class" nor a void *, try to implement something like a reusable list.

For such examples, it maybe the only feasible solution to implement an algorithm or datastructure more than once, each time adapted to the specific situation.

Not the answer you're looking for? Browse other questions tagged or ask your own question.