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