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.
10. June 2018

LINQ: .NET Collection-Klassen

Guten Morgen,

Various collectionsLINQ ist eine der interessantes Funktionen von C#. Immer, wenn man mit Daten in Listen arbeitet (was meistens der Fall ist), werden Methoden gebraucht, diese Daten zu filtern und zu manipulieren. LINQ ermöglicht a) einfachen und konsistenten Zugriff auf Listen, und b) einen Funktionalen Ansatz, mit Listen zu Arbeiten.

Dieser Artikel ist der Erste in einer Reihe von Artikeln zu LINQ. In dieser Reihe werden wir

  • uns die Collection-Klassen von .NET anschauen
  • über Lambda-Expressions, Closures, Action<> und Func<> lernen
  • uns die Macht von Extension-Methods zu Nutze machen
  • das IEnumerable<>-Interface verwenden
  • in die diversen Methoden von LINQ eintauchen
  • und uns an PLINQ (parallel LINQ) versuchen.

Also, fangen wir direkt an, die mit den verschiedenen Collection-Klassen, die in .NET Framework1 zur Verfügung stehen. Da die meisten Business-Anwendungen rein datengetrieben sind (an wie vielen richtigen Programmen ohne Datenbank haben Sie bisher gearbeitet?), ist Wissen über die Collection-Klassen einer Sprache meiner Meinung nach ähnlich grundlegend, wie Wissen über die Basis-Datentypen der jeweiligen Sprache.

Die wichtigsten Collections in .NET finden sich im Namespace System.Collections.Generic. Ihre nicht-generischen Equivalente aus dem Namespace System.Collections sollten nicht verwendet werden. Sie existieren nur noch für Abwärtskompatibilität. Concurrent Collections (concurrent = nebenläufig; in diesem Zusammenhang: multithreading-fähig) können im Namespace System.Collections.Concurrent gefunden werden.

Array / List<T>

Ein Array (bzw. eine List<T>, die im Grunde nur ein Wrapper um Array ist), ist in C# ziemlich genau das, was man von einem Array in jeder anderen Sprache erwarten würde: Eine Sammlung von Elementen, die einen zusammenhängenden Block im RAM belegt. Zugriff mittels Index ist daher sehr schnell. Einträge anhand ihrer Attribute zu suchen, erfordert aber einen vollständigen Scan des Arrays.

Einem Array Einträge hinzu zu fügen, ist normalerweise günstig, da C# das RAM in Blöcken reserviert. Nur Einfügen und Löschen von Elementen in der Mitte des Arrays ist etwas teurer, weil alle nachfolgenden Einträge verschoben werden müssen. Ist das Ihr Anwendungsfall, sollten Sie sich die LinkedList<T> anschauen (siehe unten).

HashSet<T>

Wie der Name vermuten lässt, speichert das HashSet<T> einen Hash für jeden Eintrag in der Collection. Im Gegensatz zu einem Array, garantiert das HashSet<T> aber nicht, dass die Reihenfolge der Einträge erhalten bleibt. Außerdem kann jeder Eintrag nur ein einziges Mal vorkommen, womit das HashSet<T> optimal geeignet ist, wenn jeder Eintrag in der Collection eindeutig sein muss. Ob Einträge gleich sind, wird über die Equals()-Methode der Einträge bestimmt.2

Das HashSet<T> ist fantastisch, wenn ein Eintrag mittels einer Instanz (oder eher: eines "Dings", was als identisch angesehen wird) gefunden werden soll. Ein gutes Beispiel ist eine Zufällige Liste von Zeichenfolgen. Da identische (in Hinsicht auf ihren Inhalt) Zeichenfolgen als gleich angesehen werden, ist die Konvertierung einer List<string> in ein HashSet<string> eine simple Variante, jeden String in der Collection eindeutig zu machen.

var list = new List<string> {
    "One", "Two", "Three", "Two", "Three"
};
var set = new HashSet<string>(list); // Output: "One", "Two", "Three"

Dictionary<T, U>

Das Dictionary<T, U> ist eine Key-Value-Collection. Jeder Schlüssel (vom Typ T) ist einem Eintrag (vom Typ U) zugeordnet. Zugriff über den Schlüssel ist sehr effizient, was das Dictionary<T, U> exzellent macht, um Einträge über ein Attribut (wie den Primärschlüssel) zu finden.

