I have quite often seen situation where trying to achieve more reuse actually ends up in building the Big Ball of Mud.
In some cases it is the Enterprise Domain Model that Eric Evans warns us against. Other places which are especially susceptible to this are Transaction Scripts and other procedural thingies including all sorts of Service classes.
For example, here is a Service called
TransferService which has following methods:
Even without knowing the details of these methods it looks like we have mixed different contexts together.
create are probably something related to setting up a new transfer.
getStatus seem to deal with problem solving and tracking of the transfer. Having
extendedDetails could be a sign that we have different representations of a
transfer that are probably useful in different contexts.
Now if we look at the usages of this kind of Service then obviously everybody is using it. We have achieved our ultimate goal — it is used across the entire system. So pat on the back and congratulations on a job well done?
Why is This Bad?
First, such class is probably quite big. Everyone who wants to use it for their use case needs to filter out all the irrelevant parts of the API to find the specific thing they actually need.
With size comes higher probability of duplication. It is hard to determine what is already there and what not. Hence we are more likely going to add new stuff that is already there.
Finally, we are less likely to refactor it over time due to the extensive usages and size.
How to Avoid?
First thing is that we have to be able to actually notice this kind of situation. Smells in unit tests are generally quite good indicators of having bad design in production code as well. In my previous post I wrote about some ideas how to use unit tests for improving production code.
It is always useful not to let any class grow too big. I have found ~120 lines to be max size of a test class and I think production class should follow similar limit.
Don’t obsess about reuse (also see why over-reuse is bad). It is ok to have some duplication. Especially when we are not sure yet if similarity is accidental or we are indeed dealing with the same concept. Often it seems we have a method that does almost what we need. In that case an easy option is just to parameterize that existing logic — just introduce some
if inside the method. Sometimes this is ok. However, the risk is that this may lead to mixing things that evolve due to different forces at different speed. In that case a better alternative is to find something on a lower level that can be reused completely without any parameterization or just go ahead with little bit of duplication.
Establish some high level bounded contexts which each deal with their own specific problems. Don’t reuse across these boundaries.
Originally published at tech.transferwise.com on April 26, 2017.