LiesMich.jar Teil 2

Wie man mit 3rd Party Libraries umgeht: Strategien für den erfolgreichen Einsatz von Fremdbibliotheken

Harm Gnoyke

©Shutterstock/igor.stevanovic

Wie man bewusst mit 3rd Party Libraries umgeht, ist Thema dieser zweiteiligen Serie. Nachdem wir uns im ersten Teil der Entscheidung gewidmet haben, ob eine Fremdbibliothek oder eine Eigenentwicklung eingesetzt werden soll, geht es im zweiten Teil um die Fehlersuche in einer Library und die Kommunikation mit dem Anbieter. Den Abschluss bilden mögliche Strategien bei der Aktualisierung einer Library.

Fehler in der Library – was tun?

Im ersten Teil dieser Serie wurde offensichtlich, dass Tests dabei helfen, dem Anbieter einer Bibliothek etwaige Fehler in der Library zu demonstrieren. Wenn man das Problem nicht als Unit Test nachstellt, ist in vielen Fällen ein sogenanntes „Short, Self Contained, Correct (Compilable) Example“ [1] ein guter Ansatz. Manche Anbieter haben sogar Beispielapplikationen, in denen die eigene Software demonstriert wird. Lässt sich hier der Fehler nachstellen, gibt es am wenigsten Arbeit, und die Beweispflicht ist sofort auf den Anbieter übertragen. Wenn so etwas nicht vorhanden ist, kann man seine eigene Applikation immer stärker beschneiden, bis man bei einem Beispiel landet, das der Anbieter akzeptiert.

Mit solchen Tätigkeiten kann ein Projekt viel Zeit verlieren, daher sollte das Team die Brisanz eines Fehlers von Anfang an klar bewerten und kommunizieren. Der Anbieter freut sich hier über realistische Angaben genauso wie man selbst, wenn die eigenen Benutzer Fehler melden. In der Zeit, die für einen Fix benötigt wird, kann man sich parallel Gedanken machen, ob es Workarounds für das Problem gibt. Eventuell schlägt der Anbieter selbst welche vor, oder er kann validieren, ob eigene Workarounds tragen.

Wenn man Ideen für Workarounds hat, sollte auch die Akzeptanz auf Benutzerseite früh abgeklärt werden. In Diskussionen über Workarounds und Alternativen kann ein Projekt immer wieder etwas über die Schwere von Fehlern lernen. Die Erfahrung zeigt, dass als kritisch gemeldete Fehler so mit relativ geringem Einsatz heruntergestuft werden können. Auch wenn das ein seltener Fall ist, so ist doch das Schaffen einer guten Kultur im Umgang mit Fehlern erstrebenswert.

Für Workarounds jeglicher Art gilt, dass sie immer entsprechend dokumentiert werden sollten – mindestens im Source Code mit Hinweis auf eine Bug-Tracker ID (am besten auch die externe ID aufführen) und Ansprechpartner im eigenen Team, so dass dies für alle Entwickler klar erkenntlich ist. Am Ende pflanzt sich sonst eine eventuell suboptimale Lösung fort. Wenn man ein Backlog für das Upgrade auf eine neue Version pflegt, kann auch das Ausbauen des Workarounds dort vermerkt werden, vorausgesetzt, der Bug ist in einer neueren Version gefixt.

Wenn auf der anderen Seite der Hersteller nun den Fehler akzeptiert und an einer Lösung arbeitet, kann man zusätzlich versuchen, Kontakt zu dessen Entwicklern aufzunehmen. Das geht im Open-Source-Bereich natürlich am Einfachsten, ist aber auch bei kommerziellen Anbietern möglich. Wenn rechtliche Fragen in Bezug auf Vertraulichkeit geklärt sind, können die Entwickler aus beiden Teams gemeinsam den Fehler in der eigenen Software anschauen (möglicherweise per Screen Sharing/Online Meeting). Das sind aber Extremfälle, die selten in einem Projekt vorkommen.

Zurück zum Anfang

Im vorherigen Abschnitt sind einige Themen angeklungen, um die man sich nicht erst im Fehlerfall kümmern sollte – also wenn es wahrscheinlich höheren Zeitdruck gibt. Man sollte sich von Anfang an den Zweck klar machen, für den die Library eingesetzt werden soll, und dies auch entsprechend dokumentieren.

Für die gewünschte Funktionalität können Entwickler sich einerseits mit Tests absichern, andererseits die Funktionalität kapseln, damit die Library in der Software nur an bestimmten Stellen oder auf eine bestimmte Art und Weise genutzt wird. Dabei helfen das Adapter und das Facade Pattern weiter. Für die gewünschte Kapselung unterscheiden sich die beiden Pattern nur insofern, als der Adapter vollständig in Bezug auf den Adaptee bleiben muss, die Facade hingegen auch etwas weglassen darf (Abbildung 1).

Die Patterns helfen, die Funktionalität aus Libraries zu kapseln und damit die Auswirkungen von Änderungen in der Library zu begrenzen. Die Entwickler können sich zum Beispiel auch darum kümmern, dass das Exception Handling zur restlichen Software passt und sich nicht jeder an anderen Stellen um Fehlerfälle kümmern muss. Diese Patterns helfen auch, wenn man sich noch nicht für eine Library entschieden hat und das gewünschte Verhalten mockt. Diese Mocks kann man in Tests einsetzen. Die Entscheidung für eine bestimmte Library kann mit Hilfe dieser Pattern also auch herausgezögert werden, sollte man sich noch nicht ganz sicher sein.

 

Abb. 1: Adapter / Facade

Abb. 1: Adapter / Facade

Bei großen Libraries kann es sich auch lohnen, ganz gezielt im Entwicklerteam Know-how aufzubauen. Oft bilden sich im Verlauf des Projekts automatisch Experten, die bei Problemen mit einer Library hinzugezogen werden. Diesen Entwicklern hilft es, wenn für die Projektleitung transparent ist, dass sich jemand eine gewisse Zeit mit diesem Thema beschäftigt. Ein Ansprechpartner hilft auch den anderen Entwicklern weiter, wenn diese im Umgang mit der Library unsicher sind. Diese Verantwortung lässt sich oft auf mehrere Entwickler verteilen. Im Kontakt mit dem Anbieter sollte man nur darauf achten, dass eine Person den Überblick behält. So wird vermieden, dass das gleiche Problem mehrfach gemeldet wird. Für genutzte Open-Source-Projekte kann es auch sinnvoll sein, diese Person Teil der Community werden zu lassen.

Wenn ein Teammitglied dies bereits ist, ist das natürlich ein großer Vorteil. Das kann man entsprechend frühzeitig berücksichtigen, darf aber die anderen Aspekte bei der Auswahl einer Library nicht vernachlässigen.

Strategien zur Pflege

Ein weiterer Aspekt, auf den man achten sollte, ist die Interoperabilität mit vorhandenen Libraries. Diese erschließt sich oft nicht auf den ersten Blick und wird teilweise erst zur Laufzeit sichtbar. Moderne Build-Werkzeuge wie Gradle helfen, mögliche Inkonsistenzen im Classpath frühzeitig zu entdecken und auch automatisch aufzulösen [2]. Manche Versionskonflikte lassen sich aber nur durch Anpassen der Versionen und anschließende Programmieraufwände auflösen. Und jede neu eingeführte Abhängigkeit erhöht das Risiko für solche Konflikte. Daher sollte man nicht nur die Library direkt anschauen, sondern auch ihre benötigten weiteren Abhängigkeiten.

Worauf Projekte am häufigsten stoßen, ist die Verwendung unterschiedlichster Logging Frameworks durch die 3rd Party Libraries. Früher wurde das oft zum Problem bei der Fehlersuche: Log Statements gingen verloren und man tappte im Dunkeln. Mittlerweile helfen entweder Libraries wie SLF4J [3], alle Log Statements verschiedener Logging Frameworks einzusammeln, oder aber man setzt auf Monitoring Tools wie den ELK Stack [4], der Logfiles zur Laufzeit konsolidiert und auch einfach durchsuchbar macht.

