Arndt Schönbergs Weblog

Donnerstag Feb 01, 2018

Werkzeuge zur Architekturerhaltung - Maßnahmen / QS - Teil 4

Im letzten Post haben wir definiert, welche Messwerte wir bezüglich der technischen Schulden erreichen wollen. Dieser Post beschäftigt sich mit den Maßnahmen für deren Umsetzung. Die grundlegenden Aussagen meiner Posts gelten allgemein für die Softwareentwicklung. Die Beispiele beziehen sich hier auf ein Entwicklung auf Basis von JAVA.

Reduzierung der Komplexität

Es sollen wenige Zyklen und Abhängigkeiten auf allen Ebenen (Artefakte, Packages) der Software entstehen und eine lose Kopplung mit geringer Kohäsion (Zusammenhalt in Modulen) erreicht werden, da
  • sie die Erweiterbarkeit erleihctern
  • sie weniger fehleranfällig sind
  • sie die Testbarkeit erhöhen (geringerer Aufwand bei Testerstellung, bei wenigen Abhängigkeiten)
  • sie zukünftig leichter aufzuteilen sind (Partitionierung) und damit Refactorings erleichtern
Es sollen kurze Klassen, Packages, Artefakte entstehen, da diese einfacher zu verstehen sind. Außerdem sollten nicht mehr genutzter Code-Teile gelöscht werden, da sie u.a. Mehraufwände bei Refactorings verursachen. Es sollte immer klar sein, welche Teile der Software genutzt werden und auch wofür sie genutzt werden. Wie die meisten hier angesprochenen Punkte können auch nicht genutzte Softwareteile großteils automatisch erkannt und iterativ beseitigt werden.

Einheitliche Lösungswege

Es sollten Entwurfsmuster verwendet werden. Hierdurch wird erreicht, dass einheitliche Lösungswege für ähnliche Probleme verwendet werden und wir uns besser zurecht finden.

Ordnung schaffen

Es muss eine fachliche und technische Schichtung sichergestellt und hierfür fachliche Hierarchien auf allen Ebenen (Artefakte, Packages, ..) eingeführt werden. Diese erleichtern die Orientierung. Wichtig ist, dass bei Paketen die Fachlichkeit die technischen Aspekte dominiert. Es sollte also
 
...basedata.entity 
...basedata.facade
...accounting.entity 
...accounting.facade
und nicht
 
...entity.basedata
...entity.accounting
...facade.basedata
...facade.accounting
verwendet werden. Im ersten Fall wären alle Entitäten und Facades der Bereich in einer fachlichen Struktur zusammengefasst. Dies erleichter das Arbeiten, da bei Änderungen häufig alle fachlichen Klassen genutzt werden. Im zweiten Fall wären alle Entitäten und alle Facades der Anwendung im Paketen zusammengefasst. Dies hat zur Folge, dass bei der Bearbeitung eines Bereichs viel im Paketbaum "gesprungen" werden muss.

Tests

Eine hohe Testabdeckung stellt sicher, dass Software das Gewollte erledigt.

Fehlerreduzierung

Eine Vielzahl von Fehlern lassen sich automatisch erkennen. Viele NPE oder falsche instanceof Vergleiche werden erkannt und sollten in der Entwicklung nicht mehr auftreten. Ebenso sollten Optimierungen wie die Verwendung von StringBuilder / StringBuffer und ArrayList statt Vector immer automatisch geprüft werden.

Weiterhin tragen regelmäßige Code-Reviews und last but not least auch Datenbankintegritäts-Bedingungen zur Fehlerreduzierung bei. Den letzten Punkt habe ich explizit aufgenommen, da nach meinen Eindruck dies eigentlich selbstverständliche Thema immer weniger Beachtung findet. Dadurch wird die Datenkonsistenz und damit auch die Funktion der Software gefährdet. Wir treffen erstaunlich oft auf Datenbanken ohne jegliche Konsistenzbedingungen.

Dokumentation

Insbesondere im Quellcode ist Dokumentation sehr wichtig, da wir im Code arbeiten und diesen ohne langes Suchen schnell verstehen möchten.

Strukturelle Einheitlichkeit und Konsistenz des Codes

Für eine einfache Orientierung und schnelles Verstehen des Gesehenen sind eine durchgängig einheitliche Formatierung und möglichst einheitliche Bezeichnungen im Quellcode sehr wichtig.

Werkzeuge

In den nächsten Posts stelle ich verschiedene Werkzeuge vor, die jeweils Teile der geforderten Maßnahmen sicherstellen.

