One of the most critical software principles that I see most violated is the single responsibility principle (SRP) - A module should have a single reason to change.
Some developers violate this rule and explain it by another coding principle: "Do not repeat yourself" (DRY). This principle discourages the usage of duplicate code. It promotes code reuse and consistent behaviors.
Uncle Bob distinguishes between two forms of code duplication:
- Mistakenly (good) - two parts of code look the same but are changed by different actors/ used for different things/ change at different times.
- Real (bad) - same code that changes in both cases at the same time
Code duplication can be solved by extracting a function. When the code in this function changes, all the other cases do as well. Therefore, we won't need to duplicate the changes in all the places (because we may mistakenly miss some).
But if we mistakenly extract a function to different places that do not need to change together (by the same actor/reason) - we create the wrong coupling
Over time, we clutter this function with functionality that is not relevant to all usages / add parameters and conditions. This is because a change in the shared code causes unwanted behavior in one of the cases.
The function becomes more complex. Because it is used in more places, it is more difficult to change. We need to split it, and in order to do it, we first have to debug and understand what parts of the logic are relevant to which case. We need to follow and debug all the cases.
This is more difficult and less safe than just changing one behavior and ignoring another in the most extreme case. Coupling is more expensive to fix than code duplication.
So if we have different modules that are used by different actors that are not related, and hence need to change for different reasons - it's better to decouple the logic and duplicate the code. It's not a DRY violation and it saves us from SRP violation.
There are some known responsibilities that should be taken into consideration:
- Decouple strings you format in the UI from strings that are used in business logic. Regardless of whether you support I18n or not - imagine that you have to support it in your app.
- Don't share enum objects between different business logics.
- Don't share your types across multiple business logics.