What Makes Good Code? (3/7)

João Bertolino
The Startup
Published in
5 min readSep 9, 2020

A code of principles

This is the third section of an article about code practices. In the first section, I introduced that the motivation for this article was a weak job interview and I talked a little about the SOLID principles in it, here I will have the chance to expand on this subject. The origin of the SOLID principles is tied to Object Orientation and despite all the valid criticism that Object Orientation has received over the years, SOLID principles are still valid, and they have a broad application. SOLID is an acronym and each letter stands for a principle, I will give an overview of each principle and how they can be applied in programming and real life.

S stands that classes must have a single purpose, i.e. Single responsibility. Consider a class “User” (if you do not know what a class is, read the second section), its responsibility is to hold user information. It must not contain authentication or load user data from Database, even if it seems handy in some situations. In real life, you can imagine a drawer where you put all your socks, so you should not put pants in it. It is the socks drawer, so its responsibility is to hold socks only.

Single responsibility socks drawer

O stands for open/closed principle, when you are building some software you must consider that people will want to expand on it and add new features. This principle says that your Classes must be open for extension, but closed to modification, in other words, you must be able to add new things or change something without breaking its single responsibility contract. Let us stick with our User Class, imagine now that the user can upload a photo. So, the User class needs to have a reference to that photo, but the User class is in a compiled library that is not allowed to add a new member property. So, you must come with a solution that allows that in the first place. One solution is to have Aggregation, but we will talk about it later. The principle itself states that it is important to decouple the system in a way that extension shall not require a change in the core logic. In real life you might think of a cabinet and there are places to put the drawers, but there should be nothing that ties a specific drawer to the cabinet, because if you had a damage in this drawer you would have to modify the cabinet to make a drawer replacement which makes no sense.

A drawer mechanism is open for extension and it is decoupled

L stands for Liskov substitution principle. It states that a subtype of a Class must replace the original Class without compromising the properties of the software. This is fundamental to implement proper object inheritance, but using subtypes creates hierarchy structures and we should use this with caution. If we want to make a special type of User, we can create a child class called SpecialUser. We can add different behaviors to this new class, since all the methods of the base type User maintain the same purpose. You must maintain the contract of a Class in its Subtypes. In our cabinet example, you can build different kinds of drawers, but they need to have the same size and connections to fit in the same spot that the original drawers were.

I stands for Interface segregation principle. This principle ties back to the single responsibility principle but for your interfaces and the next principle. Interfaces are good to express an abstract concept, so let us say that we want to order some User instances, how can we easily do that? Many programming languages have their own library for sorting with the most performant algorithm available today, so we don’t have to reimplement it (remember, don’t repeat yourself), but the API programmers don’t know your class User, how can you use this algorithm? You can simply implement the interface they built. For example, in C#, there is the IComparable interface that requires you to implement a single method that compares the current instance to another arbitrary instance. So, the principle states that interfaces must be small and deal with a specific feature. This has an interesting relation to real life problems. You could think about the utilities that you have as a class that implements a lot of interfaces, some better than others. For example, a knife could implement ICut, ICarve interfaces. Instead of looking for a specific tool to solve a problem, you should look for abstractions that you need to solve your problems. You could list all the abstractions and see the minimal tools that you need in your life to solve the problems that you have. This way you could minimize the effort to acquire the things you need.

So many interfaces implemented here… maybe not a good idea

D stands for Dependency inversion principle. This is a powerful principle that says: classes should depend on abstraction not in implementation. This is the heart of decoupling in code. A lot of design patterns derive from this principle like Delegation, Factory and many more. Let us suppose we get data from the User class that is in a database, but we want to be able to change the database. It is important to have an abstract provider that can be implemented in any data source. This is also important for testing. If your class has a direct reference to a database you will always need to have this database running, but if it has a dependency to an abstraction, we can just mock an implementation to test a part of the software. In real life, you could think that you do not have to depend on the actual stuff, but in the value they provide. I was getting into pizza cooking and I had trouble choosing the right flour. So, I stuck with a specific brand. Then, I watched a YouTube video where a chef explained that you must look at the proportion of protein in the flour to check if it is good for pizza. Now, I do not have a dependency on a specific brand, I just need to know if flour provides the value that I am looking for.

So, if those principles are useful for your daily life, they can be useful in any aspect of computer problems like file storage, database, distributed system and so on. In the next section, we will be discussing a broad concept of how to apply these principles in your code.

--

--