Mittwoch, 29. Oktober 2014

Einfachheit der Funktion

Im Einführungsartikel zur Einfachheit habe ich dargelegt, dass Einfachheit bzw. die damit verbundenen Gegenbegriffe (wie "Komplexität", "Kompliziertheit" oder "Schwierigkeit") als System-Eigenschaften verstanden werden müssen. Daraus folgt, dass das Phänomen der Einfachheit, dem wir uns im Rahmen der vorliegenden Artikelserie nähern wollen, aus den Blickwinkeln der vier Systemkonzepte Funktion, Struktur, Hierarchie und Repräsentation untersucht werden kann. Im vorliegenden Artikel widme ich mich einigen Gedankengängen zur funktionalen Einfachheit von Software.


1. Zwei Dimensionen: Prozesse und Daten

Das Konzept der Funktion hatten wir im allgemeinen System-Modell wie nebenstehend dargestellt. Diese Abbildung veranschaulicht bereits, dass die Herbeiführung von Funktionen im Wesentlichen von zwei interagierenden Bereichen des Software-Systems abhängt, nämlich den funktionalen Prozessen und den Daten des Systems. 

In [Wang2009] werden diese Zusammenhänge und ihre Auswirkung auf die Komplexität von Software-Systemen präziser modelliert. Um einige Eigenschaften zu ermitteln, die uns dabei helfen können, ein Software-System funktional so einfach wie möglich zu gestalten, können wir das Modell von Wang wie folgt vereinfachen:
Funktionale Komplexität = Prozesskomplexität * Datenkomplexität
Wang macht sehr konkrete Vorschläge, wie Prozesskomplexität und Datenkomplexität zu ermitteln sind. Auf einige dieser Vorschläge werde ich unten im Abschnitt "3.1. Komplexität der Verhaltenskonstrukte" noch näher eingehen. Wichtig ist an dieser Stelle, dass wir die zwei wesentlichen Dimensionen zu Beeinflussung der funktionalen Einfachheit weitgehend unabhängig voneinander untersuchen können. Dabei können wir Inputs und Outputs der Datenkomplexität zurechnen und decken somit das vollständige funktionale Modell für Software-Systeme ab.

2. Einfachheit der Daten

Es muss an dieser Stelle nicht mehr detailliert ausgeführt werden, dass eine Reduktion der Datenmenge sowohl in der Breite als auch in der Tiefe immer mit einer Vereinfachung einhergeht. Weitere Überlegungen zur Bedeutung der Reduktion hatten wir bereits im vorhergehenden Artikel angestellt. Ich konzentriere mich daher auf die wesentlichen Aspekte, die im Zusammenhang mit den elementaren Daten-Operationen Erzeugen, Lesen, Modifizieren und Löschen (create/read/update/delete, kurz CRUD) zu beachten sind.

2.1. Datenqualität beim Erzeugen, Modifizieren und Löschen

An die Qualität von Daten sind zahlreiche Anforderungen zu stellen. Ich zitiere hier einige dieser Anforderungen aus dem Wikipedia-Artikel zur Informationsqualität:
  • Korrektheit: Die Daten müssen mit der Realität übereinstimmen.
  • Konsistenz: Ein Datensatz darf in sich und zu anderen Datensätzen keine Widersprüche aufweisen.
  • Zuverlässigkeit: Die Entstehung der Daten muss nachvollziehbar sein.
  • Vollständigkeit: Ein Datensatz muss alle notwendigen Attribute enthalten.
  • Genauigkeit: Die Daten müssen in der jeweils geforderten Exaktheit vorliegen (Beispiel: Nachkommastellen).
  • Aktualität: Alle Datensätze müssen jeweils dem aktuellen Zustand der abgebildeten Realität entsprechen.
  • Redundanzfreiheit: Innerhalb der Datensätze dürfen keine Dubletten vorkommen.
  • Relevanz: Der Informationsgehalt von Datensätzen muss den jeweiligen Informationsbedarf erfüllen.
  • Einheitlichkeit: Die Informationen eines Datensatzes müssen einheitlich strukturiert sein.
  • Eindeutigkeit: Jeder Datensatz muss eindeutig interpretierbar sein.
  • Verständlichkeit: Die Datensätze müssen in ihrer Begrifflichkeit und Struktur mit den Vorstellungen der Fachbereiche übereinstimmen.
Wir können zunächst festhalten, dass sich die Handhabung von Daten insgesamt vereinfacht, wenn die betreffenden Daten diese Kriterien erfüllen. Beispielsweise würde sich aus fehlender Konsistenz für einen verarbeitenden Prozess das Problem ergeben, mit den resultierenden Widersprüchen umgehen zu müssen. Auch eine mangelnde Vollständigkeit der Daten müsste im Rahmen der Verarbeitung der Daten entsprechend gehandhabt werden. Ähnliche Argumente gelten für alle genannten Qualitätskriterien. Werden diese Kriterien bei Erzeugung, Modifikation und Löschung sichergestellt, so können sich lesende Prozesse jederzeit auf sie verlassen. Dies kann die Verarbeitung der Daten erheblich vereinfachen.

