für Entwickler Altlasten im Griff · ohne Vorbehalte auf die Aufgabe einzulassen. Zwar ist eine...
Transcript of für Entwickler Altlasten im Griff · ohne Vorbehalte auf die Aufgabe einzulassen. Zwar ist eine...
Migrieren:
AngularJS-Anwendungen auf Angular 2 portierenMainframe-Programme auf Linux betreiben
Von Swift 2 auf Swift 3 umsteigen
Aktualisieren:
Legacy-Webanwendungen modernisieren SaaS: Vom Desktop in die Cloud
Erhalten:
Crashkurs COBOLSoftware- MaintenanceArchitektur-archäologie
Verstehen:
Strukturiert entwickelnSoftwaredokumentation pflegen
Altlasten im GriffÜber 7 Gigabyte
für EntwicklerSponsored Software:Intel Parallel Studio XE2017 (Testversion fürWindows)
Multimedia: Videos, Podcasts und Literatur
Tools, Sprachen und mehr:Eclipse, GCC, GNU COBOL, Spring Boot, SonarQube etc.
Mit
DVD
Januar/Februar
2017
Legacy-Know-how:
Berichte aus dem Projektalltag
iX Developer 2017 – Legacy-Code 3
EDITORIAL
In der schönen neuen Welt der Frameworks und Tools gibt eskaum eine Woche, in der Entwickler nicht die Gelegenheit be-kommen, etwas Neues auszuprobieren. Sei es die aktualisierteVersion eines bekannten Angebots oder ein komplett neuesOpen-Source-Projekt – den Abenteuerlustigen stehen alle Türen offen und die Augen beginnen zu leuchten, wenn sie danach von ihren Experimenten berichten können.
Im Unternehmensalltag sind allerdings schon massenhaft Res-sourcen in alte Projekte geflossen und im besten Fall hat sichüber die Jahre ein fester Kundenstamm herauskristallisiert, denman nicht vor den Kopf stoßen sollte. Ist die Situation alsonicht so verfahren, dass nur noch Neuimplementierung hilft,muss sich die Mehrzahl der Programmierer mit Altlasten, ihrerPflege und Erweiterungsoptionen beschäftigen. Dabei muss essich nicht einmal immer um Legacy-Systeme à la Mainframehandeln: Durch die kürzer werdenden Release-Zyklen, wech-selnde Entwicklungstrends und hohe Personalfluktuation kannein Projekt schon nach einer vergleichsweise kurzen Lebens-dauer so verbaut sein, dass sich ein Einstieg nur schwer findenlässt, wie besonders Projektneulinge oft schmerzlich erfahrenmüssen. In solchen Fällen besteht die einzige Lösung darin, indie Rolle des Software-Archäologen zu schlüpfen und sich ohne Vorbehalte auf die Aufgabe einzulassen.
Zwar ist eine solche Arbeit nicht ganz so glamourös, wie ständig das nächste große Ding auf den Markt zu werfen, allerdings hat auch sie ihren Reiz, wie NASA-Ingenieurin Sun Kang Matsumoto in diesem Heft zu berichten weiß. So kommt man beispielsweise viel mit Kollegen und Nutzernin Kontakt, kann helfen, für sie wichtige Arbeitsmaterialienbrauchbar zu halten, und darüber hinaus an den eigenen detektivischen Fähigkeiten arbeiten. Wer sein Ego also hintersich lassen kann und sich nicht scheut, in das Dickicht ausSpaghetti-Code hinter der Dokumentationswüste einzutauchen,findet im vorliegenden Heft Tipps und Inspiration für den Alltag und trifft auf den einen oder anderen Kameraden.
Und falls es hilft: Lässt man sich nicht von dem großen „Neu“-Sticker ablenken, fällt auch bei den spannendsten aktuellenFrameworks auf, dass vor allem Performance-Optimierungen,Umstrukturierungen und Maintenance die Release Notes be-stimmen. Wie in einem Legacy-Projekt, oder?
JULIA SCHMIDT
Für Mit-Anpacker
Gut erhaltenAuch bei der Softwareentwicklung ist NachhaltigkeitTrumpf. Um aber erhalten statt wegwerfen zu können,sollte man sich ein paar Kenntnisse der Altvorderenaneignen – etwa COBOL, Fortran und JCL.
ab Seite 36
VerstehenStilSoftware vorausschauend entwickeln 8
ArchitekturAnwendungen strukturiert aufbauen 14
Dependency ManagementAbhängigkeiten in Legacy-Systemen verwaltbar machen 18
DokumentationManuels aktuell und gebrauchstauglich halten 24
LiteraturLesenswertes vor und während der Migration 28
SoftwarequalitätTechnische Schulden in Legacy-Code finden und bewerten 30
ErhaltenInterviewIm Gespräch mit Sun Kang Matsumoto, Mitarbeiterin an NASAs Voyager-Projekt 36
MaintenanceSoftwarequalität langfristig sichern 38
VorbereitungSoftwareprojekte auf strukturelle Umgestaltung vorbereiten 42
ArchitekturarchäologieAltsysteme durch schrittweise Untersuchungen verstehen 48
COBOL-KursEinführung in die Common Business Oriented Language 53
FortranEin genauer Blick auf den Formula Translator 60
Job Control LanguageBatch-Jobs in JCL programmieren 68
AktualisierenRefactoringStrategien zum langfristigen Verbessern von Quelltextstrukturen 76
TestenTestautomatisierung in Legacy-Systemen 84
WebentwicklungModernisierung von Legacy-Webapplikationen 88
AnwendungsoberflächeGUI-Redesign nach Continuous-Delivery-Prinzipien 92
4 iX Developer 2017 – Legacy-Code
INHALT | IX DEVELOPER
Besser verstehenLegacy ist nicht
gleich legacy.Manche Altsoft-ware ist vor allem
lästig, andere wert-volles Erbe, das
leicht gepflegt werdenkann. Tipps für den Weg von Garbage
zu Heritage.
ab Seite 8
Gezielt aktualisierenFrüher war zwar nicht alles schlechter, aber in denletzten Jahren haben sich Verfahrensweisen und Technologien der Softwareentwicklung teils signifikantverändert. Darum lohnt es sich, bei der Migration auch gleich zu aktualisieren, aber an den richtigenStellen.
ab Seite 76
Klug migrierenAuch in diesem Jahrtausend geschriebene Softwarekann überholt sein und muss sich den Stempel„Legacy“ gefallen lassen. Vor allem geht es dabei um überholte Versionen von Programmiersprachenoder Frameworks – der Schritt von Perl 5 auf Perl 6oder Angular auf Angular 2 kann mit Fug und Rechtals Migrationsprojekt bezeichnet werden.
ab Seite 104
SaaSVom Desktop in die Cloud 96
ModularitätMicroservices – gerade in Legacy-Szenarien geeignet 100
MigrierenMigrationStrategien im Vergleich 104
SwiftUpdate für den Objective-C-Nachfolger 110
MainframeGroßrechnerprogramme mit TXSeries auf Linux betreiben 114
PerlPerl-5-Code auf Perl 6 umstellen 120
Framework-UpdateWebanwendungen von AngularJS nach Angular 2 migrieren 122
EinsetzenErfahrungsberichtMigration eines Legacy-Systems 132
PraxisMigration hochverfügbarer Applikationsserver im Live-Betrieb 136
Legacy-IntegrationAlte und neue Systeme kombiniert einsetzen 141
SonstigesEditorial 3
DVD-Inhalt 6
Impressum 6
Inserentenverzeichnis 6
iX Developer 2017 – Legacy-Code 5
Artikel mit Verweisen ins Webenthalten am Ende einen Hinweis
darauf, dass diese Webadressen auf dem Server der iX abrufbar sind.Dazu gibt man den iX-Link in der URL-Zeile des Browsers ein. Dannkann man auch die längsten Links bequem mit einem Klick ansteuern.Alternativ steht oben rechts auf der iX-Homepage ein Eingabefeld zur Verfügung.
Alle Links: www.ix.de/ix1617SSS
Hinweis für Käufer der digitalen Versionen• PDF- und iPad-Version: In der iX-App finden Sie einen
Button zum Download des DVD-Images.• PDF-E-Book: Folgen Sie im Browser der unter „Alle Links“ angegebenen URL.
Alle Links: www.ix.de/ix1617006 x
6 iX Developer 2017 – Legacy-Code
SERVICE | DVD-INHALT/IMPRESSUM/INSERENTEN
Auf der Heft-DVDSponsored Software: Intel Parallel Studio XE 2017Das Softwarepaket enthält branchenführende C++- und Fortran-Compiler, Bibliotheken, High-Performance-Python-Pakete, Vektorisie-rungs- und Threading-Advisor, Speicher- und Threading-Debugger, Performance-Profiler und vieles mehr, was zum Erstellen schnellen, verlässlichen und parallelen Codes nötig ist.
Die nicht funktionsbeschränkte Test versionkönnen Sie 30 Tage lang nutzen.
Tools, Sprachen und mehrEclipse Neon, Babel,
Modernizr, Perlito, Sonar-Qube, Spring Boot, GCC,GNU COBOL
Konferenz-VideosKatie Roberts: Dismantling Monolithsusing Microservices and
Continuous Delivery atthe BBC (enterJS 2016)
Robin Böhm: AngularJS-Projekte sicher auf Angular 2
migrieren (enterJS 2016)Matt Zeunert: Effective JavaScript
Debugging with Chrome’s Developer Tools (enterJS 2016)
Marija Selakovic: Performance Issues and Optimizations in JavaScript (enterJS 2016)Bastian Spanneberg: Deployments automatisieren und testen mit Ansible und Serverspec (Continuous Lifecycle 2015)Bjørn Stachmann, René Preißel: Branch-Modelle in Git (Continuous Lifecycle 2015)Dave Farley: Acceptance Testing for Continuous Delivery (Continuous Lifecycle 2015)Dr. Thomas Schank, Max Albrecht: Fast and Resilient Integration Testing (Continuous Lifecycle 2015)Thorsten Maier: Code Quality in Practice (Continuous Lifecycle 2015)
SoftwareArchitekTOUR-Podcasts• Modularisierte Architektur für große Systeme• Architekturanalyse und -bewertung• Softwareanalyse mit Graphendatenbanken• Microservices und Self-contained Systems• Architektur-Refactoring• Architekturdokumentation• Architektur-Reviews
LiteraturLanglebige Software-Architekturen – Technische Schulden analysie-ren, begrenzen und abbauen (Buchauszug)Carola Lilienthal zeigt in ihrem Buch, welche Fehler in Softwarepro -jekten bei der Umsetzung der Architektur vermieden werden sollten undwelche Prinzipien einzuhalten sind, um langlebige Architekturen zu entwerfen oder bei bestehenden Systemen zu langlebigen Architekturenzu gelangen.
MAGAZIN FÜR PROFESSIONELLE INFORMATIONSTECHNIK
iX Developer Januar/Februar 2017 – Altlasten im GriffPostfach 61 04 07, 30604 Hannover; Karl-Wiechert-Allee 10, 30625 Hannover
Redaktion: Telefon: 0511 5352-387, Fax: 0511 5352-361, E-Mail: [email protected]
Chefredakteur: Jürgen Seeger (js) -386
Konzeption und redaktionelle Leitung:Julia Schmidt (jul) -378, E-Mail: [email protected]
Autoren dieser Ausgabe: Martin Aschoff, Jörg Basedow, Thorsten Behrens, Sebastian Eggers, Dirk Ehms, Wolfgang Engelhaupt, Rupert Friedrich, Gertrud Grünwied, Stephan Kaps, Christian Kirsch, Stefan Lieser, Carola Lilienthal, Frank Mahler, Frank Müller, Oliver Müller, Jens Ortwerth, Lisa-Leyla Öztürkoglu, Thorsten Rinne, Golo Roden, Thomas Ronzon, Julia Schmidt
Abbildungen: © Can Stock Photo Inc.: shut0 (S. 100), 3355m (S. 76), 5xinc (S. 48), Arogant (S. 114), Grushin (S. 122), Icefront (S. 92), Lagui (S. 68), Mediagram (S. 18, 104, 132), NataKu (S. 28) Penywise (S. 103), Photology75 (S. 14), RuslanOmega (S. 88), StephanieFrey (S. 60), bennyartist (S. 30, 110), cherijon (S. 53), christi180884 (S. 42), czalewski (S. 136), gavran333 (S. 38), kornienko (S. 24), kostiuchenko (S. 84), kuppa (S. 141), serkucher (S. 8), westergren (S. 120)
Redaktionsassistenz: Michael Mentzel (mm) -153, Carmen Lehmann (cle) -387
Korrektorat: Kathleen Tiede, Hinstorff Verlag, Rostock
Layout und Satz: Enrico Eisert, Matthias Timm, Hinstorff Verlag, Rostock
Titelidee: iX
Verlag: Heise Medien GmbH & Co. KG, Postfach 61 04 07, 30604 Hannover; Karl-Wiechert-Allee 10, 30625 Hannover; Telefon: 0511 5352-0, Telefax: 0511 5352-129
Geschäftsführer: Ansgar Heise, Dr. Alfons Schräder
Mitglied der Geschäftsleitung: Beate Gerold
Verlagsleiter: Dr. Alfons Schräder
Anzeigenleitung: Michael Hanke (-167), E-Mail: [email protected], www.heise.de/mediadaten/ix
Druck: Dierichs Druck + Media GmbH, Kassel
DVD-Herstellungsleitung: Klaus Ditze
Verantwortlich: Textteil: Jürgen Seeger; Anzeigenteil: Michael Hanke
iX Developer Januar/Februar 2017 – Altlasten im Griff: Einzelpreis € 12,90, Österreich € 14,20, Schweiz CHF 25,80, BeNeLux: € 14,80, Italien: € 16,80
Eine Haftung für die Richtigkeit der Veröffentlichungen kann trotz sorgfältiger Prüfung durch die Redaktion vom Herausgeber nicht übernommen werden. Kein Teil dieser Publikation darf ohne ausdrückliche schriftliche Genehmigung des Verlages verbreitet werden; das schließt ausdrücklich auch die Veröffentlichung auf Websites ein.
Printed in Germany
© Copyright by Heise Medien GmbH & Co. KG
Die Inserentenbytec www.bytec.de 148
dpunkt www.dpunkt.de 11
h.o.-COMPUTER Intel GmbH www.hocomputer.de 2
Rheinwerk Verlag www.rheinwerk-verlag.de 45
Event-AnzeigenMakerCon Make, heise Developer, dpunkt.Verlag 63
Developer Konferenzen heise Developer, iX, dpunkt.Verlag 67
JavaLand JavaLand GmbH, heise Developer 79
Kerberos – LDAP – Active Directory iX, heise Events 91
Connected Living ConnFerence 2017 Connected Living e.V., heise Events 109
Social Engineering iX, heise Events 115
Software Quality Days Software Quality Lab, iX 129
Open Source Monitoring mit Icinga 2, LibreOffice iX, heise Events 147
Die hier abgedruckten Seitenzahlen sind nicht verbindlich. Redaktionelle Gründe könnenÄnderungen erforderlich machen.
iX Developer 2017 – Legacy-Code 7
VerstehenLegacy ist nicht gleich legacy. Manche Altsoftware ist vor allem lästig, andere wertvolles Erbe, das leicht gepflegt werden kann. Tipps für den Weg von Garbage zu Heritage.
Software vorausschauend entwickeln 8
Anwendungen strukturiert aufbauen 14
Abhängigkeiten in Legacy-Systemen verwaltbar machen 18
Softwaredokumentation aktuell und gebrauchstauglich halten 24
Lesenswertes vor und während der Migration 28
Technische Schulden in Legacy-Code finden und bewerten 30
E in geflügeltes Wort unter Entwicklern besagt, dass schwerverständlicher Code ein erstrebenswertes Ziel sei – schließ-lich werde so deutlich, wie schwer es war, ihn zu schrei-
ben. Da Entwickler allerdings häufig in die Lage kommen, mit„fremdem“ Code arbeiten zu müssen, sollte das Streben nachhoher Verständlichkeit im eigenen Interesse liegen. Der US-ame-rikanische Informatiker Donald E. Knuth prägte in den 1980er-Jahren in dem Zusammenhang das Konzept des literarischenProgrammierensˇ[a].
Allzu oft scheitert der Vorsatz an (zumeist zeitlichen) Vorga-ben des Kunden oder des Vorgesetzten. Die Haltung, man werdenicht für schönen, sondern lediglich für funktionierenden Codebezahlt, ist weitverbreitet. Hilfreich wäre daher eine Taktik, mitder Entwickler erst gar nicht in eine Situation geraten, in der sieum zusätzliche Zeit zum Aufräumen bitten müssen.
Die Aufgabenstellung verstehen
Zunächst gilt es, das eigentliche Problem zu verstehen und zudessen Kern vorzudringen. Als Beispiel für den vorliegendenArtikel soll die Validierung einer IBAN dienen. Vermutlich ha-ben Entwickler beim Lesen der Aufgabe eine ungefähre Vorstel-lung, was zu programmieren ist. Bei genauerer Überlegung wirdjedoch klar, dass die konkreten Anforderungen gänzlich unklarsind. Vermutlich ist eine Funktion validateIban gesucht, die eineIBAN nach dem Erhalt auf Korrektheit überprüft. Allerdings er-geben sich selbst in solch einem vergleichsweise einfachen Fallzahlreiche Fragen:
• Genügt es, das Format zu kontrollieren oder ist die IBAN auchinhaltlich zu prüfen?
• Ist die Existenz der eingegeben Nummer zu klären? Eine IBANkann schließlich ein gültiges Format aufweisen und formal kor-rekt, aber nicht vergeben sein.
• Was erwartet das Programm als Ergebnis der Funktion? Sollder Rückgabewert vom Typ Boolean sein oder gibt die Funk-tion void zurück und wirft im Fehlerfall eine Ausnahme?
• Wie steht es um eine automatische Korrektur? Falls sich bei-spielsweise Zahlendreher eindeutig erkennen ließen, könntedie Funktion sie nicht automatisch korrigieren?
Die Antworten auf die Fragen wirken sich nicht nur auf die Im-plementierung, sondern auch auf die Namensgebung aus: Gibtdie Funktion beispielsweise eine korrigierte IBAN zurück, passtder Name validateIban nicht, da er nahelegt, dass ausschließlicheine Überprüfung stattfindet und die Funktion deren Ergebniszurückgibt.
Um herauszufinden, was überhaupt gewünscht ist, bietet essich an, zunächst so zu tun, als ob es die gewünschte Funktionbereits gäbe, und sich zu überlegen, wie Anwender sie benutzen.Entwickler sollten sich also in die Rolle desjenigen versetzen, derdie Funktion zukünftig verwenden muss und mit dem API-Designbeginnen statt mit der Implementierung. Den Anwender in denVordergrund zu rücken, weist einige Vorteile auf. Allen voransteht, dass das Ergebnis möglichst intuitiv benutzbar und die APIspäter nicht an die Implementierung anzupassen ist. Das Vorgehenist unter der Bezeichnung Client first Design bekannt und wirdunter anderem von Krzysztof Cwalina und Brad Abrams in ihremBuch „Framework Design Guidelines“ beschrieben [1].
8 iX Developer 2017 – Legacy-Code
VERSTEHEN | STIL
Software vorausschauend entwickeln
GebrauchsprosaGolo Roden
Wartet man lang genug, wird jeder Code Legacy. Folglichstellt sich die Frage, wie er sich
so schreiben lässt, dass er langfristigwartbar bleibt. Einer der wichtigsten Faktoren ist die Verständlichkeit: Fehltsie, lässt sich die Funktion schwer nach-vollziehen, und eine Weiterentwicklunggestaltet sich schwierig.
Schnell ersichtlich ist vermutlich, dass die Eingabe als Zei-chenkette vorliegt, wobei es unterschiedliche gültige Formategibt. Beispielsweise kann „DE78 6001 0070 0946 4157 06“genauso gelten wie „de78600100700946415706“. Damit trägtman unterschiedlichen Formatierungen Rechnung, beispiels-weise in E-Mails und im Briefverkehr. Praktisch wäre es aller-dings, die IBAN in einem ersten Schritt in eine einheitlicheForm zu bringen. Naheliegend ist die Verwendung von Groß-buchstaben und das Entfernen jeglicher Leerstellen. Das Ergeb-nis der Funktion sollte für die beiden genannten Fälle daher„DE78600100700946415706“ lauten.
Passende Namen finden
Durch die Entscheidung, eine einheitlich formatierte IBAN zu-rückzugeben, entfällt allerdings die Option, einen logischenRückgabewert zu verwenden, der angibt, ob die IBAN gültig istoder nicht. Da magische Werte wie eine leere Zeichenkette zuvermeiden sind, bleibt für den Hinweis auf ungültige Eingabenlediglich der Einsatz von Ausnahmen [b]. Das bedeutet aberauch, dass der Name validateIban für die Funktion nicht mehrpassend ist, da sie nun nicht länger zum reinen Überprüfendient.
Einen ersten Hinweis auf einen Namen liefert das Anliegen,die IBAN in ein einheitliches Format zu bringen. Vorausgesetzt,dass der Großteil der Funktionsaufrufe mit gültigen Werten statt-findet, erscheint die Validierung sogar nur noch als Nebeneffekt.Der Fokus der Funktion verschiebt sich, weshalb der Name an-zupassen ist. Dass das Vorgehen durchaus berechtigt ist, lässt sichdaran ablesen, dass die Funktion Fehler als Ausnahmen behan-delt. Das sind sie auch unter der Annahme, dass der Großteil derEingaben gültig sein wird.
Als Funktionsname bietet sich daher etwas wie ibanUniforman. Allerdings lässt sich an der Stelle ein Antipattern ablesen:Da eine Funktion eine Tätigkeit darstellt, sollte sie mit einemVerb benannt sein. Einheitliche Formatierung bekommt häufigdie Bezeichnung „kanonisch“, weshalb sich der Name canoni-calizeIban anbietet. Er gibt deutlich an, welchen Zweck dieFunktion erfüllt: Eine IBAN in eine kanonische Form zu brin-gen, wobei klar ist, dass das für ungültige Eingaben nicht funk-tionieren kann, was zu Ausnahmen führt.
Mancher mag einwenden, dass das alles vorgegeben seinmüsste, schließlich sind Produktverantwortliche oder Architek-ten mit Derartigem betraut. In vielen Fällen steht aber niemandzur Verfügung, der jegliche Entscheidung auf der Detailebenetreffen kann. Insofern müssen Entwickler wohl oder übel selbstnachdenken.
Code erzählt eine Geschichte
1986 stellte Jon Bentley in „Communications of the ACM“ dieFrage, wann man das letzte Mal einen gemütlichen Abend da-mit verbracht habe, ein gutes Stück Code zu lesen [c]. Vermutlichhaben wenige Entwickler jemals ein solches Vergnügen genos-sen. Allerdings wirft die Frage einen interessanten Gedankenauf: Wenn sich die Funktionsweise von Code von einem Men-schen erklären lässt, müsste sich Code nicht selbsterklärend for-mulieren lassen? Schließlich handelt es sich in beiden Fällen umSprache, wenn im zweiten Fall auch um eine abstrahierte Form.
Abwegig ist die Idee nicht, denn das Prinzip des Single Levelof Abstraction fordert letztlich nicht anderes: Code, der sich ineiner Funktion befindet, sollte einen gemeinsamen fachlichen
Detailgrad aufweisen. Es wäre nach dem Prinzip falsch, in einerFunktion sowohl auf ein Bitfeld zuzugreifen als auch die Aus-gabe für den Anwender aufzubereiten. Beides bewegt sich aufunterschiedlichen Abstraktionsniveaus.
Außerdem würde das Vorgehen das Single Responsibility Prin-ciple verletzen, das besagt, dass sich eine Codeeinheit lediglichum eine Aufgabe kümmern soll. Das Manipulieren eines Bitfeldsund das Aufbereiten einer Bildschirmausgabe stehen (höchstwahr-scheinlich) in keinem fachlichen Zusammenhang und sind daherzu trennen.
Bis jetzt ist die Signatur der Funktion bekannt. Sie erwarteteine Zeichenkette, gibt eine zweite zurück und löst gegebenen-falls Ausnahmen aus. Versucht man, das Grundgerüst als Java -Script-Code aufzuschreiben, ergibt sich direkt das nächste Pro-blem mit dem Parameternamen:
const canonicalizeIban = function (iban) {// ...
};
Nennt man ihn iban, impliziert das, dass es sich um eine (vali-dierte) IBAN handelt. Theoretisch könnte der Parameter aller-dings eine ungültige Zeichenkette wie foobar enthalten. AlsAusweg bietet sich der Name text an, der allerdings zu allge-mein ist und lediglich eine andere Bezeichnung für string dar-stellt. Jegliche fachliche Information ginge verloren. Besser wä-re an der Stelle potentialIban, da der Name impliziert, dass essich wahrscheinlich um eine IBAN handelt. Eine gangbare Al-ternative könnte ibanCandidate sein. Damit sieht der Funktions-rahmen wie folgt aus:
const canonicalizeIban = function (potentialIban) {// ...
};
Denkt man nun über den Funktionsrumpf nach, könnte man di-rekt nachschlagen, wie der Algorithmus zum Validieren einerIBAN funktioniert [d]. Im Zuge dessen würde man erfahren,dass eine IBAN eine Prüfsumme enthält, die sich zum Validie-ren berechnen lässt [e]. Schreibt man den Algorithmus danachdirekt in einem Rutsch auf, entsteht Code wie in einem entspre-chenden Beispiel auf StackOverflow: fachlich vermutlich kor-rekt, aber unverständlich und nicht ohne größere Mühe nach-vollziehbar [f].
Erinnert man sich hingegen an den Wunsch, dass Code eineGeschichte erzählen soll, hilft das folgende Gedankenspiel: An-genommen, ein Bekannter ruft an und fragt, woran eine gültigeIBAN zu erkennen sei. In dem Fall wäre die erste Frage vermut-lich, ob die fraglich Zeichenkombination überhaupt wie eineIBAN aussieht. Erst danach lohnt es sich, überhaupt im Detailüber das Format und die Prüfsumme nachzudenken. Eventuell
iX Developer 2017 – Legacy-Code 9
const canonicalizeIban = function (potentialIban) {if (!potentialIban) {throw new Error('Potential IBAN is missing.');
}
const canonicalizedIban = canonicalize(potentialIban);
if (!looksLikeIban(canonicalizedIban)) {throw new Error('IBAN looks wrong.');
}
if (!isValidIban(canonicalizedIban)) {throw new Error('IBAN is invalid.');
}
return canonicalizedIban;};
Listing 1: canonicalizeIban
ist es empfehlenswert, die Eingabe zunächst in ihre kanonischeForm zu bringen. Überträgt man die Überlegung auf die zuschreibende Funktion canonicalizeIban, erhält man den in Lis-tingˇ1 gezeigten Code.
Verständlicher Code
Obwohl der Code in der Form nicht läuft, ist er vom fachlichenStandpunkt aus durchaus verständlich. Zudem ist er kurz undkompakt und enthält keine Überraschungen. Alternativ hätteman den Code wie in Listingˇ2 schreiben können.
Auf den ersten Blick fällt unter anderem auf, dass der Codedurch Abkürzungen deutlich schlechter lesbar ist. So geben etwadie Variablennamen pot und can ohne Kontext wenig Aufschlussüber ihre Funktionen. Zwar erfordern die ausgeschriebenen Va-rianten mehr Schreibarbeit, Lesern fällt die Übersetzung aller-dings leichter. Darüber hinaus verschlechtert die mehrfache Ver-schachtelung die Lesbarkeit.
Um das zu vermeiden, ist es hilfreich, auf else zu verzichtenund statt des Positivfalls die Negativfälle zu testen. Zudem hel-fen Leerzeilen, die Struktur des Codes besser zu erkennen, wieder direkte Vergleich der beiden Listings zeigt.
Aus all dem lassen sich einige Regeln ableiten, die größten-teils in „Framework Design Guidelines“ enthalten sind. Dazuzählen das Verbot von Abkürzungen, der Fokus auf eine mög-lichst geringe Einrückung und kurze, in sich abgeschlosseneEinheiten mit wenig Komplexität.
Als nächstes ist die Funktion canonicalize zu schreiben. Inihr sind alle Zeichen außer den alphanumerischen zu entfernenund der Ausdruck in Großbuchstaben umzuwandeln. Den An-forderungen nach scheint der Einsatz eines regulären Ausdruckseine gute Wahl (Listingˇ3).
Obwohl der Code technisch korrekt ist, geht durch die direkteImplementierung des regulären Ausdrucks die Information überdie Intention verloren, alle Zeichen außer die alphanumerischenzu entfernen. Das führt dazu, dass andere Entwickler beziehungs-weise der Verfasser selbst nach einigen Monaten nur versuchenkönnen, aus der Implementierung auf die Intention zu schließen.Es lässt sich nicht mehr überprüfen, ob die ursprüngliche fach-liche Absicht technisch korrekt implementiert wurde. Lagert mandie Aufgabe jedoch in eine eigene Funktion aus, wird der Codenicht nur lesbarer, sondern es bleibt auch die Intention erhalten(Listingˇ4).
Modulare Umsetzung
Die Funktion keepAlphanumeric lässt sich nun vergleichweiseeinfach implementieren:
const keepAlphanumeric = function (text) {return text.replace(/[^A-Za-z0-9]/g, ,');
};
An der Stelle empfiehlt es sich, den Parameternamen von po-tentialIban auf text zu ändern, da die Funktion so allgemein undgenerisch ist, dass sie für jeden beliebigen Text funktioniert. Sieließe sich sogar in ein kleines eigenständiges Modul auslagern.Auf den ersten Blick mag das übertrieben erscheinen, allerdingserspart man künftigen Bearbeitern dadurch den Aufwand, einenpassenden regulären Ausdruck zu implementieren, zu testen, zudokumentieren und so weiter.
Genau den Ansatz verfolgt die Node.js-Community, indemsie ausgesprochen kleine Module entwickelt und veröffentlicht,die sich je nach Bedarf wie Bausteine zusammensetzen lassen.Da das Modul limit-alphanumeric die gewünschte Aufgabe er-ledigt, lässt sich der Aufruf entsprechend anpassen [g].
Auch die Funktion looksLikeIban lässt sich auf die Art im-plementieren, sofern man das Format auf das deutscher IBANsbeschränkt:
const looksLikeIban = function (potentialIban) {return /^[A-Z]{2}[0-9]{20}$/.test(potentialIban);
};
Bemerkenswert ist, wie kurz die einzelnen Funktionen jeweilssind. Zudem sind Intention und Implementierung vergleichswei-se verständlich. Das Muster lässt sich nach Belieben fortsetzen,wobei der Kern stets darin besteht, Intention und Implementie-rung sauber zu trennen. Erstere drückt den Funktionsnamen undLetztere den Funktionsinhalt aus. Beim Verletzen des Prinzipsund zu starkem Fokus auf der Implementierung lässt sich derCode schwieriger nachvollziehen.
Und nicht nur das: Durch die Kürze und Einfachheit der ein-zelnen Funktionen ist es ein Kinderspiel, sie zu testen und zudokumentieren. Vergleicht man sie mit dem Code aus der er-wähnten Frage auf StackOverflow, wird der Unterschied beson-ders deutlich [f].
Komplexere Logik implementieren
Das Schöne an der bislang gezeigten Vorgehensweise ist, dasssie auch für komplexere Logik funktioniert, wie sich am Beispielder noch umzusetzenden Funktion isValidIban zeigen lässt. IhreAufgabe besteht darin, die Landeskennung einer IBAN zu prüfenund anschließend die Prüfsumme zu berechnen. Was zunächstaufwendig klingt, lässt sich – wendet man die Vorlage der tele-
10 iX Developer 2017 – Legacy-Code
VERSTEHEN | STIL
const canonicalizeIban = function (pot) {if (potentialIban) {const can = canonicalize(pot);if (looksLikeIban(can)) {if (isValidIban(can)) {return can;
} else {throw new Error('IBAN is invalid.');
}} else { throw new Error('IBAN looks wrong.');
}} else {throw new Error('Potential IBAN is missing.');
}};
Listing 2: Alternative Fassung von canonicalizeIban
const canonicalize = function (potentialIban) {if (!potentialIban) {throw new Error('Potential IBAN is missing.');
}
return potentialIban.replace(/[^A-Za-z0-9]/g, '').toUpperCase();
};
Listing 3: canonicalize
const canonicalize = function (potentialIban) {if (!potentialIban) {throw new Error('Potential IBAN is missing.');
}
return keepAlphanumeric(potentialIban).toUpperCase();};
Listing 4: canonicalize potentialIban
fonischen Erklärung an – kompakt umsetzen. Im ersten Schrittgilt es, die IBAN in ihre einzelnen Bestandteile zu zerlegen. Da-nach lässt sich die Landeskennung leicht durch eine gesonderteFunktion überprüfen. Auch das Berechnen der Prüfsumme isteinfach: Die Bestandteile der IBAN sind auf eine bestimmte Artzu addieren und im Anschluss eine Division mit Rest durch 97(da die Prüfsumme zwischen 2 und 98 liegen kann) durchzufüh-ren. Ist der Rest gleich 1, stimmt die Prüfsumme.
Auffällig ist, dass die Funktion isValidIban wie looksLike -Iban die Regel verletzt, dass eine Funktion mit einem Verb be-nannt sein soll. Der Grund hierfür ist, dass die beiden Funktio-nen als logische Entscheider fungieren: Sie erledigen keineAufgabe, sondern geben lediglich einen Wahrheitswert als Er-gebnis zurück. Daher ist es sinnvoll, ihre Namen als logischeAussagen zu formulieren. Häufig beginnt der Name solcherFunktionen mit is, can oder has. Wie looksLikeIban zeigt, lässtsich davon aber abweichen, wenn es sich anbietet und die Ent-wickler das grundlegende Konzept weiterhin verfolgen.
Im Übrigen ist isValidIban die erste Funktion, bei der der Ein-satz eines externen Moduls von Vornherein sinnvoll ist. Da dasRechnen mit den einzelnen IBAN-Bestandteile den Werte -bereich von Zahlen in JavaScript unter Umständen übersteigt,benötigt man ein Modul wie big-integer zum Rechnen mit gro-ßen Werten (siehe Listingˇ5).
Die übrigen Funktionen sind schnell geschrieben. Aufgabevon getIbanDetails in Listingˇ6 ist es, die IBAN zu zerlegen.
Da die Landeskennung als Zeichenkette eine numerischeDarstellung besitzt, ist zudem eine Funktion getNumericIban-CountryCode nötig, die ihrerseits wiederum vergleichweise ein-fach gehalten ist:
const getNumericIbanCountryCode = function (countryCode) {return ,' +(countryCode.charCodeAt(0) - 55) +(countryCode.charCodeAt(1) - 55);
};
Zuletzt ist eine Funktion isValidCountryCode zu implementie-ren, deren Aufgabe darin besteht, in einer Liste nachzuschlagen,ob die angegebene Landeskennung existiert. Eine derartige Listesteht im npm-Modul country-list zur Verfügung, die Implemen-tierung lässt sich daher mit wenig Code erledigen [h]:
const isValidCountryCode = function (potentialCountryCode) {return countryList().getCodes().includes(potentialCountryCode);
};
Eine zentrale Erkenntnis an der Stelle ist, dass sich jeglicheKomplexität beherrschbar und überschaubar gestalten lässt, in-dem man sie in kleinere Einheiten zerlegt. Selbstverständlichentstehen auf dem Weg viele kleine Funktionen, aber im Gegen-satz zu einer einzigen großen mit prinzipiell gleichem Inhaltmuss man sie nicht auf einen Schlag lesen und verstehen. Jenach Interesse können Entwickler gezielt an der einen oder an-deren Stelle in die Details eintauchen, indem sie einen Funk -tionsaufruf auflösen. Das Vorgehen arbeitet vor allem mit derIdee, dass Funktionen ein Abstraktionsmittel darstellen, das dazudienen kann, Implementierung zu verbergen und durch Intentionzu ersetzen.
Deklarativ, nicht imperativ
Das alles erinnert stark an die sogenannte deklarative Program-mierung, in der Entwickler das gewünschte Ergebnis formulie-ren statt den Weg dorthin, wie es bei einem imperativen Ansatzder Fall ist. Daher haben sich für deklarative und imperative
iX Developer 2017 – Legacy-Code
Programmierung die Bezeichnungen Was- und Wie-Program-mierung eingebürgert.
Selbst auf dem Niveau einer einzelnen Schleife lässt sich derUnterschied zwischen beiden Herangehensweisen hervorragendveranschaulichen. Ist beispielsweise für eine Liste von Zahlendie Summe der zugehörigen Quadratzahlen zu berechnen, lässtsich das deklarativ elegant durch den Einsatz von map und re-duce erledigen, indem lediglich das gewünschte Ergebnis be-schrieben wird:
const sum = [ 1, 2, 3, 4, 5 ].map(n => n * n).reduce((sum, n) => sum + n);
Die imperative Implementierung in Listingˇ7 wirkt hingegenaufwendig, da beispielsweise anzugeben ist, wie man durch dasArray der Zahlen iterieren möchte. Der nun längere Code enthältDetails, die für die eigentliche Aufgabe nicht relevant sind. Soist beispielsweise die Existenz der Zählvariablen i der Tatsachegeschuldet, dass eine for-Schleife eine Laufvariable braucht. Inder eigentlichen Anforderung kommt i hingegen nicht vor, eshandelt sich folglich um ein technisches Artefakt, das durch-scheint, weil das Abstraktionsniveau des Codes aus fachlicherSicht nicht hoch genug ist.
Außerdem ergibt sich bei der deklarativen Variante ein ange-nehmer Nebeneffekt: Da die Implementierung nicht vorgibt, wiedie Liste der Zahlen zu durchlaufen ist, könnte die Laufzeitum-gebung entscheiden, das in einer anderen Reihenfolge zu erle-digen als in der imperativen Version, beispielsweise rückwärtsoder durcheinander. Das kann sinnvoll sein, wenn das Iterieren
rückwärts durch eine Schleife performanter ist als vorwärts. Mitder gleichen Begründung wäre sogar denkbar, dass die Laufzeit-umgebung die Ausführung von map und reduce parallelisiert,was für die for-Schleife hingegen zumindest nicht ohne aufwen-dige Analyse des Codes und gegebenenfalls Unterstützung durchden Entwickler möglich ist. Programmierer Joel Spolsky be-schreibt diesen Effekt in seinem Blogeintrag „Can Your Program-ming Language Do This?“ [i].
Simpel versus komplex
Letztlich läuft alles auf den Wunsch hinaus, Komplexität zu ver-meiden und simplen Code zu schreiben. Zwar fordert das auchdas weitverbreitete KISS-Prinzip, das besagt, dass Aufgabendurch eine möglichst einfache Lösung zu erfüllen sind. Aller-dings stellt sich in dem Zusammenhang die Frage, was der Be-griff „einfach“ genau meint. Im Englischen lässt sich etwas bes-ser zwischen simple und easy unterscheiden, im Deutschenwerden die Wörter simpel und einfach allerdings häufig gleich-gesetzt.
Das führt zu der (falschen) Annahme, dass triviale Lösungen,die man ohne großes Nachdenken erreichen kann, stets vorzu-ziehen seien. Mit dem Argument retten sich Programmierer häu-fig, wenn es darum geht, vermeintlich exotische Sprachkon-strukte wie Lambda-Ausdrücke oder Entwurfsmuster zu meiden.Oftmals gipfelt das beispielsweise in Code, der aus endlosen if-else-Kaskaden besteht, statt das wesentlich elegantere und pfle-geleichtere Strategy-Entwurfsmuster zu nutzen.
Um dem Problem aus dem Weg zu gehen, lassen sich abervier konkretere Forderungen stellen, die greifbarer als das KISS-Prinzip sind, und das sogenannte CUTE-Prinzip ausmachen:• Comprehensive: Code sollte verständlich im Hinblick auf die
eigentliche Problemstellung und die Domäne sein und mög-lichst wenig technische Konstrukte enthalten, die lediglichwegen einer fehlenden Abstraktion erforderlich sind. Zudemsind die fachlichen Konzepte explizit auszudrücken, um dieIntention einzufangen und zu bewahren. Die Implementierungrückt dadurch in den Hintergrund, sodass sich aus dem Codebesser ergibt, warum er geschrieben wurde und welchen Zwecker verfolgt.
• Unidimensional: Code sollte eine niedrige Komplexität auf-weisen und nach Möglichkeit auf Verschachtelung, Schleifenund ähnliches verzichten. Je linearer sich der Lesefluss ge-stalten lässt, desto besser. Praktischerweise lässt sich die For-derung durch den Einsatz gängiger Metriken stützen – diezyklomatische Komplexität oder der Halstead-Index etwa[2], [3].
• Terse: Code sollte kurz, kompakt und auf den Punkt gebrachtsein. Weniger Code lässt sich nicht nur schneller lesen, nach-vollziehen und verstehen, sondern er enthält auch wenigerFehler als langer. Außerdem fallen Code-Reviews leichter undTests sind schneller geschrieben, da weniger Code zu testenist. Ein gutes Hilfsmittel besteht im Auslagern logischer Funk-tionseinheiten in eigene Funktionen mit passendem Namen.
• Elegant: Code sollte im mathematischem Sinne „schön“ seinund passende Sprachkonstrukte verwenden. Beispielsweise sinddie Funktionen map und reduce häufig nicht bekannt oder gän-gig, gehören aber zum Alltag eines jeden funktionalen Ent-wicklers. Es handelt sich also nicht um exotische Sprachkon-strukte, für die es entschuldbar wäre, sie nicht zu kennen.
Insbesondere die letzte Forderung ist schwer zu konkretisieren,da Eleganz per se im Auge des Betrachters liegt. Sie sollen aller-dings auch keine strikten, messbaren Regeln darstellen, sondern
12 iX Developer 2017 – Legacy-Code
VERSTEHEN | STIL
const getIbanDetails = function (iban) {return {countryCode: iban.substr(0, 2),countryCodeNumeric: getNumericIbanCountryCode(iban.substr(0, 2)),checkSum: iban.substr(2, 2),bankCode: iban.substr(4, 8),accountNumber: iban.substr(12, 10)
};};
Listing 6: getIbanDetails
const numbers = [ 1, 2, 3, 4, 5 ];
let sum = 0;
for (let i = 0; i < numbers.length; i++) {const square = numbers[i] * numbers[i];
sum += square;}
Listing 7: Quadratzahlenberechnung imperativ implementiert
const isValidIban = function (potentialIban) {if (!potentialIban) {throw new Error('Potential IBAN is missing.');
}
const potentialIbanDetails = getIbanDetails(potentialIban);
if (!isValidCountryCode(potentialIbanDetails.countryCode)) {return false;
}
const dividend = bigInteger(potentialIbanDetails.bankCode +potentialIbanDetails.accountNumber +potentialIbanDetails.countryCodeNumeric +potentialIbanDetails.checkSum);
const divisor = 97;
return dividend.mod(divisor).value === 1;};
Listing 5: potentialIban
vielmehr einen Leitfaden bieten, an dem man sich in die richtigeRichtung entlanghangeln kann.
Fazit
Verständlicher Code ist mit vertretbarem Aufwand erreichbar.Das beschriebene Vorgehen gibt einige Richtlinien an die Handund zeigt, wie sich die Ideen an einem konkreten Beispiel um-setzen lassen. Es lässt sich auf andere Probleme aus der Praxisübertragen, die weitaus komplexer sind. Das Muster ist stetsdas gleiche: Komplexität muss zerlegt werden, Intention istwichtiger als Implementierung. Wer diesen beiden Grundge-danken folgt, kann gut les- und wartbaren Code schreiben, oh-ne dafür gesondert um Erlaubnis bitten zu müssen.
Allerdings erfordert das Vorgehen einiges an Disziplin.Nicht nur, dass Entwickler sich regelmäßig daran erinnern müs-sen, wie wichtig die Intention ist, auch die Suche nach passen-den Namen ist unter Umständen eine langwierige Aufgabe. Ab-kürzungen zahlen sich selten aus, viel mehr bergen sie häufigzusätzliche Kosten – entweder weil immer wieder über Sinnund Zweck des Codes nachzudenken ist oder weil der Aufräum-aufwand später um vieles höher ist als der initiale Entwicklungs-aufwand. (jul)
Literatur[1] Krzysztof Cwalina, Brad Abrams; Framework Design
Guidelines: Conventions, Idioms, and Patterns for Reusable.NET Libraries; Addison-Wesley Professional, 2008
[2] Thomas McCabe, A Complexity Measure, IEEE Transactionson Software Engineering; December 1976
[3] Maurice Howard Halstead, Elements of software science; Elsevier, 1977
Golo Rodenist Gründer, CTO und Geschäftsführer der the
native web GmbH, ein auf native Webtechnologien
spezialisiertes Unternehmen. Für die Entwicklung
moderner Webanwendungen bevorzugt er JavaScript
und Node.js und hat mit „Node.js & Co.“ das erste
deutschsprachige Buch zum Thema geschrieben.
iX Developer 2017 – Legacy-Code
[a] Literarisches Programmieren wikipedia.org/wiki/Literate_programming[b] Magische Zahlen wikipedia.org/wiki/Magische_Zahl_(Informatik)[c] Programming pearls: a literate program dl.acm.org/citation.cfm?id=315654[d] IBAN berechnen iban.de/berechnung_iban.html[e] IBAN-Prüfsumme iban.de/iban-pruefsumme.html[f] IBAN-Validierung auf StackOverflow stackoverflow.com/questions/21928083/iban-validation-check[g] Modul limit-alphanumeric npmjs.com/package/limit-alphanumeric[h] Modul country-list npmjs.com/package/country-list[i] Can Your Programming Language Do This? joelonsoftware.com/items/2006/08/01.html
Onlinequellen
Alle Links: www.ix.de/ix1617008 x
D ie Frage, wie sich die Codearchitektur strukturieren lässt,scheint zunächst nicht einfach zu beantworten. Zu unter-schiedlich sind Anwendungen, zu differenziert deren je-
weilige Umgebungen, als dass es das eine pauschale Vorgehens-muster gäbe. Selbstverständlich gibt es Richtlinien, an denenman sich orientieren kann, allerdings beschreiben sie stets nureinen Ausschnitt des Gesamtbildes.
Elementar für die Entwicklung moderner Web- und Cloud-Angebote sind in dem Kontext die als 12 Factor Apps bekanntenRegeln des Cloud-Dienstleisters Heroku [a]. Sie sind im Fol-genden mit 12FA abgekürzt. Ohne auf all ihre Aspekte im Detaileingehen zu wollen, zeigen die Empfehlungen rasch, in welcheRichtung man gedanklich gehen muss: So sind insbesondere dieForderung nach statuslosen Prozessen zentral, die sich schnellstarten und sauber beenden lassen. 12FA verlangt das expliziteModellieren sämtlicher Abhängigkeiten – sei es mittels Paket-verwaltung oder als externe, über eine Konfiguration beschrie-bene und angeforderte Dienste. Ein so entstandenes Modell isteine Grundlage für den Einsatz von Microservices (siehe dazuauch S. 100).
Auf den ersten Blick scheinen Letztere lediglich eine neueBezeichnung für das zu sein, was vor rund 15 Jahren der BegriffSOA beschrieb, nämlich Webservices. Tatsächlich geht der Ge-danke von Microservices aber weit über die Ideen der Service-oriented Architecture beziehungsweise REST (RepresentationalState Transfer) hinaus, denn er beschreibt Details wie die Infra-struktur und den Umgang mit ihr als Teil des Services.
Der englische Entwickler Martin Fowler beantwortet die Fra-ge nach einer Erklärung der Microservice wie folgt [b]:
„In short, the microservice architectural style is an approach todeveloping a single application as a suite of small services, eachrunning in its own process and communicating with lightweightmechanisms, often an HTTP resource API. These services arebuilt around business capabilities and independently deployableby fully automated deployment machinery. There is a bare mi-nimum of centralized management of these services, which maybe written in different programming languages and use differentdata storage technologies.“
Unabhängig von dieser Definition gibt es den Ansatz, vomeigentlichen Wort auszugehen. Ein Service ist eine unabhängige,isolierte Einheit, die eine bestimmte Aufgabe für ihre Umge-bung erfüllt. Sie agiert dabei autonom vom Rest ihrer Umweltund ist idealerweise statuslos. Wenn man so will, ist eine Funk-tion die kleinste denkbare Einheit eines Service.
Alle bislang genannten Bedingungen erfüllt beispielsweisedie in nahezu jeder Plattform verfügbare Funktion zur Berech-nung des Sinus: Sie ist unabhängig, isoliert vom Rest der Platt-form lauffähig, agiert autonom, ohne Abhängigkeit von anderenFunktionen, und ist üblicherweise statuslos implementiert. Den-noch spricht man in dem Fall von einer Funktion und nicht voneinem Service oder Dienst.
Dienste sind eigenständige Prozesse
Der Grund dafür ist, dass Dienste eigenständige Prozesse dar-stellen. Eine Funktion hingegen kommt zusammen mit vielenweiteren Funktionen in seinem Kontext zum Einsatz. Würde
14 iX Developer 2017 – Legacy-Code
VERSTEHEN | ARCHITEKTUR
Anwendungen strukturiert aufbauen
Meisterlich konstruiertGolo Roden
Für die Struktur von Code in der objektorientierten Programmierung (OOP) eignetsich der Einsatz der SOLID- Prinzipien. Sie geben fünfgrundlegende Regeln vor, an denen sich Entwickler beim Entwurf von Quelltexten orientieren sollen. Doch wiesteht die Sache bei der Anwendungsarchitektur?
man die Funktion als eigenständigen Prozess modellieren, ließesich tatsächlich von einem Dienst reden.
Der Begriff micro ist dagegen nicht so klar umrissen, aller-dings macht er klar, dass es um eine kleine Einheit geht. Mitklein geht üblicherweise der Begriff fokussiert einher, dennwenn sich Entwickler auf wenig beschränken müssen, lassensich ohnehin nicht mehr zahlreiche Themen zugleich angehen.
Zusammengefasst beschreibt der Gedanke von Microservicesalso kleine, eigenständige Einheiten, die isoliert und unabhängigvoneinander arbeiten und sich auf eine einzige Aufgabe konzen-trieren. Das erinnert ein bisschen an das Konzept der Lego-Bau-steine, die für sich genommen klein sind und nur eine einzigeAufgabe erfüllen. In beiden Fällen entsteht das eigentliche Po-tenzial aus der Kombinierbarkeit der Elemente.
SOLID für Architektur
Schlägt man den Bogen zu den aus der objektorientierten Weltbekannten SOLID-Prinzipien (siehe Kasten „SOLID im Über-blick“), fällt auf, dass sich einige von ihnen hervorragend auf denArchitekturansatz übertragen lassen. Am stärksten gilt das ver-mutlich für das S, also das Single Responsibility Principle (SRP).Es besagt, dass jede Klasse nur eine einzige Verantwortung be-ziehungsweise Aufgabe haben sollte. Der US-amerikanische In-formatiker Robert C. Martin, unter anderem Autor des Buches„Clean Code“, hat das Prinzip wie folgt umrissen: „There shouldnever be more than one reason for a class to change.“
Der Satz lässt sich auf Dienste übertragen: Sie sollten nur ei-ne einzige Verantwortlichkeit im fachlichen Sinne haben undsich nur ändern, wenn es ihre fachliche Kernkompetenz betrifft.Insofern gilt SRP auch für Dienste und ist ein erster Indikatordafür, wie Software zu schneiden sein könnte, um einen Mono-lithen in kleinere Einheiten zu zerlegen.
Der Fokus auf den fachlichen Aspekten spielt bei all dem ei-ne zentrale Rolle, da ein Dienst primär eine fachliche Einheitdarstellt und erst sekundär eine technische. Genau das meintauch Martin Fowler, wenn er schreibt: „services are built aroundbusiness capabilities.“
SRP allein genügt jedoch nicht. Ein weiterer Grund fürschlechten, unüberschaubaren und damit langfristig kaum wart-baren Code ist die typische Redundanz, die man in vielen Pro-jekten vorfindet. Das dafür gültige Rezept ist nicht Bestandteilder SOLID-Prinzipien und heißt „Don’t Repeat Yourself“(DRY). Es besagt, dass man immer, wenn man etwas zum zwei-ten Mal umsetzt, überlegen sollte, wie sich beide Vorkommengemeinsam handhaben lassen.
Auf Code einer Klasse bezogen, heißt das, dass man sich wie-derholenden Code in eine gemeinsame Methode auslagern sollte.Bei der Architektur einer Anwendung bietet es sich an, Funktio-nen, die an unterschiedlichen Stellen genutzt werden, in einengesonderten Dienst (oder zumindest ein gemeinsam verwendetesModul) auszulagern. Man kann sogar soweit gehen, zu sagen,dass sich DRY auch auf die Infrastruktur anwenden lässt: In demMoment, in dem man einen zweiten Server so aufsetzt, dass ereinem anderen ähnelt, sollte man darüber nachdenken, den Vor-gang zu automatisieren. Damit wird er nicht nur skalierbar, son-dern vor allem auch reproduzierbar und ist zudem dokumentiert.Das Vorgehen verhindert Snowflake Server und führt zu einemhöheren Grad an automatisiert verwalteter Infrastruktur [c].
Zu guter Letzt muss noch gelten, dass Dienste unabhängigvoneinander fungieren. Das lässt sich mit SRP und DRY nochnicht garantieren, allerdings gibt es dafür einen weiteren Bau-stein der SOLID-Prinzipien: Das I steht für das Interface Segre-
gation Principle und besagt, dass eine Schnittstelle nur das ab-solute Minimum an zwingend notwendigem Funktionsumfangbereitstellen sollte, um eine Aufgabe zu erledigen. Alles darüberhinaus gehört in eine gesonderte Schnittstelle.
Bei Diensten verhält es sich genauso: Bietet jeder über seineSchnittstelle nur genau die fachlichen Fähigkeiten an, die zumBewältigen einer Aufgabe erforderlich sind, werden Dienste un-abhängiger voneinander und lassen sich im Bedarfsfall zugleichleichter kombinieren, ohne unnötigen Ballast mitzuschleppen.Robert C. Martin hat das wie folgt formuliert: „Clients shouldnot be forced to depend upon interfaces that they do not use.“
Mit anderen Worten bedeutet das, dass ein Dienst, der auf ei-nen anderen zurückgreift, sich dadurch nicht an weitere Funk-tionen binden sollte, sondern nur an das, was tatsächlich fachlichrelevant ist.
Beispiele für Lego-Denken
In der Praxis gibt es zahlreiche konkrete Beispiele, die die zuvorgenannten Entscheidungskriterien demonstrieren. Interessanter-weise sind viele von ihnen älter als der Begriff der Microser-vices, was lediglich zeigt, dass die zugrunde liegenden Ideen undKonzepte wesentlich älter sind, als es der Hype vermuten lässt.
Eins der Beispiele sind die zahlreichen Werkzeuge, die unixo -ide Betriebssysteme auf der Kommandozeile zur Verfügung stel-len. Die Kommandos laufen als eigenständige Prozesse, erfüllengenau eine Aufgabe und lassen sich flexibel kombinieren. Ge-nau das zeichnet die Mächtigkeit der Kommandozeile im Ge-gensatz zu den meisten grafischen Benutzeroberflächen aus. Eine aktuellere Umsetzung liefern unterschiedliche Anwen-dungen des Toolanbieters HashiCorp, die sich zum Verwalteneiner Cloud-Infrastruktur verwenden lassen. Sie erfüllen eineabgesteckte Aufgabe und überlappen sich fachlich nicht. Theo-
iX Developer 2017 – Legacy-Code 15
SOLID im ÜberblickDie SOLID-Prinzipien sind fünf Regeln für die objektorientierte Pro-grammierung. Sie wurden von Robert C. Martin geprägt und verfol-gen das Ziel, die Wartbarkeit von Code zu verbessern.
Sie lassen sich wie folgt zusammenfassen:
• Single-Responsibility-Prinzip (SRP): Die Regel besagt, dass es für je-de Klasse nur einen Grund für Veränderung geben darf – anders aus-gedrückt, jede Klasse sollte nur genau eine fachliche Verantwort-lichkeit abdecken.
• Open-Closed-Prinzip (OCP): Klassen sollten sich erweitern lassen,ohne dass sich dadurch ihr Verhalten ändert. Eine Klasse sollte alsooffen für Erweiterung, aber geschlossen hinsichtlich der Änderungensein.
• Liskovsches Substitutionsprinzip (LSP): Dass sich eine abgeleiteteKlasse stets so zu verhalten habe, wie die zugrunde liegende Ba-sisklasse, fordert das LSP. Das Ziel ist, jederzeit an Stelle der Su-perklasse eine Subklasse ohne semantische Änderungen verwen-den zu können.
• Interface-Segregation-Prinzip (ISP): Große Interfaces sind auf meh-rere kleine aufzuteilen, sodass ein Verwender nur genau jene In-terfaces implementieren muss, die tatsächlich notwendig sind.
• Dependency-Inversion-Prinzip (DIP): Abhängigkeiten sollten stetsvon konkreten zu abstrakten Modulen verlaufen, sodass konkreteImplementierungen leichter auszutauschen sind und sich vonei-nander entkoppeln lassen.