17. June 2018

Ein Wort zu Fremdkomponenten

Guten Morgen,

eine Sache, die in der Programmierergemeinschaft immer wieder gepredigt wird, ist die Wiederverwendung von Code. Idealerweise sollte ein Problem nur und genau einmal gelöst werden. Jeder der dieses Problem antrifft, sollte diese eine Implementierung verwenden und sie erweitern, wenn Änderungen notwendig sind.1 Es gibt drei Hauptpunkte, die diese Idee unterstützen:

Das Gute

  • Weniger Entwicklungszeit, weil große Teile einer Anwendung durch existierenden Code abgedeckt sind.
  • Bibliotheken werden von vielen Programmierern genutzt und bearbeitet, ihre Code-Qualität sollte entsprechend hoch sein. Zudem sollten sie gut getestet sein.
  • Weniger Wartungsaufwand, weil die Bibliotheken von anderen gewartet werden.

Zusätzlich wird oft empfohlen, sich auf Open Source Bibliotheken zu konzentrieren. Wenn notwendig könnte die Bibliothek dann angepasst oder sogar geforked werden. Allerdings ist Open Source eine ganz eigenes Thema, das ich an dieser Stelle nicht im Detail beleuchten möchte. Man sollte aber im Hinterkopf behalten, dass auch Open Source nicht nur Voreile hat.

Viele Jahre lang, war ich ein Anhänger der Idee, Code möglichst oft wieder zu verwenden. Und ich denke weiterhin, dass man eigenen Code immer so schreiben und paketieren sollte, dass er tatsächlich wiederverwendbar ist. Man sollte so eine Reihe qualitativ hochwertiger Bibliotheken für die Verwendung in den eigenen Anwendungen erstellen.

Was Fremdkomponenten betrifft, habe ich mit der Zeit jedoch einige Nachteile entdeckt, auf die ich im Weiteren eingehen möchte.

