
Kevin Erath
Geschäftsführer
Veröffentlicht am
14. November 2020

Vor kurzem habe ich von einem Plan gesprochen, welcher einem bei der Umsetzung helfen soll und damit zu einer besseren Softwarequalität führt. Aber wie kann dieser, für ein Programm wie RomanNumerals, aussehen? Ich könnte es mit einem Flow-Chart modellieren, das wäre mir aber viel zu detailliert für einen groben Plan. Auch die objektorientierte Analyse (z. B. mit UML) scheint mir für dieses Problem nicht passend zu sein.
Vielleicht versuchen wir es eine Stufe einfacher. Wir könnten doch das Umwandeln der römischen Zahlen erst mal in einzelne Teilaufgaben zerlegen. Das wären aus meiner Sicht die folgenden Teilaufgaben.
- Römische Zahl in Ziffern zerlegen (
"XIV"
→'X', 'I', 'V'
) - Römische Ziffer in dezimale Zahl umwandeln (
'X', 'I', 'V'
→10, 1, 5
) - Subtraktionsregel anwenden (
10, 1, 5
→10, -1, 5
) - Aufsummieren (
10, -1, 5
→14
)
Wir haben also eine Kette von Schritten. Die Umsetzung der einzelnen Schritte kann nun getrennt nacheinander erfolgen. Man kann sich somit auf eine Aufgabe nach der anderen fokussieren, was letztendlich auch für Softwarequalität förderlich ist. Erst zerlegen wir die römische Zahl in Ziffern, dann wandeln wir die Ziffern in ihren Dezimalwert um. Anschließend kommt die Subtraktionsregel, welche in diesem Fall der komplizierte Teil ist. Dieser lässt sich durch Negieren der Werte, falls die folgende Zahl größer ist, dennoch einfach implementieren. Das aufsummieren ist dank LINQ als Einzeiler wieder schnell umgesetzt.
Das ganze könnte dann so modelliert werden:

Entwurf römische Zahlen konvertieren
Wenn wir jetzt jeden dieser Schritte als eigene Methode implementieren und die Verkettung der Aufrufe dann in der eigentlichen Umwandlungsmethode implementieren, entsteht folgender Code:
using System.Collections.Generic;
using System.Linq;
internal static class FromRomanNumerals
{
public static int Convert(string romanNumber)
{
var romanNumerals = SplitRomanNumerals(romanNumber);
var decimals = ConvertToDecimal(romanNumerals);
var negatedDecimals = NegateWhenLarger(decimals);
return Sum(negatedDecimals);
}
private static char[] SplitRomanNumerals(string romanNumber)
{
return romanNumber.ToCharArray();
}
private static int[] ConvertToDecimal(IEnumerable<char> romanNumerals)
{
var mapping = new Dictionary<char, int> {{'I', 1}, {'V', 5},
{'X', 10}, {'L', 50}, {'C', 100},
{'D', 500}, {'M', 1000}};
return romanNumerals.Select(x => mapping[x]).ToArray();
}
private static int[] NegateWhenLarger(int[] decimals)
{
var result = new int[decimals.Length];
for (var i = 0; i < decimals.Length; i++)
{
if (i < decimals.Length - 1 && decimals[i] < decimals[i + 1])
{
result[i] = -decimals[i];
}
else
{
result[i] = decimals[i];
}
}
return result;
}
private static int Sum(int[] decimals)
{
return decimals.Sum();
}
}
Die hier gezeigte Lösung ist zwar größer als meine erste Lösung. Aber dafür sind die einzelnen Aspekte sehr schön voneinander getrennt. Die einzelnen Methoden, mit Ausnahme der Methode Convert()
, sind komplett unabhängig voneinander. Jede nimmt etwas entgegen, macht damit etwas und liefert ein Ergebnis zurück. Wer davor war oder danach kommt, interessiert nicht. Lediglich die Hauptmethode Convert()
enthält Abhängigkeiten. Diese ist aber so einfach aufgebaut, dass man durch kurzes Anschauen bereits den Ablauf sieht. Jetzt sieht man auch, wieso ich nicht immer TDD einsetze. Das Zerlegen, Umwandeln oder Aufsummieren bekomme ich für dieses Problem auch ohne TDD implementiert. Die Umsetzung der Subtraktionsregel hingegen kann durchaus von TDD profitieren.
Ich habe hier eine kleine Entwurfsmethodik gezeigt, die auch dafür sorgt, dass der Code modularer wird. Durch diese Zerlegung muss nicht alles am Stück umgesetzt werden, was am Ende auch in einer besseren Softwarequalität mündet. Wie bin ich darauf gekommen? Flow Design ist hier das Stichwort. Die hier gezeigte Lösung kann auch auf GitHub heruntergeladen werden.
Anmerkung: Bei diesem Text handelt es sich um einen überarbeiteten Repost eines alten Blog-Artikels aus 2015 von mir.

Hier schreibt
Kevin Erath
Als Mitbegründer und Geschäftsführer von pep.digital verbringe ich zwar nicht mehr jeden Tag ausschließlich damit, coole Lösungen für unsere Kunden zu realisieren. Trotzdem finde ich immer wieder die Zeit, mich auch mal tiefer in die Technik einzutauchen und meine Erkenntnisse hier im Blog zu teilen. Und ehrlich gesagt, das Unternehmen und unsere tollen Mitarbeiter:innen weiterzuentwickeln, macht mir mindestens genauso viel Spaß.
Quellen
Weitere interessante Artikel
Wir möchten hier nicht nur über Neuigkeiten aus dem Unternehmen berichten, sondern auch das Wissen und die Erfahrung unserer Experten teilen.

Karin erzählt über ihr erstes Jahr bei pep.digital
Karin ist seit 2022 bei pep.digital und arbeitet aktuell in der Rolle als Product Owner und Requirements Engineer bei uns. Sie hat sich Stück für Stück in die Welt der agilen Softwareentwicklung eingearbeitet und freut sich, wenn am Ende eine Software steht, die dem Kunden wirklich weiterhilft. Im Artikel erzählt sie mehr über ihre Aufgaben, ihren Weg zu pep.digital und worauf sie in ihrem Werdegang besonders stolz ist.
pep.digital

Zwei Köpfe, ein Ziel: Erfolgreiche Projektumsetzung mit Proxy Product Owner
In diesem Artikel zeigen wir, wie ein Proxy Product Owner die Arbeit in Softwareprojekten erleichtern kann. Ein Proxy PO unterstützt den Product Owner, indem er Aufgaben übernimmt, für die der Product Owner oft nicht die Zeit oder Erfahrung hat. Dazu gehört die Ermittlung von Kundenbedürfnissen, das Erstellen von User Stories, die Planung der nächsten Schritte und die Klärung fachlicher Fragen. So werden Engpässe vermieden und das Entwicklungsteam bleibt produktiv.

Karin Neubauer
Business Analystin