Saturday, September 26, 2020

List of books (3rd edition)


  • Book cover
    Refactoring: Improving the Design of Existing Code (2nd Edition)

    Refactoring is one of those books that contains a lot of useful and practical wisdom. Martin Fowler presents the techniques of refactoring as a set of well-defined mechanics that can (and should) be applied to daily programming to improve the readability and maintainability of existing codebases. I had a good experience reading it from cover to cover. However, I think it would be equally good to read only the first few chapters in order, leaving the catalog to be read later as each refactoring is needed.
  • Book cover
    The Manager's Path: A Guide for Tech Leaders Navigating Growth and Change

    The Manager's Path guides you through a journey from learning how to be managed to the complexities of being a CTO. With an articulate writing style, Camille Fournier shares excellent words of wisdom and, as she focuses specifically on engineering management, delivers content that is much more valuable to technologists than generic management books. Even those software engineers who do not intend to pursue a management career will benefit greatly from reading the book, especially the early chapters on how to be managed, what to expect from a manager, mentoring and being a tech lead.
  • Book cover
    Building Evolutionary Architectures: Support Constant Change

    Rebecca Parsons and her colleagues do a great job bringing many concepts and ideas from evolutionary computing — and biological evolution in general — into the world of software architecture. They don't take a position of defending their approach as the best way (or the right way) to build software, but rather show what kind of considerations should be made if a software architect or engineering team wants to put architectural evolution among their priorities when developing systems. Highly recommended.

Composition and multiple inheritance

One of my lasts posts is a quote from Martin Fowler's Refactoring, in which he says that "inheritance has its downsides. Most obviously, it's a card that can only be played once. If I have more than one reason to vary something, I can only use inheritance for a single axis of variation.".

After some time, I started to wonder if this problem is not solved by multiple inheritance. Fortunately, this is an issue addressed by Steven Lowe's post Composition vs. Inheritance: How to Choose?, in which he argues that inheritance relationships should not cross domain boundaries (implementation domain vs. application domain). Thus, although inheritance can be used for mechanical and semantic needs, using it for both will tangle the two dimensions, confusing the taxonomy. In that sense, even when inheritance is a card that can be played multiple times, you should think carefully if it's not the case to actually play it only once.

Saturday, September 19, 2020

Replace superclass with delegate

In object-oriented programs, inheritance is a powerful and easily available way to reuse existing functionality. I inherit from some existing class, then override and add additional features. But subclassing can be done in a way that leads to confusion and complication.

One of the classic examples of mis-inheritance from the early days of objects was making a stack be a suclass of list. The idea that lead to this was reusing of list's data storage and operations to manipulate it. While it's good to reuse, this inheritance had a problem: All the operations of the list were present on the interface of the stack, although most of them were not applicable to a stack. A better approach is to make the list into a field of the stack and delegate the necessary operations to it.

This is an example of one reason to use Replace Superclass with Delegate — if functions of the superclass don't make sense on the subclass, that's a sign that I shouldn't be using inheritance to use the superclass's functionality.

As well as using all the functions of the superclass, it should also be true that every instance of the subclass is an instance of the superclass and a valid object in all cases where we're using the superclass. If I have a car model class, with things like name and engine size, I might think I could reuse these features to represent a physical car, adding functions for VIN number and manufacturing date. This is a common, a often subtle, modeling mistake which I've called the type-instance homonym.

These are both examples of problems leading to confusion and errors — which can be easily avoided by replacing inheritance with delegation to a separate object. Using delegation makes it clear that it is a separate thing — one where only some of the functions carry over.

Even in cases where the subclass is reasonable modeling, I use Replace Superclass with Delegate because the relationship between a sub- and superclass is highly coupled, with the subclass easily broken by changes in the superclass. The downside is that I need to write a forwarding function for any function that is the same in the host and in the delegate — but, fortunately, even though such forwarding functions are boring to write, they are too simple to get wrong.

As a consequence of all this, some people advise avoiding inheritance entirely — but I don't agree with that. Provided the appropriate semantic conditions apply (every method on the supertype applies to the subtype, every instance of the subtype is an instance of the supertype), inheritance is a simple and effective mechanism. I can easily apply Replace Superclass with Delegate should the situation change and inheritance is no longer the best option. So my advice is to (mostly) use inheritance first, and apply Replace Superclass with Delegate when (and if) it becomes a problem.

Martin Fowler, "Replace Superclass with Delegate", in Refactoring: Improving the Design of Existing Code (2nd Edition), 399-400.

Replace subclass with delegate

If I have some objects whose behavior varies from category to category, the natural mechanism to express this is inheritance. I put all the common data and behavior in the superclass, and let each subclass add and override features as needed. Object-oriented languages make this simple to implement and thus a familiar mechanism.

But inheritance has its downsides. Most obviously, it's a card that can only be played once. If I have more than one reason to vary something, I can only use inheritance for a single axis of variation. So, if I want to vary behavior of people by their age category and by their income level, I can either have subclasses for youg and senior, or for well-off and poor — I can't have both.

A further problem is that inheritance introduces a very close relationship between classes. Any change I want to make to the parent can easily break children, so I have to be careful and understand how children derive from the superclass. This problem is made worse when the logic of the two classes resides in different modules and is looked after by different teams.

Delegation handles both of these problems. I can delegate to many different classes for different reasons. Delegation is a regular relationship between objects — so I can have a clear interface to work with, which is much less coupling than subclassing. It's therefore common to run into the problems with subclassing and apply Replace Subclass with Delegate.

There is a popular principle: "Favor object composition over class inheritance" (where composition is effectively the same as delegation). Many people take this to mean "inheritance considered harmful" and claim that we should never use inheritance. I use inheritance frequently, partly because I always know I can use Replace Subclass with Delegate should I need to change it later. Inheritance is a valuable mechanism that does the job most of the time without problems. So I reach for it first, and move onto delegation when it starts to rub badly. This usage is actually consistent with the principle — which comes from the Gang of Four book that explains how inheritance and composition work togheter. The principle was a reaction to the overuse of inheritance.

Those who are familiar with the Gang of Four book may find it helpful to think of this refactoring as replacing subclasses with the State or Strategy patterns. Both of these patterns are structurally the same, relying on the host delegating to a separate hierarchy. Not all cases of Replace Subclass with Delegate involve an inheritance hierarchy for the delegate, but setting up a hierarchy for states or strategies is often useful.

[...]

This is one of those refactorings where I don't feel that refactoring alone improves the code. Inheritance handles this situation very well, whereas using delegation involves adding dispatch logic, two-way references, and thus extra complexity. The refactoring may still be worthwhile, since the advantage of a mutable premium status, or a need to use inheritance for other purposes, may outweigh the disadvantage of losing inheritance.

Martin Fowler, "Replace Subclass with Delegate", in Refactoring: Improving the Design of Existing Code (2nd Edition), 381-398.