Dienstag Jan 16, 2018

Werkzeuge zur Architekturerhaltung - Messen und Prozesse / QS - Teil 3

Ziel dieser Serie von Posts, ist es darzustellen, wie technische Schulden erkannt werden können. Nach der Erkennung kann man sich häufig an Mustern orientieren, um die Schulden abzubauen. Einige Werkzeuge geben uns Entwicklern auch direkt Lösungen an die Hand.

Aber auch bei der Erkennung von technischen Schulden gilt "You get what you measure" - wie in allen Teilbereichen des Managements. Wir brauchen also einen möglichst automatischen Prozesse, der uns regelmäßig an unsere Schulden erinnert, so dass
  • bekannte technische Schulden nicht in „Backlog-Vergessenheit“ geraten und
  • neue technische Schulden erkannt werden.
Hierbei müssen in unseren Projekt Prozesse etabliert werden. Diese gliedern sich in Prozesse
  • ohne Werkzeugunterstützung
  • mit Werkzeugunterstützung
    • Build brechend - automatisch
    • Warnung - manuell

Das manuelle Vorgehen ohne Hilfsmittel (z.B. sporadische Code-Reviews) ist nicht Thema dieser Blog-Serie. In den folgenden Posts werden Werkzeuge für Prozesse mit Werkzeugunterstützung dargestellt. Die Prozesse teilen sich in einen "Build brechenden" Prozess, bei dem Regelverstöße den Build der Software unterbinden, und einen Prozess, der lediglich Warnungen erzeugt, die im Anschluss manuell bearbeitet werden müssen.

Der Verlauf von technischen Schulden in Verbindung mit den verschiedenen Prozessen ist in der folgenden Grafik dargestellt

techDebt.png

Bei der Planung der QS Anforderungen ist es im Allgemeinen das Ziel, einen möglichst kleinen „Korridor“ von technischen Schulden und damit Abweichungen der Architektur zuzulassen (hier gelb markiert).

Die rote Linie zeigt den Verlauf, wenn keine QS implementiert wird. Es kann hier maximal zu „Zufallsrefactorings“ kommen, die die Situation verbessern. Zufallsrefactorings sind Reduzierungen der technischen Schulden bei der Bearbeitung einer Anforderung. Findet sich dort eine Schuld wird Sie häufig im Zuge der Umsetzung der Anforderung behoben. Es gibt aber keinen geordneten Prozess für den Abbau der Schulden. Die Kurve wird über die Zeit permanent ansteigen.

Die schwarze Linie zeigt den Verlauf bei einem automatischen "Build brechenden" Prozess. Er schafft einen guten Grundstock bei der Bekämpfung technischer Schulden, da harte Regeln neue Schulden verbieten und nach und nach auch alte Schulden abgebaut werden. Aber auch hier werden die Schulden über die Zeit ansteigen, wenn auch weniger stark. Grund hierfür ist, dass nicht alle Schulden in dem betrachteten Prozess berücksichtigt werden können, da sonst beispielsweise zu viele False/Positives entstehen und die Entwicklungsarbeit behindern.

Ein Projekt kann nur innerhalb des gewünschten Korridors verbleiben, wenn neben dem automatischen Prozess auch ein manueller etabliert wird.

Freitag Dez 08, 2017

Werkzeuge zur Architekturerhaltung - Verstehen von Quellcode / QS - Teil 2

Im letzten Post ging es um die allgemeinen Ziele bei der Architekturerhaltung. Zentraler Punkt hierbei ist das Verstehen des Quellcodes. Das Verstehen bzw. das Einarbeiten in Quellcode macht einen Großteil unserer Arbeit als Entwickler aus (sicherlich mehr als 50%).

Beim Verstehen setzen wir das "Chunking" ein. Hierbei fassen wir bewusst oder unbewusst Teilinformationen zu größeren Einheiten zusammenfassen, um diese dann besser wieder abrufen zu können. Diese Technik kommt insbesondere beim "Bottom up" Vorgehen zum Einsatz und kann durch technische Rahmenbedingungen im Quellcode unterstützt werden:
  • Ordnung im Quellcode (Artefakte, Packages, ...)
  • Geringe Komplexität
  • Einheitlicher Aufbau
  • Kurze Blöcke (Schichten, Artefakte, Packages, Klassen, Methoden)
