DRY and SRP


Introduction#

Over the years, I have gone through degrees of understanding the underlying principles, disciplines, and advice for being an effective software engineer. One instance of this is my understanding of the relationship between DRY (Don’t Repeat Yourself) and SRP (Single Responsibility Principle). How can these two possibly coexist as rules that are universally true? How could a central piece of code be written only once and have only one reason to change? This seems like an impossibility. However, let’s take another look at both of these principles and learn how exactly they can harmonize.

Conflicting Example#

Consider a piece of software for a shoe company. Customers can purchase their shoes online and pick them up at a nearby store location. The company is largely managed by some software that displays the items available at particular stores, allows the purchase of those items, then informs the selected store to reserve the items for the customer.

Somewhere inside this monolith, we have a piece of code that gets the quantity of a particular item.

getItemQuantity(itemId: string): number;

The getItemQuantity method is used both for displaying the available quantity to the user on the website and for notifying the warehouse when to ship more of a particular product to the store. Seems like this code is DRY, right?

Alas, life is not that simple. The business is unfortunately having problems. Customers are currently seeing products as unavailable on the website because their local store has 0 quantity. However, this company’s warehouses deliver to each store multiple times a day. It’s normal for the stores to have 0 quantity of a product on the shelves and yet have it restocked for pickup later in the day. Because of this discrepancy, the company is losing sales to customers who could have ordered a product and picked it up, remaining completely unaware of the internal, logistical details.

We update our code so that now getItemQuantity can retrieve the quantity of an item, but will change how it calculates the quantity according to which system is requesting it.

getItemQuantity(itemId: string, storeId: string | null): number

The user-facing website now calls getItemQuantity('abc', '123') because the website wants to know the quantity of item abc available at store 123. The warehouse calls getItemQuantity('abc', null) because they need to know how many items they need to send to the store to restock them.

We did it! we are DRY! but wait, this also just revealed that the method is violating SRP. The getItemQuantity method can change for two different reasons: the needs of the warehouse and the needs of the website users. How do we resolve this conflict?

DRY#

What does “Don’t Repeat Yourself” mean? Taken literally, it could mean that no code should ever be duplicated regardless of intent. If some code is getting the quantity of a product, for instance, then the retrieval of a product quantity should never be written again. However, that interpretation of DRY will lead you astray. Intent of the code matters. The DRY principle states the following:

Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.

“Every piece of knowledge…” is the key phrase here. DRY isn’t telling you not to repeat code. It’s telling you not to repeat knowledge! Two pieces of code can look exactly the same as each other; however, if each chunk of code is responsible for being the authority of a different piece of knowledge then it doesn’t matter if the code looks the same. In fact, once we consider this scenario through the combined lenses of DRY and SRP, we see that these chunks of code must be separated.

Conflicting Example Resolution#

Looking at the same example from above with our new understanding of DRY’s focus on knowledge as opposed to code, we can draw some conclusions. We were wrong to attempt to combine both pieces of functionality under the same method with the arguments altering its behavior. We are violating SRP by having a single function changed by two conflicting systems. So we should split it into two methods.

function getItemQuantityInStore(itemId: string, storeId: string): number {
    throw new Error("Not implemented");
}

function getItemQuantityInWarehouse(itemId: string): number {
    throw new Error("Not implemented");
}

Is this still DRY? Yes, it is. Although the implementations of each of these methods may look very similar, the knowledge that each represents is distinctly different. This sets us up for a better future by making our methods easily changeable. Their similarity in code is a temporal coincidence.

Conclusion#

DRY and SRP harmonize well with each other. They are complimentary: each principle augmenting the other and guiding good behavior. Incorporating both into your development will make your code easier to change and to maintain. That is exactly our goal when engineering software.