27. May 2018

Die Herausforderung der Anpassbarkeit

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 dies 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.
5 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.
6 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

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.

13. May 2018

GitHub zu NuGet mit AppVeyor

AppVeyor Dashboard
Wie ich in meinem letzten Post geschrieben habe, habe ich jetzt eine Reihe .NET-Projekte auf GitHub. Da all diese Bibliotheken sind, wollte ich sie zur einfachen Nutzung über NuGet zur Verfügung stellen (nicht zuletzt, weil ich selbst sie regelmäßig verwende und jederzeit zur Hand haben will). Ab in die Welt der Continuous Integration (CI) Services.

Es gibt zwei große Anbieter von CI-Services mit sehr guter GitHub-Anbindung: Travis-CI und AppVeyor. Als .NET-Entwickler hat Travis leider einen deutlichen Nachteil: ihre Server laufen mit Linux und unterstützen nur Mono und .NET Core. AppVeyor, auf der anderen Seite, bietet Windows-Server an, mit allen .NET Frameworks, die man sich nur wünschen kann. Noch dazu kostenlos für Open-Source-Projekte. Ein Fest!

Die Integration ist einfach: Man wählt das GitHub-Projekt aus, fügt den NuGet-Provider hinzu und gibt den NuGet-API-Schlüssel ein. Für meine Projekte habe ich zudem einen Release-Branch angelegt und AppVeyor so eingestellt, dass alle Branches gebaut werden, aber nur vom Release-Branch NuGet-Pakete hochgeladen werden. Außerdem war es bei einigen .NET Standard-Projekten notwendig "dotnet restore" als Pre-Build-Script einzustellen, um alle Abhängigkeiten zu aktualisieren.

Alles in allem kann ich nur empfehlen, AppVeyor auszuprobieren - insbesondere als .NET-Entwickler.
11. May 2018

Programmer's Digest auf GitHub und NuGet

Programmer's Digest hat seinen Weg auf GitHub und NuGet gefunden!
GitHub: https://github.com/programmersdigest
NuGet: https://www.nuget.org/profiles/programmersdigest

Dort findet man diverse Open Source Projekte (lizenziert unter Apache License 2.0), angefangen mit einem Object-Relational-Mapper über einen Dependency-Injection-Container bis hin zu einem Parser für das SWIFT MT940/942 Kontoauszugs-Format. Momentan sind alle diese Projekte in meiner bevorzugten Sprache, C#, geschrieben und basieren nach Möglichkeit auf .NET Standard 2.0.

Alle Projekte entstammen Programmen, die ich in meiner Freizeit schreibe, und versuchen ein für allemal bestimmte Probleme zu lösen, die mir immer wieder begegnen. Entsprechend wird an allen Projekten aktiv gearbeitet, wenn es die Notwendigkeit gibt. Allerdings kann es zu längerer Inaktivität kommen, wenn es keine bekannten Fehler gibt und keine neuen Funktionen gebraucht werden.

Verbesserungen dieser Projekte durch Fehlerberichte, Ratschläge oder, noch besser, Code, sind gerne gesehen. Um eines dieser Projekte zu verwenden, können sie einfach über NuGet eingebunden werden.

In der näheren Zukunft werde ich jedes meiner Projekte in einem separaten Artikel vorstellen. Dabei werde ich das Wie und Warum erklären und vielleicht einige interessante Codestellen beschreiben (und dazu wird es wahrscheinlich die ein oder andere Schimpftirade geben).
30. July 2015

JavaFX mit Scala

JavaFX with Scala

Einer der großen Vorteile von Scala ist die sehr gute Interoperabilität mit Java. Wäre es nicht toll, wenn man eine JavaFX Applikation mit Scala schreiben könnte? Nun, man kann (sonst wäre dieser Artikel auch arg kurz). Im Folgenden zeige ich, was man beachten muss, wenn man JavaFX und Scala verheiraten will.

Ziel ist es, eine kleine JavaFX Applikation zu schreiben, deren Oberfläche in FXML-Dateien definiert ist, und daher mittels SceneBuilder erstellt werden kann. Weiterhin wollen wir den Komfort der Dependency Injection mittels @FXML Attribut nutzen können, was dank der guten Interoperabilität glücklicherweise relativ problemlos funktioniert. Der Einfachheit halber nutzen wir den View-First Ansatz, d.h. die View bestimmt welcher Controller zu laden ist und der FXMLLoader kümmert sich darum diesen Controller zu initialisieren.

Zunächst müssen wir JavaFX laden. Hierzu wird analog zu Java eine Klasse erstellt, die von Application erbt.

// File: eu.derprogrammierer.jfx.FxApp.scala
package eu.derprogrammierer.jfx

