BPGB: Single Responsibility Preoccupation

By | June 26, 2018

It’s time for another post in my intermittent series of Best Practices Gone Bad (BPGB). This time we’ll cover the dark side of the Single Responsibility Principle (SRP). According to Robert Martin:

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

The basic concept is easy. Every object or method should be responsible for only one thing. This makes a lot of sense because combining two separate pieces of functionality in a single class or method usually makes that item harder to maintain, understand, or troubleshoot. It seems obvious that you can improve any design by ruthlessly breaking any class with more than one responsibility into two of more classes. Likewise, any method that does more than one thing should be separated into two or more methods. The logic here seems pretty obvious.

If you notice, the third word in the name is actually important. This is a principle that should help you think about how to organize your objects and methods. It is not a law that mandates how all code is written. The problem begins when you take the principle too far. You could argue that almost anything can be divided into separate responsibilities. Smaller classes and methods must be easier to understand, right? Unfortunately, if you divide a concept inappropriately, you might need to add a class to pull the concept back together.

An Example

For example, let’s say you have a circle class. To define a circle, you need a center point (let’s say just x and y), and a radius. For the sake of the argument, let’s say you want the circle to be able to return a circumference and a bounding box. (Let’s use Ruby for this example.)


class Circle
  attr_reader :x, :y, :radius

  def initialize x: x, y: y, radius: radius
    @x = x
    @y = y
    @radius = radius
  end

  def diameter
    radius * 2.0
  end

  def circumference
    diameter * Math::PI
  end

  def bounding_box
    [ x - radius, y - radius, diameter, diameter ]
  end
end

You can see that I factored out a diameter method to DRY the code, which also moved a responsibility into a separate method.

A Step Too Far

Now, someone in the throes of Single Responsibility Preoccupation might reason that the Circle class should just contain the attributes of the circle, it should not also be responsible for calculating synthetic attributes, like circumference, and bounding_box. Maybe we should separate those out. After all, other classes might need a circumference or a bounding_box. So, let’s create a Circumference class, that returns the correct length when supplied with a Circle object. And, we can create a BoundingBox class that calculates the upper, left corner and width and height, given a Circle object.

The problem is that it almost makes sense. But, I would argue that the knowledge of how to calculate the circumference, or make the appropriate bounding box is actually part of the concept of a Circle. Separating those methods out splits the concept of a Circle, moving some if its responsibility elsewhere. This leaves the concept of a Circle in three classes.

Now, some of you will think that this is a Straw Man argument. That’s not the case. I was working on a system recently with a set of classes that supplied 4 public methods as an interface. Some people suggested that we should separate each of these classes into 4 classes, one for each original method. It did take a bit of argument to show how the 4 methods together made a single concept, that would be harder to work with if it were broken down.

Conclusion

The Single Responsibility Principle is an effective mental tool to avoid classes that do too much. But, becoming preoccupied with the principle can lead to hard-to-understand code.

Leave a Reply

Your email address will not be published. Required fields are marked *