Die Schlüssel eines Dictionary<T, U> müssen eindeutig sein. Im Gegensatz zu dem HashSet<T>, was doppelte Einträge einfach ignoriert, wirft ein Dictionary<T, U> eine Exception, wenn ein Schlüssel zwei Mal hinzugefügt wird. Analog zu dem HashSet<T> wird die Gleichheit von Schlüsseln über die Equals()-Methode geprüft.

Wann immer man mehrfach über eine Liste iteriert, um einen einzelnen Eintrag über ein bestimmtes Feld zu finden (bspw. eine Person in einer List<Person> über ihren Namen), sollte man sich überlegen, ein Dictionary<T, U> zu verwenden. Selbst bei ein paar Iterationen kann es sich lohnen, temporär ein Dictionary<T, U> zu erstellen. LINQs ToDictionary()-Method hilft hier weiter.3

ConcurrentDictionary<T, U>

Alle Concurrent-Collections erlauben paralleles Lesen und Schreiben von mehreren Threads. Da sie üblicherweise eine Kopie der Collection auf jedem Thread halten müssen, und die Kopien synchronisiert werden müssen, ist es nicht ratsam Concurrent Collections in einem nicht-parallelen Anwendungsfall zu benutzen. Sie sind aber mächtige Werkzeuge in hochgradig parallelen Szenarien.

Die nützlichste Concurrent-Collection ist das ConcurrentDictionary<T, U>. Es stellt eine thread-sichere Implementierung des Dictionary<T, U> dar und ist besonders hilfreich bei der Zwischenspeicherung von Daten oder bei Multi-Threading-fähigen Service-Implementierungen.

Auch wenn alle Concurrent-Collections grundlegend thread-safe sind, ist nicht sichergestellt, dass alle Operationen auf diese Collections ebenfalls thread-sicher sind. Die Dokumentation schließt Thread-Sicherheit sogar aus für explizite Interface-Implementierungen, Extension-Methods (muss jemand an LINQ denken?) und Methoden, die Delegaten als Parameter annehmen.4 Bitte prüfen Sie immer die Dokumentation der jeweiligen Methode, insbesondere wenn Sie LINQ auf eine Concurrent-Collection anwenden.

Andere nützliche Collections

Natürlich gibt es noch eine Reihe hilfreicher, aber speziellerer Collections:

  • Stack<T>: Eine "first in last out" (FILO) Collection
  • Queue<T>: Eine "first in first out" (FIFO) Collection
  • LinkedList<T>: Eine typische "Linked List". Nützlich, wenn man häufig Einträge in der Mitte einer größeren Collection hinzufügen oder entfernen möchte.
  • SortedList<T>, SortedSet<T>, SortedDictionary<T>: Varianten von List<T>, HashSet<T> und Dictionary<T, U>, die die beinhalteten Einträge sortieren.5
  • Die anderen Collections im Namespace System.Collections.Concurrent: ConcurrentStack<T> und ConcurrentQueue<T> sind nebenläufige Implementierungen von Stack<T> und Queue<T>. ConcurrentBag<T> ist etwas speziell, da es tatsächlich einen Sack von Einträgen darstellt: Einträge sind unsortiert und können weder mittels Index noch per Instanz abgefragt werden. Man stopft Sachen hinein und iteriert über Alles, wenn man einen Eintrag herausholen will (bin ich der Einzige, der an seine Schubladen denken muss?).
  • Die verschiedenen Collections aus dem Namespace System.Collections.ObjectModel: Sie werden üblicherweise für UI-Entwicklung verwendet. Besonders die ObservableCollection<T>, die INotifyCollectionChanged implementiert und häufig für DataBindings in WPF genutzt wird.
  • Die brandneuen ImmutableCollections aus dem NuGet-Package System.Collections.Immutable:6 Die Idea ist einfach: Collections, die nicht verändert werden können, können problemlos von mehreren Threads gelesen werden. Es stehen Implementierungen für alle üblichen Collektions zur Verfügung, ImmutableList<T>, ImmutableDictionary<T, U>, ImmutableQueue<T>, usw. Obwohl diese Collections nicht direkt Teil von .NET sind, wollte ich sie erwähnt haben, weil sie eine weitere Möglichkeit schaffen, Collections von mehreren Threads aus zu verwenden.7
