14

Have you ever encountered a case of code duplication where, upon looking at the lines of code, you couldn't fit a thematic abstraction to it that faithfully describes its role in the logic? And what did you do to address it?

It is code duplication, so ideally we need to do some refractoring, like for example making it its own function. But since the code doesn't have a good abstraction to describe it the result would be a strange function that we can't even figure out a good name for, and whose role in the logic is not obvious just from looking at it. That, to me, hurts the clarity of the code. We can preserve clarity and leave it as it is but then we hurt maintainability.

What do you think is the best way to address something like this?

4 Answers 4

18

Sometimes code duplication is the result of a "pun": Two things look the same, but aren't.

It is possible that over-abstracting can break the true modularity of your system. Under the regime of modularity, you have to decide "what is likely to change?" and "what is stable?". Whatever is stable gets put in the interface, while whatever is unstable gets encapsulated in the module's implementation. Then, when things do change, the change you need to make is isolated to that module.

Refactoring is necessary when what you thought was stable (e.g. this API call will always take two arguments) needs to change.

So, for these two duplicated code fragments, I would ask: Does a change required to one necessarily mean the other must be changed as well?

How you answer that question might give you better insight into what a good abstraction might be.

Design patterns are also useful tools. Perhaps your duplicated code is doing a traversal of some form, and the iterator pattern should be applied.

If your duplicated code has multiple return values (and that's why you can't do a simple extract method), then perhaps you should make a class that holds the values returned. The class could call an abstract method for each point that varies between the two code fragments. You would then make two concrete implementations of the class: one for each fragment. [This is effectively the Template Method design pattern, not to be confused with the concept of templates in C++. Alternatively, what you are looking at might be better solved with the Strategy pattern.]

Another natural and useful way to think about it is with higher-order functions. For example, making lambdas or using anonymous inner classes for the code to pass to the abstraction. Generally, you can remove duplication, but unless there really is a relation between them [if one changes, so must the other] then you might be hurting modularity, not helping it.

0
4

When you encounter a situation like this, it's best to think about "non-traditional" abstractions. Maybe you have a lot of duplication within a function and factoring a plain old function doesn't fit very well because you have to pass too many variables. Here, a D/Python style nested function (with access to the outer scope) would work great. (Yes, you could make a class to hold all that state, but if you're only using it in two functions, this is an ugly and verbose workaround for not having nested functions.) Maybe inheritance doesn't quite fit, but a mixin would work well. Maybe what you really need is a macro. Maybe you should consider some template metaprogramming or reflection/introspection, or even generative programming.

Of course, from a pragmatic point of view, these are all difficult if not impossible to do if your language doesn't support them and doesn't have enough metaprogramming capabilities to implement them cleanly within the language. If this is the case, I don't know what to tell you except "get a better language". Also, learning a high-level language with lots of abstraction capabilities (like Ruby, Python, Lisp or D) might help you program better in lower-level languages where some of the techniques might still be usable, but less obvious.

1
  • +1 for a lot of excellent techniques compressed in a tight space. (Well, would have been +1 for the techniques described too.)
    – Macneil
    Nov 11, 2010 at 4:35
3

Personally I ignore it and move on. Chances are if it is that odd a case it is better to have it duplicated, you could spend ages refactoring and the next dev will take one look and undo your change!

2

Without a code sample, it's difficult to say why your code has no readily identifiable abstraction. With that caveat, here's a couple ideas:

  • instead of creating one new function to hold the common code, break the functionality into several distinct pieces;
  • group small pieces together based on common data types or abstract behavior;
  • rewrite the duplicate code given the new pieces;
  • if the new code still defies a clear abstraction, break it up into small as well and repeat the process.

The largest difficulty in this exercise is that your function is likely incorporating too many unrelated behaviors at a given abstraction level, and you need to handle some of them at lower levels. You correctly surmise that clarity is key to maintaining the code, but making the behavior of the code clear (its current condition) is very different from making the intent of the code clear.

Make the how of the smaller code pieces abstract by having their function signatures identify the what, and the larger pieces should be easier to classify.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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