Single responsibility principle

from Wikipedia, the free encyclopedia

The single responsibility principle ( SRP , German  principle of clear responsibility ) is a design guideline in software architecture .

definition

A widespread but flawed assumption is that SRP states that each class has only one clearly defined task to perform.

The term was introduced by Robert C. Martin in a sub-article of the same name in his publication Principles of Object Oriented Design :

"There should never be more than one reason for a class to change."

"There should never be more than one reason to change a class."

- Robert C. Martin : SRP: The Single Responsibility Principle

The term became known through his book Agile Software Development: Principles, Patterns, and Practices .

In his book Clean Architecture: A Craftsman's Guide to Software Structure and Design , Robert C. Martin discusses the misinterpretation of the SRP and suggests the “final version” of the definition.

"A module should be responsible to one, and only one, actor."

"A module should be responsible to one, and only one, actor."

- Robert C. Martin : Clean Architecture: A Craftsman's Guide to Software Structure and Design

So the SRP is not just about the individual classes or functions. Rather, it is about collections of functionalities and data structures defined by the requirements of an actor.

Generalization of the single responsibility principle

Functions and variables

A generalization of the SRP is Curly's Law , which summarizes SRP, methods should do one thing , once and only once (OAOO) , don't repeat yourself (DRY) and single source of truth (SSOT) . The SRP can and should therefore be used for all aspects of software design. This includes not only classes, but also functions and variables, among other things. It is therefore also valid when using non-object-oriented programming languages ​​and when designing service interfaces.

“A functional unit on a given level of abstraction should only be responsible for a single aspect of a system's requirements. An aspect of requirements is a trait or property of requirements, which can change independently of other aspects. "

- Ralf Westphal

“A variable should mean one thing, and one thing only. It should not mean one thing in one circumstance, and carry a different value from a different domain some other time. It should not mean two things at once. It must not be both a floor polish and a dessert topping. It should mean One Thing, and should mean it all of the time. "

- Tim Ottinger

Example
The following example sorts a series of numbers:

var numbers = new [] { 5,8,4,3,1 };
numbers = numbers.OrderBy(i => i);

Since the variable numbersfirst represents the unsorted numbers and then the sorted numbers, Curly's Law is violated. This can be resolved by introducing an additional variable:

var numbers = new [] { 5,8,4,3,1 };
var orderedNumbers = numbers.OrderBy(i => i);

Applications

A similar principle occurs in the Unix philosophy , because here applications are supposed to serve a single purpose.

Make each program do one thing well. By focusing on a single task, a program can eliminate much extraneous code that often results in excess overhead, unnecessary complexity, and a lack of flexibility.

Design any program to do a job well. By focusing on a single task, a program can eliminate a lot of unnecessary code, which often leads to excessive overhead, unnecessary complexity and a lack of flexibility. "

- Mike Gancarz : The UNIX Philosophy

Dividing applications and user interfaces according to a single purpose is not only advantageous in development. Programs and user interfaces with a clearly defined purpose are also easier to understand and learn more quickly for users. Last but not least, there are advantages with limited screen sizes, as z. B. is the case with smartphones .

Related patterns

The interface segregation principle can be seen as a special case of the SRP. It arises from the application of the SRP to interfaces.

Command-query separation is used to separate functions and the entities according to their task by between commands ( commands ), and queries ( queries is discriminated). The same applies to CQRS , which defines different code paths for database access , which can be optimized independently of one another.

Cross-sectional aspects

Cross-sectional aspects that affect the entire application represent a particular challenge with regard to the SRP. This includes logging in particular .

Deliberate violation of the SRP

Many developers are of the opinion that the SRP should be violated with cross-sectional aspects, since cross-sectional aspects, such as logging, should be as close as possible to the relevant business logic.

public sealed class PersonRepository : IPersonRepository
{
    private static ILogger Log = ...;

    public Person GetByName(string name)
    {
        try
        {
            return ...;
        }
        catch(Exception ex)
        {
            Log.Error(ex, $"Could not get Person named {name}");
            throw;
        }
    }
}

Logging directly in the method, however, means that the SRP is not adhered to and the method is spaghettified . Reading and testing the business logic is made difficult by the aspect's code.

Decorator method

A decorator method is an easy way to move the aspect and business logic into separate methods.

public sealed class PersonRepository : IPersonRepository
{
    private static ILogger Log = ...;

    public Person GetByName(string name)
    {
        try
        {
            return GetByNameWithoutLogging(name);
        }
        catch(Exception ex)
        {
            Log.Error(ex, $"Could not get Person named {name}");
            throw;
        }
    }

    private Person GetByNameWithoutLogging(string name)
    {
        return ...;
    }
}

The disadvantage is that although the aspect has been outsourced at the method level, it still exists in the class. This therefore constitutes a violation of the SRP at the class level. Although the readability is improved, the challenge during testing still arises that the aspect must also be tested.