Das Schlechte

  • Kenne Sie jede dieser 425 Bibliotheken?Jede Bibliothek von nennenswerter Größe hat eine Lernkurve. Meistens wird die Bibliothek anfänglich falsch eingesetzt, möglicherweise sogar entgegen ihres Designs. Das führt zu schlechtem Anwendungsdesign und, im schlimmsten Fall, zur Einbindung weiterer Bibliotheken, die die selben Probleme lösen sollen. Niemand möchte solchen Code warten.
  • Bibliotheken werden häufig in mehr oder minder zufälliger Art "zusammengesteckt". Drittherstellerbibliotheken zu verwenden bedarf großer Aufmerksamkeit, gerade hinsichtlich der Architektur und der korrekten Einbindung aller Bibliotheken. Da die Architektur ohnehin ein schwieriges Thema in vielen Anwendungen ist, macht "zusamenpappen" diverser Bibliotheken nur noch mehr ärger.
  • Die meisten Bibliotheken stellen viel mehr Funktionen bereit, als wir brauchen. Man mag sagen: "Was ist so schlimm daran, mehr Features zu haben, als wir brauchen?". Nun, diese Features sind Overhead. Code, den wir zum Kunden ausrollen müssen, ohne dass dieser ihn haben will. Dinge, die zu einer steileren Lernkurve führen, weil wir sämtliche Funktionen verstehen müssen, um beurteilen zu können, ob wir sie einsetzen willen oder nicht.
  • Und dann gibt es da noch diese nervigen Entwickler; meiner Erfahrung nach, wird jede Funktion, die irgendwo versteckt ist, auch irgendwann benutzt. Das ist besonders schädlich, wenn wir der Meinung sind, dass die spezifische Funktion schlecht ist. Lassen Sie mich ein Beispiel geben:
    Ich bin ein Fan von Dependency Injection. Für .NET gibt es in diesem Bereich verschiedene gute Bibliotheken, LightInjectNinjectAutofac, etc. Ich habe aber meine eigene geschrieben.2 Warum?
    Ich bin der Meinung, dass Dependency Injection eine ganz klare Beschreibung der Abhängigkeiten einer Klasse erfordert. Und diese Beschreibung lässt sich am besten über einen Konstruktor liefern, der die Bereitstellung aller Abhängigkeiten erzwingt. Ich möchte daher ausschließlich Constructor Injection verwenden. Alle Bibliotheken, die ich genannt habe, ermöglichen Property Injection. Was schön für einen Programmierer ist, weil weniger Code gebraucht wird. Der Nachteil ist, dass ich das Objekt dann instanzieren kann, ohne die Properties zu setzen. Wollen Sie in jeder einzelnen Methode sicherstellen, dass vermeintlich notwendige Abhängigkeiten möglicherweise nicht da sind? I nicht! Deshalb unterstütz mein DI-Container ausschließlich Constructor Injection, nicht mehr, nicht weniger.
  • Üblicherweise stolpern wir über einige Anwendungsfälle für eine Bibliothek, die so nicht bedacht wurden. Wir beginnen dann meistens, die Bibliothek zu erweitern. Das geschieht mit Wrappern, eigenen Ableitungen, Extension Methods, usw. Auch wenn sich das erstmal nicht so schlimm anhört, musste ich feststellen, dass diese Anbauten in den meisten Fällen zu extrem schlechtem Code und zu furchtbarer Architektur führen.
    Dass Problem ist, dass wir bei der Auswahl einer Bibliothek erst einmal nicht erwarten, dass ein Feature, was wir benötigen, nicht vorhanden ist.3 Wenn wir also eine Funktion vermissen, sind wir meistens unter Zeitdruck sie schnellstens einzubauen. Das Ganze wird noch dadurch verschärft, dass viele Bibliotheken nicht gerade leicht von Außen zu erweitern sind.4 Sie funktionieren gut für den Einsatzzweck, für den sie geschrieben wurden, machen aber Probleme, wenn etwas anderes gebraucht wird.
    An diesem Punkt wird häufig das Open Source Argument gebracht. Man könnte das vermisste Feature einfach zur Bibliothek hinzufügen. Aber widerspricht das nicht unseren ursprünglichen Zielen? Wenn ich eine Drittherstellerbibliothek verwende, möchte ich die Verantwortung den Code zu Warten und zu Erweitern los werden. Ich will den Code nicht verstehen, ich will dass er einfach funktioniert. Was ich also sicherlich nicht möchte, ist mir die Hände dreckig machen zu müssen, um zusätzliche Funktionen in eine Bibliothek einzubauen, oder sie sogar zu forken.

Das Hässliche

Sie fragen mich also "Sollte ich jetzt Fremdkomponenten verwenden, oder nicht?". Meine Antwort ist klar und eindeutig: "Das kommt darauf an.". Wie es im Leben so ist, gibt es auch hier keine einfache Antwort, keinen eindeutigen, richtigen Weg. Was ich anbieten kann, ist aber folgendes: Lassen Sie uns einige Fragen beleuchten, die man sich stellen sollte, bevor man eine Fremdkomponente verwendet:

  1. Sind Sie der Meinung, dass sie ein Stück wartbare, gut getestete, wiederverwendbare Software schreiben können, um ein spezifisches Problem zu lösen? Ist die Antwort "Nein", versuchen Sie es erst gar nicht. Investieren Sie Ihre Zeit darin, die beste Bibliothek da draußen zu finden, die Ihren Anforderungen gerecht wird.
  2. Kennen Sie eine Bibliothek (mit lang währender Erfahrung), die Ihr Problem löst? Wenn Sie oder einer Ihrer erfahreneren Entwickler eine solche Bibliothek kennen, nutzen Sie diese.5
  3. Glauben (im Unterschied zu "wissen") Sie, eine Bibliothek gefunden zu haben, die Ihr Problem lösen würde? Hier wird es schwierig. Versuchen Sie Alles über die Bibliothek herauszufinden. Probieren Sie sie aus, testen Sie die Bibliothek bis aufs Blut. Versuchen Sie dabei alle Anwendungsfälle, die Sie haben werden (und einige, die Sie lieber nicht haben wollen) zu beleuchten. Und legen Sie besonderen Wert auf die korrekte Verwendung der Bibliothek in Ihrer Anwendung.
  4. Wenn keiner der genannten Punkte auf Sie zutrifft; damit Meine ich, dass sie keine passende Bibliothek gefunden haben, dass Sie die Lernkurve als zu steil ansehen und dass Sie Bauchschmerzen von den ganzen Features kriegen, die Sie lieber nicht in Ihrer Anwendung verwenden wollen. Dann sollten Sie sich überlegen eine eigene Bibliothek zu schreiben. Natürlich gelten die üblichen Regeln: Machen Sie die Bibliothek wiederverwendbar. Implementieren Sie automatische Tests. Legen Sie ganz besonderen Fokus auf die Schnittstelle und die Gesamtarchitektur der Bibliothek. Ihre Bibliotheken sollten einen noch höheren Code-Standard haben, als Ihre Anwendungen.

