SOLID programming

“Bad programmers worry about the code. Good programmers worry about data structures and their relationships.” — Linus Torvald.

Disclaimer 1 : I’m french (for historical reasons and because of food too). So to make this reading more realistic, read this with a slight french accent.

Disclaimer 2 : I will say that it’s my interpretation of SOLID guideline’s, and it may differ in many ways with the original precept of Robert C. Martin.

There are many best practices in programming, and there are some for each language, paradigm, environment… Here, I want to highlight a few special ones (five in fact), which can be used in almost any Object Oriented Design / Language (learn once, apply many times).

Too long, didn’t read

SOLID is an acronym standing for :

Single responsibility principle : What goes into one class, what goes into another ? A class should have only a single reason to change. By doing so, you have a clear and nice object model.

: What goes into one class, what goes into another ? A class should have only a single reason to change. By doing so, you have a clear and nice object model. Open / closed principle : How to extend your code. Entities should be open for extension, but closed for modification. You should never change anything, always adding new behavior by static or dynamic polymorphism. By doing so you minimize change in your code base, thus minimizing the chance of breaking things.

: How to extend your code. Entities should be open for extension, but closed for modification. You should never change anything, always adding new behavior by static or dynamic polymorphism. By doing so you minimize change in your code base, thus minimizing the chance of breaking things. Liskov substitution principle : When and how to inherit from a class. You should be able to substitute any parent class with any of their children without any behavior modification. Same benefits as the previous one.

: When and how to inherit from a class. You should be able to substitute any parent class with any of their children without any behavior modification. Same benefits as the previous one. Interface segregation principle : How to specify an interface. Many client specific interfaces are better than a big one. An interface should be the minimal set of behaviors (or functions) you need. By enforcing that, you limit the cost of each implementation and limit dependency.

: How to specify an interface. Many client specific interfaces are better than a big one. An interface should be the minimal set of behaviors (or functions) you need. By enforcing that, you limit the cost of each implementation and limit dependency. Dependency inversion principle : How to deal with dependency. Your project shouldn’t depend of anything, make those things depend of interfaces. You should design a wrapper around your dependency. By doing so you have a near perfect isolation between your code and the rest of the universe.

All of that help you to design your object model. By enforcing those rules, you will have a better designed code. More robust, more flexible. Easier to maintain and extend.

Before anything else, why ?

Yeah, another best practices, more rules, more guidelines… I hear you. But we will not talk about coding style, bracket, naming conventions… It’s not about semantics, syntax or other language / source related stuff. These principles are almost independent from the language and will help you to make a better object model for your project.

Even though all the examples shown here are in C++, you can apply them to almost all Object Oriented Programming language. Those principles are intended for helping you to make a more flexible and understandable code. If you are not convinced yet, SOLID principles were in the core philosophy of AGILE development or adaptative development, so understand SOLID principles and you will be better to enforce or apply AGILE or adaptatives practices. And I just want to say somewhere that these practices are a subset of many principles promoted by Robert C. Martin.

Single responsibility principle

This first principle help you to design classes. It explain how to define boundaries of a class : what behavior (or function or whatever) should be in your class or not. Following this rule reduce bad coupling and enforce code isolation. With this you limit spaghetti code.

A class should have only a single responsibility.

Robert C.Martin explains that a responsibility is a “reason to change”. You may simplify that in something like “anything should do one thing”, and you will not be wrong. For functions, methods, it can (and should) be achieved. But for classes, it can become a nonsens (a car can turn and move, two separate things). The key is behind the concept of responsibility.

A responsibility is a family of functions that serves one particular actor.

An actor for a responsibility is the single source of change for that responsibility.

Those sentences explain how to identify feature boundaries at a code level. A responsibility is a feature, a capacity (print something, save something, …). An actor is the specification of this responsibility (how to print, how to save / retrieve, …). Finally a class should have only a single responsibility means that a class expresses only one feature, and this feature has only one reason (source) of change.

The usual example is the case of a class describing some state or data, and the need to display it in one form or another. In this case there is two separate things, the data itself, and how to print it. So you need two classes, one responsible for the data and one responsible for the output. Thus, if you need to change the output, the changes are contained in one class, and data are isolated. In the other hand, changing the data will limit change to spread because all other class use the public interface. If you change the interface, it’s another story (go to Open / close principle).

By doing so, you will specify very accurately each class, each interface. You will enforce a very strong code isolation and cohesion. When your specification will evolve, you will need less change in a much smaller portion of your codebase. And since every class is responsible for one single thing, it will be much easier to name it, to design tests for it, and to document it. So much win in a single rule !

Open / close principle

This principle describes how your code should evolve as your specification will change. It helps you to prepare the path for future modification. In short, you never change anything, you extend your code base. In a more concrete way (yet partially false at this point) : never change a function, make a new one and change the call. Following the previous principle (Single responsibility) will help you to apply this one.

Entities should be open for extension, but closed for modification.

I think it’s my favorite one, the simplest yet sometimes a little counter intuitive. Keywords here are extension and modification. Extension means adding a behavior, adding some code to an existing code base. Your modification should be kept as small as possible if you must change something. By following the first principle of SOLID, you have already a nice object model, and every class has a single responsibility. So for each entities of those class, you just have to identify what can be subject to a change, and design it open for extension : that is to say, make it overridable (static or dynamic polymorphism). At the end, everything should be extensible, by a way or another (deeply depending of your programming language), and almost nothing should be modified.

