13

I'm looking for guidance about DRY vs Code coupling. I do not like to duplicate my code and I also do not like code coupling between unrelated modules. So I refactor duplicate code if I find identically duplicate code a year after the duplication was introduced. However, I have increasingly experienced situations where the real world is much more unpredictable, and after refactoring the code, there arise situations which require forking out the code again.

For example, if I had code to handle gasoline cars, gasoline SUVs, electric cars and electric SUVs, lets say I refactored duplicate code into the "gasoline" hierarchy and the "electric" hierarchy, both descending from the "vehicle" hierarchy. So far so good. And then, my company introduces a hybrid car and a hybrid Semi - that would require core changes to my original hierarchy itself. Maybe it would require "composition" between the gasoline and the electric hierarchies.

Clearly code duplication is bad because it increases the time taken to implement a change common to all the above products. But refactoring common code makes it equally hard to introduce product-specific variations, and leads to a lot of "class-jumping" when one has to find the line of code to fix a bug - one change in a higher-level parent class could trigger trigger regression bugs among all descendants.

How does one strike an optimal balance between DRY vs unwanted code coupling?

  • 1
    It’s always good to not following rules blindly but engaging the brain first. – gnasher729 Jun 1 '18 at 7:23
  • fair enough - hence guidelines, not rules. – user2549686 Jun 1 '18 at 7:37
  • Have you read the Wiki page on DRY (or OAOO, as it used to be called)? wiki.c2.com/?OnceAndOnlyOnce That's where a lot of the initial discussion on it happened. (Actually, Ward invented the Wiki specifically for discussions about Patterns and Practices.) – Jörg W Mittag Jun 1 '18 at 8:56
  • Very related answers, even if the question does not quite sound like a duplicate: softwareengineering.stackexchange.com/questions/300043/… – Hulk Jun 1 '18 at 10:01
7

lets say I refactored duplicate code into the "gasoline" hierarchy and the "electric" hierarchy, both descending from the "vehicle" hierarchy. So far so good.

And then, my company introduces a hybrid car and a hybrid Semi - that would require core changes to my original hierarchy itself. Maybe it would require "composition" between the gasoline and the electric hierarchies

I think this is one of the main reasons people are moving towards composition over inheritance.

Inheritance forces you into large restructuring when you have a conceptual change like the one you describe.

When the restructuring is 'too big/hard' then people write duplicate code to avoid it.

Rather than remote the duplicate by moving the code up the inheritance chain you can move it out into a helper class or service and then inject that class as part of a composition where required.

Whether the resulting design is OOP is open to debate

  • 1
    +1 for pointing out that inheritance is the problem, not DRY. The easiest way to reuse code is to remove unnecessary dependencies, and all too often people miss that fact that a parent class (and it's descendants) are all dependencies when using inheritance. Only when you reduce or eliminate dependencies do you actually get reusable code. – Greg Burghardt Jun 1 '18 at 17:30
  • 3
    "Whether the resulting design is OOP is open to debate". Everything is open to debate. The question to ask is, is it open to sensible, rational debate? The answer is no. I think your last paragraph detracts from an otherwise very good answer. – David Arno Jun 1 '18 at 18:48
  • @davidarno dont really follow you, i read OOD in the title as Object Orientated Design? so im addressing the question without trawling through the debate – Ewan Jun 1 '18 at 19:53
  • 1
    @Ewan: I am here with David Arno. I think it is well known for more than 2 decades that the believe "if there is no inheritance, then it is not OOP" is a fallacy. – Doc Brown Jun 1 '18 at 20:11
  • jeeze this is exactly the kind of discussion i was trying to avoid. there are views on both sides, there is no definitive answer – Ewan Jun 1 '18 at 23:05
3

You are correct, following the DRY principle, can increase coupling between otherwise unrelated modules. Especially in larger software systems, this can lead to situations where not following DRY can be the better alternative.

Unfortunately, your example is not well suited to demonstrate this - the problems described there are caused by classical errors in wrong suboptimal usage of inheritance. However, for what I have written above, it does not matter if you refactor common code to a common base class, or into helper class (composition). In both cases, one might be forced to put the common code into a library L and reference that library from two formerly unrelated programs A and B.

Let us assume A and B were completely unrelated before, they can be versioned, released and deployed independently. By putting common code into a shared lib L, however, new requirements for A may induce changes to L, which may now cause changes to B. So this causes the need for additional tests, and probably a new release and deploy cycle for B.

So how can you handle this situation, if you are not willing to abandon from the DRY principle? Well, there are some well known tactics to approach this:

  1. Either keep A, B, and L as part of the same product, with one common version number, a common build, release and deploy process, with a high degree of automation

  2. or make L a product on its own, with minor version numbers (no incompatible changes) and major version numbers (maybe containing breaking changes), and let A and B allow each one to reference a different version line of L.

  3. Make L as SOLID as possible, and care for backwards compatibility. The more modules in L can be reused without modification (OCP), the less likely breaking changes will occur. And the other principles in "SOLID" are helping to support that goal.

  4. Use automatic tests especially for L, but also for A and B.

  5. Be careful what you put into L. Business logic which should exist only in one place in a system is a good candidate. Things which just "look alike" and might vary differently in the future are bad candidates.

Note when A and B are developed, maintained and evolved by different, unrelated teams, the DRY principle becomes quite less important - DRY is about maintainability and evolvability, but letting two different teams provide individual maintenance effort can be sometimes more effective than tying their products together because of a little bit of reusage.

So in the end, it is a tradeoff. If you want to follow the DRY principle in larger systems, you need to invest much more effort into creating robust, reusable components - typically more than you expect. You have to trust your judgement when it is worth it, and when not.

1

DRY is yet another structural rule. That means if you take it to extremes it's as bad as if you ignore it. Here's a metaphor to get you out of the structural mind set.

Love your twins. Kill the clones.

When you blindly copy and paste to avoid keyboard typing you're mindlessly creating clones. Sure they do what you want but they weigh you down because now changing that behavior is so expensive.

When you reproduce identical behavior with identical code because of a different responsibility happens to have the same need now but that may subject you different changes you have a twin that should be free to change and grow as an individual as time goes on.

You can scan their DNA (code) all you like. Clones and twins are hard to tell apart if all you do is look at them. What you need is to understand the context they live in. Why they were born. What their ultimate fate is likely to be.

Let's say you work for company ABC. It's run by three company execs, A, B, and C. All of them want you to make a software product but they each have their own departments. They all want you to start small. Just output a simple message for now. So you write "Hello World".

A hates it, wants you to put the company name there.

B loves it, wants you to leave it alone and add the company name to a splash screen.

C just wants a calculator and thought the message was just a way for you to get started.

One thing you're sure of, these guys have very different ideas of what this project is about. Sometimes they're going to agree. Sometimes they're going to pull you in different directions.

When you duplicate code because you're creating the ability for that code to vary independently you are creating a twin. When you duplicate code because typing is a pain and copy and paste is easy you're creating evil clones.

A, B, and C are different masters that your code must serve. No one line of code can serve more than one master for long.

  • First of all, many thanks to all, for all the comments. – user2549686 Jun 11 '18 at 6:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.