topic: ASP .NET by Milos Protic relates to: Web Development, Advice, DRY, Software, Design, Principle on February, 15 2020
Software Design Principles - The Dry (Don't-Repeat-Yourself) Principle
Introduction
Lately, I've been focusing my writings on the .NET and Microsoft related technologies. Although the given design principle is applicable in every programming language, this article will not be an exception and the example will be written in C#.
Ok, what does the DRY principle stand for? The abbreviation stands for Don't-Repeat-Yourself, but make no mistake, is not as simple as it seems. One might think that this principle is only related to the code repetition, but that would be wrong. The actual definition of the DRY principle is as follows:
The DRY principle states that every piece of knowledge must have a single, unambiguous, authoritative representation within a system.
This doesn't only refer to the code repetition. It is also related to the actual knowledge and context of the code being used. For example, we could have a sales product and manufacturing product in our domain.
Both products would have a lot in common and would have the same properties for example. But from the context perspective in the application domain, they represent different things. Leaving them as two separate entities would not be the DRY principle violation.
Example
Ok, let us see some examples. The initial code looks like this:
class Program
{
static void Main(string[] args)
{
WriteSomething();
WriteSomethingElse();
WriteSomethingAgain();
Console.ReadLine();
}
private static void WriteSomething()
{
var city = "Novi Sad";
var state = "Serbia";
var name = "Milos Protic";
var age = 30;
Console.WriteLine($"{name} lives in {city}, {state} and he is {age} years old!");
}
private static void WriteSomethingElse()
{
var city = "Novi Sad";
var state = "Serbia";
var name = "John Doe";
var age = 29;
Console.WriteLine($"{name} lives in {city}, {state} and he is {age} years old!");
}
private static void WriteSomethingAgain()
{
var city = "Novi Sad";
var state = "Serbia";
var name = "Jack Fishing";
var age = 45;
Console.WriteLine($"{name} lives in {city}, {state} and he is {age} years old!");
}
}
What is the problem in the code above? As you can see we have the same variables declared in each of our methods, and two of them have the same value across all of them. And not only that, the values are hard-coded.
You might say that is not a problem, the program is simple, but imagine that we have these methods scattered across our codebase and that you want to change the city or the state. That would be a real pain. You would be required to revisit all the places where the values are located and hard-code new ones.
To fix this, we should move them somewhere so they can be reused across our methods. Considering that they are constants (never going to change) we can declare them as const
variables at the class level.
class Program
{
private const string CITY = "Novi Sad";
private const string STATE = "Serbia";
static void Main(string[] args)
{
WriteSomething();
WriteSomethingElse();
WriteSomethingAgain();
Console.ReadLine();
}
private static void WriteSomething()
{
var name = "Milos Protic";
var age = 30;
Console.WriteLine($"{name} lives in {CITY}, {STATE} and he is {age} years old!");
}
private static void WriteSomethingElse()
{
var name = "John Doe";
var age = 29;
Console.WriteLine($"{name} lives in {CITY}, {STATE} and he is {age} years old!");
}
private static void WriteSomethingAgain()
{
var name = "Jack Fishing";
var age = 45;
Console.WriteLine($"{name} lives in {CITY}, {STATE} and he is {age} years old!");
}
}
Ok, our program is improved, but we still have issues. What if we wanted to reuse those constants across classes and methods within the entire codebase and not just within the main program? With the current setup, this would not be possible and we would repeat the constant variables declaration in each class where required.
The solution is to place the constants into a separate class responsible only for providing constant values across the codebase and update the variables declared at the top of our program.
A static class to hold our constants:
public static class Constants
{
public const string CITY = "Novi Sad";
public const string STATE = "Serbia";
}
The main program:
class Program
{
private const string CITY = Constants.CITY;
private const string STATE = Constants.STATE;
// removed for brevity
}
Ok, let's move on and see what else we can improve. You might notice that we are repeating the name and age variables. Yes, they have different values across our methods, but we can improve our program by introducing the Person
class for example.
Our class looks like this:
public class Person
{
public Person(string name, int age)
{
Name = name;
Age = age;
}
public string State => Constants.STATE;
public string City => Constants.CITY;
public int Age { get; set; }
public string Name { get; set; }
}
And the updated program looks like this:
class Program
{
static void Main(string[] args)
{
WriteSomething();
WriteSomethingElse();
WriteSomethingAgain();
Console.ReadLine();
}
private static void WriteSomething()
{
var person = new Person("Milos Protic", 30);
Console.WriteLine($"{person.Name} lives in {person.City}, " +
$"{person.State} and he is {person.Age} years old!");
}
private static void WriteSomethingElse()
{
var person = new Person("John Doe", 29);
Console.WriteLine($"{person.Name} lives in {person.City}, " +
$"{person.State} and he is {person.Age} years old!");
}
private static void WriteSomethingAgain()
{
var person = new Person("Jack Fishing", 49);
Console.WriteLine($"{person.Name} lives in {person.City}, " +
$"{person.State} and he is {person.Age} years old!");
}
}
Now we are going something and the next thing we should focus on is the actual method used to write the message in the console. If we look closely, we will see that each Console.WriteLine
function call looks the same. It takes the person properties and concatenates them into a string. So, we can create our own custom method accepting the current person as a parameter and inside of that method write the message to the console.
The new method looks like this:
private static void WriteMessage(Person person)
{
Console.WriteLine($"{person.Name} lives in {person.City}, " +
$"{person.State} and he is {person.Age} years old!");
}
And our program after update looks like this:
class Program
{
static void Main(string[] args)
{
WriteSomething();
WriteSomethingElse();
WriteSomethingAgain();
Console.ReadLine();
}
private static void WriteSomething()
{
var person = new Person("Milos Protic", 30);
WriteMessage(person);
}
private static void WriteSomethingElse()
{
var person = new Person("John Doe", 29);
WriteMessage(person);
}
private static void WriteSomethingAgain()
{
var person = new Person("Jack Fishing", 49);
WriteMessage(person);
}
private static void WriteMessage(Person person)
{
Console.WriteLine($"{person.Name} lives in {person.City}, " +
$"{person.State} and he is {person.Age} years old!");
}
}
Now, we are starting to feel very pleased with the program we have written. It looks good, it's clear and easy to follow. But, we can still improve it :).
For example, if we think about it, we create a person and then pass it to another method just to use it and write the information about it. It seems that the printing of the information could be the responsibility of the person itself, meaning that we could create our WriteMessage
method within the Person
class.
After doing that our updated code looks like this:
public class Person
{
public Person(string name, int age)
{
Name = name;
Age = age;
}
public string State => Constants.STATE;
public string City => Constants.CITY;
public int Age { get; set; }
public string Name { get; set; }
public void PrintInformation()
{
Console.WriteLine($"{Name} lives in {City}, " +
$"{State} and he is {Age} years old!");
}
}
and the program:
class Program
{
static void Main(string[] args)
{
WriteSomething();
WriteSomethingElse();
WriteSomethingAgain();
Console.ReadLine();
}
private static void WriteSomething()
{
new Person("Milos Protic", 30)
.PrintInformation();
}
private static void WriteSomethingElse()
{
new Person("John Doe", 29)
.PrintInformation();
}
private static void WriteSomethingAgain()
{
new Person("Jack Fishing", 49)
.PrintInformation();
}
}
Now the program looks a lot simpler. We still have the same result but in a much more better fashion. As you guessed, there is more stuff we can improve. Ask your self, what can we do to get rid of the methods and don't repeat the user creation in each of them? In my mind, it looks like we should create a list of persons and for each of them invoke the PrintInformation
method.
A neat thing is that our list can be queried from the database and our code would not change and it would produce the same output. The reason for this is that we did a good job designing our codebase. Ok, let's create our list.
class Program
{
static void Main(string[] args)
{
PrintPersonsInformation();
Console.ReadLine();
}
private static void PrintPersonsInformation()
{
var persons = new List<Person>()
{
new Person("Milos Protic", 30),
new Person("John Doe", 29),
new Person("Jack Fishing", 49)
};
persons.ForEach(
person => person.PrintInformation()
);
}
}
The list is in-memory due to our example, but imagine that the source is somewhere else, in the database for example. In that case, we would have something like this:
class Program
{
static void Main(string[] args)
{
PrintPersonsInformation();
Console.ReadLine();
}
private static void PrintPersonsInformation()
{
GePersons()
.ForEach(
person => person.PrintInformation()
);
}
}
Wow, this looks and feels totally different from the initial program! The same result, but far better design, at least I think it is. There are no unneeded variables, no code repetition, every part of the system doing what it is responsible for...we should be proud :)
If you scroll up and take a look at the initial code, and scroll back down to the refactored code, you should be able to see the benefits of following the DRY principle.
Conclusion
So, to summarize, the DRY principle is not just about code repetition, it's about the contextual, single representation of the parts in our system, therefore be aware of that.
That is all I have for this article. If you liked it, please consider supporting me by buying me a coffee. To stay tuned, follow me on twitter or subscribe here on devinduct.
Thank you for reading, and see you soon!
Subscribe to get the latest posts delivered right to your inbox