Ein weitere Punkt, der uns das Verstehen erleichter ist beim "Top down" Vorgehen das Vorhandensein von Hierarchien. Dies finden wir im Quellcode bei
  • Zusammenfassen von Teilinformationen durch Ordnung (Artefakte, Packages, Klassen, Methoden)
  • wenige Zyklen erleichtern die Orientierung
Und der letzte große Punkt, der ebenfalls aus dem Bereich der "Top down" Analyse stammt, sind die Entwurfsmuster, die das Verständnis dadurch verbessern, dass
  • ein ähnliches Vorgehen bei Lösungen verwendet wird.
  • Namen von Klassen z.B. Factory bereits auf Inhalte und Vorgehen schließen lassen.

Donnerstag Nov 09, 2017

Monolith vs. Microservices ...

… oder warum nicht jeder Monolith geschlachtet werden muss.

Seit einiger Zeit ist der Hype ungebrochen, jedes monolithische System schnell zu ersetzen und jede Neuentwicklung als Microservice-Architektur zu realisieren (eventuell nicht jedes aber doch eine Vielzahl ;-) ).

Problem Mensch

Aus meiner Sicht ist eines der Hauptprobleme in der Softwareentwicklung, dass nicht Vorhandensein von (geeigneten) Architekturregeln. Ob dies durch Fehler in der Entwicklung, mangelndes Know How oder fehlende Ressourcen für die Prüfung der Einhaltung der Architektur verursacht wird, ist nicht relevant. Das Ergebnis ist ein schwer wart- und erweiterbares Softwaresystem. Bevor wir uns also Gedanken um den geeigneten Architekturansatz machen, ist sicherzustellen, dass es überhaupt eine Architektur gibt und diese durch einen QS-Prozess auch abgesichert wird. Schlechte Software wird auch als Microservice nicht besser.

Pro und Kontra

