I was attempting to make some changes to some fairly old code in our codebase (something I probably wrote myself…) which hasn’t been touched on for a while.
Naturally, my first step is to understand what the code does, so I started by looking at the class where I need to make my changes.
Perhaps unsurprisingly, I couldn’t figure out how it works.. The class contains only a handful of override methods, but I have no idea how they fit together.
So I started digging deeper through several layers of abstract classes, each filling in parts of the puzzle, until I reached the base of this class hierarchy.
By this point, I’m staring at a control flow full of strategically placed gaps. Going back-and-forth along the class hierarchy several times, I ended up with a vague sense of how the various pieces of logic scattered across the hierarchy fit together.
What’s more, where we needed to deviate from the control flow dictated by the base class, we have had to reinvent a brand new control flow mid-hierarchy, making it even harder for me to understand what’s going on.
This is not where I want to be… I want to be able to reason about my code easily, and with confidence.
I suspect a great many of you have experienced similar pains in the past, but how do you go about applying Composition over Inheritance?
Wikipedia’s definition and example of Composition over Inheritance focuses only on domain modelling, and I’m generally not a fan of conclusions such as:
To favor composition over inheritance is a design principle that gives the design higher flexibility, giving business-domain classes and more stable business domain in the long term.
In other words, HAS-A can be better than an IS-A relationship.
What does this even mean?!? Are you able to back up these claims of “high flexibility” and “more stable business domain in the long term” with empirical evidence?
In my view, the real benefit of Composition over Inheritance is that it encourages better problem decomposition – if you don’t break up the problem into smaller pieces (that are easier to tackle) first you have nothing to compose with later on. Scott Wlaschin’s railway oriented programming approach is an excellent example of how to apply composition in a practical and elegant way.
And the challenge of problem decomposition is not limited to code organization. Microservices are all the rage right now, and the move from monolithic architectures to microservices is another example of problem decomposition, albeit one that happens at a higher level.
So I’ll be taking knife to the aforementioned class hierarchy and replacing them with small, composable units, using a language that is a great fit for the job – F#!
Will you follow my lead?
I’m an AWS Serverless Hero and the author of Production-Ready Serverless. I have run production workload at scale in AWS for nearly 10 years and I have been an architect or principal engineer with a variety of industries ranging from banking, e-commerce, sports streaming to mobile gaming. I currently work as an independent consultant focused on AWS and serverless.
Here is a complete list of all my posts on serverless and AWS Lambda. In the meantime, here are a few of my most popular blog posts.
- Lambda optimization tip – enable HTTP keep-alive
- You are thinking about serverless costs all wrong
- Many faced threats to Serverless security
- We can do better than percentile latencies
- I’m afraid you’re thinking about AWS Lambda cold starts all wrong
- Yubl’s road to Serverless
- AWS Lambda – should you have few monolithic functions or many single-purposed functions?
- AWS Lambda – compare coldstart time with different languages, memory and code sizes
- Guys, we’re doing pagination wrong