Solid principles

Solid principles

Facilitate software understanding, development, and maintenance

Β·

5 min read

S.O.L.I.D

We know that the bad code slow us down, why we write bad code? The answer is I have to go faster (you don’t go fast writing crappy) You want to go fast, you do a good job.

[[Robert Cecil Martin]] Create this order of the principles because each of them complement the previous or solve a problem. He did not create the principles, just organize in a way that make sense to learn.

These principles they are not bound to a specific language, you may use them with any language.

They work as a guideline to write a more composable, flexible code that can be changed more easily, maintainable. When you are working with solid you need to think on the end goals you do not say that you code is solid.

S: Single Responsibility / Atomic code SRP

The class should have only one reason to change.

A class must have one, and only one, reason to change. This principle states that a class should have only one responsibility and only one reason to change, it is very common to see classes like Controllers or Activity's with giant numbers of lines that do a million things.

If your class implements multiple responsibilities, they are no longer independent of each other. Therefore, you will need to change your class more often. Consequently, it will have more side effects, and requires a lot more work than it should have. So, it’s better to avoid these problems by making sure that each class has only one responsibility.

Duplication some times does not mean that the code is really duplicated, because sometimes the code can be representing different types of context, so even they have some shared behaviors, they need to be separated due the difference of context. In other words, DDD (Domain Driven Design), they serve different domains.

Goals

  • High code cohesion, every code that you right should be relevant to the context that hi is tied up
    • Things they are related should be together.
  • Low coupling, you should not have different actors or contexts impacting your code
    • Things that are independent should not be coupled.

A lot of people think that our class should have only one method or something similar, but the real implementation of single responsibility is the concept of atomic, you should break your code in pieces and each piece is responsible for one thing. So when you need to change that piece, you know exactly what it does. And another benefit of having atomic code is that you can create unit testing more easily. [[Rodrigo Martins]]


class WorkCalculator {

    fun calculateSomeWork() {
        // Do some work
        uploadWorkToAPI()
    }

    fun uploadWorkToAPI() {
        // Http.post() ...
    }
}

The responsibility of this class should be calculated something and not calculate and upload to some API, the uploadWorkToAPI should be on a separated class like a repository or dataSource that handles only this.

O: Open Closed OCP

Appreciate the extension and not the modification.

Modules should be open for extension, but closed for modification. The idea of this principle is: If you have already a code running on production, you should not change it to add a new behaviour to your application, you need to extend it and add the new business rules.

This helps us to avoid unexpected issues and bugs by changing the current code.

Using this principle is one of the things that make you move fast.

L: Liskov Substitution LSP

Derived classes must be usable through the base class interface, without the need for the user to know the difference. (What this means is: If you derive a square from a rectangle, you have to have a logic where you change the behaviour of the rectangle to be a square, the right thing would be to have a shape class, where each shape implements its shape).


interface Account {

    fun calcOne()

    fun calcTwo()
}


class UserAccount : Account {

    override fun calcOne() {
        // Implement the method
    }

    override fun calcTwo() {
        // Implement the method
    }
}

class CompanyAccount : Account {

    override fun calcOne() {
        // Implement the method
    }

    override fun calcTwo() {
        throw Exception()
    }
}

Here we are breaking the Liskov substitution, and it can be fixed using the interface segregation.

I: Interface Segregation ISP

This principle teach us that: Specific interfaces are better than general ones. Meaning that you should break your interfaces or abstract classes by context and atomic as possible, because doing this you can avoid issues where you implement one interface and needs to override a method that does nothing.

If you have experience with Android development you can found a example of this in some of the ui listeners that we use, some of them we need to implement and we end up with methods that we will never use.


interface AccountOne {

    fun calcOne()
}

interface AccountTwo {

    fun calcTwo()
}

D: Dependency Inversion DIP

Always depend on a abstrcat class instead of a concrete class

The idea is to take the responsibility of creating its dependencies from the class that will use them, and add them as a parameter that can be injected or passed through the constructor method. When we export the dependency and use abstract classes, we can mock the concrete class and facilitate our unit tests.


class Engine

class Car {

    val engine: Engine = Engine()
    // Instead of instantianting the engine inside the car class we should pass as a argument to the class, so we can mock it and test it latter, for instance.
}

class Car(val engine: Engine) {
    // ...
}

tags: #πŸ›  #development #software #principles #clean #architecture

Did you find this article valuable?

Support Engineer Journal by becoming a sponsor. Any amount is appreciated!

Β