Pro Yearly is on sale from $80 to $50! »

Single Responsibility Principle

7bc333703f15905509a0cef6e27e96c8?s=47 Robert
June 25, 2018

Single Responsibility Principle

Slidedeck from my presentation for Clean Code Days Munchen 2018.

7bc333703f15905509a0cef6e27e96c8?s=128

Robert

June 25, 2018
Tweet

Transcript

  1. " Robert Bräutigam MATHEMA So ware GmbH. SINGLE SINGLE RESPONSIBILITY

    RESPONSIBILITY PRINCIPLE PRINCIPLE
  2. None
  3. GOAL GOAL TO DEFINE THE TO DEFINE THE SRP SRP

    IN A IN A PRAGMATICAL* PRAGMATICAL* AND AND OBJECTIVE* OBJECTIVE* WAY, ALLOWING IT TO WAY, ALLOWING IT TO BE USED BE USED DIRECTLY IN OUR DAY-TO-DAY DIRECTLY IN OUR DAY-TO-DAY PROGRAMMING. PROGRAMMING.
  4. DEFINITIONS DEFINITIONS Each so ware module should have one and

    only one responsibility. "Each so ware module should have one and only one reason to change" "Gather together the things that change for the same reasons. Separate those things that change for different reasons." "Same reason" means it originates from the same business person. (Really???)
  5. RATIONALE RATIONALE We want maintainability! Minimize work for a given

    amount of change. Minimize amount of code to read/write. So: Small classes. High probability of localized change. Low probability of change propagating to other classes.
  6. SINGLE RESPONSIBILITY PRINCIPLE SINGLE RESPONSIBILITY PRINCIPLE == == MAXIMIZE MAXIMIZE

    COHESION COHESION MINIMIZE MINIMIZE COUPLING COUPLING
  7. COHESION & COUPLING COHESION & COUPLING It's about dependencies. Dependencies

    inside an object make it more cohesive. Dependencies between objects make them more coupled. What are dependencies? Physical relationships, like a method calling another one, or method referencing a variable. Semantic relationships.
  8. PHYSICAL DEPENDENCIES PHYSICAL DEPENDENCIES This class is cohesive. public final

    class Amount { private final int cents; private final Currency currency; public Amount(int cents, Currency currency) { this.cents = cents; this.currency = currency; } public Amount subtract(Amount other) { if (currency != other.currency) { ...error...} return new Amount(cents - other.cents, currency); } public boolean isEqualTo(Amount other) { return subtract(other).cents == 0; } }
  9. PHYSICAL DEPENDENCIES PHYSICAL DEPENDENCIES Class has coupling to Amount. public

    final class Account { private Amount balance; ... public void debit(Amount amount) { ... balance = balance.subtract(amount); } }
  10. SEMANTIC DEPENDENCIES SEMANTIC DEPENDENCIES Class knows too much about Amount.

    It is semantically coupled. High probability they change together. public final class CurrencyConverter { public Amount convert(Amount amount, Currency currency) { return new Amount( amount.getValue() * exchange.getDailyRate( amount.getCurrency(), currency), currency); } }
  11. SEMANTIC COUPLING SEMANTIC COUPLING It is always a code smell.

    It is also a design smell. Much worse than physical coupling, since it is invisible (to the compiler). Changes are very likely to propagate through semantic couplings, sometimes in subtle and unexpected ways. Very o en facilitated by getters.
  12. DESIGNING A UI: METERS DESIGNING A UI: METERS

  13. METER METER These are Water or Gas Meters, a part

    of an IoT network, in which we're writing the server code, with following requirements: A meter may receive requests for making a readout of current values. A meter may at any time update its capabilities. These capabilities have to be displayed on the web interface.
  14. METER DESIGN METER DESIGN public final class Meter { private

    boolean gzipSupported; private Encryption encryptionSupported; private X509Certificate encryptionCertificate; ... public void receiveReadoutRequest() { ... using capabilities ... } public void updateCapabilities(...) { ... update capabilities ... } public String displayHtml() { return " ... html code ... "; } }
  15. COMMON INTERPRETATION OF SRP COMMON INTERPRETATION OF SRP Heavy semantic

    and physical coupling, very unmaintainable. Violation of SRP! public final class Meter { private boolean gzipSupported; private Encryption encryptionSupported; private X509Certificate encryptionCertificate; ... ...getters, setters... } public final class MeterView { public String displayMeter(Meter meter) { return "...html with meter.getGzipSupported(), meter.getEncryptionSupported(), meter.getX()..."; } }
  16. MORE PRAGMATIC INTERPRETATION MORE PRAGMATIC INTERPRETATION No more HTML, no

    details of design, at the same time Tags do not know Meter. public final class Meter { private boolean gzipSupported; private Encryption encryptionSupported; private X509Certificate encryptionCertificate; ... public Component display() { return new Tags( gzipSupported?new Tag("GZIP"), encryptionSupported?new Tag("ENC"), encryptionSupported.display(), ...); } }
  17. UGH, UI IN THE DOMAIN, I FEEL DIRTY! UGH, UI

    IN THE DOMAIN, I FEEL DIRTY! Maybe that is just a culture of discrimination. Why shouldn't the UI be part of the business?
  18. UI IN THE DOMAIN: CONS UI IN THE DOMAIN: CONS

    It's just wrong UI is not important! I don't want to change business logic because of colors. What if I want to change the Web UI to Swing?
  19. UI IN THE DOMAIN: PROS UI IN THE DOMAIN: PROS

    UI is by it's nature tightly coupled/cohesive to the domain. UI is usually an important part of an application. UI is actually part of the requirements! Business people actually talk about the UI, it is part of the common understanding and vocabulary! Details of the UI don't have to be in the Domain!
  20. COMPOSITION AND SRP: USER COMPOSITION AND SRP: USER REGISTRATION REGISTRATION

  21. USER REGISTRATION USER REGISTRATION Users for our system with following

    requirements: User may register with username and password. User may authenticate herself with given password. At the registration an email should be sent as confirmation.
  22. "TRADITIONAL" DESIGN "TRADITIONAL" DESIGN "Don't mix SQL with SMTP" public

    class UserManager { public boolean authenticate(String username, String password) { String passwordHash = sql.select("from user ...", ...); return HashUtils.match(password, passwordHash); } public void register(String username, String password, String emailAddress) { sql.insert("into user ...", ...); new SmtpClient().send(emailAddress, "Hello "+username+", welcome to Application"); } }
  23. ADDING "TRADITIONAL" SRP ADDING "TRADITIONAL" SRP public class User {

    private String username; private String passwordHash; private String emailAddress; ...getters, setters... } public class EmailNotificationService { private SmtpClient smtpClient; ... public void sendNotification(User user) { smtpClient.send(user.getEmailAddress(), "Hello "+user.getUsername() +", welcome to Application"); } }
  24. ADDING "TRADITIONAL" SRP ADDING "TRADITIONAL" SRP This is still too

    much. public class UserRepository { ... public void insert(User user) { sql.insert("into user ...", user.getUsername(), HashUtils.hash(user.getPassword()), user.getEmailAddress()); } public User select(String username) { return sql.select(...); } }
  25. ADDING "TRADITIONAL" SRP ADDING "TRADITIONAL" SRP public class InsertUserCommand {

    public void execute(User user) { sql.insert("into user ...", user.getUsername(), HashUtils.hash(user.getPassword()), user.getEmailAddress()); } } public class SelectUserCommand { public User execute(String username) { return sql.select(...); } }
  26. ADDING "TRADITIONAL" SRP: RESULT ADDING "TRADITIONAL" SRP: RESULT public class

    UserManager { private InsertUserCommand insertUserCommand; private SelectUserCommand selectUserCommand; private EmailNotificationService notificationService; public boolean authenticate(String username, String password) { User user = selectUserCommand.execute(username); return HashUtils.match(password, user.getPasswordHash()); } public void register(String username, String password, String emailAddress) { User user = new User(username, password, emailAddress); insertUserCommand.execute(user); notificationService.sendNotification(user); } }
  27. ADDING "TRADITIONAL" SRP: RESULT ADDING "TRADITIONAL" SRP: RESULT Familiar to

    most people Seems clean enough It is the beginning of the end! Leads to high fragmentation (one method per class designs). Leads to injection/dependency/testing hell.
  28. ADDING "TRADITIONAL" SRP: ANALYSIS ADDING "TRADITIONAL" SRP: ANALYSIS Instead of

    decoupling, we actually have very tight coupling. Semantic as well as physical. No cohesion, or very little cohesion. Has very little to do Object-Orientation, this may or may not be a problem for some. Look at the vocabulary: User, UserManager, InsertUserCommand, SelectUserCommand, EmailNotificationService
  29. SIDENOTE: DON'T DO SIDENOTE: DON'T DO Utils Utils public final

    class PasswordHash { ... public static PasswordHash compute(String clearText) { byte[] randomSalt = ...; return compute(randomSalt, clearText); } public static PasswordHash compute(byte[] salt, String clearText) { byte[] calculatedHash = ...; return new PasswordHash(salt, calculatedHash); } public boolean matches(String clearText) { return this.equals(new PasswordHash(salt, clearText)); } public boolean equals(Object o) { ... return Arrays.equals(this.hash, o.hash); } }
  30. ALTERNATIVE DESIGN WITH SRP ALTERNATIVE DESIGN WITH SRP The "core"

    is an interface on which all functionalities are implemented on. public interface User { boolean authenticate(String password); void register(); }
  31. ALTERNATIVE DESIGN WITH SRP ALTERNATIVE DESIGN WITH SRP Only SQL

    code, cleanly separated. public final class SqlUser implements User { ... @Override public boolean authenticate(String password) { return new PasswordHash( sql.select("passwordhash from user...", ...)) .matches(password); } @Override public void register() { sql.insert("into user...", ...); } }
  32. ALTERNATIVE DESIGN WITH SRP ALTERNATIVE DESIGN WITH SRP Unfortunately delegation

    is not part of the language (like inheritance). Yet. public class DelegatingUser implements User { private final User delegate; public DelegatingUser(User delegate) { this.delegate = delegate; } @Override public boolean authenticate(String password) { return delegate.authenticate(password); } @Override public void register() { delegate.register(); } }
  33. ALTERNATIVE DESIGN WITH SRP ALTERNATIVE DESIGN WITH SRP public final

    class NotifiedUser extends DelegatingUser { ... public NotifiedUser(String username, String emailAddress, User delegate) { super(delegate); ... } @Override public void register() { super.register(); smtpClient.send(emailAddress, "Hello "+username+", welcome to Application"); } }
  34. ALTERNATIVE DESIGN WITH SRP: RESULT ALTERNATIVE DESIGN WITH SRP: RESULT

    public User createUser(String username, String password, String emailAddress) { return new NotifiedUser(username, emailAddress, new SqlUser(username, password, emailAddress)); }
  35. ALTERNATIVE DESIGN WITH SRP: ALTERNATIVE DESIGN WITH SRP: ANALYSIS ANALYSIS

    Instead of separating per technology, it separates based on vertical features. Slight but crucial difference. Objects are cohesive and truly decoupled. I.e. SRP. Each feature is testable. It is OO. No data is pulled out of objects.
  36. PRELIMINARY SUMMARY PRELIMINARY SUMMARY I don't think it means what

    I was thaught it means. (If it is, it shouldn't...) Single Responsibility Principle == Cohesion & Coupling So...
  37. WHAT YOU SHOULD DO FOR YOUR CODE: WHAT YOU SHOULD

    DO FOR YOUR CODE: 1. Make sure your class is physically cohesive. Methods and fields refer to each other. Don't forget the Constructor! 2. Make sure the physical coupling is not stronger than the physical cohesion. 3. Make sure you have your data, and don't need to get your data! This avoids semantic coupling. 4. Don't be dogmatic, UI (HTML, JSON/HTTP, SOAP) is functionality too!
  38. SOME (UNEXPECTED?) SOME (UNEXPECTED?) CONSEQUENCES CONSEQUENCES MVC MVC

  39. "TRADITIONAL" MVC "TRADITIONAL" MVC View is JSF or plain HTML

    with substitutions public class Person { // Model private String name; private int age; private String address; ...setters, getters... } public class PersonController { public void greet(Person person) { ... } public void add(Person person) { ... } }
  40. "TRADITIONAL" MVC "TRADITIONAL" MVC Despite claims that this is for

    decoupling, it actually increases coupling. Everything is strongly coupled to the data object, both controller and view. The whole thing must change if Person changes. Only allows limited "view" changes to be localized. Makes code unmaintainable.
  41. ALTERNATIVE MVC ALTERNATIVE MVC Model = The "business" object itself

    Controller = Abstract UI Component View = HTML the Component reads public final class Person { // Model private final String name; private final int age; private final String address; ... public Component displaySummary() { return new InfoPanel() .addInfo("Name", name) .addInfo("Age", age) .addInfo("Address", address); } }
  42. ALTERNATIVE MVC WITH INPUT ALTERNATIVE MVC WITH INPUT This is

    what we should mean by cohesion! public final class Person { // Model private final String name; private final int age; private final String address; public Person(String name, int age, String address) { ... } ... public InputComponent<Person> displayInput() { return new InputGroup() .add(new TextInput("Name", name)) .add(new NumberInput("Age", age, ...)) .add(new TextInput("Address", address)) .map(Person::new); } }
  43. ALTERNATIVE MVC ALTERNATIVE MVC Responsibilities cleanly separated. Person doesn't know

    details of View, View doesn't know details of Person. Composable!
  44. ALTERNATIVE MVC: COMPOSABILITY ALTERNATIVE MVC: COMPOSABILITY public final class Person

    { // Model private final String name; private final int age; private final Address address; public Person(String name, int age, Address address) { ... } ... public InputComponent<Person> displayInput() { return new InputGroup() .add(new TextInput("Name", name)) .add(new NumberInput("Age", age, ...)) .add(address.displayInput()) .map(Person::new); } }
  45. SOME (UNEXPECTED?) SOME (UNEXPECTED?) CONSEQUENCES CONSEQUENCES LAYERED ARCHITECTURES LAYERED ARCHITECTURES

  46. LAYERED ARCHITECTURE LAYERED ARCHITECTURE

  47. PROBLEMS WITH LAYERS PROBLEMS WITH LAYERS The "Domain" is only

    1/4 of the Application Is a technical design, business-agnostic. Layers usually leak data upwards and create coupling (DTOs) Layers therefore tightly coupled to layers below. ⇒ Changes escalate and expand upwards!
  48. LAYERED ARCHITECTURE LAYERED ARCHITECTURE

  49. LAYERED ARCHITECTURE LAYERED ARCHITECTURE

  50. LAYERED ARCHITECTURE LAYERED ARCHITECTURE How many classes would you have

    to change if a domain object changed slightly? Is it more than 1? Almost automatically violates SRP. Changes are not localized.
  51. DOMAIN-DRIVEN DESIGN + SRP DOMAIN-DRIVEN DESIGN + SRP

  52. BUILDING BLOCKS OF DDD BUILDING BLOCKS OF DDD

  53. BUILDING BLOCKS OF DDD BUILDING BLOCKS OF DDD

  54. DDD + SRP DDD + SRP DDD makes the case

    for the ubiquitous language. The same language in code as between people. ⇒ Responsibilities can not be arbitrary, have to come from the business as well. Among other things: Persistence, Json/XML Formatting, Validation, Rules, Commands, etc., are therefore usually not valid responsibilities.
  55. SUMMARY SUMMARY We got a useful definition of SRP, based

    on Cohesion and Coupling DDD implies responsibilities are not arbitrary, have to come from the requirements. Using SRP to increase Maintainability leads to a different design than most are familiar with. Among others: UI, MVC and Layered Architectures have superior alternative interpretations for most cases.
  56. THANKS THANKS

  57. QUESTIONS? QUESTIONS? robert@mathema.de https://javadevguy.wordpress.com/ @robertbrautigam