6. June 2018

Microsoft kauft GitHub

Guten Morgen,

GitHub Octocat

es hat rumort im Internetz, jetzt ist es bestätigt: Microsoft hat GitHub gekauft1 - und die Community ist in Panik. Viele OpenSource-Projekte befürchten, dass Microsoft seinen Einfluss nutzen wird, um Ihnen zu schaden und die eigene Agenda durchzudrücken. Schauen wir uns die Vor- und Nachteile, sowie die Intentionen der Beteiligten einmal genauer an.

Warum sollte Microsoft GitHub kaufen?

Ich glaube die Antwort ist einfach: Microsoft hat GitHub seit vielen Jahren als Code-Hosting-Plattform genutzt. Sie haben einige ihrer größten Projekte 2014/2015 auf GitHub umgezogen,2 und damit die Schließung ihrer eigenen Code-Hosting-Plattform CodePlex vorbereitet.3 Heute hat Microsoft in seinem Haupt-Account über 1800 Repositories auf GitHub. Darüberhinaus nutz Microsoft Git mittlerweile als Haupt-Versionsverwaltung, und hat den gesamten Windows-Quellcode in das "größte Git Repo auf dem Planeten" umgezogen.4 Strategisch macht es meiner Meinung nach absolut Sinn für Microsoft, GitHub zu kaufen. Dieser Schritt erlaubt es ihnen, ihre meistgenutzte Code-Hosting-Plattform an die eigenen Bedürfnisse anzupassen. Und an die Bedürfnisse ihrer Kunden (= Entwickler auf den Microsoft Plattformen).

Warum sollte GitHub verkauft werden?

2012 hat Chris Wanstrath, einer der Gründer von GitHub, seinen Platz als CEO geräumt, um Tom Preston-Werner (einem weiteren Mitgründer) die Führung zu überlassen. 2014 wurde Chris Wanstrath dann erneut CEO, wollte diesen Posten 2017 aber wieder abgeben (Mitte 2018, hatte GitHub noch immer keinen Ersatz für ihn gefunden). Die Firma hatte zu dieser Zeit einen Jahresumsatz von 200 Millionen Dollar und einen Marktwert von geschätzt 2 Milliarden Dollar.5 Sie hat aber noch nie einen Pfennig Gewinn gemacht. Wenn Sie in dieser Situation wären und Microsoft würde sagen: "Ihre Firma ist 2 Milliarden Dollar wert, wir geben Ihnen 7,5 Milliarden dafür (und haben einen guten CEO dafür)", was wäre Ihre Antwort? Ich weiß, dass ich verkaufen würde!

Und was ist mit uns?

Microsoft logo

Was heißt das alles für uns, Benutzer von GitHub? Ich bin der Meinung, dass Microsoft GitHub nicht umkrempeln wird. Es wird Veränderungen geben, das ist klar, und einige werden besser sein, andere eher nicht. Insgesamt denke ich aber, dass GitHub jetzt endlich eine stabile Firma geworden ist (und damit eine verlässliche Plattform). Microsoft ist nicht darauf angewiesen, dass GitHub irgendwann einmal Gewinn erwirtschaftet. Sie brauchen Entwickler, die ihre Produkte nutzen, sie brauchen eine Community. Und das ist es, was sie sich von diesem Kauf erhoffen. Ich bin außerdem froh zu sehen, dass GitHub wieder einen guten CEO hat. Nicht dass Wanstrath nicht gut war (er hat GitHub zu dem gemacht, was es heute ist), er wollte aber nicht weiter machen und es war kein Ersatz in Sicht. Nun, dieses Problem ist jetzt gelöst.

Alles in Allem freue ich mich auf die Entwicklung von GitHub als Teil von Microsoft. Natürlich gibt es Risiken, es gibt aber auch große Chancen für GitHub, noch besser zu werden. Ich hoffe, dass wir letztere zu genießen kriegen.

27. May 2018

Die Herausforderung der Anpassbarkeit

Guten Morgen,

