Don't Repeat Yourself (DRY) Design Principle

The idea behind the Don’t-Repeat-Yourself (DRY) design principle is an easy one:

  • A piece of logic should only be represented once in an application. 
  • Avoiding the repetition of any part of a system is a desirable trait.
  • Code that is common to at least two different parts of your system should be factored out into a single location so that both parts call upon it.
  • All this means that you should stop doing copy+paste right away in your software.
Your motto should be the following:
 

Repetition is the root of all software evil

  • Repetition does not only refer to writing the same piece of logic twice in two different places. It also refers to repetition in your processes – testing, debugging, deployment, etc. 
  • Repetition in logic is often solved by abstractions or some common service classes whereas repetition in your process is tackled by automation.
  • A lot of tedious processes can be automated by concepts from Continuous Integration and related automation software such as TeamCity
  • Unit testing can be automated by testing tools such as NUnit and xUnit
  • DRY is known by other names as well: Once and Only Once, and Duplication is Evil (DIE)

We will take a look at the “logic” side of the DRY with a simple example.

Magic strings

These are hard-coded strings that pop up at different places throughout your code: connection strings, formats, constants, 

  1. internal static class Problem {  
  2.     public static void DoSomething() {  
  3.         string address = "Isline, US";  
  4.         string format = "{0} is {1}, lives in {2}, age {3}";  
  5.         Console.WriteLine(format, "Abraham""a good friend", address, 30);  
  6.     }  
  7.     public static void DoSomethingAgain() {  
  8.         string address = "Isline, US";  
  9.         string format = "{0} is {1}, lives in {2}, age {3}";  
  10.         Console.WriteLine(format, "Jeethu""a neighbour", address, 54);  
  11.     }  
  12.     public static void DoSomethingMore() {  
  13.         string address = "Isline, US";  
  14.         string format = "{0} is {1}, lives in {2}, age {3}";  
  15.         Console.WriteLine(format, "Pramod""my brother", address, 4);  
  16.     }  
  17.     public static void DoSomethingExtraordinary() {  
  18.         string address = "Isline, US";  
  19.         string format = "{0} is {1}, lives in {2}, age {3}";  
  20.         Console.WriteLine(format, "Parvathy""my sister's best friend", address, 4);  
  21.     }  
  22. }  

This is obviously a very simplistic example but imagine that the methods are located in different sections or even different modules in your application. In case you want to change the address you’ll need to find every hard-coded instance of the address. Likewise if you want to change the format you’ll need to update it in several different places.

We can put these values (address and format) into a separate location, such as Constants.cs,

  1. internal class Constants {  
  2.     public static readonly string Address = "Isline, US";  
  3.     public static readonly string StandardFormat = "{0} is {1}, lives in {2}, age {3}";  
  4. }  

The updated program looks as follows,

  1. internal class SolutionIteration1 {  
  2.     public static void DoSomething() {  
  3.         string address = Constants.Address;  
  4.         string format = Constants.StandardFormat;  
  5.         Console.WriteLine(format, "Abraham""a good friend", address, 30);  
  6.     }  
  7.     public static void DoSomethingAgain() {  
  8.         string address = Constants.Address;  
  9.         string format = Constants.StandardFormat;  
  10.         Console.WriteLine(format, "Jeethu""a neighbour", address, 54);  
  11.     }  
  12.     public static void DoSomethingMore() {  
  13.         string address = Constants.Address;  
  14.         string format = Constants.StandardFormat;  
  15.         Console.WriteLine(format, "Pramod""my brother", address, 4);  
  16.     }  
  17.     public static void DoSomethingExtraordinary() {  
  18.         string address = Constants.Address;  
  19.         string format = Constants.StandardFormat;  
  20.         Console.WriteLine(format, "Parvathy""my sister's best friend", address, 4);  
  21.     }  
  22. }  

If we change the constants in Constants.cs then the change will be propagated through the application.

However, we still repeat the following two lines over and over again.

  1. string address = Constants.Address;  
  2. string format = Constants.StandardFormat;  

The VALUES of the constants are now stored in one place, but what if we change the location of our constants to a different file? Or decide to read them from a file or a database? Then again we’ll need to revisit all these locations.

We can move those variables to the class level and use them in our code as follows:

Updated code as below,

  1. internal class SolutionIteration2 {  
  2.     private static string address = Constants.Address;  
  3.     private static string format = Constants.StandardFormat;  
  4.     public static void DoSomething() {  
  5.         Console.WriteLine(format, "Abraham""a good friend", address, 30);  
  6.     }  
  7.     public static void DoSomethingAgain() {  
  8.         Console.WriteLine(format, "Jeethu""a neighbour", address, 54);  
  9.     }  
  10.     public static void DoSomethingMore() {  
  11.         Console.WriteLine(format, "Pramod""my brother", address, 4);  
  12.     }  
  13.     public static void DoSomethingExtraordinary() {  
  14.         Console.WriteLine(format, "Parvathy""my sister's best friend", address, 4);  
  15.     }  
  16. }  

We’ve got rid of the magic string repetition, but we can do better.

Notice that each method performs basically the same thing: write to the console.

This is an example of duplicate logic. The data written to the console is very similar in each case;

We can factor it out to another method,

  1. private static void WriteToConsole(string name, string description, int age) {  
  2.     Console.WriteLine(format, name, description, address, age);  

Updated Code below,

  1. internal class SolutionIteration3 {  
  2.     private static string address = Constants.Address;  
  3.     private static string format = Constants.StandardFormat;  
  4.     public static void DoSomething() {  
  5.         WriteToConsole("Abraham""a good friend", 30);  
  6.     }  
  7.     public static void DoSomethingAgain() {  
  8.         WriteToConsole("Jeethu""a neighbour", 54);  
  9.     }  
  10.     public static void DoSomethingMore() {  
  11.         WriteToConsole("Pramod""my brother", 4);  
  12.     }  
  13.     public static void DoSomethingExtraordinary() {  
  14.         WriteToConsole("Parvathy""my sister's best friend", 4);  
  15.     }  
  16.     private static void WriteToConsole(string name, string description, int age) {  
  17.         Console.WriteLine(format, name, description, address, age);  
  18.     }  
  19. }  

Magic Numbers

It’s not only magic strings that can cause trouble but magic numbers as well.

Imagine that you have the following class in your application,

  1. internal class Associate {  
  2.     public string Name {  
  3.         get;  
  4.         set;  
  5.     }  
  6.     public int Age {  
  7.         get;  
  8.         set;  
  9.     }  
  10.     public string Department {  
  11.         get;  
  12.         set;  
  13.     }  
  14. }  

We will mock the database as below,

  1. private static IEnumerable < Associate > GetAssociates() {  
  2.     return new List < Associate > () {  
  3.         new Associate() {  
  4.                 Age = 30, Department = "IT", Name = "Prasad"  
  5.             },  
  6.             new Associate() {  
  7.                 Age = 34, Department = "Marketing", Name = "Praveen"  
  8.             },  
  9.             new Associate() {  
  10.                 Age = 28, Department = "Security", Name = "Pramod"  
  11.             },  
  12.             new Associate() {  
  13.                 Age = 40, Department = "Management", Name = "Steve"  
  14.             }  
  15.     };  
  16. }  

Notice the usage of the index 1 in the following method,

  1. private static void DoMagicInteger() {  
  2.     var associates = GetAssociates().ToList();  
  3.     if (associates.Count > 0) {  
  4.         Console.WriteLine(string.Concat("Age: ", associates[1].Age, ", department: ", associates[1].Department, ", name: ", associates[1].Name));  
  5.     }  
  6. }  

We only want to output the properties of the second associate in the list, i.e. the one with index 1.

  • One issue is a conceptual one: why are we only interested in that particular associate? What’s so special about him/her? This is not clear for anyone investigating the code. 
  • The second issue is that if we want to change the value of the index then we’ll need to do it in three places. If this particular index is important elsewhere as well then we’ll have to visit those places too and update the index.

We can solve both issues using the same simple techniques as in the previous example. Set a new constant in Constants.cs

  1. internal class Constants {  
  2.     public static readonly string Address = "Isline, US";  
  3.     public static readonly string StandardFormat = "{0} is {1}, lives in {2}, age {3}";  
  4.     public static readonly int IndexOfMyFavouriteAssociate = 1;  
  5. }  

Then introduce a new class level variable

  1. private static int indexOfMyFavouriteAssociate= Constants.IndexOfMyFavouriteAssociate; 

The updated DoMagicInteger() method looks as follows,

  1. private static void DoMagicInteger() {  
  2.     var associates = GetAssociates().ToList();  
  3.     if (associates.Count > 0) {  
  4.         var associate = associates[indexOfMyFavouriteAssociate];  
  5.         Console.WriteLine(string.Concat("Age: ", associate.Age, ", department: ", associate.Department, ", name: ", associate.Name));  
  6.     }  
  7. }  

We will be discussing the below two DRY series in the upcoming tutorials.

  • Repeated logic
  • RepeatedExecution Pattern