API-Design. Kai SpichaleЧитать онлайн книгу.
Das Geheimnisprinzip wurde bereits 1972 von David Parnas in seinem Aufsatz »On the Criteria To Be Used in Decomposing Systems Into Modules« [Parnas 1972] eingeführt. Als Kriterium zur Modularisierung schlug Parnas das Kapseln von Implementierungsentscheidungen vor. Diese Entscheidungen sollen hinter wohldefinierten Schnittstellen verborgen bleiben, sodass diese geändert werden können, ohne dass alle abhängigen Module angepasst werden müssen. Das Geheimnisprinzip verbessert die Wartbarkeit von Software und macht sie stabiler. Module anderer Entwickler können wiederverwendet werden, ohne Details über deren Implementierung kennen zu müssen. Für eine korrekte Benutzung reicht es, zu wissen, welche Funktionen das Modul bietet und wie diese über die Schnittstelle des Moduls genutzt werden können.
Datenkapselung (encapsulation)
Geheimnisprinzip und Datenkapselung stehen im engen Zusammenhang und werden häufig synonym verwendet. Trotzdem handelt es sich um unterschiedliche Softwaredesignprinzipien. Das Hauptanliegen von Kapselung ist das Ziehen einer Grenze um etwas, sodass man zwischen innen und außen unterscheiden kann. Eine objektorientierte Sprache wie Java bietet Objekte für diesen Zweck. Datenkapselung ist demzufolge das Feature einer Programmiersprache, mit dessen Hilfe das Geheimnisprinzip umgesetzt werden kann.
Trennung von Zuständigkeiten (separation of concerns)
Die Trennung von Zuständigkeiten ist ein Softwaredesignprinzip zur Aufteilung der Software in unterscheidbare Sektionen, die jeweils eine Zuständigkeit haben. Diese Aufteilung unterstützt einen modularen Entwurf im Zusammenspiel mit Datenkapselung und dem Geheimnisprinzip. Vorteile dieser Aufteilung sind vereinfachte Entwicklung und Wartung, weil indivduelle Sektionen unabhängig voneinander wiederverwendet oder weiterentwickelt werden können.
APIs nicht nur für Komponenten- und Teamgrenzen
APIs werden häufig zur Integration von Komponenten eingesetzt. Nach dem Gesetz von Conway stimmen diese technischen Schnittstellen häufig mit Team- oder Organisationsgrenzen überein. APIs – insbesondere die auf der hier betrachteten Objektebene – sind jedoch auch innerhalb einer Komponente oder eines Teams zu finden, denn prinzipiell kann API-Design in der gesamten Codebasis eingesetzt werden, um den Code zu verbessern.
public class NoEncapsulationOrInformationHiding {
public static final int STATUS_ENABLED = 0;
public static final int STATUS_DISABLED = 1;
public int currentStatus = STATUS_ENABLED;
}
Dieses Beispiel zeigt, dass der Einsatz einer objektorientieren Sprache nicht automatisch Datenkapselung und das Geheimnisprinzip sicherstellen.
public class EncapsulationWithoutInformationHiding {
public static final int STATUS_ENABLED = 0;
public static final int STATUS_DISABLED = 1;
private int currentStatus = STATUS_ENABLED;
public int getStatus() {
return currentStatus;
}
}
Diese zweite Variante ist insofern besser, weil der interne Zustand des Objektes gekapselt wird. Kein Benutzer dieser Klasse ist in der Lage, unkontrolliert den Wert des Attributes currentStatus zu ändern. Das Geheimnisprinzip wird dennoch nicht befolgt, weil implementierungsspezifische Details über eine Zugriffsmethode (accessor method) exponiert werden.
public class EncapsulationAndInformationHiding {
private static final int STATUS_ENABLED = 0;
private static final int STATUS_DISABLED = 1;
private int currentStatus = STATUS_ENABLED;
private int getStatus() {
return currentStatus;
}
public boolean isEnabled() {
return getStatus() == STATUS_ENABLED;
}
}
Diese Variante nutzt Datenkapselung zur korrekten Umsetzung des Geheimnisprinzips. Benutzer dieser Klasse können den Status abfragen, ohne dass sie erfahren, wie dieser realisiert ist. Auch das Design dieser API hat sich verbessert, sodass die Klasse intuitiver benutzt werden kann.
API-Design fördert Clean Code
Wie das vorherige Beispiel zeigt, kann eine gute API das Schreiben von lesbarem und wartbarem Clientcode unterstützen. Clean Code [Martin 2008] – eine Sammlung verschiedener Maßnahmen zur Verbesserung der Verständlichkeit von Quellcode – verfolgt ähnliche Ziele. Das gleichnamige Buch von Robert C. Martin deckt viele Themen ab, die ebenfalls für gutes API-Design relevant sind:
Auswahl aussagekräftiger Namen
Entwurf von Klassen und Methoden
Fehlerbehandlung
Boundaries
Im Gegensatz zu Clean Code schaut API-Design jedoch nicht in das Innere von Klassen und Methoden. Deswegen enthält dieses Buch auch keine Empfehlung zur Formatierung von Quellcode, zur Benutzung von Instanzvariablen oder Schachtelungstiefe von If-Anweisungen.
4.2Utility-Bibliothek
Auch Utility-Bibliotheken haben eine API. Diese Bibliotheken sind häufig klein und haben keinen globalen Zustand. Globaler Zustand entsteht beispielsweise durch eine Datenbank, eine Konfiguration oder Initialisierung. Charakteristisch für diese APIs sind statische Methoden, wie man sie in Apache Commons Lang findet. Ein passendes Beispiel sind die statischen Methoden der Klasse StringUtils:
chomp
contains
appendIfMissing
equals
Auch Joda-Time, Guava und FEST sind Utility-Bibliotheken. Selbst das JDK kann als Beispiel genannt werden. Typischerweise bieten Utility-Bibliotheken konkrete Klassen zur Benutzung an.
4.3Service
Charakteristisch für APIs dieser Kategorie sind Fassaden und Datentransferobjekte zur Kapselung einer zugrunde liegenden Komponente, sodass man diese Kategorie auch »Component Boundary« nennen könnte. Die API eines Service kapselt das Klassenmodell der Komponente vollständig oder partiell. Bei einer partiellen Kapselung werden Elemente der internen Implementierung der Komponente in der API verwendet, sodass Benutzer ebenfalls davon abhängig werden.
Den überladenen Begriff »Service« finden man im Domain-Driven Design (DDD) nach Eric Evans [Evans 2004] als Bezeichnung für eine Operation, die nicht natürlich einem anderen Domänenobjekt zugeordnet werden kann und deswegen Teil eines Serviceobjektes ist. Diese Bedeutung von »Service« passt nicht zu dieser API-Kategorie, wenngleich die Platzierung von Methoden eine wichtige Aufgabe beim API-Design darstellt.
Domain-Driven Design, Application Services und Onion Architecture
Ein Application Service – ebenfalls ein Baustein im Domain-Driven Design nach Vaughn Vernon [Vernon 2013] – ist ein treffenderes Beispiel. Diese Services bieten Operationen für externe Benutzer und kapseln das zugrunde liegende Domänenmodell. Die Application Services sind ebenfalls ein Bestandteil der Zwiebelarchitektur (Onion Architecture) nach Jeffrey Palermo [Palermo 2008]. Die API eines Application Service