Skip to content
This repository has been archived by the owner on Feb 18, 2024. It is now read-only.

Latest commit

 

History

History
673 lines (539 loc) · 14.4 KB

10-cleancode.adoc

File metadata and controls

673 lines (539 loc) · 14.4 KB

Clean Code

Herkunft

TPM

Total Productive Maintenance

  • Qualitätsansatz

  • ~1960, japanische Autoindustrie

  • Konzentration auf Instandhaltung des Arbeitsplatzes

  • ähnlich Lean Produktion

  • Fundament: 5S-Prinzipien

5S

Seiri

Aussortieren; Übersicht schaffen - wo finde ich Dinge wieder, Namensgebung

Seiton

Ordentlichkeit; Code sollte da stehen, wo ich ihn erwarte

Seiso

Säubern; Abfall und Einzelteile entfernen

Seiketsu

Standardisierung; konsistenter Codierstil

Shutsuke

(Selbst-) Disziplin & ständige Verbesserung

Inspiration

Qualität ist das Ergebnis einer Million selbstloser Akte der Sorgfalt.

— Robert “Uncle Bob” Martin

Inspiration

Wir (Entwickler) sind Autoren. Ein Merkmal von Autoren ist es, dass sie Leser haben.

— Robert “Uncle Bob” Martin

Inspiration

Sauberer Code kann von anderen Entwicklern gelesen und verbessert werden.

— Dave Thomas

Chaos im Code

  • je älter ein Projekt, desto höher der Aufwand, neue Funktionen hinzuzufügen

  • damit die Produktivität wenigstens annähernd gleich bleibt, wird (leider) der Fokus der Arbeit auf neue Funktionen gelegt

    • Folge: Code verrottet - wichtige Basis-Arbeiten werden vernachlässigt

      • keine neuen Tests

      • Konzepte werden durch Ausnahmen aufgeweicht

      • Dokumentation wird nicht nachgezogen

  • Gesetz von LeBlanc: Später heißt niemals

Chaos im Code

  • Schlussfolgerung:

    • es reicht nicht aus, guten Code zu schreiben

    • Code muss auch sauber gehalten werden

    • sofort & kontinuierlich

Chaos im Code

Leave the campground cleaner than you found it

— Robert “Uncle Bob” Martin

Aussagekräftige Namen

Zweckbeschreibende Namen

public List<int[]> getThem() {
   List<int[]> list1 = new ArrayList<int[]>();
   for (int[] x : theList)
      if (x[0] == 4)
         list1.add(x);
   return list1;
}

Zweckbeschreibende Namen

  • Kontext geht nicht aus dem Code hervor

  • Code ist implizit, sollte aber explizit sein

Zweckbeschreibende Namen

public List<int[]> getFlaggedCells() {
   List<int[]> flaggedCells = new ArrayList<int[]>();
   for (int[] cell : this.gameBoard)
      if (cell[STATUS_VALUE] == FLAGGED)
         flaggedCells.add(cell);
   return flaggedCells;
}
Note

Ist ein Teil eines Mine-Sweeper Spieles

Fehlinformationen vermeiden

  • keine irreführenden Hinweise, z.B. für eine Gruppe von Konten:

private Map accountList;
  • zwei Namen sollten sich nicht geringfügig unterscheiden, z.B.

XYZControllerForEfficientHandlingOfStrings
XYZControllerForEfficientStorageOfStrings

Unterschiede deutlich machen

public static void copyChars(char c1[], char c2[]) {
   for (int i=0; i < c1.length; i++) {
      c2[i] = c1[i];
   }
}

Unterschiede deutlich machen

public static void copyChars(char source[], char destination[]) {
   for (int i=0; i < source.length; i++) {
      destination[i] = source[i];
   }
}

Unterschiede deutlich machen

  • Namen wie c1 sind nicht irreführend, sondern informationsleer

  • zusammengesetzte Klassennamen können auch informationsleer sein

    • Product

    • ProductInfo

    • ProductData

Aussprechbare Namen verwenden

class DtaRcrd102 {
   private Timestamp genymdhms;
   private Timestamp modymdhms;
}

Aussprechbare Namen verwenden

class DtaRcrd102 {
   private Timestamp genymdhms;
   private Timestamp modymdhms;
}
ymdhms

Year, Month, Day, Hours …​

Aussprechbare Namen verwenden

class DtaRcrd102 {
   private Timestamp genymdhms;
   private Timestamp modymdhms;
}
ymdhms

Year, Month, Day, Hours …​

class Customer {
   private Timestamp generationTimestamp;
   private Timestamp modificationTimestamp;
}

Suchbare Namen verwenden

int s = 0;
for (int j=0; j<34; j++) {
   s += (t[j]*4)/5;
}
  • Die Länge eines Namens sollte der Größe seines Geltungsbereichs entsprechen

  • Suche nach t oder 5 ergibt in der gesamten Codebasis viele Treffer

Suchbare Namen verwenden