2.2. Vereinfachung von Lesezugriffen

Quelle: Wikipedia
Um mit Daten arbeiten zu können, müssen sie erst gefunden werden. Der Nutzer A in der nebenstehenden Abbildung möchte gerne auf B zugreifen, um eine (womöglich triviale) Operation ausführen zu können. Dieses Unterfangen erweist sich hier alles andere als einfach. Übertragen auf Software-Systeme könnte es sich um die Formulierung einer Abfrage in einer Abfragesprache, um die Navigation in einem Objektgeflecht oder um andere Mechanismen handeln, um an das benötigte Daten-Objekt zu gelangen. Neben den Mechanismen zum Auffinden von Daten können auch Zusatzaufwände zum Entpacken oder Transformieren von Daten entstehen. Es versteht sich von selbst, dass die Einfachheit eines Systems erheblich leiden würde, wenn sich alle Daten-Zugriffe als derart aufwändig erwiesen. Diese Erkenntnis mag trivial sein, doch erfahrungsgemäß wird auf dieses Thema viel zu wenig Wert gelegt, so dass nicht wenige Software-Systeme unter überflüssig komplexen Mechanismen zum Auslesen der benötigten Daten leiden.

3. Einfachheit der Prozesse

Prozesse repräsentieren im funktionalen System-Konzept das Verhalten des Systems. Im Rahmen des Einführungsartikels hatte ich die folgende Unterscheidung des Structure-Behavior Model von Jurgen Appelo widergegeben [Url:Appelo2010]:
Demgemäß sind bzgl. der Einfachheit von Prozessen zwei Fragestellungen zu untersuchen:
  • Wie komplex sind die Konstrukte, welche das System-Verhalten steuern?
  • Welche Aspekte des Systems beeinflussen die Vorhersagbarkeit des System-Verhaltens negativ?

3.1. Komplexität der Verhaltenskonstrukte

[Wang2007] unterscheidet insgesamt zehn generische Verhaltenskonstrukte, die er in [Wang2009] in folgende fünf Gruppen einteilt:
  • Sequenz
  • Verzweigung
  • Iteration
  • Einbettung
  • Nebenläufigkeit
Sequenz, Verzweigung und Intervall sind selbsterklärend und entsprechen dem Verständnis der entsprechenden Kontrollstrukturen aus imperativen Programmiersprachen. Unter Einbettung versteht Wang Funktionsaufrufe oder Rekursionen. Unter Nebenläufigkeit werden parallele Ausführung und Interrupt-Verarbeitung verstanden.  

In empirischen Untersuchungen hat Wang die kognitiven Aufwände untersucht, die mit der Verwendung dieser Konstrukte verbunden sind. Die Sequenz wird als einfachstes Konstrukt betrachtet und dient als Eichpunkt einer Skala der kognitiven Gewichte (engl. unit of cognitive weight). Eine Verzweigung weist gegenüber der Sequenz das 3- bis 4-fache Gewicht auf, Schleifen und Funktionsaufrufe das 7- bis 8-fache, Rekursionen das 11-fache, parallele Ausführung das 15-fache und Interrupt-Verarbeitung das 22-fache Gewicht. Die empirischen Untersuchungen bestätigen, dass diese Verhältnisse (im Unterschied zu den Absolutwerten) unabhängig von den Vorraussetzungen konkreter Personen weitgehend stabil sind.

Diese Ergebnisse zeigen, dass insbesondere die besonders komplex einzustufenden Konstrukte wie Rekursion und Nebenläufigkeit wohlbedacht und nur dann eingesetzt werden sollten, wenn keine andere anforderungsgerechte Lösung mit einfacheren Mitteln realisiert werden kann.

3.2. Aspekte der Vorhersagbarkeit

Die Vorhersagbarkeit von Prozessen kann außer durch die Verwendung komplexer Verhaltenskonstrukte durch weitere Faktoren negativ beeinflusst werden. Betrachten wir zur Veranschaulichung die folgenden beiden Systeme:
Quelle: Wikipedia, Artikel "Uhr" und "Roboter"
Eine mechanische Uhr kann aus einer großen Menge von Teilen bestehen, die ohne entsprechende Fachkenntnis kaum funktionsgemäß zusammengesetzt werden können. Nichtsdestotrotz ist das Verhalten einer Uhr, solange sie nicht defekt ist, in hohem Maße vorhersagbar. Ganz anders verhält es sich bei einem hochentwickelten Roboter, der über zahlreiche Sensoren verfügt und sein Verhalten permanent neuen Daten anpassen muss, die er über seinen eigenen Zustand und die Umwelt erhält.

