Das Herzstück des Programmierens ist die Modellierung – also die Erstellung eines Domänenmodells beziehungsweise eines Modells von Objekten und Prozessen im Arbeitsspeicher des Computers. Programmiersprachen liefern dafür Syntaxelemente, mit denen sich Randbedingungen für Modelle formulieren lassen. Jede zusätzliche Struktur, die eingeführt wird, um Funktionalität zu erweitern, bringt allerdings weitere Einschränkungen mit sich. Eine höhere Abstraktionsebene kann umgekehrt einige dieser Beschränkungen aufheben und die Komplexität des Modells sowie des Codes reduzieren. Wir balancieren daher ständig zwischen dem Ausweiten von Funktionen und deren Zusammenfassung zu einem allgemeineren Modell – und dieser Prozess sollte iterativ immer wieder durchlaufen werden.
Erstaunlich ist, dass Menschen mithilfe von Modellen und Abstraktionen Probleme lösen können, deren Komplexität ihre Gedächtnis- und Denkkapazität eigentlich übersteigt. Wie präzise ein Modell ist, bestimmt seinen Nutzen für Entscheidungen und Steuerungsmaßnahmen. Ein Modell bleibt stets unvollständig und spiegelt nur einen kleinen Ausschnitt der Realität wider – eine oder mehrere ihrer Seiten. Unter klar begrenzten Einsatzbedingungen kann ein Modell jedoch dem realen Objekt einer Domäne praktisch gleichwertig sein. Es gibt physikalische, mathematische, Simulations- und viele weitere Modelle; uns interessieren hier vor allem Informations- und algorithmische Modelle.
Abstraktion ist eine Verallgemeinerung, die viele unterschiedliche, aber ähnliche Fälle zu einem einzigen Modell zusammenführt. Uns beschäftigen Datenabstraktionen und abstrakte Algorithmen. Die einfachsten algorithmischen Abstraktionen sind Schleifen (iterative Verallgemeinerung) und Funktionen (Prozeduren, Routinen). Eine Schleife beschreibt mit einem einzigen Befehlsblock viele Iterationen, indem sie diesen mehrfach mit unterschiedlichen Variablenwerten ausführt. Funktionen werden ebenfalls vielfach mit unterschiedlichen Argumenten aufgerufen. Typische Datenabstraktionen sind Arrays, assoziative Arrays, Listen, Mengen usw. In Anwendungen fasst man Abstraktionen zu Schichten (Abstraktionsebenen) zusammen:
- Niedriglevel-Abstraktionen sind direkt in der Programmiersprache eingebaut (Variablen, Funktionen, Arrays, Events).
- Höhere Abstraktionen finden sich in Plattformen, Runtimes, Standard- und externen Bibliotheken oder werden aus einfachen Abstraktionen selbst aufgebaut.
Sie heißen „abstrakt“, weil sie generische Aufgaben lösen, also nicht domänenspezifisch sind.
Das Aufbauen von Abstraktionsschichten ist vielleicht die wichtigste Programmieraufgabe; davon hängen Flexibilität, Änderungsaufwand, Integrationsfähigkeit und Lebensdauer einer Software ab. Alle Schichten, die nicht an eine Fachdomäne gebunden sind, nennen wir Systemschichten. Darüber legt der Entwickler Applikationsschichten, deren Abstraktionsgrad und Universalität abnehmen, je konkreter sie auf Aufgaben zugeschnitten werden.
Abstraktionen verschiedener Ebenen können im selben Adressraum (einem Prozess bzw. einer Anwendung) oder in getrennten laufen. Ihre Trennung und Interaktion erreicht man über APIs, Modularität, Komponentenansätze – oder schlicht Disziplin, indem man direkte Aufrufe „mitten aus“ einer Komponente „mitten in“ eine andere vermeidet, sofern Sprache oder Plattform das nicht erzwingen. Das gilt selbst innerhalb eines Prozesses, wo theoretisch jede Funktion überall aufrufbar wäre. Ziel ist es, die Kopplung zwischen Schichten und Komponenten zu verringern, damit sie austauschbar, wiederverwendbar und separat entwickelbar bleiben. Zugleich erhöht man die Kohäsion innerhalb einer Schicht oder eines Moduls, was Lesbarkeit, Verständlichkeit und Änderbarkeit verbessert. Gelingt es, Abstraktionsebenen sauber zu trennen und Module so klein zu halten, dass ein Entwickler sie vollständig überblicken kann, wird der Entwicklungsprozess skalierbar, steuerbar und vorhersagbar. Dieses Prinzip liegt auch der Microservices-Architektur zugrunde, gilt aber genauso für Module im selben Prozess.
Generell gilt: Je besser ein System verteilt ist, desto besser ist es auch zentralisierbar. Aufgaben werden auf der passenden Ebene gelöst, wo ausreichende Informationen vorliegen; starre Verbindungen zwischen Modellen unterschiedlicher Abstraktion entfallen. Dadurch kommt es zu keiner unnötigen Eskalation von Problemen nach oben, Entscheidungsknoten werden nicht „überhitzt“, Datenverkehr bleibt minimal und die Reaktionsgeschwindigkeit steigt.