solid

SOLID


Motivation

  • The more things change, the more they break
  • Anforderungen ändern sich ⟹ Code ändert sich

Waterfall Model


Agile

Agile Model


Ziel

ETC - easier to change

Clean Code
Code leichter lesbar ⟹ ETC
DRY - Don't repeat yourself
eine Änderung ⟹ ETC
Orthogonalität
Keine Codeverflechtungen ⟹ ETC
z.B. Datenbank wird von MySQL auf PostgreSQL portiert, GUI bleibt gleich

Namen

  • Principle of least Surprise
  • Variablen, Funktionen, Klassen sinnvoll benannt
  • Name > Typ
  • Keine Abkürzungen, Klarheit > Kürze
int x = 5;
✅ var age = 5;
❌ List<Student> list = ...;
✅ var students = ...;
bool f = true;
✅ var isActive = true;

Funktionen

  • Funktion macht nur eine Sache
  • Viele kurze Funktionen (< 20 lines)
  • < 4 Parameter
  • ähnliche Parameter in Objekte zusammenfassen
public void processOrder(Order order) {
  // validate the order
  code 
  // save to database
  code
  // send confirmation mail
  code
} ❌ 
public void processOrder(Order order) {
  validate(order);
  save(order);
  sendConfirmationMail(order.customer);
} ✅ 

Formatting

  • Konsistent
  • Leerzeilen zur Unterteilung
  • Linter
public int sum(int a, int b) { return a + b; } ❌
public int sum(int a, int b) { 
  return a + b; 
} ✅ 

Duplication

  • Duplication vermeiden
  • Redundanz
  • Single source of truth
if(user.getAge() > 18) { ... }
...
if(customer.getAge() > 18) { ... }
...  ❌
boolean isAdult(Person person) {
    return person.getAge() > 18;
} ✅ 
class Person {

  public boolean isAdult() {
      return age > 18;
  }
} ✅ 

Lesbarkeit

  • Code sollte eine Geschichte erzählen
  • Don't try to be clever
  • Simple control flows
nums = [i for i in range(100) if not i%2 and i%3] ❌
nums = []
for i in range(100):
    if i % 2 == 0 and i % 3 != 0:
        nums.append(i) ✅ 

Kommentare

  • Unfähigkeit, Gedanken in Code zu fassen
  • Warum?
  • Müssen gewartet werden
// session-id ❌
int sessionId = 0;
// expiry date ❌
Date expiry = new Date();
// if during work hours ❌
if (currentTime.hours >= 9 && currentTime.hours <= 17) { ... }
// Matches a Dutch zipcode, eg, '1974 XA' or '4844RA' ✅ 
Pattern DUTCH_ZIPCODE = Pattern.compile("[1-9][0-9]{3} ?[A-Z]{2}");
var duringWorkHours = currentTime.hours >= 9 && currentTime.hours <= 17; ✅ 
if (duringWorkHours) { ... }

Komplexe Zusammenhänge

var resultCode = ...;
var isSucess = resultCode == 200;
var isTemporaryError = resultCode == 503 || resultCode == 504;

// Sometime we receive a temporary error. 
// However, in all cases which were researched, results were successfully stored.
// Because of this we will return true in that case.
return (isSucess || isTemporaryError); ✅ 
// Don't rewrite this to try-with-resources because that will
// auto-close the socket. The socket is needed every 15 minutes so the 
// error might not show directly but this will break the application
try {
    ... open a socket
} catch (IOException e) {
    ... handle error
} ✅ 

Testbarkeit

  • Automatisierte Tests
  • Nicht nur happy path
  • Testqualität >> Codequalität

SOLID

Guidelines, keine Regeln

"Uncle Bob" Robert C. Martin

  • Single responsibility principle
  • Open closed Principle
  • Liskov substitution principle
  • Interface segregation principle
  • Dependency inversion principle

SRP - Single responsibility principle


SRP - Single responsibility principle

Edsger W. Dijkstra - Seperation of concerns

Gather together the things that change for the same reasons. Separate things that change for different reasons.

public class Employee {

    public Money calculatePay()
    public void save()
    public String reportHours()
}
  • Buchhaltung verlangt Änderung ⟹ Recompile
  • DBA verlangt Änderung ⟹ Recompile
  • HR verlangt Änderung ⟹ Recompile

class Book {
 
    public String getTitle()
    public String getAuthor()
    public Page nextPage()
    public void printCurrentPage()
    public void save()
}

class Book {
  
     public String getTitle()
     public String getAuthor()
     public Page nextPage()
     public Page getCurrentPage()
}
interface Printer {
 
    void printPage(Page page);
}
class PlainTextPrinter implements Printer {
 
    public void printPage(Page page) {}
}

class HtmlPrinter implements Printer {
 
    public void printPage(Page page) 
}