in der Softwareentwicklung wird zwischen Produkten und Projekten unterschieden. Die Unterscheidung ist folgende:1

  • Ein Produkt wird für den Markt entwickelt. Die Sofwarefirma investiert Geld in die Entwicklung eines Produktes mit dem Ziel, es einer großen Anzahl von Kunden zu verkaufen. Investieren meint in diesem Sinn Geld vorschießen, was vielleicht Gewinne bringt, wenn das Produkt fertig ist. Ein Produkt lockt daher mit gutem Gewinn (einmal Schreiben, häufig Verkaufen), birgt aber auch beträchtliche Risiken (der Markt interessiert sich nicht für das Produkt).
  • Ein Projekt wird für einen einzigen Kunden entsprechend seiner Bedürfnisse entwickelt. Üblicherweise muss die Softwarefirma auch hier das notwendige Budget für die Entwicklung vorlegen. Das Risiko ist aber deutlich geringer, da es mit Sicherheit einen Kunden gibt, der später die Entwicklungskosten der Software tragen wird. Der Gewinn ist andererseits aber auch niedriger, weil die Projektsoftware nicht an andere Kunden verkauft werden kann.

Viele Softwarefirmen haben Ihren Platz in der Mitte zwischen beiden Varianten gefunden: Sie bieten am Markt ein Standardprodukt an, schneiden dieses aber auch für die jeweiligen Kundenbedürfnisse zu. Dieses "Zuschneiden" wird üblicherweise Anpassung (oder dengl. Customizing) genannt - und ist eine der größten Herausforderungen beim Design eines Produkts.

In meinen Jahren als Softwareentwickler habe ich diverse Produkte angetroffen, die vielfach angepasst wurden. Und die meisten hatten mindestens eins der folgenden Probleme:

  • Alles ist private
    Dieser Umstand kann für einen Entwickler, der ein Produkt anpassen soll, extrem frustrierend sein. Das Produkt bietet nur wenige Einstiegspunkte, die nicht die benötigte Freiheit bieten. An jeder Ecke muss das Produktentwicklungs-Team gebeten werden, einen weiteren Einstiegspunkt zu definieren, oder noch eine Methode auf public umzustellen. Das kann ein zwei-Tage-Projekt schnell zu einer mehrwöchigen Odyssee machen.
  • Alles ist public
    Obwohl dieser Punkt das genaue Gegenteil des vorherigen ist, ist er kein Stück besser für eine Firma. Wenn irgendeine Methode public ist, wird sie jemand benutzen (und es vergessen). Eine öffentliche Schnittstelle kann niemals geändert werden, ohne die Sorge, dass eine Anpassung kaputt geht. Und wenn diese Anpassung wichtig war, kann aus einer kleinen Produktänderung ganz schnell ein politischer Alptraum werden. Das Resultat ist eine Sackgasse: wenn alles public ist, kann nichts mehr geändert werden. Die Produktentwicklung kommt zum Erliegen.
  • Die Produktarchitektur ist ein Chaos
    Eine chaotische Architektur ist schon für die Produktentwicklung schlimm genug. Ab einem gewissen Punkt, wird es immer schwieriger, eine schlechte Code-Basis zu verändern. Das Risiko, bestehende Funktionalität zu beschädigen, wird immer größer. Wie soll jemand eine komplexe kundenspezifische Funktion in eine solche Code-Basis einbauen, ohne etwas kaputt zu machen? Und wir soll man so eine Anpassung umsetzen, ohne dass sie noch schlechter wird als der bereits jetzt schlechte Code? Noch schlimmer: Kunden wollen manchmal Anpassungen auf Basis bestehender Anpassungen haben. Das ist wie ein schlechtes Essen mit miserabler Sauce und noch ekelhafterer Würzung.
  • Eine ausgezeichnete Vertriebsabteilung
    Einige Verkäufer können so ziemlich alles an den Mann bringen - und tun es auch.2 Und dass obwohl den Entwicklern die Haare zu Berge stehen, weil sie die Anforderungen kaum "hingebastelt" kriegen. Das resultiert häufig in Workarounds mit den seltsamsten Seiteneffekten.
  • Der "Produktaufkleber" wird auf Anpassungen geklebt
    Je nach Vertragswerk mit dem Kunden, kann eine Softwarefirma Anpassungen in das Produkt aufnehmen. Das ist super, wenn eine Anpassung wohlüberlegt als neue Funktion in das Produkt eingefügt wird. Wenn jedoch eine Anpassung nach der anderen einfach in das Produkt "reingeklatscht" wird, wird das Produkt nach einer Weile sehr wahrscheinlich chaotisch. Der Grund ist schnell erklärt: Eine Anpassung muss üblicherweise nicht alle Funktionen des Produkts berücksichtigen. Es ist klar, welche Funktionen der Kunde benötigt und einsetzt. Um die Kosten für die Anpassung niedrig zu halten, werden nur die notwendigen Änderungen gemacht. Warum sollte man eine Anpassung mit Funktion x kompatibel machen, wenn der Kunde diese Funktion gar nicht benutzt? Diese Anpassung einfach so in den Produktcode zu übernehmen führt aber zu einem Problem: Ein anderer Kunde wird die Funktion x sehr wohl verwenden, leider ist sie aber inkompatibel.

