Читать книгу Prinzipien des Softwaredesigns - John Ousterhout - Страница 10
Symptome der Komplexität
ОглавлениеKomplexität manifestiert sich ganz allgemein in drei verschiedenen Ausprägungen, die im Folgenden beschrieben werden. Jede dieser Manifestationen erschwert es, Entwicklungsaufgaben zu erledigen.
Ausweiten von Änderungen: Das erste Symptom der Komplexität ist, wenn für eine scheinbar einfache Änderung Code an vielen verschiedenen Stellen angepasst werden muss. Stellen Sie sich beispielsweise eine Website mit vielen Seiten vor, die jeweils ein Banner mit einer Hintergrundfarbe anzeigen. In vielen frühen Websites war die Farbe explizit auf jeder Seite definiert (siehe Abbildung 2-1 (a)). Um den Hintergrund für solch eine Website anzupassen, müsste jede bestehende Seite per Hand geändert werden – für eine große Site mit Tausenden von Seiten nahezu unmöglich. Zum Glück nutzen moderne Websites einen Ansatz wie den in Abbildung 2-1 (b), bei dem die Bannerfarbe einmal an zentraler Stelle definiert wird und alle einzelnen Seiten auf diesen gemeinsamen Wert zugreifen. So kann die Bannerfarbe der gesamten Website durch eine einzelne Änderung angepasst werden. Eines der Ziele guten Designs ist, die Menge an Code zu verringern, die durch jede Designentscheidung betroffen ist, sodass für Designänderungen nicht so viele Codeanpassungen erforderlich sind.
Abbildung 2-1: Jede Seite einer Website zeigt ein farbiges Banner an. In (a) ist die Hintergrundfarbe jedes Banners explizit auf jeder Seite festgelegt. In (b) steht die Hintergrundfarbe in einer gemeinsamen Variablen, und jede Seite bezieht sich auf diese Variable. In (c) nutzen manche Seiten eine zusätzliche Farbe zum Hervorheben, bei der es sich um eine dunklere Variante der Hintergrundfarbe handelt – ändert sich die Hintergrundfarbe, muss sich auch die Farbe der Hervorhebung ändern.
Kognitive Last: Das zweite Symptom der Komplexität ist die kognitive Last, die sich darauf bezieht, wie viel man beim Entwickeln wissen muss, um eine Aufgabe zu erfüllen. Eine höhere kognitive Last bedeutet, dass mehr Zeit mit dem Aufnehmen und Erfassen der erforderlichen Informationen verbracht werden muss und dass es ein höheres Fehlerrisiko gibt, weil eventuell etwas Wichtiges vergessen wurde. Stellen Sie sich beispielsweise eine Funktion in C vor, die Speicher anfordert, einen Zeiger auf diesen Speicher zurückgibt und annimmt, dass der Aufrufende den Speicher freigeben wird. Das erhöht die kognitive Last beim Einsatz der Funktion – wird vergessen, den Speicher freizugeben, führt das zu einem Speicherleck. Kann das System so umstrukturiert werden, dass sich der Aufrufende nicht um das Freigeben des Speichers kümmern muss (dasselbe Modul, das den Speicher anfordert, sorgt auch dafür, dass er wieder freigegeben wird), reduziert das die kognitive Last. Solcherart kognitive Last entsteht auf vielerlei Weisen, wie zum Beispiel durch APIs mit vielen Methoden, globale Variablen, Inkonsistenzen und Abhängigkeiten zwischen Modulen.
Systemdesigner gehen manchmal davon aus, dass sich Komplexität anhand der Menge an Codezeilen messen lässt. Sie nehmen an, dass eine kürzere Implementierung auch einfacher sein muss – sind nur ein paar Zeilen Code für eine Änderung erforderlich, muss die Änderung einfach sein. Diese Sichtweise ignoriert aber die Kosten, die mit der kognitiven Last verbunden sind. Ich habe Frameworks gesehen, die es erlaubten, Anwendungen mit ein paar Zeilen Code zu schreiben. Es war aber außerordentlich schwierig, herauszufinden, wie diese Zeilen auszusehen hatten. Manchmal ist ein Ansatz, der mehr Codezeilen erfordert, tatsächlich einfacher, weil er die kognitive Last verringert.
Unbekannte Unbekannte (Unknown Unknowns): Das dritte Symptom der Komplexität ist, dass nicht offensichtlich ist, welcher Code angefasst werden muss, um eine Aufgabe abzuschließen, oder welche Informationen man haben muss, um die Aufgabe erfolgreich umzusetzen. In Abbildung 2-1 (c) kann man dieses Problem sehen. Die Website nutzt eine zentrale Variable, um die Hintergrundfarbe des Banners festzulegen, daher scheint eine Änderung einfach zu sein. Aber ein paar Webseiten verwenden eine dunklere Schattierung der Hintergrundfarbe für Hervorhebungen, und diese dunklere Farbe ist explizit in den einzelnen Seiten festgelegt. Ändert sich die Hintergrundfarbe, muss sich auch die Farbe für die Hervorhebungen ändern. Leider ist das beim Entwickeln oft nicht klar, daher wird eventuell nur die zentrale Variable bannerBg geändert, ohne die Farbe zum Hervorheben zu ändern. Und selbst wenn sich ein Entwickler dieses Problems bewusst ist, ist nicht offensichtlich, welche Seiten die Farbe zum Hervorheben verwenden – jede Seite der Website muss also durchsucht werden.
Von den drei Manifestationen der Komplexität sind die unbekannten Unbekannten (Unknown Unknowns) die schlimmsten. Denn das bedeutet, dass es etwas gibt, das Sie wissen müssen, es aber keine Möglichkeit gibt, herauszufinden, was das ist oder ob es überhaupt ein Problem ist. Erfahren werden Sie es erst, wenn nach einer Änderung Fehler auftauchen. Dass sich Änderungen ausweiten, ist nervig, aber solange klar ist, welcher Code angepasst werden muss, wird das System funktionieren, sobald die Änderung abgeschlossen ist. Genauso wird eine hohe kognitive Last die Kosten für eine Änderung erhöhen, aber wenn klar ist, welche Informationen gelesen werden müssen, wird die Änderung immer noch sehr wahrscheinlich richtig sein. Bei unbekannten Unbekannten ist unklar, was zu tun ist oder ob eine vorgeschlagene Lösung überhaupt funktionieren wird. Sicher kann man nur sein, wenn man jede Zeile Code im System liest, was für ausgewachsene Systeme ab einer bestimmten Größe völlig unmöglich ist. Und selbst das mag nicht ausreichen, weil eine Änderung von einer subtilen Designentscheidung abhängen kann, die nie dokumentiert wurde.
Eines der wichtigsten Ziele guten Systemdesigns ist Offensichtlichkeit. Das ist das Gegenteil von hoher kognitiver Last und unbekannten Unbekannten. In einem offensichtlichen System kann man beim Entwickeln schnell verstehen, wie der bestehende Code funktioniert und was erforderlich ist, um eine Änderung vorzunehmen. Ein offensichtliches System ist eines, in dem ein Entwickler oder eine Entwicklerin schnell eine Vermutung dazu anstellen kann, was zu tun ist, ohne allzu sehr nachdenken zu müssen – und dabei trotzdem zuversichtlich sein kann, dass diese Vermutung korrekt ist. In Kapitel 18 werden Techniken vorgestellt, mit denen Code offensichtlicher wird.