Agile Software development — SOLID Principles

Elizabeth Le
JOOR Engineering
Published in
7 min readAug 15, 2022

by Irene Fernandez

The only valid measurement of code quality: WTFs/minute (source: Clean Code Robert C. Martin)

Learning how to produce good code is a difficult job. There are some great examples of progress when people with good intentions collaborate together in a society.

For example, one of the most famous and popular IDEs, Visual Studio Code, is an open source project. It has great documentation on how to collaborate to the codebase and nowadays it has over 1600 contributors! React and Django are also collaborative projects and they are widely used in web development. Talk about some good intentions!

You can see this same concept applied in code that is produced in a software development team.

The code that we write will be later worked on by our fellow companions, and we will work on their code. If we don’t care about the quality of the code, we are adding clutter to our code and in the end we will be working a code with an insane amount of wtf/minute which will infuriate us and produce even worse code!

Lets see some examples of bad quality code and their consequences:

On September 23, 1999 the Mars Climate Orbiter (formerly the Mars Surveyor ’98 Orbiter) was lost on its way to Mars. The reason? A miscalculation in its trajectory caused by a mismatch between imperial units (e.g. feet, inches) and metric units (e.g. meters). The spacecraft descended too low into Mars atmosphere and either burnt up or bounced off into space.

The estimated total cost of this error was around $650,000,000.

While this is an extreme example, bad quality code does have an huge impact on tech companies.

Firstly, the codebase will likely have bugs, which fixing requires takes time and money. A buggy product will cause dissatisfaction among clients, which will reduce income and generate bad reviews. As a result we will likely be facing the feared “tech debt”.

Technical debt, typically referred to as tech debt, is cutting corners to save time in the short-term and leaving problems to be fixed at a future time.

It’s like cleaning your apartment by stuffing everything in your closet. You cleaned up quickly, but when you open the closet to look for something, everything is disorganized and will spill out the door. In the end, you’ll be doing more work in the long run than if you had just tidied up your apartment properly in the first place.

There are several principles and patterns which can help us become a “good citizen” and improve our software day by day. These sets of principles give several guidelines for creating software that is scalable, easy to maintain and clean. One of the most known sets is called SOLID principles.

SOLID principles are 5 basic principles for object-oriented programming, explained in “Agile Software Development: Principles, Patterns, and Practices” by Robert C. Martin.

SOLID stands for :

  • Single responsibility principle
  • Open-closed principle
  • Liskov substitution principle
  • Interface segregation principle
  • Dependency inversion principle

1. Single Responsibility Principle

“The Single Responsibility Principle (SRP) states that each software module should have one and only one reason to change.”

This statement means that each module should only take care of one task. A module shouldn’t work like a multi-functional Swiss army knife, because that increases dependencies in our code, and we will have extra functionality that isn’t necessary in a single module.

“A Swiss army knife definitely doesn’t have just one responsibility!”

For example, let’s say we have a web application for handling your bank account. In the backend, there is a class for a person, referred to as the Client, and another one for the Account. If we want to accept this principle, the Account class should handle all methods related to it such as: checking account balance, adding funds, deducting funds, etc. Although the Client class should be able to USE these methods in order to operate with the Account, by no means should these methods belong to the Client class because it is not the Client’s responsibility!

Here at JOOR we apply this principle by separating each domain representation in our different platforms, such as Orders, Users, etc., and these domains will ship with everything they need such as tests, mappers for adapting the data or even interfaces if needed.

One tip for this principle is naming our methods explicitly, like checkAccountBalance. This way is easier to locate where this method should live and its function is apparent.

2. Open-Closed Principle

“Software entities (classes, modules, functions, etc. ) should be open for extension, but closed for modification”

Bertrand Meyer is generally credited for having originated the term open–closed principle, which appeared in his 1988 book “Object Oriented Software Construction”.

An entity can allow its behavior to be extended without modifying its source code. So, how can we do this? By using abstractions, like polymorphism, inheritance or applying design patterns.

