One of the most common terms in software engineering is the DRY principle (Don’t Repeat Yourself), but many engineers associate this principle with “Don’t copy-paste lines of code.” This is a part of DRY; however, DRY is about duplication of knowledge.
But first, why is duplication evil?
- The apparent issue with duplication is the unhealthy growth of your code. In other words, there are many duplicated lines in your code, so it becomes difficult to browse your code.
- In the software lifecycle, the change specification or the maintenance of the project, e.g., by fixings bugs, is more likely to happen. Then, if your code isn’t DRY, you are going to make changes in different places. This isn’t the problem; the real problem is how many places you are going to forget.
- Once you have read code that isn’t DRY, you are going to find yourself describing it as non-modular or violating SOLID principles, and it doesn’t respect any fancy word used to describe well-written code.
Now that we are aware that duplication is evil, what does this DRY (Don’t Repeat Yourself) principle say?
“Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.” — from The Pragmatic Programmer book
After a brief introduction about DRY, let’s discuss some DRY code smells and find ways to fix it.
Note: Attempt to spot code smells from the first code snippet in each section contains code smells, before reading explanations and code suggestion (second snippet).
1. Duplication In Code
void printTransaction() {
if (isProduct) {
if (isFromVendor)
System.out
.println(String.format("Product %s : - %10.2f $", name, amount));
else
System.out
.println(String.format("Product %s : + %10.2f $", name, amount));
} else {
if (isFromVendor)
System.out
.println(String.format("Service %s : - %10.2f $", name, amount));
else
System.out
.println(String.format("Service %s : + %10.2f $", name, amount));
}
}
This is a common case of duplication. It’s pretty evident that there is a duplication of formatting and duplication of determining “+/-” signs to represent in/out transactions. Below is a suggestion on how we can improve this code:
void printTransaction() {
String transactionType = isProduct ? "Product" : "Service";
System.out.println(getformattedTransaction(transactionType));
}
String getformattedTransaction(String transactionType) {
String formattedAmount = isFromVendor ? String.format("- %10.2f", amount)
: String.format("+ %10.2f", amount);
return String.format("%s %s : %s $", transactionType, name, formattedAmount);
}
2. Not All Duplications Are DRY
boolean isUsernameValid() {
return username.length() > 5;
}
boolean isPasswordValid() {
return password.length() > 5;
}
This is a tricky example. As we saw in the introduction, DRY is about knowledge duplication rather than code. It’s purely a coincidence that the username and password in the example both satisfy the same criteria of validity, but nothing prevents modifying the criteria of validating the password, for example, without affecting the criteria of validating the username, simply because password and username are independent entities.
So, before grouping a duplicate code, consider this question: “Is it a duplication or just a coincidence?”
3. Duplication in Documentation
// This method calculates the final price of a product
// By adding to the product's cost the taxes and the transportation cost
// Tax percentage is 20%
// & Average transportation cost is 200 $
double finalPrice(double productCost) {
return productCost + productCost * 0.2 + 200;
}
As a student, I fell into this mistake, believing that adding comments would make my code clearer. However, most of the time, like in the example above, it leads to duplication of information.
Let’s suppose that the product price formula changes regularly due to some external factors (e.g., regulations, changes in transportation cost, etc.). To keep the code coherent, the developer needs to update the formula twice (code and comment), but from my experience, it’s more likely to update the code and forget about the comment, which can lead -in our case- to two different formulas.
As a refactor to the code above, it’s preferable to get rid of comments and move magic numbers with a well-named constant.
static final double TAX_PERCENTAGE = 0.2;
static final double AVERAGE_TRANSPORTATION_COST = 200;
double finalPrice(double productCost) {
return productCost + productCost * TAX_PERCENTAGE
+ AVERAGE_TRANSPORTATION_COST;
}
This section is not about getting rid of all comments in your code. But keep in mind that comments are helpful when used to answer the “Why” question.
Finally, documentation is not restricted to comments but includes all forms of documentation. For example, if you are developing a public API or a library and you need to provide documentation, think of using a documentation generator such as Sphinx for Python.
4. Duplication in Data
class StopWatch {
private LocalDateTime startDateTime;
private LocalDateTime endDateTime;
private Long durationInMilliSecond;
// Constructor
// getters & setters
}
The example above seems to be fine, but it’s very likely to have bugs as this design allows developers to modify start/end DateTime and forget to modify duration. Therefore, the solution for this kind of duplication is to make the duration calculated when it’s needed.
class StopWatch {
private LocalDateTime startDateTime;
private LocalDateTime endDateTime;
// Constructor
// getters & setters
public Long getDurationInMilliSecond() {
return ChronoUnit.MILLIS.between(startDateTime, endDateTime);
}
}
Conclusion
As we’ve seen, DRY is not limited to copy-paste code, but it’s about duplication of knowledge.
I hope you found this article insightful. Thank you for reading.
Please make sure to follow up to get the updates.
You can also find me elsewhere on the internet.
DRY: The Unspoken Parts was originally published in Better Programming on Medium, where people are continuing the conversation by highlighting and responding to this story.