Viele dieser Herausforderungen können nur mit viel Selbstbeherrschung und guter Voraussicht angegangen werden. Allerdings gibt es verschiedene Wege den einzelnen Problemen zu begegnen:

  • Bei der Entwicklung eines neuen Produkts muss klar sein, ob die Software anpassbar sein muss, oder nicht. Im Zweifelsfall sollte die Antwort ja lauten.3
  • Jede Software muss eine klare Architektur haben. Dies gilt besonders, wenn andere die Software erweitern können sollen. Ich persönlich bin der Meinung, dass die genaue Ausprägung der Architektur nicht so wichtig ist, wie die konsistente Einhaltung dieser Architektur. Wenn ein neues Team-Mitglied (egal ob in der Produkt- oder Projektentwicklung) einen Teil der Anwendung verstanden hat, sollte dieses Wissen auf alle anderen Teile der Software übertragbar sein.
  • Überlegen Sie die Implementierung eines Plugin-Systems Plugins sind ein mächtiges Werkzeug für Anpassbarkeit. Im Extremfall kann die gesamte Funktionalität eines Produkts in mehrere Schichten von Plugins gepackt sein, womit alles für den Kunden angepasst werden kann (allerdings wird die Architektur dann schwieriger zu verstehen).
  • Verwenden Sie automatisierte (Regressions-)Tests. Automatisiertes Testen hilft nicht nur bei der Entwicklung des Produkts oder der spezifischen Anpassung. Der tatsächliche Nutzen wird erst ersichtlich, wenn automatisierte Regressionstests auf einmal für das Produkt und alle seine Anpassungen durchgeführt werden können. Immer wenn die Produktentwicklung (mal wieder) die Schnittstelle kaputt gemacht hat, wird der Fehler durch automatisierte Tests gefunden, bevor die Änderung zum Kunden ausgerollt wird. Das verhilft dem Produktentwickler, wie auch dem Projektentwickler zu ruhigeren Nächten, weil beide ziemlich sicher sein können, dass ihr Zeug auch am nächsten Tag noch funktioniert.
  • Während der Umsetzung der Anpassung sollten Produkt-Features, die nicht kompatibel sein werden, berücksichtigt und dokumentiert4 werden. Sollte später jemand die Anpassung in das Produkt übernehmen, ist die Chance damit höher, dass inkompatible Funktionen entdeckt werden.
  • Es sollte nicht alles public/private gemacht werden. Für jeden Teil der Anwendung muss bedacht werden, ob Anpassbarkeit notwendig ist oder nicht. Alles was anpassbar sein soll, muss eine klare öffentliche Schnittstelle (im Grunde eine API) besitzen. Diese Schnittstelle muss derart gestaltet sein, dass a) sämtliche notwendige Funktionalität vorhanden ist und b) die Schnittstelle um Funktionalität erweitert werden kann, die wir in a) vergessen haben.5 Wenn die Produktentwicklung sich unsicher ist, welche Teile der Schnittstelle öffentlich sein müssen, sollte gewartet werden, bis die erstem Anpassungen umgesetzt werden. Meiner Erfahrung nach ist es besser zu viel private statt public zu machen. Allerdings muss es einen einfachen Weg für Projektentwickler geben, die benötigte öffentliche Schnittstelle zu erhalten, wenn sie gebraucht wird. Das führt mich zum nächsten und wichtigsten Punkt:
  • Reden Sie mit Ihren Kollegen (Kaffeemaschine sind super dafür). Meiner Erfahrung nach, gibt es häufig Mauern6 zwischen Produktentwicklung und Projektentwicklung. Insbesondere wenn es sich dabei um unterschiedliche Abteilungen handelt. Allerdings wissen nur die Mitarbeiter der Customizing-Abteilung, was sie wirklich brauchen.