int realDaysPerIdealDay = 4;
const int WORK_DAYS_PER_WEEK = 5;
int sum = 0;
for (int j=0; j < NUMBER_OF_TASKS; j++) {
   int realTaskDays = taskEstimate[j] * realDaysPerIdealDay;
   int realTaskWeeks = (realTaskDays / WORK_DAYS_PER_WEEK);
   sum += realTaskWeeks;
}

Codierungen vermeiden

// Datentypen
private String szVorname;
private Integer nId;
// Geltungsbereich
private String pri_szVorname;
public Integer pub_nId;

Codierung vermeiden

  • Codierung von Informationen in Namen von Variablen

    • Datentyp oder Geltungsbereich

    • Ungarische Notation

  • Nachteile

    • Änderungen müssen überall nachgezogen werden

    • Präfixe und Suffixe werden bald vom Entwickler ignoriert

Methodennamen

  • Verben verwenden, z.B.

    • downloadEmailAttachments()

  • nur ein Wort pro Konzept

    • fetch, retrieve, get …​ sind Synonyme

Domänen Namen

  • Problemdomäne

    • Begriffe/Konzepte des Bereichs, für den die Software bestimmt ist

    • z.B. BeneficialOwner

      • Bezug auf wirtschaftlich Berechtigten eines Bankkontos

  • Lösungsdomäne

    • Begriffe/Konzepte der Informatik, Algorithmen, Pattern

    • z.B. AccountVisitor

      • Bezug auf Visitor-Pattern

Funktionen

Beispiel