Im folgenden habe ich einige Pro- und Kontra-Punkte zu Monolithen und Microservices zusammengestellt.
  • In einem Monolithen sind die meisten Refactorings wesentlich einfacher und im Allgemeinen mit wenigen Aktionen in der IDE realisierbar.
  • Reporting, Batchläufe und andere Aktionen, die aus vielen Quellen gespeist werden müssen, sind bei Monolithen deutlich einfacher zu realisieren, da auf die zentralen Informationen des Gesamtsystems zugegriffen werden kann.
  • Monolithen haben interne Abhängigkeiten, die gut erkennbar sind. Bei Microservices wird viel Komplexität in die Netzstruktur verschoben, die schwerer erkenn- und wartbar sind. Probleme von verteilten Systemen (Ausfälle, Verlässlichkeit von Knoten) können bei Monolithen nicht auftreten.
  • Systeminterene Schnittstelle können bei Monolithen einfacher geändert werden. Bei Microservices kommt hier Consumer-driven Contracts (z.B. https://jaxenter.de/microservices-consumer-driven-contract-testing-mit-pact-20574) für die Verbesserung der Situation zum Einsatz.
  • Durch statische Codeanalysen lässt sich bei einem Monolithen die Gesamtarchitektur besser prüfen, als bei Microservices. Insbesondere Gesamtsystemprüfen mit jQAssistant und ähnlichen Tools sind hier einfach automatisierbar. Automatische und manuelle Prüfungen der Architektur sind die Basis für gute Codequalität und damit effizientes Arbeiten an dem Prosukt.
  • Eventual Consistency (Achtung nicht eventuell, sonder letztendlich) kann, muss aber nicht bei Monolithen eingesetzt werden.
  • Monolithen in Verbindung mit Architektur- und Toolvorgaben machen es einfach, die Anzahl der verwendeten Technologien zu reduzieren. Die Verwendung von weniger Technologien bedeutet, dass viele Teammitglieder an verschiedenen Stellen mit geringer Einarbeitungszeit eingesetzt werden können. Es ist im Allgemeinen für die Wartbarkeit eben kein Vorteil, wenn jeder Microservice einen eigenen Technologie Stack hat. Dies sollte nur geschehen, wenn es deutliche Vorteile gibt.
  • Bei einem guten Design und guter Umsetzung ist auch ein Monolith modular aufgebaut. Ein gutes Design und Prüfung der Einhaltung hierfür benötigen beide Architekturen.
  • Einige Artikel beschreiben es als Vorteil, dass nicht gelungene Microservices „weggeworfen“ werden können. Bei einem guten, modularen Aufbau eines Monolithen können ebenfalls Teile ausgetauscht werden. Grundsätzlich fördert eine solche Argumentation aber „schlechte“ Software und ich kenne wenige zahlende Kunden, die einer solchen Argumentation überhaupt folgen wollen.
  • Domain-Driven-Design ist in beiden Welten umsetzbar.
  • Achtung bei Microservices, die über Datenbanken integrieren oder die Modelle anderer Services direkt weiterverwenden, da in solchen Fällen kein klarer Bounded Context vorliegt und somit ein Pro von Microservices weniger stark greift.
  • Monolithen haben das Problem, dass die Stabilität eines Moduls, sich auf das ganze System auswirken kann. Abgeschwächt gilt dies auch für Microservices, wenn abhängige Dienste instabil laufen.
  • Die physikalische Verfügbarkeit kann in beiden Fällen durch Replikation (Cluster von Application Servern bzw. einzelner Schichten oder Servicereplikation) erreicht werden. Dies ist bei Microservices deutlich einfacher. Außerdem können Microservices gut „bei Bedarf“ skalieren.
  • Die Skalierbarkeit der Entwicklerteams ist bei Microsrvices besser, da es weniger Abhängigkeiten in der Codebasis gibt. Andererseits verzögern Schnittstellenabsprachen das Vorgehen, so dass aus meiner Sicht mit guter Qualitätssicherung beide Architekturen in gewissen Maßen skalieren.
  • Microservices neigen zur Codeduplizierung mit all ihren Nachteilen.
Aus meiner Sicht gibt es Anwendungsklassen, bei denen Microservices insbesondere ihren Skalierungsvorteil klar ausspielen können. Gerade im Web-Systemen mit vielen Nutzern und überschaubaren Abläufen funktioniert dies prächtig. Bei vielen sogenannten betrieblichen Anwendungen ist aus meiner Sicht ein gut strukturierter und qualitätsgesicherter Monolith meistens besser für die Zielerreichung. Wenig Komplexität in den Kommunikationsstrukturen erleichtern die Erweiterbarkeit.

Dienstag Nov 07, 2017

Werkzeuge zur Architekturerhaltung / QS - Teil 1

Nach etwas Pause in diesem Blog werde ich nach und nach einige Posts zum "Thema Werkzeuge zur Architekturerhaltung / QS" veröffentlichen. In diesem Post geht es um die QS im Rahmen von Java Projekten. Viele Punkte - aber nicht alle Werkzeuge - sind aber auch auf andere Sprachen zu übertragen. Das Einhalten einer Softwarearchitektur stellt sicher, dass wir im Sinne unseres Kunden, der auch eine interne Fachabteilung sein kann, eine Investitionssicherheit mit dem Produkt liefern. Ziele sind:

  • Funktionserfüllung
    • Initiale und dauerhafte Erfüllung von (Kunden)Anforderungen
  • Zeitlich angemessene Arbeiten an dem Produkt
    • Erweiterung
    • Wartung
    • Schnelle Reaktion auf Kundenanforderungen / Funktionsumsetzung
    • ggf. zeitnahe Einarbeitung neuer Mitarbeiter
Um diese Ziele erreichen zu können, müssen wir uns schnell in den Quellcode einarbeiten und ihn verstehen können. Die Rahmenbedingungen hierbei sind
  • die Domäne, in der wir uns aktuell befinden
  • und die Technische Rahmenbedingungen
Erstere ist von uns nicht beeinflussbar, da Sie durch den Kunden vorgegeben wird. Bei dem zweiten Punkt können wir durch Definition einer Architektur und durch deren werkzeugunterstützte Einhaltung sehr viel erreichen und somit die Qualität und Zielerreichung der Software deutlich verbessern. Dabei ist es nicht relevant ob es sich um fremde Code-Teile oder eigene Code-Teile handelt. Es ist wohl jeden von uns schon einmal passiert: wir implementieren etwas, sehen darüber uns sagen „chic“, klar, deutlich, gute Lösung! Nach einigen Monaten in anderen Projekten ist man bei erneuter Durchsicht, da Änderunegen anstehen, der Meinung, dass eventuell an der einen oder anderen Stelle etwas mehr Informationen und ein klarer Entwicklungsstil besser gewesen wäre.

Ziel der in dieser Postserie beschriebenen Punkte ist es, Quellcode und Architektur so zu gestalten, dass alle beteiligten Entwickler sich schnell einarbeiten können und damit die wirtschaftliche Entwicklung sicherstellen.

Insbesondere wollen wir eine geringe Architekturerosion (wenige neue technische Schulden) und die Vermeidung von Code-Smells vorantreiben.

Calendar

Feeds

Search

Links

Navigation