Wer die Wahl hat, hat die Qual: Umgang mit Abhängigkeiten in der Softwareentwicklung
Make-or-buy? Entscheidungen für die Verwendung von Frameworks oder Third Party Software führen oft zu Diskussionen über Umfang, Qualität und damit sinnvollen Einsatz von Zeit und Geld. Wir zeigen, warum es nicht immer sinnvoll ist, auf fertige Lösungen zu setzen und wann sich eine Eigenentwicklung lohnt.
Ob große Frameworks oder Bibliotheken oder kleine Tools oder Sammlungen von nützlichen Funktionen: man hat den Eindruck, dass für alle Probleme bereits Lösungen existieren. Zumeist sind diese kostenlos als Open Source Software (OSS) verfügbar. Manchmal kosten sie Geld, aber die Kosten sind heutzutage oft sehr gering. Eine Entwicklerstunde ist deutlich teurer. Fremde Software ist dank Package Manager schnell integriert. Das freut Entwickler und Projektleiter oft gleichermaßen – wenn wir doch alle auch damit Aufwand und Geld sparen können. Wo ist also der Haken?
Der Trugschluss mit den Kosten
Kosten in der Softwareentwicklung bestehen in den meisten Fällen zu fast 100% aus Personalkosten. Dadurch entsteht ein großer Anreiz, Entwicklerzeit zu sparen. Das Versprechen eines Tools zur Reduktion der Entwicklerzeit ist daher naheliegend. Und auch der Schluss, fertige Software zu benutzen, um Teile der Entwicklung einzukaufen ist prinzipiell richtig, denn nur durch weniger Entwicklungsaufwand wird am Ende Zeit und damit Geld gespart.
Aber hier liegt auch das Problem versteckt. Wie schätzt man denn genau ein, ob eine fertige Software Zeit sparen kann? Nicht die erste Implementierung, sondern die Zeit für das Lösen von Problemen sowie die stetige Wartung der Software sind Kostentreiber. Diese Zeitaufwände hängen vor allem davon ab, ob das Entwicklungsteam die Software versteht und sich im Code gut auskennt. Dies ist bei fremder Software erst nach langer Einarbeitung mit viel Erfahrung möglich. In jedem Fall ist die Abwägung zwischen Verwendung von fremder Software und dem Schreiben eigener Software nicht trivial, denn die Folgekosten können nur sehr schwer abgeschätzt werden.
Oft herrscht die Tendenz, am Anfang die Versprechungen überzubewerten, da die Entscheidung auf Basis unvollständiger Daten getroffen wird und die sofortigen positiven Effekte überwiegen (kurzfristig weniger Zeit für Einarbeitung und Integration der fremden Software). Paketmanager wie NuGet oder npm haben dieses Prinzip auf die Spitze getrieben und die Suche und Integration von Fremdsoftware gnadenlos vereinfacht.
Wie wägt man nun ab?
Bei der Entscheidung für eine Eigenentwicklung oder die Benutzung einer fertigen Software liegen gewöhnlich wenig Erfahrungswerte zu Grunde. Zudem weiß man unter Umständen erst spät, ob es funktionale Unterschiede gibt, die sich auf den ersten Blick nicht erkennen lassen. Sie treten erst später in der Implementierung auf – ein Problem, das man nicht ganz beseitigen kann.
Will man also eine gute Entscheidung treffen, braucht es Faktoren, an denen man mit wenig Wissen ableiten kann, ob make oder buy die bessere Wahl ist. Folgende Kniffe können die Entscheidung vereinfachen.
So oder so ähnlich könnte die Argumentation lauten, wenn man von der Fremdsoftware nur einen kleinen Teil benötigt. Was ist aber, wenn nur der kleine Teil benötigt wird? Ist es dann nicht einfacher, die Funktionalität selbst zu schreiben? Brauche ich wirklich eine Komponente wie leftpad?
Es ist sinnvoll, den Umfang der benötigten Lösung einmal zu betrachten. Manchmal sind reale Argumente für die komplexere Lösung von der Stange gar nicht vorhanden und man entscheidet sich für trotzdem für ein Potential, das man gar nicht ausschöpfen wird.
Zukunftssicher
Externe Komponenten werden unabhängig in Stand gehalten und aktualisiert
Es ist sicher richtig, dass Komponenten anderer Hersteller oft über Aktualisierungen und Sicherheitsupdates verfügen, die die Funktionalität erhöhen und Stabilität verbessern. Gerade die erlangte Maturität einer fremden Komponente gibt Aufschluss über deren Zukunftssicherheit. Faktoren beinhalten:
-
Engagement, Beliebtheit (bei Open Source Software)
-
Software hat ein stabiles Geschäftsmodell, mit dem der Hersteller Geld verdient
-
Durchsetzung im Markt
Diese Faktoren wirken einfach auszuwerten, geben zum Beispiel die Sterne auf GitHub eine Indikation der Beliebtheit der Software. Die Durchsetzung im Markt oder die Frage nach dem Geschäftsmodell bleibt oft nur qualitativ auswertbar.
Der Integrationsaufwand bleibt
Ungeachtet der vorherigen Faktoren bleibt immer ein gewisser Integrationsaufwand. Bei der Eigenentwicklung ist dieser inklusive. Bei der Fremdsoftware besteht die Integration oft aus Verständnisaufbau durch Dokumentation und Trial and Error. Dieser Aufbau ist bei umfangreicheren Lösungen tendenziell größer. Bei kleineren Lösungen kann es deswegen sinnvoll sein, diese selbst zukunftssicher zu entwickeln, denn bei der Eigenentwicklung kennt das eigene Entwicklungsteam die Lösung sehr genau und kann sie stabil halten.
Nichts ist umsonst
Kostenlos ist beides nicht, denn die Aktualität kostet in jedem Fall immer Integrationsaufwand, auch wenn durch automatisierte Versionsverwaltung in den Paketmanagern und Ansätzen wie Semantic Versioning versucht wird, der Dependency Hell (z.B. durch viele Abhängigkeiten und Abhängigkeiten-Kaskaden) zu entkommen.
Ein Praxisbeispiel – Caliburn.Micro
Wie einfach eine Implementierung sein kann sieht man an einem Beispiel aus einem unserer Softwareteams, die mit Hilfe von WPF und .NET Software erstellen. Dafür haben sie das Framework Caliburn.Micro eingesetzt. Der primäre Grund war die Benutzung des MVVM-Patterns.
Die Entscheidung war einfach, denn Caliburn.Micro ist kostenlos und sehr einfach zu benutzen und zu integrieren. Mit der Beschränkung auf das MVVM-Pattern hatte das Team nur einen kleinen isolierten Teil gewählt, der für sie wichtig war. Trotzdem traten relativ früh Probleme auf, die die Entwicklung unnötig kompliziert gemacht haben:
-
Im MVVM-Pattern, wie es bei Caliburn.Micro implementiert wird, passiert das Matching von View und View Model über den Namen der Komponenten.
-
Bei der zusätzlichen Integration von DevExpress konnte dieses Pattern so nicht verwendet werden. Stattdessen musste das Standard WPF-Pattern verwendet werden, bestehend aus XAML + Code-Behind.
-
Durch die Benutzung von zwei Patterns ist das Team immer wieder in das WPF-Pattern gefallen, da nicht klar war, an welcher Stelle jetzt welches Pattern verwendet wird.
Zuerst wurde also der Umfang der Lösung betrachtet. Das MVVM-Pattern selbst ist ein kleinerer isolierter Teil. Es lässt sich einfach heraustrennen und selbst lösen, ohne Abhängigkeit zu Caliburn.Micro. Die Integration und die Einarbeitung in das externe Framework war ähnlich aufwendig.
Die Umsetzung der Eigenentwicklung beinhaltete demnach:
-
Das Isolieren des MVVM Pattern Matchings über Filename-Konvention (statt Namen der Komponenten) und
-
das Einbinden in WPF
Eigene Ideen können nun einfacher und schneller umgesetzt werden und der Code ist um ein Vielfaches übersichtlicher und schlanker, da auch nur solche Funktionen umgesetzt wurden, die wir tatsächlich benötigen. Unser Framework ist damit deutlich einfacher zu verstehen und somit bereit für jegliche Wartung und Erweiterung. Die Nutzung unserer firmeneigenen Standardformate und Standardstrukturen erleichtern den Umgang mit der Software firmenintern. Somit können Probleme durch bereits auch schon vorhandene Lösungen leichter beseitigt werden.
Dass es nicht viel Programmierarbeit benötigt, zeigt der selbst erstellte Code, der die durch Caliburn.Micro erhofften Vorteile abbildet.
/// <summary>
/// Searches for View models and views that match the name convention and adds them to the resource dictionary
/// </summary>
/// <param name="typeNamespace"> The namespace in which will be searched for the view models</param>
/// <param name="res">Reference to Dictionary in which the template should be stored</param>
/// <param name="relTypes">Array of types that contains among other types, the view model types and view types</param>
/// <returns>List that contains the imported templates</returns>
public static List<String> GenerateTemplateDictionary(String typeNamespace, ResourceDictionary res, Type[] relTypes)
{
// get names of namespaces for VMs and Views by convention
String vmNs = $@"{typeNamespace}.ViewModels";
String viewNs = vmNs.Replace("Model", String.Empty);
// getting types of VMs and Views from that namespaces
var viewModelQuery = from t in relTypes
where t != null && t.IsClass && !t.IsAbstract && t.Namespace != null && t.Namespace.StartsWith(vmNs)
select t;
var viewModelTypeList = viewModelQuery.Where(t => t != null).GroupBy(t => t.Name).Select(g => g.First()).ToDictionary(t => t.Name, t => t);
var viewQuery = from t in relTypes
where t != null && t.IsClass && !t.IsAbstract && t.Namespace != null && t.Namespace.StartsWith(viewNs)
select t;
var viewTypeList = viewQuery.Where(t => t != null).GroupBy(t => t.Name).Select(g => g.First()).ToDictionary(t => t.Name, t => t);
var foundItems = new List<String>();
// link the VM types to View types by convention into a datatemplate and add the datatemplate to app.resources
foreach (var vmt in viewModelTypeList)
{
var viewKey = vmt.Key.Replace("Model", String.Empty);
if (viewTypeList.ContainsKey(viewKey))
{
var viewType = viewTypeList[viewKey];
var template = CreateTemplate(vmt.Value, viewType);
if (!res.Contains(template.DataTemplateKey ?? throw new InvalidOperationException()))
{
res.Add(template.DataTemplateKey ?? throw new InvalidOperationException(), template);
foundItems.Add($@"Found and add VM -> View: {viewType.FullName} -> {vmt.Value.FullName}");
}
else
foundItems.Add($@"Pair is already known VM -> View: {viewType.FullName} -> {vmt.Value.FullName}");
}
}
return foundItems;
}
private static DataTemplate CreateTemplate(Type viewModelType, Type viewType)
{
// The only way to get around some problems with binding later is this way of creating datatemplate objects
// see www.ikriv.com/dev/wpf/DataTemplateCreation/
const String xamlTemplate = "<DataTemplate DataType=\"{{x:Type vm:{0}}}\"><v:{1} /></DataTemplate>";
var xaml = String.Format(xamlTemplate, viewModelType.Name, viewType.Name);
var context = new ParserContext { XamlTypeMapper = new XamlTypeMapper(new String[0]) };
context.XamlTypeMapper.AddMappingProcessingInstruction("vm", viewModelType.Namespace ?? throw new InvalidOperationException(), viewModelType.Assembly.FullName);
context.XamlTypeMapper.AddMappingProcessingInstruction("v", viewType.Namespace ?? throw new InvalidOperationException(), viewType.Assembly.FullName);
caliburnmicro.com
context.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
context.XmlnsDictionary.Add("x", "http://schemas.microsoft.com/winfx/2006/xaml");
context.XmlnsDictionary.Add("vm", "vm");
context.XmlnsDictionary.Add("v", "v");
var template = (DataTemplate)XamlReader.Parse(xaml, context);
return template;
}
Die Gründe sind oft dieselben. Es sind wenige Entwickler verantwortlich für das Projekt, die Entwickler verändern ihr privates oder berufliches Setup und können eine oft privat durchgeführte Entwicklung nicht mehr aufrecht erhalten.
Fazit
Das Beispiel zeigt, dass Entscheidungen und Abwägungen in diesem Fall dazu geführt haben, dass sich die Investition gelohnt hat. Die Freiheit, diese Entscheidung im Entwicklungsteam zu treffen, charakterisiert die Arbeit bei OHB Digital Services.
Der Gestaltungsraum für diese Entscheidungen ist groß, eigene Ideen können eingebracht werden und es wird nicht vom Projektleiter vorgegeben, welche Art von Bibliothek genutzt werden soll. Aus der Tatsache, dass im Team entschieden wird, ergeben sich somit oft die besseren Lösungen.
Für uns hat sich im Laufe der Zeit gezeigt, dass es häufiger sinnvoll ist, eigene Lösungen zu erstellen und ein einfaches kleines Framework selbst zu entwickeln, anstatt komplexere Third-Party-Frameworks zu benutzen.
Wenn man sich dennoch für eine Fremdbibliothek entscheidet, sollte man sich immer bewusst sein, dass dies kein Selbstläufer ist. Statt einfach zu integrieren und zu vergessen ist auch bei Fremdsoftware eine hohe Sorgfalt und Pflege wichtig. Steigt die Anzahl der Abhängigkeiten verliert man mitunter den Blick auf versteckte Inkompatibilitäten. Auch aufwendige Fehlerbehebungen können die Folge sein, wenn die Fremdsoftware nicht tiefgreifend verstanden ist. All das darf nicht in Vergessenheit geraten.
Am Ende soll natürlich das bestmögliche Produkt für den Kunden bereitstehen und hiernach sollte auch die Entscheidung getroffen werden.
Ihre Reise mit OHB Digital Services
Nutzen Sie das Wissen aus der Raumfahrt für Ihr Business. OHB Digital Services GmbH ist seit vielen Jahren ein verlässlicher Partner für sichere & innovative IT-Lösungen. Wir sind Teil eines der erfolgreichsten Raumfahrt- und Technologieunternehmen in Europa. Mit unseren Produkten und Services unterstützen wir Sie u.a. bei der Digitalisierung Ihrer Unternehmensprozesse entlang der Wertschöpfungskette und bei allen sicherheitsrelevanten Fragen. Kontaktieren Sie uns gerne.