Everybody Freeze

Die einfachste Strategie beim Einsatz neuer Libraries ist Everybody Freeze. Dabei wird keine Library aktualisiert, um die eigene Software stabil zu halten. Das Projekt ist frei von äußeren Einflüssen und kann sich voll auf die eigene Funktionalität konzentrieren. Das hört sich paradiesisch an für den Projektleiter, ist jedoch langfristig nur schwer durchzuhalten. Im Laufe der Zeit werden neue Features gefordert, und technologische Neuerungen müssen auch Einzug halten.

Ein Beispiel zeigt die Schwäche dieser Strategie auf. Ein Projekt setzt eine Open Source Library ein. Wer einen Fehler im Code der Library findet, fixt ihn einfach selber. Die Zusammenarbeit mit der Community ist zu aufwändig. Daher wird diese erst einmal auf später verschoben.

Parallel arbeitet (leider!) die Community weiter an neuen Features, und es kommt der Tag, an dem das Team deshalb auf eine neuere Version umsteigen möchte. Nun befindet man sich in der Sackgasse: Beim Update auf die neueste Version verliert man die eigenen Bugfixes und dadurch kostbare Zeit (Abbildung 2). Letztendlich ist solch ein Vorgehen nur das Kopieren der Open Source Library zu definierten Zeitpunkten. Es ist manchmal schwer genug, der Kopien innerhalb der eigenen Software Herr zu werden. Deshalb sollte jedes Projekt davon absehen, dieses Problem durch solch ein Handeln noch zu vergrößern.

Abb. 3: Wie man in der Sackgasse landet

Abb. 2: Wie man in der Sackgasse landet

Always Update

Man sollte daher besser häufig aktualisieren. Die große Kunst ist nun, nicht in das andere Extrem Always Update zu verfallen, bei dem letztlich Außenstehende die Updates in der eigenen Software diktieren. Gerade beim Einsatz kommerzieller Produkte sollte man sich bewusst sein, dass dies teilweise auch eine Strategie des Anbieters ist, um möglichst wenige Branches pflegen zu müssen und den Kunden so möglichst zeitnah Bugfixes zu liefern. Auch im Open-Source-Bereich werden möglichst wenige Branches gepflegt; hier gibt es aber im Notfall immerhin den Zugriff auf die Quellen eines alten Standes.

Die Wahrheit liegt also meistens in der Mitte: Die Updates von 3rd Party Libraries werden kontinuierlich in die eigene Software eingepflegt, und eigene Änderungen oder Änderungswünsche fließen möglichst schnell zurück zum Anbieter. Für diese kontinuierlichen Arbeiten sollte ein Team sich immer etwas Zeit einräumen und diese entweder einmal pro größerem Release oder aber in einem expliziten Sprint durchführen (Abbildung 3).

 

Abb. 4: Wie man nicht in der Sackgasse landet

Abb. 3: Wie man nicht in der Sackgasse landet

 

Major – Minor – Patch

Um den Aufwand für ein Update zu schätzen und damit auch einen günstigen Zeitpunkt mit geringem Risiko für das Projekt zu erhalten, muss man sich die Art der Änderung in der Library anschauen. Die Versionsnummer kann hier schon viel verraten. Viele Libraries halten sich an die Versionsnummernvergabe nach Semantic Versioning [5]. Die Versionsnummer setzt sich aus drei Stellen zusammen: MAJOR.MINOR.PATCH (Bsp. für eine Versionsnummer: 3.7.42). Eine Erhöhung einer Versionsnummer an einer der drei Stellen folgt festen Regeln:

  • MAJOR: bei inkompatiblen API-Änderungen (z.B. 3.7.42 à0.0)
  • MINOR: bei neu hinzugefügter Funktionalität, rückwärtskompatibel zur Vorversion (z.B. 3.7.42 à8.0)
  • PATCH: bei Bugfixes, rückwärtskompatibel zur Vorversion (z.B. 3.7.42 à7.43)

