The Single Responsibility Principle

March 17, 2014

Let me tell you the story of a developer. His name is John and he’s working in a medium-size company, developing with 10 colleagues the internal tools for users from: accounting, customer relations, human resources, marketing, management reports and operations. Since John is mainly specialized in HR, he gets requests such as:

We’re introducing our first bonus: 5% for employees who get more than 9/10 from the 360 feedback. We need an alert in the application to show when someone qualifies. The bonus will apply automatically for the next month’s pay.

John starts the task by looking at the following piece of code:

class Employee{
    public Money calculatePay(){...}
    public void save(){...}
    public String reportHours(){...}
}

and suddenly understands he has a big problem. Can you see it?

Before we find out what the problem is, let’s analyze the context.

What is likely to change?

Software changes because users ask for changes. They ask for changes because something in their life has changed, and there is a gap between what they have and what they need.

In terms of code, this means that some changes are more likely to happen together. HR departments are more likely to ask for changes in the way the pay is computed. Managers are more likely to ask for changes in reporting. IT is more likely to ask changes for things such as the database server or the operating systems. These three types of users are more likely to ask the changes separately than together.

Taking advantage of change patterns

Since we expect the software changes to come in a certain way, could we optimize the design of the code for it? Sure we can. The idea is simple and powerful: separate the code that changes together from the code that changes separately. This idea is known as the Single Responsibility Principle (SRP). You might have seen it stated in different ways:

Every class should have a single responsibility, and that responsibility should be entirely encapsulated by the class

or

Any class or method should have only one reason to change.

or

Things that change for the same reason should be kept together.

Things that change for different reasons should be separated.

What’s John’s problem?

Looking at the code, John probably has to change the calculatePay() method to take into account the bonus. When doing so, the class Employee obviously changes. This means anything that depends on the class Employee needs recompilation and redeployment. But the Operations department hasn’t requested any change, why do we need to redeploy the Operations module that uses Employee.reportHours()?

Moreover, if John works overtime to finish his task and inadvertently introduces a mistake in the reportHours() method on Friday evening before the release (we know this happens often), the situation gets very weird. A change requested by the HR department has caused all the Operations reports to fail. The Operations module wasn’t retested, after all the change was in the HR module, right? John will be in a tough position, answering to at least two angry managers.

Even if John has no idea of the application architecture and doesn’t see this coming, he probably has to ask the IT team to deploy at least two modules. That implies lots of paper work that John would rather not do. Moreover, the IT team will be unhappy about the change.

Briefly, John is in a lot of trouble because one class doesn’t follow the Single Responsibility Principle. TheEmployee class is responsible to at least three different roles:

  • HR, through calculatePay() method
  • Operations, through reportHours() method
  • IT, through save() method

Spotting SRP violations

The example above is one of the typical SRP violations: class methods that do very different things.

Let’s explore other typical violations. How about this method stub:

public int computeAverage(Student student){
    // compute average for student
    ....
    // save to database
    try{
        // save average to database
    } catch(SQLException exc){
        // report error
    }

}

This method has at least three responsibilities (or reasons to change):

  • computes the average – a business rule
  • saves to database
  • reports error if save fails

The method name is deceptive. It should becomputeAverageAndSaveToDatabaseAndReportErrorIfSaveFails(). The SRP violation becomes clear becausethe method name contains And.

The third type of typical SRP violation is having classes with generic names. The ones I’ve met the most are: ManagerBagStuff or my favorite one coming from WordPress: functions.php (technically not a class, but the point still stands). If you take time to try to name this type of classes so that the new name expresses what it does, you will probably end up with a lot of Ands in the name. These classes are typically places where developers drop any method that doesn’t belong anywhere else.

Measuring SRP violations

We’ve seen how to visually identify SRP violations, but how about measuring them? There’s no easy way to do it, but there is one. The key is to focus on the files that change together. The best source of information is therefore your source control. Michael Feathers (whom you can meet at the I T.A.K.E. Unconference this year) wrote a few scripts to build graphs that show the files that change together most often. As far as I know, there is no tool to do this now, but his talk is nonetheless showing a useful direction for measuring your design quality.

How to refactor SRP violations

The key is to extract the responsibilities where they belong. Of course, that’s not always easy. Sometimes it’s not clear where the responsibilities belong. Other than building a design sense and reviewing the design with your colleagues, there’s not much you can do.

If we take as example the last method, it should probably turn into:

public class AverageCalculationService{

    private StudentRepository studentRepository;
    private AverageCalculator averageCalculator;

    public AverageCalculationService(StudentRepository studentRepository, AverageCalculator averageCalculator){
        this.studentRepository = studentRepository;
        this.averageCalculator = averageCalculator;
    }

    public void computeAverageForStudent(Student student){
        List marks = studentQuery.getMarks(student);
        int average = averageCalculator.computeAverage(marks);
        studentQuery.saveAverage(average);
    }
}

public class ArithmeticAverageCalculator implements AverageCalculator{
    public int computeAverage(List marks){
        // compute average
        return average;
    }
}

where StudentRepository and AverageCalculator are interfaces. This code allows us to change separately:

  • The way the average computes: create a new class that implements AverageCalculator and pass an instance to the AverageCalculationService. Nothing changes in the AverageCalculationService or in theStudentRepository.
  • The database: create a new class that implements StudentRepository (eg. saves to MySql instead of Oracle) and pass an instance to the AverageCalculationService. Nothing changes in theAverageCalculationService or the AverageCalculator.
  • The business logic: let’s assume we need to add a business rule that says the average is only saved when greater than 5. No problem: change the code in the AverageCalculationService, nothing changes in the other classes.

There’s one important point to make this work: all implementations of StudentRepository have to behave in a similar way. But more about it when we’ll discuss Liskov Substitution Principle and Design by Contract.

Conclusion

We have seen in the previous article of this series that design is a set of self-imposed constraints on the code organization. SRP is such a constraint. It works best with the other (S)OLID principles that we’ll discuss in the next blog posts. It is also useful by itself on many occasions. Like the other (S)OLID principles, the purpose of SRP is to optimize for change.

Stay tuned for the next blog post about design principles.

Categorised in: , , ,

1 Comment

    Leave a Reply

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