Praktisches API-Design

Werbung
Praktisches
API-Design
Kai Spichale
adesso AG
18.03.15
Eine API ist wie die Spitze eines
Eisbergs ..
2
http://www.pixelio.de/media/685029
Klassische Funktionen einer API
Entkopplung von
Implementierung
Client
API
Definiert Komponente
durch ihre Operationen
mit Ein- und
Ausgabewerten
3
Implementation
Softwarewiederverwendung
und Integration
Kommunikationsproblem bei
Softwarewiederverwendung
?
Lösung: Kommunikation durch API
>  APIs werden für Menschen geschrieben
>  Perspektive ist beim API-Design entscheidend
4
Für wen ist API-Design relevant?
5
Entwurf
6
Empfohlene Vorgehensweise
► 
Anwendungsfälle
Feedback
7
Klein mit den wichtigsten
Anforderungen beginnen
► 
API häufig schreiben
► 
Regelmäßiges Feedback
API schreiben
Qualitätsmerkmale
8
Qualitätsmerkmale
► 
Konsistent
► 
Intuitiv verständlich
► 
Dokumentiert
► 
Einprägsam und leicht zu lernen
► 
Führt zu lesbarem Code
► 
Minimal
► 
Stabil
► 
Einfach erweiterbar
Grundvoraussetzung: Nützlich und korrekt :-)
9
Konsistent
str_repeat
str_split
str_word_count
► 
10
strcmp
strlen
strrev
Konzeptionelle Integrität = kohärentes Design mit der
Handschrift eines Architekten
Intuitiv verständlich
► 
► 
► 
11
Idealerweise ist Client-Code ohne Dokumentation
verständlich
Vorwissen ausnutzen und bekannte Konzepte
wiederverwenden
Starke Begriffe etablieren und diese konsequent
wiederverwenden
Java Collection API
java.util.List
add
addAll
contains
java.uilt.Set
add
addAll
contains
java.util.Map
put
putAll
containsKey,
containsValue
containsAll
remove
removeAll
containsAll
remove
removeAll
remove
-
12
Dokumentiert
► 
13
Gute Dokumentation ist unverzichtbar
Einprägsam und leicht zu lernen
Einflussfaktoren
>  Konsistenz, Verständlichkeit und Dokumentation
>  Größe (Anzahl der Konzepte)
>  Vorwissen der Benutzer
>  Time to First Hello World
14
Führt zu lesbaren Code
EntityManager em = ...;
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Order> cq = builder
.createQuery(Order.class);
Root<Order> order = cq.from(Order.class);
order.join(Order_.positions);
cq.groupBy(order.get(Order_.id)).having(
builder.gt(builder.count(order), 1));
TypedQuery<Order> query = em.createQuery(cq);
List<Order> result = query.getResultList();
15
Führt zu lesbaren Code
EntityManager em = ...;
QOrder order = QOrder.order;
JPQLQuery query = new JPAQuery(em);
List<Order> list = query.from(order)
.where(order.positions.size().gt(1))
.list(order).getResults();
16
Schwer falsch zu benutzen
new java.util.Date(1983, 20, 1);
int year = 1983 – 1900;
int month = 1 - 1;
new java.util.Date(year, month, 1);
17
Minimal
► 
Hinzugefügte Elemente können nachträglich nur schwer
entfernt werden
► 
Im Zweifel Elemente weglassen
► 
Trade-off: Minimalität vs. Komfort
>  Gut:
java.util.List.removeAll()
>  Schlecht: java.util.List.removeAllEven()
18
Minimal
► 
Auslagerung in Hilfsklassen
>  z.B.: java.util.Arrays, java.util.Collections
>  Alle notwendige Methoden müssen public sein
>  Utility-Klassen können leicht um neue Hilfsmethoden erweitert
werden
>  Collection-Interfaces bleiben klein
► 
Performance und Nebenläufigkeit
>  putIfAbsent() wäre in separater Hilfsklasse nicht threadsicher
► 
Optionale Methoden und Konstruktoren kennzeichnen
>  z:B.: java.io.PrintWriter enthält „convenience“ Text
19
Stabil
► 
API-Änderungen sollten nur mit Sorgfalt durchgeführt
werden
Client
Client
Vertrag
API v1
20
Client
Einfach erweiterbar
► 
► 
► 
21
Welche Änderungen wird es in Zukunft geben?
Erweiterung idealerweise abwärtskompatibel, sonst neue
API-Version notwendig
Anpassungsaufwand für Clients minimieren
Abwärtskompatibilität
22
Kompatibilität
► 
Trade-off: Abwärtskompatibilität vs. Flexibilität
► 
Code-Kompatibilität
>  Einfachste Form der Kompatibilität
public class MyOwnClass extends OtherBaseClass {
public List<ResultItem> getResults() {
return …
}
}
► 
23
Potentielle Konflikte durch Vererbung
Kompatibilität
► 
► 
Binär-kompatibel: Programm kann neue Version ohne
erneute Kompilierung verwenden
Beispiel:
>  Kompilieren mit log4j 1.2.15
>  Linken gegen log4j 1.2.17
► 
Grundvoraussetzung: Dynamisches Linken
>  Einsprungadresse von virtuellen Methoden wird zur Laufzeit
bestimmt
24
Funktionale Kompatibilität
► 
Verschiedene Bausteine können gelinkt werden und erfüllen
ihre erwartete Funktion
Erwartetes Verhalten
Tatsächliches Verhalten
Tatsächliches Verhalten
der neuen Version
Bekannt als Amöben-Effekt (Jaroslav Tulach)
25
Design-Repertoire
26
Erzeugsmuster
► 
Verwendung von Konstruktoren mit Factories und Builder
minimieren
Vorteil
► 
Verwendung unterschiedlicher Subtypen
► 
Erzeugung komplexer Objekte vereinfachen
► 
Keine Konstruktoren mit vielen Parametern
27
Fluent API
► 
Führt zu leicht lesbaren Client-Code
► 
Realisierung einer internen DSL
► 
28
API-Benutzung wird in Kombination mit
Autovervollständigung in IDE vereinfacht
Vererbung
Kontroverse Ansätze:
>  Open Inheritance
>  Designed Inheritance
Standardmäßig alle konkreten Klassen final machen
>  Vererbung verletzt Datenkapselung
>  Klassen müssen speziell für Vererbung entworfen werden
>  Für Wiederverwendung Kompositionen nutzen
Beispiele:
>  Richtig: Set extends Collection
>  Falsch:
29
OrderPositionSet extends Set<OrderPosition>
Template-Methode
abstract class OrderSorter {
public void sort(List<Order> orders) {
…
if(compare(o1, o2)) {
…
}
abstract compare(Order o1, Order o2);
}
30
API versus SPI
Application Programming Interface
► 
Wird von Benutzern aufgerufen
► 
z.B.: FileSystem API, FileObject
► 
Methoden können i.d.R. hinzugefügt werden
Service Provider Interface
► 
Wird durch Client-Code implementiert (Callback durch
Framework)
► 
z.B.: LocalFileSystem, JarFileSystem werden implementiert
► 
Neue Methoden brechen Client-Code
31
Template-Methode
public class OrderSorter {
private final Comparator<? super Order> comp;
public OrderSorter(Comparator<? super Order> comp) {
this.comp = comp;
}
public void sort(List<Order> orders) {
Collections.sort(orders, comp);
}
}
32
Kontextobjekt als Parameter für SPI
► 
Schlechter Stil:
public interface MyServiceProviderInterface {
void callbackMethod(String param);
}
► 
Besser:
public interface ServiceProviderInterface {
void callbackMethod(Context param);
}
interface Context {
String message();
Integer id();
}
33
Methoden mit Destruktorsemantik
Beispiele
► 
JUnit: @Before, @After
► 
JDBC: Belegen und Freigeben von Ressourcen
Reihenfolge beachten!
► 
34
Evtl. Ressourcen in umgekehrter Reihenfolge freigeben
Interface-Evolution
► 
Hinzufügen einer neuen abstrakten Methode war vor Java 8
eine inkompatible Änderung
>  Alte Lösung: Neues Interface erweitert altes
► 
Ab Java 8 Default-Methoden
► 
Bevorzuge Klassen vor Interfaces
35
Invarianten und Bedingungen
► 
► 
► 
36
Klassen sind selbst für die Einhaltung ihrer Invarianten
verantwortlich (nicht Client)
Klassen sollten ihrer Vorbedingungen möglichst gut
überprüfen
Nachbedingungen werden mit Tests überprüft
Fehlermeldungen
► 
Hilfreiche Fehlermeldungen erleichtern Benutzung
► 
Vermeidung von Checked Exceptions
► 
Exceptions auf einheitlichen Abstraktionsniveau
► 
Fail Fast: Melde Fehler so früh wie möglich
>  Kompilierzeitpunkt: statische Typisierung
>  Laufzeit: Methoden sollte falsche Parameter / Zustand sofort
überprüfen
37
Immutability / Minimale Änderbarkeit
► 
Alle public und konkreten Klassen final und immutable
machen
Vorteile:
>  Thread-safe
>  Wiederverwendbar
>  Einfach
Nachteile:
>  Separates Objekt für jeden Wert
► 
38
Falls mutable, dann sollte Zustandraum klein sein
Sinnvolle Defaults
► 
Vermeidung von Boilerplate-Code
► 
Geringe Einstiegshürden und Unterstützung für Neueinsteiger
► 
► 
39
Boolean sind standardmäßig false (entsprechende Namen
wählen)
Defaults müssen dokumentiert sein
Globaler Zustand / Konfiguration
Globaler Zustand ist problematisch weil:
>  Schwer testbar, nicht flexibel
>  Fehleranfällig, schwer verständlich
Empfehlung:
>  Sei funktional, vermeide Zustand
>  Einbindung einer Bibliothek sollte keine Änderungen des globalen
Zustand notwendig machen
>  Falls doch, dann ist es eine Applikation und keine Bibliothek
Beispiel:
>  Log4j wird beim Laden automatisch konfiguriert
>  Unterschiedliche Konfigurationen für Log4j in einem Prozess nicht
möglich
40
Dokumentation
41
Inhalt einer API-Dokumentation
► 
► 
► 
42
Allgemeine Übersicht:
>  Beschreibt Anwendungsgebiet und Funktion
>  Evtl. mit Architekturübersicht
Entwicklerhandbuch:
>  Beschreibt wiederkehrende Aufgaben i.d.R. mit Beispielen
Referenz
Dokumentation mit Beispielen
► 
Snippet:
>  Quellcode-Auszug mit Beschreibung
► 
Test:
>  Demonstrieren Funktionsumfang und Benutzung
► 
Tutorial:
>  Schritt-für-Schritt-Anleitung
>  Kochbuch
► 
Beispielapplikation:
>  Quellcode für komplette Applikation zeigt Benutzung der API
43
Empfehlung: Vorträge von Joshua Bloch
44
Kai Spichale
@kspichale
http://spichale.blogspot.de/
https://www.xing.com/profile/Kai_Spichale
45
Herunterladen