Tuesday, April 27, 2010

Inheritance in a large and diverse codebase

Numerous authors (Alexandrescu, Myers, Lippman, LaJoie, Stroustrup, etc) have discussed inheritance and composition issues and the advantages of each. Their feeling vary only slightly culminating in the common mantra "prefer composition over inheritance."

Inheritance has the effect of tightly coupling child classes to their parent such that changes to the parent necessarily mean changes to the child. Usually this is desired, but this can lead to unintended behavior changes in the child classes. Generally, we want code to be as dynamic as possible at runtime meaning that we want state to change. Once a class is instantiated, it cannot be changed and is thus less dynamic than if a class contained another allowing you to modify the state.

Deep inheritance trees are worse than no inheritance trees. Stroustrup correctly identifies that an inheritance tree over three layers deep becomes very difficult to maintain and debug. Anything beyond seven (I have seen this) is madness and nearly impossible to understand. Creating large inheritance hierarchies for the sake of maximal reuse or adding behaviors in stages simply makes your code very difficult to use. But he does go on to say "The fact that inheritance can be misused and overused is a reason for caution; not a reason for prohibition" (C++ Programing Language, 2nd ed)

In large codebases, engineers simply don't have time to learn every inheritance tree sufficiently and thus containment is preferred. If the need arises, one can always convert containment to inheritance, which I have had occasion to do recently. However, converting from inheritance to containment is very difficult. Also containment is easier to understand and debug.

With all of that said, sometimes containment doesn't make any sense at all. A canary is a bird and only under the strangest design considerations would a canary have a bird. In a large codebase, inheritance does add semantic clarity and also makes some forms of polymorphism not only logical but preferable.

So, in conclusion, use inheritance sparingly, prefer containment where possible, and avoid deep inheritance hierarchies and you will make your large codebase much easer to understand.

No comments: