[Reading Time: 15mn]
Following my previous post on Technical Debt, some readers argued that yes, it is definitely easy to put the blame on developers. But, put under pressure to deliver business-critical features with time-to-market as the main requirement, it is very difficult to produce “right” code – the kind of code you will not resent a few days later.
I am a big proponent of equipping developers with patterns. This is totally different from using frameworks – I will dedicate another blog post to that topic in the future. As a developer, patterns are maps: given a specific requirement aspect and environment, they reveal the best possible paths of implementation. They are invaluable. And because they are cataloged, shared and well known, they help produce “right” maintainable code more easily.
Environments are key: you won’t use the same patterns in object oriented development (OOD) as you would in functional programming (FP) – these 2 are like extremes actually. One collection of patterns you could use when doing OOD is SOLID. The acronym has been crafted my Michael Feathers after the first 5 principles of OOD by Robert C. Martin, a.k.a Uncle Bob. Taken as a whole, I think these are 5 indispensable patterns to follow in order to reduce the risk of Technical Debt. Here’s a quick rundown of what they are about.
S stands for Single Responsibility (SRP). This one is at the core of OOD: any code entity (a class, a method, a module, etc..) must have just one responsibility. They must answer just one requirement so that, the next time this requirement changes, only this entity will change. It’s a biggy actually. And yes, it will lead to more classes or methods or modules – but it will also dramatically improve the maintainability of your application, and enforce the “design for change” principle I outlined in my previous post.
O stands for Open/closed (OCP). It means that a software entity should be open for extension but closed for modification. A class for example, can allow its behaviour to be altered by way of subclassing / inheriting from it – not by changing its source code. This generally leads to a broader design pattern where APIs are described in interfaces / abstract classes and their implementation reside in concrete classes. Existing interfaces are closed for modifications but can be reused through inheritance. This is the “polymorphic” interpretation of the Open/Closed principle introduced by above mentioned Uncle Bob.
L stands for Liskov Substitution (LSP). It means that objects in an application can be exchanged with instances of their subclasses without impacting the correctness of that application. So in the case where I have a plain vanilla Bond object in my program, I should be able to substitute it for say, a Perpetual Bond object because the latter inherits from the former, it is a specialised version of it. Of course, your class hierarchy must be designed specifically in order to enforce the Liskov principle. But the reward is that indeed, your program will become more extensible – and less “Technical Debt” prone.
I stands for Interface Segregation (ISP). You could summarise it with “One client, one interface”. Favour many client-dedicated interfaces as opposed to a global, general-purpose interface. This principle is actually based on the Single Responsibility principle already discussed. But attention, it might be counter-intuitive for some cost-minded managers: developing several interfaces *sounds* way more expensive than developing just one. But of course, it is not about developing “just one”: if you go the general-purpose route, you will have to fit all client-dedicated requirements into just one entity, which will need to be changed each time one client has change requests. More (brittle) code, more complexity, more risks. And think of the clients whole will be forced to know about aspects or methods which aren’t of interest to them.
D stands for Dependency Inversion (DIP). Simply put, this is about depending upon abstractions and not upon concretions. There are many examples but say you need to log code execution details. To do that, you should depend on a generic log interface and use some runtime mechanism in order to access a concrete log implementation through the interface. This mechanism is often times based on well-known patterns like Dependency Injection, Service Locator or Plugin. It allows you to separate the glue code from the logic of your application, and also, to switch and reuse concrete implementations without changing your code.
As a conclusion, I would like to clarify one point: I am not sure that OOD is the best possible approach for all the Line-Of-Business application development needs faced by Corporations. Even more so in the context of Technical Debt. Some might even go further and say that the OOD paradigm incur more Technical Debt and that it might be safer to switch to other paradigm for some specific requirements – I don’t want to start a flame war, just to announce that I will be posting on that topic in the future!
But let’s get real: there are already a ton of OOD applications, and the mainstream languages / frameworks used today (Java, .NET) use the OOD paradigm – which means that more of them are to come. In this context, our developers need to be equipped with the best possible design tools – and the SOLID patterns discussed here are definitely part of them. And because they are extensions to the OO design principles themselves, they should have a very low cost of adoption.
Until next time.