Softwarearchitektur für komplexe Webanwendungen
25. Juli 2017
Veröffentlicht in:
WebentwicklungDie Kunst, Dinge zu benennen
Die leider viel zu früh verstorbene Netscape-Legende und Internet-Koryphäe Phil Karlton hat einmal gesagt:
"Es gibt in der Informatik nur zwei schwierige Dinge: Cacheinvalidierung und Dinge beim Namen zu nennen."
Beim zweiten Bestandteil dieses Bonmots schlägt Karlton eindeutig eine Brücke von der Informatik, vom Programmieren, zur Literatur.
Das Schaffen guter Literatur, also erfolgreiches Schreiben, ist ein hartes Geschäft. Denn nur gut geschriebene Texte lassen sich auch gut lesen und sind erfolgreich. Und Karlton schlägt diese Brücke nicht ganz zu Unrecht, denn Code und Literatur haben eins gemeinsam: Sie werden nur einmal geschrieben, aber viele Male gelesen. Und damit tragen wir nicht der Tatsache Rechnung, dass Code interpretiert und am Ende von einer Maschine gelesen wird. Vielmehr wird Code von anderen Entwicklern und auch Benutzern gelesen, um ein Programm zu verstehen und es zu modifizieren und zu erweitern.
Beim Programmieren sollten wir also immer im Hinterkopf behalten, dass wir Code nicht nur für uns oder eine Maschine schreiben, sondern für echte Menschen, die diesen Code lesen wollen (oder müssen).
Die Vermittlung bestimmter Konzepte und ihre präzise Benennung spielen also nicht nur in der Literatur, sondern auch in der Informatik eine entscheidende Rolle. Dass Phil Karlton mit seiner Aussage zur Schwierigkeit der Benennung in der Informatik recht hat und welche Auswege es aus diesem Dilemma gibt, wollen wir im folgenden Text darlegen.
Wie man Dinge benennt - ein Gedankenspiel
Um die Schwierigkeit anschaulicher Benennung zu demonstrieren, schlagen wir ein kleines Gedankenspiel vor. Spielen Sie mit? Gut!
Aufgabe 1
Stellen Sie sich ein Sofa vor - so ein richtig schönes, großes, gemütliche Plüschsofa! Wie würden Sie den Raum nennen, in dem dieses Sofa steht? Richtig, das war nicht schwer, oder? Ein Sofa steht natürlich im Wohnzimmer.
Aufgabe 2
Stellen Sie sich nun eine Toilette vor - einen klassischen Flachspüler! Den finden Sie wo? Genau, im Badezimmer. Das war auch nicht schwer, oder?
Diese beiden vermeintlich simplen Fragen führen uns aber zu zwei entscheidenden Erkenntnissen aus der Innenarchitektur, die sich problemlos auf die Informatik übertragen lassen.
Schlussfolgerung 1
Der Name eines Raumes kann aus seinen Einrichtungsgegenständen abgeleitet werden. Oder um es etwas allgemeiner zu formulieren: Der Name einer übergeordneten Struktur kann aus seinen untergeordneten Elementen erschlossen werden. Couch - Wohnzimmer. Toilette - Badezimmer.
Schlussfolgerung 2
Ausgehend vom Raumnamen lässt sich auf bestimmte Einrichtungsgegenstände schließen. In einem Wohnzimmer finden sich vermutlich noch ein Beistelltisch oder ein Sessel. Dafür hängt im Badezimmer in der Regel noch ein Handwaschbecken. Allgemeiner lässt sich also formulieren: Aus dem Namen einer übergeordneten Struktur lässt sich auf ihre Elemente schließen.
Diese scheinbar trivialen Überlegungen führen uns zur nächsten Aufgabe.
Aufgabe 3
Stellen Sie sich vor, Sie betreten einen Raum, in dem sich ein Bett und eine Toilettenschüssel befinden! Wie nennen Sie diesen Raum? Rumpelkammer, Gefängniszelle, Gruselkammer, Hubbediblubb? - Schwierig, oder?
Die Schwierigkeit der Benennung liegt hier nicht in der Anzahl der Objekte. Ein Raum mit 16 Betten ist immer noch ein Schlafzimmer, wenn auch vermutlich eher ein Schlafsaal in einer Jugendherberge. Und ein Raum mit 16 Toilettenschüsseln ... Sie wissen, worauf wir hinaus wollen.
Die Schwierigkeit liegt eher darin, dass ein Bett und eine Toilette recht wenig miteinander zu tun haben. Wir erwarten diese Einrichtungsgegenstände nicht zusammen in einem Raum. Also haben wir Schwierigkeiten ihn korrekt zu benennen.
Daraus lässt sich ableiten:
Schlussfolgerung 3
Die Eindeutigkeit eines Raumnamens hängt davon ab, wie eng die Einrichtungsgegenstände funktional verwandt sind. Oder allgemeiner formuliert: Der Name einer übergeordneten Struktur wird umso klarer, je enger ihre Elemente zusammenhängen.
Genug gespielt. Wenden wir uns wieder der Informatik zu, indem wir unsere drei Schlussfolgerungen auf einige reale oder ausgedachte Beispiele lenken. Schließlich gibt es in der Informatik Komponenten, Klassen, Funktionen Dienste und Anwendungen. Schauen wir doch einmal, was passiert, wenn wir unsere drei Schlussfolgerungen hierauf übertragen. Enden wir dann im Wohnzimmer oder doch im Gruselkabinett?
Beispiel 1 - HTTP und Autos
Lassen Sie uns zu Beginn ein wenig Verwirrung stiften, indem wir annehmen, ein Auto ließe sich als Methode in das Konzept HTTP (Hypertext Transfer Protocol) einfügen. Und lassen Sie uns kurz annehmen, dass das sinnvoll sei, da es sich sowohl bei einem Auto als auch bei HTTP in gewissem Sinne um Transportmittel handelt. Autos bewegen Menschen über Straßen, das Hypertext Transfer Protocol transportiert Daten durchs Internet. Drückt man diese Vorstellung konsequent in Code aus, erhält man in etwa Folgendes:
<?php
interface Transportmittel {
/ * Methoden für ein Auto * /
public function Gaspedal ();
public function Bremse ();
/ * Methoden für einen HTTP-Client * /
public function makeGetRequest (string $param);
}
Übersetzt man diesen Code in die Welt der Verkehrsmittel, so erhält man eine Art Auto mit drei Pedalen: Gas, Bremse und makeGetRequest.
Sie waren schon am Anfang skeptisch, stimmts? Zurecht, denn es ergibt keinen Sinn, HTTP und Autos in eine Klasse zu stecken, nur weil es sich in einem gewissen Sinne um Transportmittel handelt.
Wie sieht es denn mit etwas weniger Fantastischem aus, wie etwa dem nächsten Beispiel!
Beispiel 2 - Base
Die Klasse Base existiert im Gegensatz zum HTTP-Auto tatsächlich innerhalb des Ruby-Lokalisierungspaketes i18n.rb. In dieser gut dokumentierten Bibliothek finden sich folgende Angaben (hier der Übersichtlichkeit halber verkürzt dargestellt):
<?php
class Base
def config
def translate
def locale_available?(locale)
def transliterate
end
Wenn man genau hinschaut, hat Base ein nicht zu leugnendes Potenzial zum Gruselkabinett. Base ist eine schlechte Bezeichnung, die kaum Rückschlüsse auf die enthaltenen Elemente zulässt. So lässt sich mit Base etwas konfigurieren, übersetzen oder überprüfen, denn Base kann auch herausfinden, ob überhaupt eine Lokalisierungs-Datei vorhanden ist. Kurzum - Base ist ein ungeordnetes Sammelsurium verschiedener Elemente, die lose mit dem Thema Lokalisierung verknüpft sind.
Trotz dieser Inkohärenz funktioniert die Klasse Base.
Beispiel 3 - Design und Benennung hängen zusammen
Discourse, die quelloffene Managementsoftware für Internetforen und Mailinglisten, enthält einige recht anschauliche Beispiele dafür, wie sich Klassen durch eine exaktere Benennung übersichtlicher gestalten ließen. Als Beispiel soll die Klasse PostAlerter dienen, die Definitionen folgender Elemente enthält:
- notify_post_users
- notify_group_summary
- notify_non_pm_users
- create_notification
- unread_posts
- unread_count
- group_stats
Der Name PostAlerter selbst legt nahe, dass die Klasse Funktionen enthält, mit der jemand oder etwas bei neu eingegangenen Posts alarmiert werden kann. Die ersten vier Elemente notify_post_users, notify_group_summary, notify_non_pm_users und create_notification lassen diesen Schluss auch zu. Aber wie sieht es mit den restlichen drei Elementen aus? Die Elemente unread_posts, unread_count und group_stats scheinen etwas ganz anderes abzubilden. Sie scheinen für das Thema Statistik von Bedeutung zu sein.
Wäre es dann nicht übersichtlicher, die vorhandene Klasse aufzuspalten? Die ersten vier Elemente verbleiben in der Klasse Postalerter, wohingegen die übrigen Elemente die Klasse PostStatistics formen.
Dass es noch schlimmer geht, beweist Beispiel 4.
Beispiel 4 - Horrornamen in Spring
Das quelloffene Javaframework Spring enthält einige Klassen, die geradezu Paradebeispiele dafür sind, wie Dinge keinesfalls benannt werden sollten. So findet sich im Spring-Code eine Klasse mit dem hübschen Namen SimpleBeanFactoryAwareAspectInstanceFactory. Sie enthält folgende Elemente:
- public function getAspectClassLoader()
- public function getAspectInstance()
- public function getOrder()
- public void setAspectBeanName(String aspectBeanName)
- public function setBeanFactory(BeanFactory beanFactory)
Da bleibt eigentlich die großartige Evelyn Hamann in einer ihrer zahlreichen Rollen an der Seite Loriots zu zitieren: "Ach ..."
Nach all dem Chaos wollen wir als letztes Beispiel aber noch einen Blick auf eine wirklich gelungene Nomenklatur werfen:
Beispiel 5 - Hoffnungsschimmer Arc
Arc, als ein Bestandteil der JavaScript-Bibliothek D3.js zeigt, wie der Idealfall einer übersichtlichen Benennung aussieht. Die Funktion "export default" enthält eine ganze Reihe von Methoden, die sich auf Bögen beziehen. Sie sind alle nach demselben Schema benannt, sodass sich unter anderem die Methoden arc.centroid, arc.innerRadius, arc.outerRadius oder arc.padRadius innerhalb dieser Klasse finden.
Dem menschlichen Leser ist bei der Lektüre des Arc-codes sofort klar, wozu diese Methoden dienen und worauf sie angewendet werden können. Logischerweise befinden sich alle Methoden innerhalb einer einzigen, sauber benannten Klasse.
Und das führt uns zur wichtigsten Frage: Wie benennt man Klassen, Funktionen und andere Programmbestandteile sauber und nachvollziehbar?
Wie man Dinge sauber benennt
Die oben genannten Beispiele enthalten einige wichtige Hinweise darauf, was bei einer sauberen Benennung von Programmteilen zu beachten ist. Hierzu zählen:
1) Dinge trennen, die nicht zusammen gehören
Die Toilette im Schlafzimmer lehrt uns, Dinge, die nicht ausreichend funktionell zusammenhängen, zu trennen. (Den funktionellen Zusammenhang, dass man sowohl auf Betten als auch auf Toilettenschüsseln sitzen kann, darf man getrost als "nicht ausreichend" deklarieren.)
Hierzu müssen in einem ersten Schritt alle Elemente einer Klasse identifiziert und benannt werden. Im zweiten Schritt folgt dann die Sortierung anhand der Funktionalität. Elemente mit ähnlicher Funktion werden jeweils in gesonderten Klassen gesammelt.
In der Welt der Innenarchitektur bedeutet das, dass Handwaschbecken und Flachspüler wieder ins Badezimmer geräumt werden, wohingegen die Nachttischlampe weiter ihren Platz im Schlafzimmer neben dem Bett einnehmen darf.
Treten Schwierigkeiten bei der eindeutigen Benennung einer Klasse auf, so ist das fast immer ein Hinweis, dass noch nicht alle Elemente in die jeweils richtige Klasse sortiert wurden.
2) Neue Zusammenhänge entdecken
Manchmal kann es sinnvoll sein, Elemente, die sich zu etwas neuem Ergänzen, in eine neue eigene Klasse zu überführen. Um noch einmal ein Beispiel aus der Innenarchitektur zu wählen, sehen wir uns einen Raum mit Couch, Tisch und Kühlschrank an. Natürlich, es handelt sich bei diesem Raum um eine offene Wohnküche. Die Öffnung des Kochbereichs hin zum Wohnraum eröffnet völlig neue Strukturen und Kommunikationsformen.
Es lohnt sich, die Elemente einer Klasse aus verschiedenen Blickwinkeln zu betrachten. Mitunter kann es lohnenswerter sein, eine Klasse nicht zu trennen, sondern neu zu definieren. Schnell eröffnen sich da neue Möglichkeiten.
3) Kriterien für die Gruppierung benennen
Es kann vorkommen, dass alle Elemente perfekt benannt und in Klassen sortiert sind, und trotzdem stimmt irgendetwas nicht. So wird beispielsweise die Toilettenschüssel selbst dann nicht zum weißen Bett und zum weißen Nachttisch passen, wenn sie strahlend weiß ist. Sie hat ganz einfach eine andere Funktion als die beiden anderen Gegenstände. In diesem Beispiel wurde mit der Farbe "weiß" ganz einfach das falsche Sortierkriterium gewählt.
Ähnlich verhält es sich bei der Informatik. Elemente und Klassen können noch so sauber benannt sein. Wenn sie nach dem falschen Muster sortiert sind, fliegt Ihnen die schönste Ordnung um die Ohren.
Eine Frage der Kommunikation
Code wird in erster Linie für Menschen geschrieben. Und Menschen müssen sich innerhalb eines Codeabschnitts schnell und sicher zurechtfinden. Eine wichtige Voraussetzung hierzu ist die präzise Benennung aller vorhandenen Bestandteile. Diese muss eindeutig sein und gleichzeitig Rückschlüsse auf die Struktur des Programms und die Anordnung seiner Elemente erlauben. Diese präzise und nachvollziehbare Benennung von Funktionen, Klassen und Elementen ist unverzichtbarer Bestandteil der Kommunikation zwischen Entwickler und Softwarerezipient.
Wir hoffen, dass unsere zugegebenermaßen etwas drastischen Beispiele dabei behilflich sein konnten, Ihr Gespür für die Benennung von Programmkomponenten zu schärfen, um Ihren Code so noch besser kommunizieren zu können. Teilen Sie uns Ihre eigenen Lösungen und Gedanken für das Problem, das schon Phil Karlton beschäftige, in einem Kommentar mit. Wir freuen uns auf die Kommunikation mit Ihnen.
Können wir weiterhelfen?
Sie haben ein spannendes Projekt und möchten mit uns zusammenarbeiten? Kontaktieren Sie uns jetzt!