Taking our previous example, in the case we need to extend our data, the technical answer is simple. Since we have noticed that data can change, we mark the set method overridable ( virtual keyword in C++) and each method in the Data class is designed to ignore every thing it can’t understand in the data field. By doing so, if we need to extend our Data type, we can inherit from the existing class, override the set method and expose more fields. The two new classes are substitutable (see Liskov substitution principle) and changes are kept as little as possible (nearly nothing in our case). The existing printer do not change at any point, and we just create a new one to extend our code.

The benefit here is you keep change as minimal as you can. By doing so you minimize the chance of breaking thing and it helps you in the holy goal of retro compatibility. It doesn’t break existing behavior, all older test should pass without any problem and you shouldn’t need to mess with half of your code base at each evolution.

Liskov substitution principle

This rule explains when you can inherit from a class, and when you don’t. Or at least how to modify your object model to do the right thing. You should be able to substitute any parent class with any of their children without behavioral modification (it can imply some code modification, but any behavior must remain the same).

You should be able to substitute any parent class with any of their children without behavioral modification.

Sometimes, the natural languagecan fool you from an object model perspective. In many case, when your see some “B is aspecialised A”, you think B should inherit from A, and it make sense to you. But when you code it, some strange behavior and corner cases may appear very soon. The usual example is “a square is a special case of a rectangle” in mathematics, but not in an object model context. A rectangle have an independant height and width, not a square.

So a rectangle cannot be substitutable by a square, it breaks the Liskov principle. Maybe those two class should be inherited from a third class (like Shape or whatever), but they are not related like you thought.

Benefits may be less obvious here, but they are worth it. By following Barbara Liskov advice (yes she is a woman, I think it is worth mentioning this), you will achieve a better encapsulation leading to a more efficient use of polymorphism. It fits particularly well with the next principle.

Interface segregation principle

This one saves you from fat interfaces, which become difficult and costly to comply with. Normally you should never have to implement a function (behavior) that you do not need. Having many client specific interface is better than a big (fakely) generic one. An interface should be the very minimal set you can expose. Like usual, following the previous rules grantly help you following this one.

An interface should be the minimal set you can expose.

Let’s start with an example. Imagine an Actor class in a video game project. It is by definition an interface that will grow exponentially during the lifetime of your project. You will add to this class a collision interface, a way to display it, followed by a movement system and whatever suits your needs. Each time you add some capabilities to this interface, anything inheriting from the Actor interface must be updated, and very few of them will really need to implement all of those behaviors. In the other hand, a simpler actor (like a light, or I don’t know) will be full of empty methods (like draw or collide or anything else), messing with your object model (oh, I can do gravity.draw() ?). At the end of the journey, you will finish with an interface looking like a fatty interface, full of methods vaguely related between themselves, introducing a lot of useless coupling everywhere in your code. You will end with what we call a God Class, a class that knows and can access nearly everything. It’s a very bad case in Object Programming.

But there is another way : split the fatty interface in many minimal ones. You need a collision system, let’s create an interface just for this. You want to add some display capabilities, new interface. Any new independent behavior (or at least not strongly related to an existing one), new interface. With that, anytime you will create a new concrete class for your game, you will inherit from each interface you want to implement. In some languages (like Java or C#) it can be non-trivial to implement concrete classes this way without multiple inheritance (like in C++); but by creating an interface inheritance tree, you can work around many languages limitations.

By designing your interfaces this way, you will enforce a better code isolation everywhere those interfaces are used. Object managers and others high level behaviors will be simpler and will have access only to their relative stuff, limiting useless coupling. You will have a better and stronger encapsulation. In the meantime, you will take full advantage of dynamic polymorphism (and the static one too, but less).

Dependency inversion principle

The last principle is the easiest to implement and use. But sometimes it can be hard to impose to a team (and harder to impose to a manager). It states that you should not depend on libraries (or other external stuff), but they should depend on an interface. In a more direct way, you should make a (little) wrapper around external dependencies and only use the wrapper’s interface. By doing so you have a near perfect isolation between your project and your dependency.

Abstractions should not depend on details. Details should depend upon abstractions.

When you use some library (the STL can be the only exception to this statement), the short path is to include the header everywhere it’s needed, and call directly the external function. It works relatively well, and it is designed for that. Until the library changes just a little and breaks your code. Now you have unknown changes to perform everywhere in your code (depending on the library, but it could become a nightmare very fast). And less often, but it can happen, a change on your side can interact badly with some external stuff and break everything. The obvious answer is to make a wrapper between your project and the library. The less automatic thing, but it should be, is to design and use this wrapper from the very beginning.

You will profit from that in two major way. First it forces you to think about what you really need from this interface : what is the minimal set you can use ? (See Interface segregation principle). It will lead to the conclusion that it exists a better (smaller) library usable and it would be very good. You will notice it before making things harder to reverse. Secondly, on the long run, you will isolate modifications occurring in the project or the library : any change to one or another will be limited to the wrapper. This design permits you to reverse the depency graph : your project doesn’t depend anymore on any external stuff, it’s the external stuff whom depends from the wrapper.

Profit

When you can (and trust me, you will not be in a case in which you can’t anytime soon), apply all SOLID principles : the overall quality of your object model will be greatly increased. Code quality will be improved too, but in a less direct way (I think code quality depends on other highly ponderated concepts like coding rules, language use, etc…).

Better encapsulation, more cohesion, low coupling and strongest isolation : all of those holy grail of programming can be approached (if not achieved) through those principles. It could sometimes imply more work to do, but it will be always a win in the long run. Your project will be more flexible and any extension (no change, remember ?) would be like absorbed by your code base.

Further reading

Ludovic Falcot, développeur C++ dans l’agence Extia PACA.