public class HtmlUnit {
  public static String testableHtml(
     PageData pageData,
     boolean includeSuiteSetup
   ) throws Exception
   {
     WikiPage wikiPage = pageData.getWikiPage();
     StringBuffer buffer = new StringBuffer();
     if (pageData.hasAttribute("Test")) {
       if (includeSuiteSetup) {
         WikiPage suiteSetup =
           PageCrawlerImpl.getInheritedPage(
               SuiteResponder.SUITE_SETUP_NAME, wikiPage
           );
     ...

Beispiel

  • Beispiel aus Fitnesse

    • FitNesse begann als ein HTML und Wiki "front-end" für FIT ("Framework for Integrated Testing")

    • Wiki Seite == Page

    • Test-Suite == Zusammenfassung mehrere Tests

    • Teststruktur

      • ggf. Suite Setup

      • Setup

      • Test (== pageDate)

      • TearDown

      • ggf. Suite TearDown

Code nochmal

public class HtmlUnit {
  public static String testableHtml(
     PageData pageData,
     boolean includeSuiteSetup
   ) throws Exception
   {
     WikiPage wikiPage = pageData.getWikiPage();
     StringBuffer buffer = new StringBuffer();
     if (pageData.hasAttribute("Test")) {
       if (includeSuiteSetup) {
         WikiPage suiteSetup =
           PageCrawlerImpl.getInheritedPage(
               SuiteResponder.SUITE_SETUP_NAME, wikiPage
           );

Erste Verbesserung

public static String renderPageWithSetupsAndTeardowns(
   PageData pageData, boolean isSuite
) throws Exception {

   boolean isTestPage = pageData.hasAttribute("Test");
   if (isTestPage) {
      WikiPage testPage = pageData.getWikiPage();
      StringBuffer newPageContent = new StringBuffer();
      includeSetupPages(testPage, newPageContent, isSuite);
      newPageContent.append(pageData.getContent());
      includeTearDownPages(testPage, newPageContent, isSuite);
      pageData.setContent(newPageContent.toString());

   }

   return pageData.getHtml();
}

Klein

  • Funktionen sollten klein sein

Wie kann das erreicht werden?

  • keine verschachtelten Strukturen

  • die einzig erlaubte Einrückungstiefe sollte dann möglichst nur eine Anweisung enthalten

Klein

public static String renderPageWithSetupsAndTeardowns(
   PageData pageData, bool isSuite
) throws Exception {
   if (isTestPage(pageData)) {
      includeSetupAndTeardownPages(pageData, isSuite)
   }
   return pageData.getHtml();
}

Eine Aufgabe erfüllen

  • eine Aufgabe

    • Wenn alle Schritte einer Funktion eine Abstraktionsebene unter dem Zweck liegen, der durch den Namen ausgedrückt wird

  • Hilfsmittel

    • einen UM-ZU-Absatz formulieren

UM RenderPageWithSetupsAndTeardowns ausZUführen, prüfen wir, ob eine Seite eine Testseite ist, und wenn dies der Fall ist, schließen wir die Setups und Teardowns ein. In beiden Fällen stellen wir die Seite in HTML dar.

Beschreibende Namen

  • gute Namen für kleine Funktionen finden, die eine Aufgabe erledigen

  • lange beschreibende Namen sind besser als kurze geheimnisvolle Namen

  • lange Namen sind besser als lange Kommentare

  • mehrere Wörter per Konvention trennen

    • CamelCaseSchreibweise

  • verschiedene Namen ausprobieren und Code lesen

    • IDE unterstützt das

  • Namen sollten in einem Modul konsistent sein

    • Synonyme vermeiden

Funktionsargumente

  • je weniger Argumente, desto besser

    • jedes Argument erfordert konzeptionelle Kraft beim Lesen

    • Name und Typ des Arguments könnten zu anderer Abstraktionsebene gehören

    • das Testen einer Funktion wird aufwändiger

      • die Kombinationen aller Argumente mit allen möglichen Werten

Funktionsargumente

  • Output-Argumente vermeiden, da ungewohnt

    • Input: Argumente

    • Output: Rückgabewert

Funktionsargumente

  • Argument als Output verwendet

public static void splitToList(String source, List parameter) {
   String[] array = source.split(",");
   parameter.addAll(Arrays.asList(array));
}

Funktionsargumente

  • Argument als Output verwendet

public static void splitToList(String source, List parameter) {
   String[] array = source.split(",");
   parameter.addAll(Arrays.asList(array));
}
  • Rückgabewert als Output

public static List splitToList(String source) {
   String[] array = source.split(",");
   return Arrays.asList(array);
}

Flag-Argumente

  • Hinweis darauf, dass mehrere Aufgaben erfüllt werden

// Aufruf
  render(true);
// Definition
class Renderer {
   void render(boolean isSuite) {}
}

Flag-Argumente

  • Besser mehrere Methoden

// Definition
class Renderer {
   void renderForSuite() {}
   void renderForSingleTest() {}
}

Dyadische Funktionen

  • Funktionen mit 2 Argumenten

  • Verwender muss die Reihenfolge und Bedeutung kennen

    • oder Definition nachschlagen → Aufwand!

  • oft unvermeidbar

// Aufruf
  int result = getResult(); // 24
  assertEquals(24, result);
// Definition
class Assert {
   void assertEquals(int expected, int actual) {}
}

Nebeneffekte vermeiden

public boolean checkPassword(String userName, String password){
   User user = UserGateway.findByName(userName);
   if (user != User.NULL) {
      if (user.password.equals(password)) {
         Application.loginUser(user);
         return true;
      }
   }
   return false;
}
Note

Application.loginUser(user) wird hier nicht erwartet. Evlt. is login nur möglich, wenn user nicht bereits eingeloggt ist …​ (zeitliche Kopplung)

Anweisung oder Abfrage

  • Funktion sollte entweder

    • etwas tun, oder

    • etwas antworten

public boolean set(String attribute, String value){
   if (internalList.contains(attribute)) {
      internalList.set(attribute, value);
      return true;
   } else {
      return false;
   }}
// mögliche Verwendung
  if (set("username", "robkle")) ...
Note

Verwendung ist nicht klar, was die Methode macht set im Kontext von if könnte auch als Adjektiv verstanden werden.

Fehlercode vs Exceptions

  • Fehlercode

    • muss sofort geprüft werden

  • Exception

    • kann am Ende behandelt werden

    • ist ebenfalls eine Aufgabe

      • kann in separate Funktion ausgelagert werden

Fehlercode vs Exceptions

Beispiel mit Fehlercodes inkl. Behandlung

if (deletePage(page) == E_OK) {
   if (registry.deleteReference(page.name) == E_OK) {
      if (ConfigKeys.deleteKey(page.name.makeKey()) == E_OK) {
         logger.log("page deleted");
      } else {
         logger.log("config key not deleted");
      }
   } else {
      logger.log("deleteReferences from registry failed");
   }
} else {
   logger.log("delete failed");
}

Fehlercode vs Exceptions

Beispiel mit Exceptionbehandlung

try {
   deletePage(page);
   registry.deleteReference(page.name);
   ConfigKeys.deleteKey(page.name.makeKey());
}
catch (Exception e)
{
   logger.log(e.getMessage());
}

Fehlercode vs Exceptions

Exceptionsbehandlung auslagern

public void delete(Page page) {
   try {
      deletePageAndAllReferences();
   }
   catch (Exception e)
   {
      logError(e);
   }
}

public void deletePageAndAllReferences(Page page) {...}
public void logError(Exception e) {...}

Don’t Repeat Yourself

  • Viele Innovationen der Software-Entwicklung haben nur ein Ziel

    • Duplizierung zu vermeiden

    • Wiederverwendung fördern

  • Duplikate könnten bei einem Umbau vergessen werden

  • Beispiel

Kommentare

Über Kommentare

  • Kommentare sind kein Ersatz für schlechten Code

  • Kommentare können durch selbsterklärenden Code vermieden werden

// Check to see, if the employee is eligible for full benefits
if ((employee.flags & HOURLY_FLAG) &&
   employee.age > 65)
   ...

Alternative

if (employee.isEligibleForFullBenefits())
   ...

Gute Kommentare

  • Copyright Header

  • nicht-triviale Methoden-Beschreibung

  • nicht-triviale Klassen-Beschreibung

  • Erklärung der Absichten

  • Klarstellungen

  • Warnung vor Konsequenzen

  • TODO-Kommentare

Schlechte Kommentare

  • Geraune

  • Redundante Kommentare

    • Wiederholung des Codes

  • irreführende Kommentare

  • Positionsbezeichner

  • Kommentare hinter schließenden Klammern

  • Auskommentierter Code