Skip to main content

DRY Principle

Introduction

Duplicate Icon

The DRY principle, which stands for "Don't Repeat Yourself," is a software development principle that aims to avoid duplicating information. The principle states that every piece of knowledge or logic should have a single, unambiguous representation within a system.

This principle is usually taken to include code duplication, but that isn't actually what it was originally intended to mean. The original intent was to avoid duplication of knowledge, not just code. Modern interpretations have expanded the principle to also include processes and documentation.

WET Principle

The WET principle, which stands for "Write Everything Twice" or "Waste Everyone's Time" or even "We Enjoy Typing" is a humorous take on the DRY principle. This shouldn't be taken seriously, but it's a fun way to remember the DRY principle.

References

The examples used here were adapted from:

Importance of the DRY Principle

Duplicate code can come from a variety of sources, such as copy-pasting code, not reusing code, not abstracting common functionality, or simply not knowing the codebase well enough. This can lead to a variety of problems, such as:

  • Maintenance: When a bug is found in duplicated code, it must be fixed in multiple places, which can be time-consuming and error-prone.
  • Consistency: Duplicated code can lead to inconsistencies in the codebase, as changes made in one place may not be reflected in other places.

While a small amount of duplication may not be harmful, excessive duplication will make defects more likely, bugs will be harder to fix, and the codebase will be harder to maintain.

Information Duplication

The most problematic form of duplication is information duplication. This occurs when the same information is stored in multiple places, such as in code comments, documentation, or configuration files. When this information changes, it must be updated in multiple places, which can lead to inconsistencies and errors.

Even if these errors only appear as a mismatch between the code and the comments, they can still be harmful. For example, if a comment says that a function returns a value in a certain format, but the function actually returns a different format, this can lead to confusion and bugs. This is why comments that describe what the code does are often considered harmful, as they can become out of date and misleading.

More harmful is when the information duplication is part of the code itself. For example, an important value might just appear as a magic number in multiple places in the code. If this value changes, it must be updated in multiple places, which can be error-prone.

Example

Consider the following code snippet, these two functions calculate the processing fee and total cost of an item:

fun processingFee(itemPrice: Double): Double {
// charge a 5% processing fee
val marketplaceFee = 0.05
return itemPrice * marketplaceFee
}

fun totalCost(itemPrice: Double): Double {
// charge a 5% processing fee
val marketplaceFee = 0.05
return itemPrice * (1 + marketplaceFee)
}

The duplicate code in this case is quite obvious: the marketplaceFee value is repeated in both functions. Also note that the totalCost could also be calculated as itemPrice + processingFee(itemPrice), further reducing duplication.

const val MARKETPLACE_FEE = 0.05 // 5% processing fee

fun processingFee(itemPrice: Double): Double {
return itemPrice * MARKETPLACE_FEE
}

fun totalCost(itemPrice: Double): Double {
return itemPrice + processingFee(itemPrice)
}

The refactored code ensure that any changes to the marketplace fee is only defined in one place, making it easier to maintain and less error-prone. It also eliminate the need to repeat the calculation of the processing fee in the totalCost function.

While this example is quite simple, the DRY principle becomes more important as the codebase grows in size and complexity. Information duplication should always be avoided. Define values in just one place, and reuse them as needed.

Code Duplication

Code duplication is the most common form of duplication, and it occurs when the same code appears in multiple places in the codebase. The convenience of copy-pasting code is the most common cause of code duplication, but it can also occur when developers don't know that a piece of code already exists, or when they don't know how to reuse code.

Consider this simple class that defines an Animal, and contains logic to print the animal's details and store them in a file:

class Animal(val name: String, val species: String, val size: String) {

fun printAnimal() {
println("Animal name: $name")
println("Animal species: $species")
println("Animal size: $size")
}

fun storeAnimalInFile(fileName: String) {
val animal = "Animal name: $name\n" +
"Animal species: $species\n" +
"Animal size: $size\n"
File(fileName).writeText(animal)
}
}

The code duplication here is a little more subtle, but it's still present. The printAnimal and storeAnimalInFile functions both contain the same logic for formatting the animal's details. This logic should be extracted into a separate function to avoid duplication.

