Geschreven door Ian van Nieuwkoop

Don't Repeat Yourself and Don’t Pay Inheritance Tax

Development6 minuten leestijd

We all write code which ends up frustrating us or our colleges sooner or later. Sometimes you copy-paste some code and don’t get around to tidying it up or you make a design decision which seems like a good idea at the time, but later turns out to be a time-consuming mistake.

In this blog I’m going to describe two sources of frustration, with the focus on Java, when trying to change code: duplication and unnecessary coupling caused by inheritance.

DRY – Don't Repeat Yourself

It can be very frustrating when you need to make a change and you think: “I am finished", only to find out that you need to make the same change in a dozen other places.

When designing a system, you should always follow what is called the DRY (Don’t repeat yourself) principle: Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.

However, this doesn’t mean every identical line of text or code is duplication. It could just be the case that the implementation happens to be identical.  

DRY is about the duplication of knowledge; it is found in:

  • Documentation
  • Functionality
  • And of course, Code

It’s easy to forget that documentation also needs to be updated, along with the changes you make to your codebase. The only thing worse than no documentation is wrong documentation. Out-of-date documentation is very misleading and can cost you a lot of unnecessary time.

Comments are just another form of duplication. Functions and variables with clear names do not need comments.  Comments inside of methods are a sign the method is too large and should be split into private methods, with descriptive names.

Developers who don’t know what their colleges are building are likely to write similar code. They end up duplicating functionality which already exists.

This can only be reduced by better communication. Active and frequent communication during the daily standup, forums etc. You should read other people’s source code and documentation, either informally or during code reviews.

Finally, duplication in code. It’s best to start small. First, by locating and extracting the smaller pieces into separate methods. Then you can more easily start locating and removing the larger pieces of duplicate code. Remembering, of course, to run unit tests after each significant change, to prevent having to roll back too many changes.

Move duplication across multiple classes, to its very own class. After extraction this new class needs to be coupled to the classes using it. In Java you basically have two options:

  • Option 1: A “has-a” relationship. The original classes can be given an instance of the new class as a private attribute. This is also called composition or delegation, because you delegate functionality to components of a class.
  • Option 2: A “is-a" relationship. The original classes can extend the new class.

When choosing the option to implement, the most important thing to take into consideration is: Inheritance Tax.

Inheritance Tax

You need to make another change, and again you think you’re done. However, you find out you just broke the code in a dozen other places.

Fortunately, there are several best practices when it comes to reducing unnecessary coupling. One of which is:  Don’t pay Inheritance Tax. Inheritance is a form of coupling and therefore must be avoided if unnecessary and most the time, this is the case.

Paradoxically, this is not what you have probable been taught. Using inheritance for behavioral composition and polymorphism is a common practice you find in every OOP textbook. However, in practice it has some serious disadvantages.

Firstly, you might not directly realize this, but when a class is extended it creates coupling between multiple concrete classes. Not only is a child class coupled to a parent, the parent’s parent, and so on. But also, any object which uses this child is now not only coupled to this child, but also to all the ancestors. 

Secondly, in a limited domain, it makes sense that a Car is a kind of Vehicle. However, as soon as the domain you are working in becomes more complicated and closer to reality, these models become more complex and nearly impossible to model. For example, what happens when a Car is also a kind of Asset, InsuredItem, and so on. This needs multiple inheritance, not only isn’t that possible in some programming languages, like Java, but when available you end up with complex models that end up just causing you a headache.

Instead use one of these techniques, so that you never need to use inheritance again:

Interfaces. Implementing Interfaces give us polymorphism without inheritance. These declarations don’t normally create any code and a class can implement an unlimited number.

Delegation. By implementing a class instance as an attribute, you can hide all the unnecessary methods of the class you aren’t using. Significantly reducing its complexity. However, this comes at the cost of writing more code.

Mixins. The basic idea of a mixin is simple: you want to be able to extend classes and objects with new functionality without using inheritance. Say you have two related functions called ‘find(id)' and ‘findAll()' and you want multiple classes to be able to use these functions. But you don’t want to use inheritance and delegation adds unnecessary code. If the language you are programming in has this feature built in, it would look something like this:

// in sudo code

mixin CommonFinders {

  def find(id){ … }

  def findAll(){ … }

}

 
class AccountRecord implements BasicRecord with   CommonFinders { … }

This isn’t available in Java. However, since Java 8 we can define Interfaces with (public) default methods. This allows us to add methods with implementations to interfaces. Java 9 added the ability to use private methods in interfaces, enabling us to have nice clean default methods. In Java we could emulate a mixin using an interface as followed:

// in Java 9+ code

public interface CommonFindersMixIn {

  default Record find(String id) { … }

    default Record findAll() { … }

 

// some private methods

// private void example () { … }

}

class AccountRecord implements   BasicRecord, CommonFindersMixin { .. }
 

One problem remains. Because private attributes are still not allowed in interfaces, this limits your options when writing complex methods. In this case you will have no choice but to use delegation and be forced to write a bit more code.

Summary

Duplication and inheritance are nothing more than anti-patterns. Practices which only have short-term advantages, but eventually end up causing more harm than good. Clean code is not only concise and void of duplication, but it also definitely doesn’t extend concrete classes unless absolutely necessary.

References
David Thomas and Andrew Hunt (2020): The Pragmatic Programmer.
Michael Feathers (2005): Working Effectively with Legacy Code.
Nicolo Pignatelli (2018): Inheritance Is Evil. Stop Using It.