Aspect-oriented programming

The aspect-oriented programming (AOP) represents an alternative approach to outsource the aspect. The logic is only defined via a label and implemented by an aspect weaver .

public sealed class PersonRepository : IPersonRepository
{
    [LogToErrorOnException]
    public Person GetByName(string name)
    {
        return ...;
    }
}

The disadvantage here is that the SRP is not adhered to, since the aspect remains in the class. In addition, not all aspects can possibly be outsourced. For example, in the above example, no parameterized error message can be specified with an attribute. As a result, the solution has almost the same complexity as the original solution in many places:

public sealed class PersonRepository : IPersonRepository
{
    public Person GetByName(string name)
    {
        try
        {
            return ...;
        }
        catch(Exception ex)
        {
            LogTo.Error(ex, $"Could not get Person named {name}");
            throw;
        }
    }
}

In addition, the logic of the aspect is still in the class after the compilation process and therefore continues to make testability more difficult.

Subclass

Another way to separate the aspect from the business logic is to introduce derived classes .

public class PersonRepository : IPersonRepository
{
    public abstract Person GetByName(string name)
    {
        return ...;
    }
}

public sealed class LoggingPersonRepository : PersonRepository
{
    private static ILogger Log = ...;

    public Person GetByName(string name)
    {
        try
        {
            return base.GetByName(name);
        }
        catch(Exception ex)
        {
            Log.Error(ex, $"Could not get Person named {name}");
            throw;
        }
    }
}

However, this solution violates the principle of using composition instead of inheritance . Another disadvantage is that all classes and methods have to be opened for inheritance, which also violates the open-closed principle .

Subclasses for outsourcing aspects are therefore an antipattern .

Decorator

Aspects can be implemented using a decorator and thus separated from the business logic .

public sealed class PersonRepository : IPersonRepository
{
    public Person GetByName(string name)
    {
        return ...;
    }
}

public sealed class PersonRepositoryLoggingFacade : IPersonRepository
{
    private static ILogger Log = ...;
    public IPersonRepository Repository { get; }

    public LoggingPersonRepository(PersonRepository repository)
    {
        Repository = repository;
    }

    public Person GetByName(string name)
    {
        try
        {
            return Repository.GetByName(name);
        }
        catch(Exception ex)
        {
            Log.Error(ex, $"Could not get Person named {name}");
            throw;
        }
    }
}

The advantage here is that the principle of composition is followed instead of inheritance . The class PersonRepositorycan then be closed against inheritance, since an extension through composition is possible at any time. Another advantage is that the aspect can be exchanged by configuring the dependency injection . In addition, the logging logic can be tested independently of the business logic.

A disadvantage is the higher maintenance effort, since in the dependency injection both the class with the business logic and the class with the aspect must be managed. The separation also makes traceability (e.g. in which class an error occurred) more difficult.

Ravioli code

The consistent application of the single responsibility principle means that instead of the spaghetti code , a so-called ravioli code is created. This is code with a lot of small classes and small methods.

Raviolicode has the disadvantage that the number of classes in large projects means that there is less clarity. This applies in particular to the Functor classes that occur in object-oriented programming languages, i.e. classes with only a single method.

The SRP therefore makes a clean structuring by means of modules , namespaces and facades absolutely necessary so that the clarity is not lost.

Individual evidence

  1. ^ Robert C. Martin: Clean Architecture: A Craftsman's Guide to Software Structure and Design . 1st edition. Prentice Hall, 2017, ISBN 978-0-13-449416-6 , pp. 62 .
  2. ^ Robert C. Martin: The Principles of OOD. May 11, 2005, accessed April 22, 2014 .
  3. ^ Robert C. Martin: SRP: The Single Responsibility Principle. (PDF) February 1997, accessed April 22, 2014 (English).
  4. ^ Robert C. Martin : Clean Code: A Handbook of Agile Software Craftsmanship . Prentice Hall International, ISBN 978-0-13-235088-4 .
  5. Once And Only Once. In: Cunningham & Cunningham. Retrieved April 26, 2014 .
  6. ^ Ralf Westphal: Taking the Single Responsibility Principle Seriously. In: developerFusion. February 6, 2012, accessed April 22, 2014 .
  7. Jeff Atwood: Curly's Law: Do One Thing. In: Coding Horror. March 1, 2007, accessed April 22, 2014 .
  8. Mike Gancarz: The UNIX Philosophy . 1995, ISBN 1-55558-123-4 , The UNIX Philosophy in a Nutshell, pp. 4 (English).
  9. ^ Ravioli Code. In: Portland Pattern Repository. May 21, 2013, accessed March 4, 2017 .
  10. ^ Functor Object. In: Portland Pattern Repository. November 10, 2014, accessed March 4, 2017 .