Geeks With Blogs
Stefan Steinegger
| Home |

DRY (Don't Repeat Yourself) is a basic software design and coding principle.

But there is just no silver bullet. While DRY should increase maintainability by avoiding common design mistakes, it could lead to huge maintenance problems when misunderstood.

The root of the problem is most probably that many developers believe that DRY means that any piece of code that is written more then once should be made reusable. But the principle is stated as "Every piece of knowledge must have a single, unambiguous, authoritative representation within a system." So the important thing here is "knowledge". Nobody ever said "every piece of code".

I try to give some examples of misusing the DRY principle.

Code Repetitions by Coincidence

There is code that is repeated by pure coincidence. It is not the same code because it is based on the same piece of knowledge, it is just the same by coincidence.

It's hard to give an example of such a case. Just think about some lines of code the developer thinks "I already wrote something similar". Then he takes the original code, puts it into a public method, even worse into a base class where none had been there before, puts some weird arguments and some if or switch statements into it to support all special cases and calls this "increasing maintainability based on the DRY principle".

The resulting "reusable method" is usually something the developer not even can give a meaningful name, because its contents isn't anything specific, it is just a bunch of code. For the same reason, nobody will really understand this piece of code. Typically this method only makes sense to call after some other method had been called. All the symptoms of really bad design is evident.

Fact is, writing this kind of "reusable methods" is worse then copy pasting! Believe me. What will happen when you change this weird piece of code? You can't say what'll happen, because you can't understand what the code is actually doing. So better don't touch it anymore. Maintainability just died.

Of course this problem is with any badly designed code. But because the developer tried to make this method as reusable as possible, large parts of the system get dependent on it. Completely independent parts get tightly coupled by this common piece of code. Changing on the single common place will have effects anywhere in the system, a typical symptom of too tight coupling.

Without trying to dogmatically (and wrongly) apply the DRY principle, you just had a system with a weak design. Now you get a system which just can't be maintained anymore.

So what can you do against it?

  • When making code reusable, always identify the generally reusable parts of it. Find the reason why the code is repeated, find the common "piece of knowledge". If you have to search too far, it's probably not really there. Explain it to a colleague, if you can't explain or the explanation is to complicated, it's probably not worth to reuse.
  • If you identify the piece of knowledge, don't forget to carefully find the place where it should be implemented.
  • Reusing code is never worth giving up a clean design. Methods always need to do something specific. If you can't give it a simple and explanatory name, you did probably something weird.
  • If you can't find the common piece of knowledge, try to make the code simpler. For instance, if you have some complicated string or collection operations within this code, write some general-purpose operations into a helper class. If your code gets simple enough, its not so bad if it can't be reused.
  • If you are not able to find anything simple and reasonable, copy paste it. Put a comment into the code to reference the other copies. You may find a solution later.

Requirements Repetitions by Coincidence

Let's assume that you need to implement complex tax calculations for many countries. It's possible that some countries have very similar tax rules. These rules are still completely independent from each other, since every country can change it of its own. (Assumed that this similarity is actually by coincidence and not by political membership. There might be basic rules applying to all European countries. etc.) Let's assume that there are similarities between an Asian country and an African country.

Moving the common part to a central place will cause problems. What happens if one of the countries changes its rules? Or - more likely - what happens if users of one country complain about an error in the calculation? If there is shared code, it is very risky to change it, even for a bugfix.

It is hard to find requirements to be repeated by coincidence. Then there is not much you can do against the repetition of the code. What you really should consider is to make coding of the rules as simple as possible. So this independent knowledge "Tax Rules in Timbuktu" or wherever should be as pure as possible, without much overhead and stuff that does not belong to it. So you can write every independent requirement short and clean.

DRYing try-catch and using Blocks

This is a technical issue. Blocks like try-catch or using (e.g. in C#) are very hard to DRY. Imagine a complex exception handling, including several catch blocks. When the contents of the try block as well as the contents of the individual catch block are trivial, but the whole structure is repeated on many places in the code, there is almost no reasonable way to DRY it.

try
{
  // trivial code here
  using (Thingy thing = new thingy)
  {
    //trivial, but always different line of code
  }
}
catch(FooException foo)
{
  // trivial foo handling
}
catch (BarException bar)
{
  // trivial bar handling
}
catch
{
  // trivial common handling
}
finally
{
  // trivial finally block
}

The key here is that every block is trivial, so there is nothing to just move into a separate method. The only part that differs from case to case is the line of code in the body of the using block (or any other block). The situation is especially interesting if the many occurrences of this structure are completely independent: they appear in classes with no common base class, they don't aggregate each other and so on. Let's assume that this is a common pattern in service methods within the whole system.

Examples of Evil DRYing in this situation:

  • Put a if or switch statement into the method to choose the line of code to execute. There are several reasons why this is not a good idea: The close coupling of the formerly independent implementation is the strongest. Also the readability of the code and the use of a parameter to control the logic.
  • Put everything into a method which takes a delegate as argument to call. The caller just passes his "specific line of code" to this method. The code will be very unreadable. The same maintainability problems apply as for any "Code Repetition by Coincidence" situations.
  • Enforce a base class to all the classes where this pattern appears and use the template method pattern. It's the same readability and maintainability problem as above, but additionally complex and tightly coupled because of the base class. I would call this "Inheritance by Coincidence" which will not lead to great software design.

What can you do against it:

  • Ideally, the individual line of code is a call to a class or interface, which could be made individual by inheritance. If this would be the case, it wouldn't be a problem at all. I assume that it is no such a trivial case.
  • Consider to refactor the error concept to make error handling easier.
  • The last but not worst option is to keep the replications. Some pattern of code must be maintained in consistency, there is nothing we can do against it. And no reason to make it unreadable.

Conclusion

The DRY-principle is an important and basic principle every software developer should master. The key is to identify the "pieces of knowledge". There is code which can't be reused easily because of technical reasons. This requires quite a bit flexibility and creativity to make code simple and maintainable.

It's not the problem of the principle, it is the problem of blindly applying a principle without understanding the problem it should solve. The result is mostly much worse then ignoring the principle.

Posted on Friday, April 16, 2010 3:48 PM | Back to top


Comments on this post: Evil DRY

# re: Evil DRY
Requesting Gravatar...
Very nice article, but I think you can take it one step further. You seem to be focusing on procedural reuse, yet you mention capturing "knowledge" of what a function does. You should consider wrapping this knowledge up with OOP. Not only does this give you clearer operations on objects, but you can also capture (and enforce) certain system states before executing code. Encapsulating state and guarding state changes leads to much more testable code in my experience.
Left by Ryan on Apr 16, 2010 9:20 PM

# re: Evil DRY
Requesting Gravatar...
@Ryan: Thanks for the comment. Of course, in OOD most of the "pieces of knowledge" are represented by classes, while the method is the smallest piece of functionality.

What I describe is a kind of bad coding style which comes far before OO principles. When developers are not able to structure the functionality in meaningful functions, you don't have to talk about OO. (It's also possible that education of developers is too focused on OO principles, that they never learned to write functions, which is actually much more basic.) The next step is that they do such weird things with OO principles, and I don't have to tell you where this leads to.
Left by Stefan Steinegger on Apr 16, 2010 9:52 PM

# re: Evil DRY
Requesting Gravatar...
@Stefan Haha that is very true!
Left by Ryan on Apr 18, 2010 5:23 AM

# re: Evil DRY
Requesting Gravatar...
I can't imagine this being much of a problem, I think its too contrived to imagine DRY being evil :) I can't imagine it ever causing massive problems, but may require tiny fixups over time.

I think your struggle to get a concrete example shows how strange a situation it would have to be. Coincidental coupling can be a problem. However, its not as bad as it may seem, if something converges, it will diverge at a later date as different scenarios arise. An example would be classes having a "Name" method / property and trying to capture that in a base class and making everything that has a "Name" use it. "Named Object". (though depending on context this might be useful, but lets assume no one wants to polymorphically access "Name")

try catches can be reasonably captured through Actions/lambdas. Again exception handling strategies
are a different issue than dry. But common try catch can be captured in a DRY way.

But the bigger thing is, this is really a process of learning DRY / OO principles..... the process of DRYing your code is likely to give you a lot design insight that these problems will become evident if you do even get them. Not that it is a silver bullet, but you are likely to be doing better software trying to DRY your code than not drying your code.

Left by Keith Nicholas on Apr 19, 2010 3:17 AM

# re: Evil DRY
Requesting Gravatar...
@Keith: Did I forget to point out how basic the DRY principle is? I just wanted to explain how the most basic programming principle can be misused when developers don't think.
Left by Stefan Steinegger on Apr 19, 2010 10:22 AM

Your comment:
 (will show your gravatar)


Copyright © StefanSteinegger | Powered by: GeeksWithBlogs.net