Wie man Third-Party-Code sauber integriert
Heutzutage ist es so einfach wie nie, Third-Party-Code in der eigenen Software zu verwenden. Fast jede Programmiersprache hat seinen eigenen Package-Manager. Von Maven bei Java, npm bei Javascript bis hin zu Composer bei PHP ist alles vertreten.
Heutzutage ist es so einfach wie nie, Third-Party-Code in der eigenen Software zu verwenden. Fast jede Programmiersprache hat seinen eigenen Package-Manager. Von Maven bei Java, npm bei Javascript bis hin zu Composer bei PHP ist alles vertreten.
An vielen Stellen im eigenen Projekt macht es Sinn, sich auf Third-Party-Code zu stützen. Etablierte Projekte anderer Entwickler zeichnen sich meistens durch mehr Expertise in der jeweiligen Thematik und Stabilität des Codes aus. Darüber hinaus spart man natürlich auch Zeit und kann sich auf die eigentliche Business-Logik des eigenen Projekts konzentrieren.
Gerade bei Code, den man nicht selbst schreibt und auf den man keinen direkten Einfluss hat, ist aber erhöhte Achtsamkeit geboten. In diesem Artikel geht es deswegen nicht um Lizenzen, das Deployment oder ähnliche andere organisatorische Probleme mit solchen Packages, sondern darum, wie man Third-Party-Code sinnvoll und sicher in das eigene Projekt integriert.
Was ist Third-Party-Code?
Von Third-Party spricht man, wenn Code von Entwicklern stammt, die nicht im eigenen Haus arbeiten. Allerdings sollte teilweise auch Code, der in-house produziert wird, im Zweifel wie Code von externen Entwicklern behandelt werden.
Grundsätzlich definiert man Code als Third-Party, wenn er ein eigenständiges Package oder Projekt darstellt und auf das man keinen direkten Einfluss hat. Das beinhaltet auch ein Package, das von einem anderen Team in der Firma entwickelt wird und bei dem man kein Mitspracherecht hat.
Die Konsequenz ist, dass man sich nicht 100 Prozent auf den Code verlassen kann. Das spiegelt sich letzten Endes in verschiedenen Bereichen wider. Schnittstellen können sich ändern, Sicherheitslücken auftauchen, Standard-Verhalten angepasst werden oder Bugs ohne Fix liegen bleiben. Im schlimmsten Fall könnte das Package auch nicht mehr weiterentwickelt werden.
Wie sollte man Third-Party-Code einbinden?
Gegen all diese Fälle sollte man sich absichern, wenn man Third-Party-Code verwendet. Die Probleme lassen sich beispielsweise mit einer zusätzlichen Abstraktionsschicht lösen. In welcher Form diese Schicht eingebaut wird, ist erst einmal egal und richtet sich nach Komplexität und Anwendungsfall des Codes. Das kann ein Wrapper sein, ein Adapter oder sogar eine Fassade bei komplexeren Themen. Die klassischen Design-Pattern der Softwareentwicklung bieten viele Möglichkeiten, um eine solche Abstraktion zu realisieren.
Die zusätzliche Abstraktion sorgt dafür, dass die Schnittstelle zur Third-Party-Library nicht mehr von der entsprechenden externen Stelle definiert wird, sondern vom eigenen Code. Das ist Code, den man direkt beeinflussen kann und der sich nicht von heute auf morgen grundlegend verändert, wenn man die neueste Version des Projekts aus dem Repository zieht.
Wenn der Third-Party-Code hinter dieser Abstraktion versteckt ist und alle Stellen im Projekt nur mit der eigens definierten Schnittstelle arbeiten, erleichtert das die fortlaufende Arbeit und die Wartung des Third-Party-Codes gleich doppelt.
Vorteile der Abstraktion
Zum einen kann man einfacher auf Veränderungen reagieren. Ein Beispiel aus unserem Alltag bei onOffice war das Upgrade der algo26-matthias/idna-convert – Library zur Konvertierung von URLs nach Puny-Code und zurück. Beim Upgrade der Library sind wir eine komplette Major-Version nach oben gesprungen, bei denen es frei nach Semantic Versioning zu Breaking Changes kam. So änderte sich das Fehlerverhalten und die IDNA-Version wurde standardmäßig auf eine neuere Version gestellt.
Dank der Abstraktion waren diese Änderungen aber kein Problem. Es musste nur an einer einzigen Stelle, nämlich der Wrapper-Klasse, die Third-Party-Library umkonfiguriert und das ursprüngliche Fehlerverhalten nachgestellt werden. Alle weiteren Code-Stellen im Projekt, die die Library über den Wrapper verwendet haben, konnten unangetastet bleiben. Das spart Zeit und Arbeit und sorgt nebenbei dafür, dass man Libraries häufiger aktualisiert.
Zum anderen vereinfachen sich Tests. Durch den Wrapper existiert nur noch eine einzige Klasse im Projekt, die mit dem Third-Party-Code arbeitet. Diese kann dann ausführlich getestet werden, um das erwartete Verhalten abzudecken. Wenn sich das Verhalten des Third-Party-Codes nach einem Update ändert, meldet dies der Test sofort. Überall sonst, wo die Library gebraucht wird, wird auf diesen Wrapper zurückgegriffen. Das heißt auch, dass man beim Testen dieser Code-Stellen problemlos auf Mocks zurückgreifen kann, ohne dass man Gefahr läuft, Verhaltensänderungen des Third-Party-Codes nicht mitzubekommen, da das erwartete Verhalten bereits über den Test der Abstraktion sichergestellt ist.
Wann sollte man auf eine Abstraktion verzichten?
Nicht immer macht es Sinn, eine Abstraktionsschicht einzuführen, da nicht jeder Third-Party-Code eine einfache Library ist. Das naheliegendste Beispiel ist die Programmiersprache selbst, die man verwendet. Auch hierbei handelt es sich um Third-Party-Code. Dieser ist jedoch um einiges stabiler, was Schnittstellen und Verhalten angeht, als kleinere und größere Projekte, die man so auf GitHub findet. Deswegen sollte man nicht jede Funktion der Programmiersprache hinter einen Wrapper packen. Hier macht es wenn überhaupt eher Sinn, komplette Konzepte und Systeme als eigene Schnittstelle anzubieten, zum Beispiel der Zugriff auf das Dateisystem oder das Versenden von E-Mails.
Auch Frameworks sollten von einer Abstraktion ausgenommen werden. Bei ihnen ist der Unterschied zu Libraries, dass man nicht den Third-Party-Code in das eigene Projekt integriert, sondern das eigene Projekt in das Framework integriert. Gleichzeitig bedient man sich bei Frameworks auch eines viel größeren Spektrums an Funktionalität.
Wenn man zum Beispiel einen PHPUnit-Test schreibt, leitet man von der Test-Basisklasse ab, die PHPUnit stellt. Selbst mit einer Zwischenklasse als Abstraktionsschicht kann man sich nicht komplett von PHPUnit selbst lösen. Die Abstraktion würde also ihren Zweck gar nicht erfüllen können.
Das ist auch der Grund, warum eine Entscheidung für ein Framework immer wohlüberlegt sein sollte. Die Kopplung zwischen dem Framework und dem eigenen Code ist äußerst hoch und Veränderungen des Frameworks können oftmals nicht abgewendet werden. Es ist auch nur mit sehr großem Aufwand möglich, nachträglich das Framework zu wechseln.
Fazit
Immer wenn man mit Code arbeitet, auf den man keinen direkten Einfluss hat, sollte man Vorsicht walten lassen und sich gegen ungewollte Veränderungen in Verwendung und Verhalten schützen. Das lässt sich am einfachsten über eine eigene Abstraktionsschicht lösen, die die Definition der Schnittstelle und des Verhaltens wieder in das eigene Projekt zieht. Allerdings macht eine solche Abstraktion nicht bei jeder Form von Third-Party-Code Sinn. Es gilt, den richtigen Ansatz von Fall zu Fall abzuwägen.