BrainsToBytes

The Single Responsibility Principle

The Single Responsibility Principle is probably the most confusing of all 5 S.O.L.I.D principles. It took me quite a while to understand why it's an important thing, and where to apply it. The phrasing has changed through the years, but in most places, you will find a variant of the following idea:

A module should have one, and only one reason to change.

It's a very simple concept, but the word 'reason' can be somewhat confusing. Fixing a bug counts as a reason? What about refactoring the code to make it more maintainable? For gaining a better understanding of what this principle is about, we should think of the central actor of software development: people.

All the software you create is in some way or another made to serve the needs of a person or a group of individuals. When software changes, it is usually to accommodate the changing requirements of these actors. With that in mind, we can rephrase the principle as:

A module should be responsible for one, and only one actor

Here, the word module is used in the abstract sense. In most popular programming languages, you can replace module with class.

Ok, get it, but what happens if I don't follow this principle?

You're gonna have a bad time

It's much easier to understand the effects of violating this principle with an example. Suppose we have a Warehouse class:

    class Warehouse
        def calculate_total_assets
            #...
            inventory_information = get_tabulated_inventory_info()
            #...
        end

        def need_additional_stock?
            #...
            inventory_information = get_tabulated_inventory_info()
            #...
        end


        def save_inventory_records
            #...
        end

        private
        def get_tabulated_inventory_info
            #...
        end
    end

This class has the following methods:

  • calculate_total_assets calculates how much money our stock is worth. It's specified and used by the finances department
  • need_additional_stock? analyzes the current inventory and decides if the new stock should be ordered. Used and specified by the warehouse manager
  • save_inventory_records stores the stock information on a database or other form of datastore, specified and used by the engineering department

Changes for this class can come from any of the 3 actor mentioned above (in bold), they are the 3 reasons for it to change. Why is the current design a problem?

Well, suppose we need to modify the way information is obtained from get_tabulated_inventory_info because of a request from finances. We go in and perform the change, and the finances department is very happy with the new behavior, but in the process, we also broke need_additional_stock?. Warehouse management is not going to find out about this problem until it finds out the ordering of new stock is totally messed up.

The problem with violating this principle is that accommodating changes from different sources in the same module/class routinely cause unexpected issues. If the reason for a module to change is concentrated in a single actor, modifications tend to be more straightforward and predictable.

I see why it's a problem, but how can I solve it?

The usual solution is to split the module into as many modules as sources of responsibility.

The Warehouse class has 3 sources of responsibility, so we can split it into 3 classes and move the inventory info into its own data structure. After that, we can create a facade whose only responsibility will be delegating the messages to the right classes:

srp.svg

People are usually concerned with this approach because of 2 reasons, but they are usually not a problem.

The first one is duplication. If you think about it, both AssetCalculator and StockManager will need to implement their own versions of get_tabulated_inventory_info. Because the way they manage information is different (they don't need exactly the same info), both methods will eventually evolve into different things. Even if both classes hold an exact copy of get_tabulated_inventory_info, they are conceptually different and belong into two separate methods.

The second concern is about the size of the new classes. They are afraid of ending up with a bunch of very small, one-method classes. This rarely happens in real life, as even simple operations like calculate_total_assets need several supporting private functions to get their job done. You'll end up with leaner, more cohesive classes with better defined public interfaces.

Isn't it about 'doing one thing and only one thing'?

No, that's a different principle, used when you are refactoring your functions into smaller ones:

Functions should do one, and only one thing, and do it well.

Because of the name (Single Responsibility Principle), it usually gets confused with this other (very important) principle, but they mean different things and are applied at different levels. SRP is about modules and how they accommodate the changes coming from users, while this other principle is about functions.

What I need to remember about SRP

The difference between the SRP and the other S.O.L.I.D principles is the role of what the context around the code plays. While the other principles deal with the properties within the solution, the SRP deals with the relation between the structure of the code and the actors your program serves.

The factor that shapes this principle is the flow of changes: from the people who state the requirements to the modules that serve them. Ultimately, the principle is about people.

What to do next:

  • Share this article with friends and colleagues. Thank you for helping me reach people who might find this information useful.
  • Read Uncle Bob's article on the SRP.
  • There's more info about the SRP in chapter 7 of Clean Architecture. This and other very helpful books are in the recommended reading list.
  • Send me an email with questions, comments or suggestions (it's in the About Me page). Come on, don't be shy!
Author image
Budapest, Hungary
Hey there, I'm Juan. A programmer currently living in Budapest. I believe in well-engineered solutions, clean code and sharing knowledge. Thanks for reading, I hope you find my articles useful!