Dependency Inversion Principle


Dependency Inversion Principle

  1. High-level modules should not depend on low-level modules. Both should depend on the abstraction.
  1. Abstractions should not depend on details. Details should depend on abstractions.

module-model


Mit DIP

module-model

  • Klassen haben nur Referenzen auf Interfaces
  • Detail-Klassen implementieren diese Interfaces
  • Wie kommt das Objekt in die Klasse?

Dependency Injection

public class TextEditor {
    private SpellChecker checker;

    public TextEditor() {
        this.checker = new EnglishSpellChecker();
    }
}

"new is glue"

public class TextEditor {
    private SpellChecker checker;

    public TextEditor(SpellChecker checker) {
        this.checker = checker;
    }
}

Open closed principle


Open closed principle

A Module should be open for extension but closed for modification.

Code ist so zu schreiben, dass neue Features eingefügt werden können, ohne den bestehenden Code zu verändern.


Extension? ⟹ extends!

animal>eagle-uml

import Eagle;

class App {

    public static void main(String[] args) {
        Eagle eagle = new Eagle();
        eagle.makeSound();
        eagle.fly();
    }
}

makeSound returnt String ⟹ 🤔


Vererbung

+
  • DRY - Code existiert nur einmal
  • Polymorphie
class Derived extends Super {

    public int foo() {
        return getX() + super.foo();    
    }     
}
  • Erzeugt Abhängigkeit ⟹ nicht ETC
++
  • DRY - Auslagern in eigene Klasse (SRP)
  • Polymorphie - Interfaces

public interface CalculatorOperation { }
public class Addition implements CalculatorOperation{
    private double left;
    private double right;
    private double result;

    ...
}
public void calculate(CalculatorOperation operation) {
    if (operation instanceof Addition addition) {
        var result = addition.getLeft() + addition.getRight();
        addition.setResult(result);
    } else if (operation instanceof Subtraction subtraction) {
        var result = subtraction.getLeft() - subtraction.getRight();
        subtraction.setResult(result);
    } 
}

Customer will Multiplication
⟹ Änderung notwendig ⟹ nicht OCP


public interface CalculatorOperation { 

    double perform();
}
public class Addition implements CalculatorOperation{
    private double left;
    private double right;

	@Override
    public double perform() {
        return left + right;
    }
}
public void calculate(CalculatorOperation operation) {
    var result = operation.perform();
}

OCP ✔️


sealed

sealed class Animal permits Dog, Cat, Alien { }
class Fish extends Animal { } 🚫
final class Dog extends Animal { }
sealed class Cat extends Animal permits Tiger { }
final class Tiger extends Cat { }
non-sealed class Alien extends Animal { }
var sound = switch(animal) {
	case Dog dog -> dog.bark();
	case Tiger tiger -> tiger.roar();
	case Alien alien -> alien.sound();
	// no default needed
}


Liskov substitution principle

Sei q(x) eine beweisbare Eigenschaft von Objekten x des Typs T. Dann soll q(y) für Objekte y des Typs S wahr sein, wobei S ein Untertyp von T ist

A program that uses an interface must not be confused by an implementation of that interface.

  • normalerweise ✔️
  • Verletzung: teilweises Implementieren eines interfaces

public class UserAdder {

    public void addUser(Collection<User> users, User user) {
        users.add(user);
    }
}

SRP ✔️

var adder = new UserAdder();
var users = List.of(new User("Albert"), new User("Bernd"));
adder.addUser(users, new User("Christina"));
Exception in thread "main" java.lang.UnsupportedOperationException

Interface segregation principle


Interface segregation principle

Keep interfaces small so that users don’t end up depending on things they don’t need.

public interface FileStorer {

    byte[] load(Path path);
    void delete(Path path);
    void save(Path path, Stream<String> lines);
    boolean exists(Path path);
    void createDirectory(Path path);
    Stream<Path> getSubDirectories(Path path);
    Stream<Path> getFiles(Path path);
}

Aus bestehendem interface wird für jede Client-Rolle ein neues interface abgesondert


RoleInterfaces

without-isp

with-isp


SRP vs ISP

  • SRP trifft Aussagen über die Implementierung
  • ISP trifft Aussagen über Interfaces
  1. SRP Verletzung
  2. neue Klassen
  3. neue Rollen
  4. ISP

Zusammenfassung

  • SRP - nur eine Sache machen
  • OCP
  • Klassen extenden keine Klassen
  • Composition > Inheritance
  • alte Funktionalität nicht beeinflussen
  • LSP - Interfaces vollständig implementieren
  • ISP - SRP auch für Interface
  • DIP
  • alle Typen Interfaces (außer domain)
  • kein new (außer domain)

  • Testbarkeit ✔️
  • Ersetzbarkeit ✔️
  • Wiederverwendbarkeit ✔️