Meiner Meinung nach ist Anpassbarkeit eine riesige Herausforderung. Customizing ist eine der größten Einnahmequellen vieler Software-Unternehmen, aber es gibt keinen einen Weg, sie korrekt umzusetzen. Trotzdem hoffe ich, hier einige Denkanstöße gegeben zu haben, die die Arbeit von Produkt- und Projektentwicklung gleichermaßen verbessern.

  1. Das ist natürlich eine vereinfachte Unterscheidung. Wie üblich gibt es in der Realität viele Übergänge zwischen Produkt und Projekt. Eine Softwarefirma könnte bspw. eine Anpassung vergünstigt anbieten, dafür aber die Erlaubnis vom Kunden einholen, die Anpassung auch anderen Kunden zu verkaufen.
  2. Diese Anmerkung sollte nicht zu ernst genommen werden. Wie so oft, sind weder der Verkäufer, noch die Softwareentwicklung hier "schuld". Es gibt aber Anpassungen, die besser nicht gemacht worden wären. Und bessere Kommunikation zwischen den Abteilungen kann Wunder bewirken, um solche Missstände zu vermeiden.
  3. Zugelassene Antworten sind: ja und vielleicht.
    4 Diese Dokumentation könnte sogar in Form eines Tests abgebildet werden: Wenn Funktion x aktiv ist, schlägt der Test mit einer passenden Fehlermeldung fehl.
  4. Meines Erachtens nach ist das eine der größten Schwierigkeiten. Eine gute Lösung für dieses Problem ist eine Kombination aus Plugin-System und Dependency Injection. Plugins kriegen ihre Abhängigkeiten übergeben, die wiederum eine leicht zu erweiternde öffentliche Schnittstelle haben. Allerdings kann man mit diesem Thema Bücher füllen, es kann hier also nicht vollständig behandelt werden.
  5. Die Größe dieser Mauer kann von Gartenzaun bis zu Chinesischer Mauer reichen. Nur offene Kommunikation kann zu guter Software führen. Insbesondere wenn die Software noch nicht am Markt etabliert ist.
21. May 2018

Denken Sie an Ihre Zukunft

Guten Morgen,

wir haben wirklich Glück, wissen Sie das? Im Jahr 2018 verdient ein Softwareentwickler in Deutschland durchschnittlich 52.000 €.1 Im Vergleich dazu liegt das durchschnittliche Gehalt aller Arbeitnehmer in Deutschland bei 41.000 €.2 Wenn man sich verschiedene Berufsgruppen anschaut, wird IT als einer der lukrativsten Arbeitsmärkte angesehen.3 Dazu kommt, dass Managementpositionen in der IT einen höheren Gehaltsaufschlag bringen, als in anderen Berufen.4

Gerade habe ich meinen Rentenbescheid erhalten und ich war ein bisschen irritiert. Rechnet man Inflation und Erhöhungen von Gehalt und Renten heraus, wird meine Netto-Rente gerade mal 1/2 meines aktuellen Netto-Einkommens betragen. Und damit stehe ich nicht schlechter da als Andere.5 Das ist ein deutlicher Verlust an Lebensstandard!

Und deshalb bin ich der Meinung, wir haben Glück: Als Softwareentwickler (mit überdurchschnittlichem Gehalt), können wir uns auf die Zukunft vorbereiten, etwas Geld beiseite legen, um unseren Lebensstandard in der Rente beibehalten zu könne. Das ist es, worauf ich dränge!

Ich will an dieser Stelle keine Vorschläge machen, wie man sein Geld am besten anlegt um die Rente aufzustocken. Dafür gibt es Experten und ich gehöre nicht dazu. Ich will aber eine Weitere Sache hervorheben: Je früher man startet, umso besser - denken Sie an Zinseszins.