Grundlagen des API-Designs mit Java

APIS mit Java designen: Die Grundlagen
Keine Kommentare

Wer sich mit C++ nicht anfreunden kann, sucht sein Glück in der Java-Welt. Dieser alte Kalauer ist zumindest insofern wahr, als die beiden Sprachen einander in vielerlei Hinsicht stark ähneln. Der wichtigste Unterschied ist jedoch, dass Java in puncto Aufbau rigider ist und mit Managed Memory aufwartet. Analog zu C++ gilt aber auch im Fall von Java, dass die Programmiersprache – trotz diverser Versuche – nicht auszurotten ist. Was es beim API-Design für diese Plattform zu beachten gilt, zeigt dieser Artikel.

Vor der Arbeit an einem API sollte man natürlich überlegen, was am Ende der Arbeiten entstehen soll. Auch wenn Java aufgrund der rigideren Spezifikation weniger Spielraum bietet als C++, sind in der Literatur trotzdem drei verschiedene API-Arten bekannt.

Eine Frage der Gestalt

Am unteren Ende der Komplexitätshierarchie finden sich hierbei Utility-Bibliotheken, die im Grunde genommen aus einer Gruppe statischer Methoden bestehen. Entwickler suchen sich eine oder mehrere für sie interessante Funktionen aus, integrieren sie in ihr Programm und freuen sich ob der eingesparten Zeit. Im Idealfall sind die in einem Utility-API enthaltenen Funktionen komplett zustandslos. Ist dies aus irgendeinem Grund nicht möglich, so bietet sich beispielsweise die Nutzung eines bei jedem Aufruf zu übergebenden Statusobjekts an.

Services und Frameworks sind im Vergleich dazu wesentlich komplexere Aufbauten, die dem Entwickler wesentlich mehr Vorschriften in Bezug auf das Hantieren mit den Elementen auferlegen. Der wichtigste Unterschied zwischen einem Service und einem Framework ist – aus akademischer Sicht – die Kontrolle: Ein Service ist eine Abstraktionsschicht, die den Programmierer, wie in Abbildung 1 gezeigt, von der internen Architektur des anzusprechenden Diensts isoliert.

Abb. 1: Dank des Service-API muss der Entwickler nichts über Remote Procedure Calls wissen

Abb. 1: Dank des Service-API muss der Entwickler nichts über Remote Procedure Calls wissen

Frameworks setzen stattdessen auf eine Inversion der Kontrolle. Sie errichten eine mehr oder weniger komplexe Ausführungsumgebung, in der der Entwickler seine Logik dann in Form von Callbacks oder ähnlichen Sprachelementen einschreibt.

Beim Design eines Frameworks ist es – insbesondere für wenig etablierte Unternehmen – ratsam, „minimalinvasiv“ vorzugehen. Wenn Facebook oder Google einem Entwickler ein bestimmtes Design Pattern vorschreiben, wird er dieses grummelnd akzeptieren. Ob er dies auch bei weniger etablierten Playern macht, darf bezweifelt werden.

Schnelle Auslieferung

Eines der stärksten Verkaufsargumente für Gradle bzw. Android Studio ist mit Sicherheit die Möglichkeit, Bibliotheken durch Anpassen einer Datei in ein Projekt zu integrieren: Download und Co. erledigt der Build Daemon automatisch. Aus der Logik folgt, dass das hauseigene API – wann immer aus lizenzrechtlichen Gründen möglich – über den Bibliotheksmanager der meistverwendeten Programmierumgebung der Zielgruppe bereitgestellt werden sollte. Wer seine Android-Bibliothek also nicht im Gradle Repository hostet, muss sich über langsamere Adaption nicht wundern.

Andererseits gibt es immer wieder Entwickler, die die manuelle Auslieferung von Code bevorzugen. Dieser Bevölkerungsgruppe kann man durch prominentes Platzieren eines JAR-Downloadlinks unter die Arme greifen: Das Durchsuchen von Maven artet mitunter in Arbeit aus.

Bissigkeit im Sprachstandard

Oracle bzw. Sun haben in den letzten Jahren eine Vielzahl moderner Konzepte in ihre Sprache integriert. Daraus folgt allerdings nicht, dass die Java-Standardbibliothek frei von Optimierungspotenzial ist. Ein Klassiker ist beispielsweise folgende Funktion:

class Thread implements Runnable {
  public static boolean interrupted() {
    return currentThread().isInterrupted(true);
  }

Als Entwickler ist man bei der Betrachtung dieses Adjektivs dazu versucht, davon auszugehen, dass die Methode prüft, ob der Thread unterbrochen ist. Leider ist dies falsch: Die Methode liefert zwar die gewünschte Information, setzt dabei aber auch den Interrupted-Wert auf Falsch (Abb. 2).

Abb. 2: Diese Methode hat einen unangenehmen Nebeneffekt, den man beim Namen nicht errät

Abb. 2: Diese Methode hat einen unangenehmen Nebeneffekt, den man beim Namen nicht errät

Aus ergonomischer Sicht wäre es sinnvoll, an dieser Stelle auf einen längeren Namen zu setzen, der die Nebenanweisung ebenfalls klar benennt. Das gilt auch im Bereich von Paketen. Die erste wichtige Regel ist, Paketnamen immer mit dem Namen des Unternehmens anfangen zu lassen: Ein von SUS stammendes Paket sollte com.sus.X heißen.

Wichtig ist, sich am Design des Java-API als Ganzes zu orientieren. Im Bereich von Enums gilt es beispielsweise als korrekt, Einzelversionen von Nomen zu verwenden: statt BackgroundColors sollte die Enum BackgroundColor heißen, auch wenn sie eine Gruppe verschiedener Farben enthält.

International JavaScript Conference 2017

The Vue of Search Engine Optimization – How to make your Single-Page App rank well

mit Dennis Schaaf (futurice) & André Scharf (DigitasLBi)

Von 0 auf 100 – Performance im Web

mit Sebastian Springer (MaibornWolff GmbH)

I didn’t know the Browser could do that

mit Sam Bellen (madewithlove)

Dass die Entwicklung eigener Collections nicht oder nur wenig sinnvoll ist, folgt aus der Logik. Wenn sich Ihre Aufgabe mit einer normalen Collection lösen lässt, sollten Sie dies tun. Ist aus irgendeinem Grund die Implementierung einer eigenen Collection unumgänglich, empfiehlt sich die Nutzung von allgemein bekannten Methoden:

  • size()
  • remove()
  • removeAll()

Unsere drei hier abgedruckten Beispiele demonstrieren die Nutzung einer „Zusatzmethode“. In der Welt von Java ist das Anfügen von All am Ende einer Methode universell akzeptiert und wird von den Entwicklern im Allgemeinen erwartet.

Der letzte „namensbezogene“ Tipp ist das Ausnutzen der nur leidlich beliebten Dokumentationskommentare. Auch wenn das Ausfüllen der Formfelder für Arbeit sorgt, danken es die Nutzer spätestens beim ersten Aufruf des IntelliSense-Features. Einbindungen auf Beispielcode, Hintergrundinformationen und Co. sind auch wünschenswert: Muss der Entwickler in Richtung von Google wechseln, so geht wertvolle Zeit verloren.

Die Java-Checkliste

Unter https://theamiableapi.com/2012/01/16/java-api-design-checklist/ findet sich eine Liste mit einigen Dutzend Kriterien, die bei der Prüfung von APIs hilfreich sind.

Arbeiten ohne Altlasten

Anders als in C++ konnten die Java-Designer ohne Altlasten arbeiten. Daraus folgt, dass die Entwickler eine Vielzahl von (damals) fortgeschrittenen Features anbieten konnten. Intelligent ist beispielsweise das Implementieren von equals(), hashCode() und toString(). Entwickler und Frameworks bzw. Containerklassen gehen davon aus, dass die Funktionen am Platz sind. Das gilt auch für die Implementierung der Comparable– und Serializable-Interfaces.

Exceptions sind dem durchschnittlichen Entwickler primär lästig – allzu oft findet sich (auch in vom Autor verfasstem) Beispielcode ein „Exception-Griller“, der nach folgendem Schema aufgebaut ist:

try{
//. . .
}
  catch (Exception e) {
..//STFU the compiler
}

Die erste Maßnahme zur „Erträglichmachung“ von Exceptions ist das Ableiten der eigenen Exceptions von Exception oder RuntimeException. Denn wer eigene Klassen einführt, erhöht den Umfang des zur Beruhigung des Compilers notwendigen Codes.

Bei der Arbeit mit Enumerationen ist es sinnvoll, immer einen Wert anzulegen, der das Nichtvorhandensein von Informationen anlegt. Frei nach Einstein findet sich immer eine Situation, in der diese Möglichkeit sinnvoll wäre.

Ein Studie der Forscher Scheller und Kühn an der TU Wien zur transparenten Bewertung von APIs [1] ergab, dass die Erhöhung der Parameterzahl zu einer exponentiellen Steigerung der Komplexität führte. In Java lässt sich dies bis zu einem gewissen Grad durch Einführung von Defaultparametern kompensieren. Diese Vorgehensweise ist insofern sinnvoll, als sich das API als Ganzes so kompakter halten lässt.

Mach‘ die Acht

Die Aufregung um die achte Version der Programmiersprache Java ist im Großen und Ganzen abgeklungen. Die diversen neuen Features haben sich mehr oder weniger gut etabliert. Das mit Abstand wichtigste Helferlein ist die Optional-Klasse, die NullPointerExceptions auf die Liste der bedrohten Tierarten setzen soll. Ein klassischer Anwendungsfall für die Klasse sähe folgendermaßen aus – dank des Zurückgebens eines Optional-Elements treten keine Null Exceptions mehr auf:

public Optional<String> holeName() {
  return Optional.ofNullable(name);
}

In der Praxis ist der Overhead geringer, als man auf den ersten Blick annehmen mag. Die in Java 8 enthaltene Flussoptimierung eliminiert die Erzeugung von Optional-Elementen in vielen Fällen selbsttätig.

Java-Entwickler zeigen sich Design Patterns im Allgemeinen aufgeschlossener als ihre mit C++ oder anderen Sprachen arbeitenden Kollegen. Daraus folgt, dass der sogenannte „Factory Pattern“-Overhead weniger stark ausfäll – manche Autoren von Büchern zu Java empfehlen sogar das Kaschieren von internen Implementierungen:

Point point = Point.of(1,2);

Das dahinterstehende Reasoning ist, dass diese Vorgehensweise im Haupt-API durchaus häufig vorkommt und den Entwicklern somit nicht komplett fremd ist.

Ähnliches gilt auch im Bereich funktionaler Interfaces: Das Einschreiben von Fehler-Handlern durch Nutzung einer abstrakten Klasse mag gut etabliert sein, verlängert den Implementierungscode aber immens. Eine sauberere Lösung zum Einschreiben eines Handlers wäre die folgende Konstruktorkette:

Levitator atsepsut = Levitator.builder()
  .withCrashHandler(IOException::printStackTrace)
  .raise();

Bei der Arbeit mit Java 8 sei angemerkt, dass viele der in der Sprachversion eingeführten Methoden für Entwickler, die mit früheren Versionen von Java arbeiten, ungewohnt sind. Im Zweifelsfall führt ein A/B-Test oder eine Kundenbefragung schnell zu brauchbaren Informationen.

Fazit

Wie beim Design von Programmierschnittstellen für C++ gilt auch im Fall von Java, dass umfangreiche Kenntnisse sowohl der zugrunde liegenden Sprache als auch der JVM von immensem Vorteil sind. Ein empfehlenswerter Klassiker ist das 2001 von Joshua Bloch veröffentlichte „Effective Java: A Programming Language Guide“. Als zweite Maßnahme empfiehlt sich das Studium der Konkurrenz und der Standardbibliothek. Wer sich an die dort etablierten Standards und Vorgehensweisen hält, spart seinen Anwendern viel Zeit.

Literaturverzeichnis

  • [1] Scheller, Thomas; Kühn, Eva: „Automated measurement of API usability: The API concepts framework“, Information and Software Technology, 61, 2015, S. 145–162

Entwickler Magazin

Entwickler Magazin abonnierenDieser Artikel ist im Entwickler Magazin erschienen.

Natürlich können Sie das Entwickler Magazin über den entwickler.kiosk auch digital im Browser oder auf Ihren Android- und iOS-Devices lesen. In unserem Shop ist das Entwickler Magazin ferner im Abonnement oder als Einzelheft erhältlich.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu:
X
- Gib Deinen Standort ein -
- or -