Bei Hochzählen einer Stelle werden die folgenden Stellen wie in den angefügten Beispielen jeweils auf 0 gesetzt. Wenn sich eine Library an dieses Schema hält, sind die groben Auswirkungen auf die eigene Software direkt an der Versionsnummer abzulesen. Bei Veränderungen der MAJOR-Stelle ist Programmieraufwand im eigenen Code zu erwarten, bei den anderen beiden Stellen eher nicht.

Auch wenn sich die eingesetzte Library nicht an dieses im Open-Source-Bereich mittlerweile recht verbreitete Schema hält, kann jedes Team die Regeln von Semantic Versioning nutzen, um ein gemeinsames Verständnis über Änderungen zu erhalten. Wenn man eine Kapselung für eine Library geschaffen hat, kann man diese auch nach dem entsprechenden Schema in seinem Projekt veröffentlichen, also Semantic Versioning für die Kapselung nutzen. Auch für nur intern veröffentlichte Module hilft eine solche Versionsnummernvergabe den Nutzern einer Bibliothek weiter, wenn diese vor der Frage stehen, ob sie die neue Version einbinden wollen.

 

Abb. 5: Übersicht – Der bewusste Umgang

Abb. 4: Übersicht – Der bewusste Umgang

 

Not invented Here Syndrom

Es gibt gute Möglichkeiten, sich beim Einsatz vor den aufgeführten Situationen zu schützen – niemand muss Angst vor dem Einbinden von 3rd Party Libraries haben. Abbildung 4 zeigt eine Zusammenfassung der im Artikel besprochenen Themen in der Übersicht.

Um dies zum Abschluss noch einmal zu unterstreichen, soll auf das Not-Invented-Here-Syndrom aufmerksam gemacht werden, welches die Folge von konsequenten „Make“-Entscheidungen ist. Schon im Jahr 1923 beobachtete Mark Twain (siehe Zitat) solch ein Verhalten.

Mark Twain in Some National Stupidities [6]
„The slowness of one section of the world about adopting the valuable ideas of another section of it is a curious thing and unaccountable. This form of stupidity is confined to no community, to no nation; it is universal. The fact is the human race is not only slow about borrowing valuable ideas — it sometimes persists in not borrowing them at all.“

In manchen Kreisen findet diese Ablehnung von Ideen, die nicht im eigenen Hause entwickelt wurden, mit der Begründung statt, „um sich von Wettbewerbern abzuheben“. Ein Einsatz von 3rd Party Libraries steht indes mit dem Ziel, sich von Wettbewerbern abzuheben, keineswegs im Widerspruch. Vielmehr kann ein Team sich mit den richtigen Entscheidungen genau auf die Dinge in der Software konzentrieren, die den Unterschied machen. Es ist hierbei allerdings wichtig, nicht nur die Anfangskosten für die Einführung einer Library zu berücksichtigen oder nur den anfänglichen Nutzen zu sehen, sondern auch die langfristigen Konsequenzen von Anfang an klar zu bewerten.

Aufmacherbild: Stack of Used Old Books in the School Library, Toned Cross Processed Image von Shutterstock / Urheberrecht: igor.stevanovic

Geschrieben von
Harm Gnoyke

Harm Gnoyke (embarc GmbH; hg@embarc.de) hat gute und schlechte Erfahrungen mit 3rd Party Libraries in internationalen Java Projekten von ganz klein bis ganz groß gesammelt. Für embarc unterstützt er Kunden bei Analyse und Design komplexer Softwarearchitekturen.

Kommentare

Hinterlasse einen Kommentar

1 Kommentar auf "Wie man mit 3rd Party Libraries umgeht: Strategien für den erfolgreichen Einsatz von Fremdbibliotheken"

avatar
400
  Subscribe  
Benachrichtige mich zu:
trackback

[…] – Bewusster Umgang mit 3rd Party Libraries Blogserie von Harm Gnoyke Teil 1 Teil 2 erschienen am 22./29. […]