class Animal(val name: String, val species: String, val size: String) {

fun printAnimal() {
println(serializeAnimal())
}

fun storeAnimalInFile(fileName: String) {
File(fileName).writeText(serializeAnimal())
}

override fun serializeAnimal(): String {
return "Animal name: $name\n" +
"Animal species: $species\n" +
"Animal size: $size\n"
}
}

We can do even better by overriding the toString function, which is a standard way to represent an object as a string in Kotlin:

class Animal(val name: String, val species: String, val size: String) {

fun printAnimal() {
println(this)
}

fun storeAnimalInFile(fileName: String) {
File(fileName).writeText(this)
}

override fun toString(): String {
return "Animal name: $name\n" +
"Animal species: $species\n" +
"Animal size: $size\n"
}
}
note

This class is actually pretty bad, as it violates the Single Responsibility Principle. It's doing too much: it's storing the animal's details in a file, printing the animal's details, and representing the animal as a string. A better design would separate these concerns into different classes. Building a data class to represent the animal, and a separate class to handle the serialization and deserialization of the animal would be a better design.

Taken Too Far

While less common, the DRY principle can be taken too far. There are cases where duplication is actually beneficial, such as when it has a significant performance impact. A more common example is when code is only superficially similar, but has different requirements or is likely to change in different ways.

For example, consider the following two classes, Animal and Car. They both contain logic to build an HTML string that represents the object:

class Animal(val name: String, val image: String, val cost: Double) {

fun buildAnimalDivString(): String {
return "<div>\n" +
" <p>$name</p>\n" +
" <img src=\"$image\"/>\n" +
" <p>Buy for \$$cost</p>\n" +
"</div>"
}
}

class Car(val name: String, val image: String, val cost: Double) {

fun buildCarDivString(): String {
return "<div>\n" +
" <p>$name</p>\n" +
" <img src=\"$image\"/>\n" +
" <p>Buy for \$$cost</p>\n" +
"</div>"
}
}

Adding a common superclass to these classes seems like a good way to reduce duplication:

abstract class Product(
val name: String,
val image: String,
val cost: Double) {

fun buildDivString(): String {
return "<div>\n" +
" <p>$name</p>\n" +
" <img src=\"$image\"/>\n" +
" <p>Buy for \$$cost</p>\n" +
"</div>"
}
}

class Animal(name: String, image: String, cost: Double) :
Product(name, image, cost)

class Car(name: String, image: String, cost: Double) :
Product(name, image, cost)

The duplicate code is now gone, but consider what happens when the requirements for Animal and Car diverge. For example, what if we need to add a breed property to Animal, and make and model to Car? The buildDivString function will now have to handle these differences, which can lead to complex and error-prone code. Maybe we could add a classification method to each subclass that we could use in the div string.

abstract class Product(
val name: String,
val image: String,
val cost: Double) {

abstract fun classification(): String

fun buildDivString(): String {
return "<div>\n" +
" <p>$name</p>\n" +
" <img src=\"$image\"/>\n" +
" <p>Buy for \$$cost</p>\n" +
" <p>${classification()}</p>\n" +
"</div>"
}
}

class Animal(
name: String,
image: String,
cost: Double,
val breed: String) : Product(name, image, cost) {

override fun classification(): String {
return breed
}
}

class Car(
name: String,
image: String,
cost: Double,
val make: String,
val model: String) : Product(name, image, cost) {

override fun classification(): String {
return make + " " + model
}
}

If sometime later we were asked to add a color property to Car that needed to be displayed separately, we would have to change the buildDivString function again, possibly needing to check the type of the object to determine how to display the color. Checking which subclass you have is definitely a Code Smell and may be a sign of a bad abstraction. For this case the apparent code duplication implied that the classes were equivalent, which is not the case. This issue could have been avoided by talking to the product owner about the class.

The Code of Duplicate Code is Much Lower than the Cost of a Bad Abstraction

Conclusion

The DRY principle is an important software development principle that aims to avoid duplicating information. Duplicate code can lead to a variety of problems, such as maintenance issues and inconsistencies in the codebase. While a small amount of duplication may not be harmful, excessive duplication can make defects more likely, bugs harder to fix, and the codebase harder to maintain. Information duplication should always be avoided, and code duplication should be minimized.

The DRY principle is not a hard and fast rule, and there are cases where duplication is actually beneficial. It's important to strike a balance between avoiding duplication and creating complex and error-prone abstractions.

Image Credits

Duplicate icons created by Freepik - Flaticon