Following our previous example, when deducting money from an account, we can do it by several methods: national transfer, purchase with debit card, or through a third-party like PayPal. If we build our method with a switch statementa conditional statement for each different method that we want to usewe will need to modify this switch whenever we want to add or disable a method of paying. A good solution would be to declare deduct as an abstract method, and move the implementation to child classes, one per method.

If we want to solve poor eyesight, we would want to add glasses, instead of replacing the impaired eye with a bionic one to achieve OCP

3. Liskov Substitution Principle

“Objects of a superclass shall be replaceable with objects of its subclasses without breaking the application”

The Liskov Substitution principle was introduced by Barbara Liskov in her conference keynote “Data abstraction” in 1987, and helps us to determine whether our application also follows the Open-Closed Principle.

For example, let’s look at a situation that doesn’t follow this principle:

Here we have a base class called Product that represents all the clothing products we have at JOOR. Below it we find two children classes: Shirt and Trouser.

>>> class Product:… def __init__(self, sl, ll, cl, mt):self.color = clself.material = mtself.sleeve_length = slself.leg_length = ll>>> class Shirt(Product):>>> class Trouser(Product):

Trouser doesn’t have sleeve_length, and Shirt doesn’t have leg_length. By using this hierarchy of classes, we should return nothing, or an exception when we ask for these attributes. The best solution is to leave the common attributes like material, color, etc. in Product, and then create subclasses for products with more specific attributes like leg_length for Trouser, and sleeve_length for Shirt. Since these subclasses are inheriting from Product, they will all have the same base attributes.

>>> class Product:… def __init__(self, cl, mt):self.color = clself.material = mt>>> class Shirt(Product):… def __init__(self, sl):self.sleeve_length = sl>>> class Trouser(Product):… def __init__(self, ll):self.leg_length = ll
“When refueling our car, if 95% gasoline was not available, we could still use 98% gasoline since our car is a gasoline engine. However, we could never use Diesel since it is incompatible with our engine and can break it.”

4. Interface Segregation Principle

“Keep interfaces small so that users don’t end up depending on things they don’t need.”

Another way to express this principle is: classes that implement interfaces or abstract classes shouldn’t be forced to implement methods that they don’t use.

For example, let’s say we have an abstract class for a MobilePhone. We shouldn’t create a module for NFC on the parent class, because it is a functionality that not all phones have. Even though we could avoid this with a “not implemented” exception, or just return nothing, we are adding noise and making the design confusing and difficult to maintain.

5. Dependency Inversion Principle

“High level modules should not depend upon low level details.”

This means that abstractions shouldn’t depend on details — concrete classes should depend on abstractions and not the other way around.

One good way to check if we are not following this principle is to write unit tests simulating all external dependencies with mocks. We will confirm that these classes don’t have strong coupling, because we will be using interfaces for their interactions and changes on one class shouldn’t affect the other.

This can be seen when reading configuration data from files with a fixed format, like text files. If the input format changes for a DB, or a XML file, the implementation of the class will change. It would be better to use abstract classes or interfaces instead of a specific implementation of the configuration class.

“It’s easier to connect electric devices with a plug rather than to use wires and create new connections to the net, right?”

SOLID Principles after all this time?

To sum up in Robert C. Martin’s words:

“The SOLID principles remain as relevant today as they were in the 90s (and indeed before that). This is because software hasn’t changed all that much in all those years — and that is because software hasn’t changed all that much since 1945 when Turing wrote the first lines of code for an electronic computer. Software is still if statements, while loops, and assignment statements — Sequence, Selection, and Iteration.”

We’re hiring at JOOR!

We’re currently hiring at JOOR! If you liked this article and you think you would be a good fit to working on problems and technologies similar to the ones we mentioned, please check out our open positions page and apply to join us today!

References:

  1. https://blog.cleancoder.com/uncle-bob/2020/10/18/Solid-Relevance.html
  2. Clean Code: A Handbook of Agile Software Craftsmanship (Robert C. Martin) (Robert C. Martin Series) 1 Aug. 2008

--

--