Die Vorhersagbarkeit einer Software-Funktion wird wesentlich durch folgende Faktoren bestimmt:
  • Einfluss der System-Zustände auf die Funktion: Eine Funktion ist sehr vorhersagbar, wenn sie ausschließich von ihren Inputs abhängt. Auf identische Inputs folgt dann immer dasselbe Verhalten. Die Vorhersagbarkeit nimmt ab, je mehr innere System-Zustände die Funktion beeinflussen. Für Software-Systeme kann daraus gefolgert werden, dass Funktionen so weit als möglich nur auf Funktions-lokale Daten zugreifen sollten. Je umfangreicher eine Funktion von Daten aus anderen Systembereichen zugreift und je mehr solcher Systembereiche sie dafür benötigt, desto weniger vorhersagbar ist die Funktion.
  • Zeitpunkt der Inputs: Eine Funktion ist umso früher vorhersagbar, je weniger sie von Eingabedaten abhängt, die erst im Laufe der Funktionsausführung eintreffen. Im Idealfall stehen bereits mit Beginn der Funktionsausführung alle relevanten Informationen zur Verfügung. Treffen einige Informationen erst später ein (z. B. durch Rückkopplungen oder Störgrößen), so tritt Klarheit über die Funktionsausführung erst später ein und die Vorhersagbarkeit wird insgesamt gemindert.
  • Nichtdeterministische Aspekte: Funktionen sollten bei identischen Inputs und identischem System-Zustand immer denselben Output erzeugen. Es ist daher darauf zu achten, dass nichtdeterministische Effekte vermieden werden. Ein typischer Kandidat für einen nichtdeterministischen Input ist die Systemzeit. Macht eine Funktion von der exakten Systemzeit Gebrauch, so wird sie sich nahezu unmöglich zwei Mal in derselben Weise ausführen lassen. Ähnliche Effekte können durch Rundungsfehler bei der Verwendung von Gleitkommazahlen ergeben. Für zahlreiche Systemtypen sind derartige Effekte kaum auszuschließen, bspw. für eingebettete Systeme, deren Sensordaten in die Funktion einfließen. Ein probates Mittel für den Umgang mit diesem Problem kann eine Vereinfachung oder Abstraktion der Daten sein: An Stelle der Systemzeit könnte ein Tagesdatum angenommen werden; Gleitkomma-Operationen können mit einer festgelegten geringeren Präzision ausgeführt werden; und Sensor-Daten lassen sich ggf. ebenfalls runden oder auf andere Weise in wirksame Kategorien einteilen.
  • Zeitlich komplexe Funktions-Abfolgen: Im Rahmen des Abschnitts "3.1. Komplexität der Verhaltenskonstrukte" wurde bereits darauf hingewiesen, dass Konstrukte wie parallele Ausführung oder Interrupt-Handling das Verständnis des System-Verhaltens erschweren können. Die zeitliche Abfolge von Funktionen kann jedoch noch in vielerlei anderer Weise zu Problemen führen, welche die Vorhersagbarkeit mindern. Weitere Beispiele hierfür sind asynchrone Kommunikationsformen, lange Wartezeiten oder transaktionale Konzepte. Wann immer der Ablauf einer Funktion sich vom Idealbild eines linearen Ablaufs entfernt, leidet die Vorhersagbarkeit.
Es ist erwähnenswert, dass sich Probleme, die auf schwer vorhersagbarem Verhalten beruhen, in der Regel nicht durch Vereinfachungen der Struktur beheben lassen. [Url:Appelo2010] unterscheidet daher Simplifizierung von Linearisierung und meint mit letzterer die Verbesserung der Vorhersagbarkeit eines System-Verhaltens.

3.3. Testbarkeit als Gradmesser

Die in den Abschnitten "3.1. Komplexität der Verhaltenskonstrukte" sowie "3.2. Aspekte der Vorhersagbarkeit" dargestellten Themen sind in zweierlei Hinsicht ernüchternd:
  • Zum einen lassen sich zahlreiche Faktoren, welche die Einfachheit beeinträchtigen, in größeren Software-Systemen kaum vermeiden. 
  • Zum anderen handelt es sich hier nicht um eine abgeschlossene Liste dieser Faktoren, sondern lediglich um einige prominente und naheliegende Beispiele. 
Es existiert jedoch ein probates Mittel, das beim Streben nach funktionaler Einfachheit sehr gute Dienste leistet, und dieses Mittel ist der Einsatz von Tests. Tests prüfen Funktionen, und diese Prüfung soll so einfach wie möglich durchführbar sein. Je einfacher dies tatsächlich möglich ist, je testbarer also eine Funktion ist, als desto einfacher und vorhersagbarer kann die Funktion angesehen werden. Wann immer sich einzelne Funktionen zur schwer testen lassen, ist dies ein klarer Hinweis auf fehlende funktionale Einfachheit.

4. Quellen

[Url:Appelo2010] - Simplicity - A New Model, Jurgen Appelo, siehe http://www.noop.nl/2010/09/simplicity-a-new-model.html (2010) 

[Wang 2007] - Software Engineering Foundations - A Software Science Perspective, Wang, Yingxu (2007)

[Wang2009] - On the Cognitive Complexity of Software and its Quantification and Formal Measurement, Yingxu Wang (2009)