4 ms·
I think a key distinction often lost here is that generic code and abstract code are different. Abstract code hides details, generic code allows its use in more
by seadan83 3y ago
I think a key distinction often lost here is that generic code and abstract code are different. Abstract code hides details, generic code allows its use in more places. When hiding details, often it also becomes more generic. Making code generic does not necessarily hide details, it can very well often expose additional details
Also seemingly not mentioned - SRP (single responsibility principle). SRP & DRY should be considered together. If a person DRY'ies up code without regard to SRP, they're making any code that can be generic, generic. A rule of thumb is generic code is 3x more expensive than non-generic code.
==============
To illustrate, here is an example (and pretend that these examples are duplicated in 20 different places that all need the account balance sum):
--------------
Example (1) - non-generic, non-abstact
```
int savingsBalance = 1;
int checkingBalance = 1;
int totalBalance = savingsBalance + checkingBalance;
```
--------------
Example (2) - generic, minimally abstract
```
int savingsBalance = 1;
int checkingBalance = 1;
int totalBalance = addBalances(savingsBalance, checkingBalance);
```
--------------
Example (3) - abstract, potentially generic:
```
int totalBalance = addBalances();
```
===============
Now consider what happens if we need to add a 'brokerage account balance' to the mix (and let's say we get that value via an API call). These example change in the following ways:
Example (1), updated:
```
int savingsBalance = 1;
int checkingBalance = 1;
int brokerageBalance = fetchBrokerageBalance();
int totalBalance = addBalances(savingsBalance, checkingBalance, brokerageBalance);
```
Example (2), updated:
```
int savingsBalance = 1;
int checkingBalance = 1;
int brokerageBalance = fetchBrokerageBalance();
int totalBalance = addBalances(savingsBalance, checkingBalance, brokerageBalance);
```
Example (3), updated & unchanged:
```
int totalBalance = addBalances();
```
Example (1) & Example (2) have similar scaling behavior here (scaling relative to complexity). This illustrates a very key difference between abstract and generic code.
Now, let's say on another hand that whether we should include brokerage balance is conditional. In example 1, we have the same logic to be applied in 20 different places. We can mutate example 3 to be more generic (EG: pass in flag - `addBalances(Flags.includeBrokerageAccount)`). At this point we can say that the abstraction is wrong and needs to be split into different methods (which is fine!). Making example 3 more generic is more complex, we incur the penalty of having generic code. Example 1 is arguably the worst to have since we will get subtle errors if we fail to update everything. In part these design principles are also there to help protect updates and make them safe (very similar to the ACID guarantees of database that help make it so you can update data without breaking the overall database)
Another mention, which I won't go into detail over, boiler plate code has yet different characteristics.
In sum, it's largely a question of what kind of coupling is best and how to deal with that coupling. Duplicated code is coupled without any runtime or compile time checks that it stays in sync (if you forget to update something from example 1 above, it's a bug!). Keeping code consolidated into a common procedure does not remove that coupling, it just changes the nature of the coupling and makes it more explicit. Common code between micro-services couples those micro-services together (and that can be very bad).
Thus, we need to look at a lot of things when applying DRY: we need to consider SRP, whether we are coupling services together, and whether or not if we are simplying making non-generic code generic.
- seadan83 3y agoTypo in that updated Example (1), should have been: int totalBalance = savingsBalance + checkingBalance + brokerageBalance; It's hard to explain such complicated concepts super concisely. What I'm getting is that DRY is often equated to merely making code generic and re-used, while the goal of DRY is not at all about re-use. Generic code is more complicated than non-generic code, thus if we make code generic for the sake of making it used in many places - that is likely going to make things more complex. It's a fundamental misunderstanding that DRY is simply the act of using human pattern matching to make all similar looking code generic and re-used. Instead DRY is more about: "are we sanitizing data before sending it to the front-end? Than that should be done in one place." "Where are we configuring database connections?" etc.. Further DRY should not be the only guiding factor, SRP & coupling should always be considered at the same time as DRY.