import javafx.application.Application
import javafx.stage.Stage

class FxApp extends Application {
override def start(primaryStage: Stage): Unit = {
primaryStage.setTitle("Fx App")
primaryStage.show()
}
}

In diesem Beispiel wird der Fenstertitel gesetzt und das Fenster geöffnet - später werden wir es auch noch mit Inhalt füllen.

In einer üblichen JavaFX Anwendung gibt es zudem eine statische main()-Funktion. Da Scala aber keine statischen Funktionen kennt, werden wir stattdessen ein Companion Object für die FxApp-Klasse erstellen. Dabei ist zu beachten, dass ein Companion Object nicht automatisch die Funktionen der Basisklasse implementiert. Daher funktioniert die folgende direkte Übersetzung des normalen Java-Codes nicht.

object FxApp extends Application {
def main (args: Array[String]): Unit = {
launch(args:_*) // Cannot resolve symbol launch
}
}

Glücklicherweise bietet Application eine Funktion an, die für unsere Zwecke genutzt werden kann. Sie erlaubt es uns, launch() auf der Basisklasse aufzurufen und den Typ unserer FxApp anzugeben.

// File: eu.derprogrammierer.jfx.FxApp.scala

...

object FxApp {
def main (args: Array[String]): Unit = {
Application.launch(classOf[FxApp], args:_*)
}
}

Die Applikation kann nun gestartet werden und zeigt ein leeres JavaFX(!) Fenster. Das ist schön ung gut, aber wir wollen das Fenster noch mit leben füllen und dann war da auch noch die Sache mit FXML, nicht oder?

Nun, bevor wir uns mit FXML beschäftigen können, müssen wir aber einen Controller anlegen, den wir dann in der View nutzen können. Eine einfache Controller-Implementierung ist der Java-Version sehr ähnlich.

// File: eu.derprogrammierer.jfx.view.MainViewController.scala
package eu.derprogrammierer.jfx.view

import javafx.fxml.FXML
import javafx.scene.control.{Label}

class MainViewController {
@FXML private var testLabel: Label = _

def initialize(): Unit = {
testLabel.setText("Initialized!")
}

def handleTest(): Unit = {
testLabel.setText("Test successful!")
}
}

Wie man sieht, wird die View ein Label "testLabel" beinhalten. Dessen Text wird in der initialize()-Funktion verändert. Zudem gibt es den Handler handleTest(), der den Text erneut verändert. Dieser soll durch einen Button ausgelöst werden.

IntelliJ IDEA meint man könnte testLabel zu einem val umwandeln, allerdings funktioniert die JavaFX Injection dann nicht mehr. Zudem sei an dieser Stelle erwähnt, dass es in Scala nicht möglich ist, das @FXML Attribut für Felder wegzulassen, wenn man sie public macht. Das ist der Fall, weil Scala für ein public Field in Wirklichkeit Getter- und Setter-Methoden implementiert, die auf ein privates Feld zugreifen. Dieser für normale Programmierung unmerksame Unterschied hat bei Reflection, wie sie für FMXL genutzt wird, große Auswirkungen!

Main.fxml in SceneBuilder

Da unser Controller jetzt fertig ist, können wir die View erstellen. Hierzu wird im Package "view" eine Datei "MainView.fxml" angelegt. Im SceneBuilder wird eine AnchorPane in das leere Fenster gezogen. Mittig in diese werden nun ein Button und ein Label platziert.

Action handleTestLabel fxid

Der Button erhält die Action "handleTest", das Label wird mit der fx:id "testLabel" versehen.

MainView Controller ClassAußerdem möchte unsere View noch einen Controller haben. Wir geben den gesamten Namen unseres Controllers in das Feld Controller class mit.

Was jetzt noch bleibt, ist das Laden der View in unserer FxApp-Klasse. Dazu wird die start()-Funktion wie folgt angepasst:

override def start(primaryStage: Stage): Unit = {
primaryStage.setTitle("Fx App")

val fxmlLoader = new FXMLLoader(getClass.getResource("view/MainView.fxml"))
val mainViewRoot: Parent = fxmlLoader.load()

val scene = new Scene(mainViewRoot)
primaryStage.setScene(scene)
primaryStage.show() }

Dazu sind weitere Imports erforderlich:

import javafx.fxml.FXMLLoader
import javafx.scene.{Scene, Parent}

Wird das Programm jetzt gestartet, sieht man, dass das Label korrekt initialisiert wurde. Ein Klick auf den Button ändert den Text des Labels auf "Test successful!". Wahrlich ein erfolgreicher Test!

Wir haben nun eine vollwertige JavaScalaFX Applikation. Hieraus eine "Hello World!"-App zu machen, überlasse ich als Übung dem Leser.