In jedem Fall, sollten Sie nur Drittherstellerbibliotheken verwenden, von denen Sie überzeugt sind. Stellen Sie sicher, dass die Lizenz akzeptabel ist, dass die Bibliothek gut gewartet wird und aktiv von einer größeren Gruppe von Programmierern weiterentwickelt wird, dass der Code ein hohe Qualität aufweist und gut getestet ist. Und schauen Sie nicht auf den Preis.6 Für eine Anwendung sind verwendete Bibliotheken üblicherweise eine Investition auf Lebenszeit. Lassen Sie sich nicht dazu hinreißen, eine "mögliche Alternative" der "perfekten Bibliothek" vorzuziehen, weil es sie kostenlos gibt. Solche Entscheidungen werden Sie zehnfach bei dem Wartungsaufwand heimsuchen.

Ich hoffe, dass ich Ihnen einige nützliche Hinweise zu der Auswahl von Fremdkomponenten geben konnte. Fällen Sie keine hastigen Entscheidungen. Überlegen Sie sich genau, welche Lösung die beste für Ihr Problem ist, und bleiben SIe bei Ihrer Antwort. Ich bin überzeugt, dass Sie dann mit Ihren Entscheidungen erfolgreich sein werden.

  1. Das ist in dieser Form natürlich unmöglich, weil es immer mehrere Wege gibt, ein komplexes Problem zu lösen. Und keiner davon ist perfekt. Das Ziel ist aber weiterhin gültig.
  2. Die man hier finden kann: https://github.com/programmersdigest/Injector
  3. Obwohl wir genau das erwarten sollten! Erwarten Sie nicht, dass eine Bibliothek all Ihre Bedürfnisse abdecken wird. Denken Sie voraus. Implementieren Sie Möglichkeiten, um weitere Funktionalität zu einer Bibliothek hinzuzufügen, bevor sie in Verlegenheit kommen, es tun zu müssen. Sie werden es schneller brauchen, als Sie erwarten.
  4. Ich schaue Dich an, Microsoft, mit deinen sealed classes.
  5. Natürlich sollte man die Bibliothek trotzdem prüfen, testen und so weiter. Einen Entwickler an der Hand zu haben, der Ihre Bedürfnisse gut kennt, dessen Meinung Sie vertrauen, der viel Erfahrung mit der spezifischen Bibliothek hat und der voraussichtlich noch lange Teil Ihres Teams sein wird, ist eine zielich gute Ausgangssituation.
  6. Diese Argument gilt hauptsächlich für Firmen. Für einen privaten Entwickler spielt der Preis sicherlich eine Rolle (das ist bei mir nicht anders). Andererseits ist das Risiko einer falschen Entscheidung in diesem Fall ja auch viel geringer, als bei einer Firma, in der mehrere zig oder gar hundert Mitarbeiter an der Anwendung beteiligt sind.