The Strange Relationship Between Duplication and Coupling
Unless you accept some data duplication here and there, you’re dragging yourself into strange problems related to coupling. Learn when to let it go.
Join the DZone community and get the full member experience.
Join For FreeThis short post hopefully contains no new knowledge for you. Its mere intention is to make you contemplate an interesting relationship between coupling and duplication for a while.
Duplication Is Bad!
As professional programmers, true software craftsmen, the clean coders, [insert your title here], we’re often taught that duplication is bad and should be avoided at all cost. We even have (at least) two widely known principles related to this problem, namely, "Don’t Repeat Yourself" and "Once And Only Once."
The idea behind these principles is obvious — if some piece of information/behavior is represented in two separate places, we have to update both when our understanding changes. If it’s in five places, we have to update all five, and so on. As we all know, this is both annoying and error prone. How the hell am I supposed to track all such places in a codebase with thousands or millions of lines of code?
And so we, professional programmers, eliminate duplication mercilessly whenever we see it.
But So Is Coupling!
The principle is often taken to the extreme where every piece of code that looks like some other piece of code has to be eliminated. A prime example of this being all the systems where the same class is used to model the domain, to be a REST request, to be saved in the database, and sometimes even more. Well, why would I create three classes that look exactly the same, just to, later on, map between the three?
class CreateEmployeeRequest {
String firstName;
String lastName;
BigDecimal salary;
}
class Employee {
String firstName;
String lastName;
BigDecimal salary;
}
class MongoEmployee {
@Id String firstName;
String lastName;
BigDecimal salary;
}
Heck, I even copy-pasted the class when writing this small example! That’s an obvious violation! Kill it!
The thing is, you should actually consider that kind of design. Really carefully. And, as you probably figured out already, you could do that for the sake of decoupling.
Whenever you add a new field to the Employee class, you have to update all three. That’s the obvious downside of duplication. What many people fail to see, is that this kind of “update”, does not have to be exactly the same in all three cases. Let’s consider a simple evolution of the Employee class:
class Employee {
String firstName;
String lastName;
BigDecimal salary;
PaymentStrategy paymentStrategy;
}
This paymentStrategy could be different kinds of payment methods, or different payment intervals, or whatever alike. Anyway, it’s a classical strategy example. How would the two other classes evolve now?
Obviously, we are not going to serialize the strategy as-is. We need some enum or a String, that will appropriately represent the kind of strategy. If we were to use the same class for serializing purposes, our mind will keep suggesting that we drop the pattern altogether and do it some other way.
Now, there are “workaround patterns” like an enum-based strategy, but does that really make your code simpler? Let’s consider the two snippets:
class EmployeeFactory {
Employee create(String firstName, String lastName, BigDecimal salary, String paymentMethod) {
PaymentStrategy paymentStrategy = getPaymentStrategy(paymentMethod);
return new Employee(firstName, lastName, salary, paymentStrategy);
}
private PaymentStrategy getPaymentStrategy(String paymentMethod) {
switch (paymentMethod) {
case "BANK_TRANSFER":
return new BankTransferPaymentStrategy();
case "CASH":
return new CashPaymentStrategy();
default:
throw new IllegalArgumentException();
}
}
}
enum PaymentMethod implements PaymentStrategy {
BANK_TRANSFER {
@Override
public void pay(BigDecimal amount) {
new BankTransferPaymentStrategy().pay(amount);
}
}, CASH {
@Override
public void pay(BigDecimal amount) {
new CashPaymentStrategy().pay(amount);
}
};
}
It’s like… how much time did you save? How much better is the code below than the one above?
And that’s not everything. If the paymentStrategy is required to add an employee, and you’re using the Employee class as a request, you just changed your API contract in a breaking way! How nice of you, huh?
There’s More!
Let’s say that you’re given a requirement to add some information about the organizational hierarchy to the system and display that as a nice “tree”. Simple change, we just need the immediate manager and we can go from there.
class Employee {
String firstName;
String lastName;
BigDecimal salary;
PaymentStrategy paymentStrategy;
Employee manager;
}
The tree does the job and everybody’s happy. As a next step, they ask you to add department information to the few. Another simple change to the Employee class!
class Employee {
String firstName;
String lastName;
BigDecimal salary;
PaymentStrategy paymentStrategy;
Employee manager;
Department department;
}
I hope that there’s a big, red light shining in your head now. We’re filling our simple payroll system with irrelevant information! What’s more, we’re tying the payroll stuff with the organizational stuff by making them use the same Employee class all over the place. That’s bad coupling, again!
In such a case, if you were to think about a possible solution, you might easily end up with this:
package payroll;
class Employee {
String firstName;
String lastName;
BigDecimal salary;
PaymentStrategy paymentStrategy;
}
package organization;
class Employee {
String firstName;
String lastName;
Employee manager;
Department department;
}
That’s… dddd… duplication! Wooops!
Summary
As I said, I hope that nothing here was new to you. At the same time, I find this relationship between coupling and duplication something important enough to mention here. If you were having a hard time justifying to yourself the use of patterns like DTO, or architectural styles such as Clean Architecture, now you have at least two extra arguments. Unless you accept some data duplication here and there, you’re dragging yourself into problems like “I can’t update my domain class, because it will break some contract” or “every single thing in my system depends on this class.” Don’t go there.
Published at DZone with permission of Grzegorz Ziemoński, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments