57

I'm working on a software project where we have to build three APIs. One for the home banking channel, one for the agency channel and a third for the mobile channel.

The agency API is the most complete one as it has all the functionalities .. then a bit smaller Home API and then mobile API.

The architects here made a common layer (cross channel EJB services shared by all APIs). But then the APIs are different.

There is no big difference for now between the APIs. The big team started with the agency channel, and we are adapting it now for home channel. We are just enriching objects specifically to our home app. Otherwise, the code is 95% similar between APIs. The APIs is build on top of Spring MVC, and it has (controllers, models & some utilities).

Basically the controllers are doing the mapping BO to ChannelObject (it seems to me not the right place to do that), and some extra utilities and serializers. All is duplicate for now. They are saying that the reason for duplication is they want the APIs independent. "If tomorrow we want a different behaviour for home than agency or mobile we won't struggle!!"

Is there a case where we should accept duplicate code?

4 Answers4

88

Sandi Metz, a renowned software engineer and author in the Ruby ecosystem, has a great blog post and a talk where she also talks about the relationship between duplication and abstraction. She comes to the following conclusion

duplication is far cheaper than the wrong abstraction

And I completely agree with her. Let me give you more context to this quote. Sometimes finding the right abstraction is very difficult. In such cases it is tempting to just go for any abstraction to reduce duplication. But later you might find out that your abstraction doesn't hold for all cases. However, it is costly to change everything again and to go a different route (for a far better explanation watch her talk!).

So yes, for me, there are exceptional cases, where accepting duplication is the right decision, especially when you are not sure of what is to come and requirements are likely to change. From your post I take that there is much duplication now, but your colleagues suggest that this might change and to not couple both application to each other. In my opinion this is a valid argument and can't be disregarded in general.

larsbe
  • 938
70

Duplication can be the right thing to do, but not for this reason.

Saying "we might want these three places in the code base to behave different even though right now, they're identical" is not a good reason for large-scale duplication. This notion could apply to every system, and it could be used to justify any duplication, which is obviously not reasonable.

Duplication should be tolerated only when removing it would be overall more costly now for some other reason (can't think of a good one right now, but be assured there can be one - virtually everything in programming is a trade-off rather than a law).

For what you're doing, the right solution could be e.g. extracting the behaviour that is duplicated right now into a Strategy or some other pattern that models behaviour as classes and then use three instances of the same class. That way, when you do want to change the behaviour in one of the three places, you only have to create a new Strategy and instantiate that in one place. That way you only have to add some classes and leave the rest of the code base almost completely untouched.

Kilian Foth
  • 110,899
34

If people start reasoning about design with the words "if tomorrow", this is often a big warning sign for me, especially when the argument is used to justify a decision which includes extra work and effort, for which noone really knows if this will ever pay off, and which is harder to change or revert than the opposite decision.

Duplication of code reduces the effort only for a short term, but it will increase the maintainance efforts almost immediately, proportional to the number of duplicated lines of code. Note also that once code is duplicated, it will become hard to remove the duplication when it turns out this was the wrong decision, whilst if one does not duplicate code now, it is still easy to introduce duplication later if it turns out sticking to DRY was the wrong decision.

Said that, in larger organizations, it is sometimes beneficial to favor independency of different teams over the DRY principle. If removing the duplication by extracting the 95% common parts of the APIs two a new component leads to a coupling of two otherwise independent teams, this might not be the wisest decision. On the other hand, if you have limited resources and there will be only one team maintaining both APIs, I am sure it will be in their own interest not to create any double effort and avoid any unnecessary code duplication.

Note further it makes a difference if "Home" and "Agency" APIs are used by fully different applications exclusively, or if one might try to write a component build on top of those APIs which can be used in a "Home" context as well as in an "Agency" context. For this situation, having the common parts of the APIs exactly identical (which you can only guarantee if the common parts are not duplicated), will make the development of such a component probably much easier.

So if it turns out there will be really different sub teams, each one responsible for each of the APIs, each one with a different schedule and resources, then it is time to duplicate the code, but not "just in case".

Doc Brown
  • 218,378
14

Duplication to prevent coupling. Let's say that you have two big systems and you force them to use the same library. You may be coupling the release cycle of both systems. This may not be too bad but let's say that one system needs to introduce a change. The other needs to analyze the change and may be affected. Sometimes it may break things. Even if both parties are able to coordinate the changes it could be a lot of meetings, going through managers, testing, dependencies and the end of the small autonomous team.

So you are paying the price of duplicated code to gain autonomy and independence.

Borjab
  • 1,339