Wir haben also gesehen, besprochen, dass wir durch
das Abhängigkeiten auflösen, durch dieses Ummanteln erreichen können.
Und nun ist die Frage, wie können wir das in unserem Entwicklungsvorgehen einbetten?
Damit wollen wir uns nun beschäftigen. Ich möchte ihnen vorstellen,
welches Vorgehen Michael Feathers hier vorschlägt,
um systematisch schrittweise die Testbasis zu vergrößern,
dieses Auflösen von Abhängigkeiten zu erreichen und nach und nach die Testbasis zu erhöhen,
um damit die Code-Basis besser beherrschen zu können.
Dieses Sicherheitsnetz, von dem er sprach, für Cover and Motify zu erreichen.
Wir haben eben am kleinen Beispiel das schon kurz gemacht,
aber jetzt wollen wir das mal die Schritte im Einzelnen vorgehen.
Ich möchte Ihnen zunächst erst mal die Schritte vorstellen.
Wir beziehen uns dabei oder besprechen dabei auch das,
was hier in der Frage 15 besprochen wird.
Also dieses Verfahren vorgehen, wenn wir jetzt hier besprechen und
dabei auch zeigen, wie wir beim Ummanteln diese Abhängigkeiten auflösen können
Zunächst erst kurz im Überblick und dann gehen wir auf jeden Punkt einzeln ein.
Dieses Änderungspunkte identifizieren, der erste Punkt, das war erst mal,
wir müssen im Kohl erst mal suchen, worum geht es denn überhaupt?
Das war in unserem Beispiel jetzt hier oben,
dass man eben ermittelt, ja okay,
wir müssen hier diese beiden Methoden in dem Fall erst mal,
das ist das, was wir bearbeiten wollen.
Ja, also wir müssen sozusagen schauen,
wo fachlich wird der Bezug in unserem System, wenn sie wollen,
können sie Architekturschrei sagen oder in unserer Implementierung hätte der Bezug zur den Anforderungen besteht,
wo sind wir die fachlichen Änderungen, die wir überhaupt brauchen.
Denn wir wollen ja die Tests vor allen Dingen dort machen und die Refactoring-Stock machen,
wo wir am Ende im nächsten Schritt etwas tun wollen.
Sie erinnern sich aus dem Kapitel 4-3,
wir machen die Refactorings Anlass für Refactorings, ist häufig dort,
wo wir hinter fachliche Änderungen machen sollen.
Dass wir ermitteln, dass fachliche Änderungen, wo ist eine Bezug,
und dort wollen wir Sachen suchen, dort wollen wir Änderungen machen,
Verbesserungen machen. Dann suchen wir anhand von Auswirkungen,
wo können wir denn den Test platzieren,
wo können wir denn etwas absichern, wo können wir denn sehen,
dass eine Änderung, eine Wirkung erreicht,
wie wir dann wohl am Ende das gleiche Verhalten wieder erhaben wollen.
Und dort ermitteln wir Abhängigkeiten, die wir dann auflösen,
wo wir dann so eine Ummantelung schreiben wollen.
Wir hatten ja eben so ein kleines Beispiel.
Und dann schreiben wir die eigentlichen Tests. Und wenn wir
das getan haben, haben wir das Refactoring soweit getrieben,
dass wir da eine Absicherung durch Tests haben.
Und dann könnten wir auch Refactorings oder funktionale Änderungen beides machen
in der Reihenfolge, wie wir es immer sollten.
Also eigentlich das Refactoring zuerst durchführen
So, ich möchte jetzt gern mit Ihnen gemeinsam durchgehen, wie können wir das tun?
Am Ende sollte uns immer das große Ziel klar sein,
das Risiko für Veränderungen, nämlich das sind eigentlich das,
was wir hier unten machen wollen, Refactorings machen und natürlich auch Erweiterungen,
funktionale Änderungen, dass wir das besser tun können.
Wenn wir nochmal einen Bezug nehmen zu etwas,
das wir ganz am Anfang haben, ändern Sie sich bitte an das Stage-Meutel,
wo wir immer versuchen wollen, wir beschäftskritische Systeme in dem Abschnitt,
in dem Bereich Evolutionary Changes in dem Bereich,
in dem Blog Evolution zu bleiben,
wo wir emotionäre Veränderungen machen können, halt so Veränderungen,
Strukturanpassungen umfassen, um diese Strukturanfassungen sind durch Perfactoring möglich.
Deswegen schauen wir uns nochmal an, wie das aussieht,
wie das im Einzelnen abläuft. Das Ziel dieses Vorgehens ist es,
dass wir die Absicherung, eine bessere Absicherung erreichen.
Das ist das, was im Zitat umwandeln mit Tess heißt.
Das ist eins der Ziele, das wollen wir erreichen,
indem wir funktionale Änderungen und Refactoring trennen
Wir wollen es erreichen, indem wir schrittweise die Menge der Tests,
die wir verfügbar haben, auf Unitest-Ebene erhöhen.
Und dem wir schrittweise
die Abhängigkeiten entkoppeln
Und damit sind genau diese Abhängigkeiten gemeint, die wir hier
im Text kurz vorher hatten. Zum Beispiel solche Abhängigkeiten wie,
dass wir eine Kommunikation mit der Datenbank haben oder Netzwerkabhängigkeiten,
Zukunft auf ein Dateisystem oder irgendwie Sachen,
die erst eine Konfiguration vorher herfordern.
Alles, das sind Sachen, die Höhnetests verhindern und
wir wollen ja
Unites erreichen
Ups, das macht mich mehr als Pfeil.
Und das ist dann gerade dadurch erreichbar,
indem wir fortschreibende Beherrschung der Kohlbasis
durch Refectorings erreichen
Zuerst würde es uns gelingen, dass wir kleine Inseln haben,
wo wir durch Unitests etwas abgesichert haben,
weil wir schön Änderungen durchgeführt werden.
Später werden diese Bereiche immer mehr zwischen Inseln werden wir,
wenn wir dann am Ende es schaffen,
dass ganze Bereiche durch Unitests abgedeckt werden.
Und dann werden wir nach und nach einen Zustand bekommen,
wo wir den Code besser durch Refactorings erschließen können,
wo es uns gelingt,
Änderungen häufiger durchzuführen
Wenn es dadurch, dass wir das konzentrieren,
immer auf diese Sachen,
ich gehe noch mal zurück zu dem Text hier,
für diese Punkte, wo wir häufig Änderungen zu machen haben,
sind das auch die kritischen Punkte.
Und wir werden uns natürlich die Sachen auch aussuchen,
wie der Quellcode schlecht ist. Das heißt,
diese 20 Prozent, die 80 Prozent des Erkors machen,
das ist das, wo wir wirklich gute Verbesserungen erreichen können.
Das ist das, was wir mit gutem und konzentriertem Einsatz
einen wirklichen Effekt betrachten.
Lassen Sie uns die einzelnen Schritte des Vorgehens hier von diesen
fünf Schritten
genauer anschauen
Der erste Schritt, den wir uns hier genauer anschauen,
ist dieses Identifizieren von Änderungspunkten. Wir wollen uns die Punkte raussuchen,
an denen wirklich Use Cases geändert werden sollen und solche,
darunter diese greifen, wo die Wartbarkeit schlecht ist,
wo wir Risiko haben,
wo wir die Verwende von Refectorings
überhaupt ansetzen müssen
Um in den Zustand Evolutionen immer wieder das Stage-Model heranziehen zur
Erläuterung, hinkommen. Diese Punkte sind nicht offensichtlich.
In vielen Fällen kennen wir das System wenig zu wenig,
um es gleich zu sagen können, was denn betroffen ist.
Wir wissen, was wir häufig wissen, ist,
was der Änderungsbedarf fachlich bedeutet.
Aus Sicht der Anwendung über des Anwenders Das betrifft also Requirements
, die Anforderungsebene. Und was wir erreichen wollen,
ist, wo sind die Stellen zum Ändern?
Wollen Architektur und Quellcode
Diese Stellen müssen wir finden und das wird häufig,
ist uns häufig in vielen Fällen,
in solchen Systemen nicht genau genug bekannt,
weil man die Systeme einfach, die sind zu groß,
man hat sich selber nicht entwickelt, sie sind manchmal schwer verständlich.
Und dazu ist es notwendig, dass man zunächst erst mal
die Anwendung in den Bereichen genauer kennenlernt.
Dazu lernen. Dazu benutzt man solche Techniken wie das,
was wir unter Abdeckungsanalyse gesprochen haben, auf Systemebene,
den erstmal durch Instrumentierung herausbekommt,
wo sind Vokidenten in meiner Einwendung überhaupt lang?
Was passiert denn da? Und das bringt so ein
bisschen Licht ins Dunkel. Und das ist der Punkt,
wo wir das überhaupt erst mal wissen, an den Stellen,
wäre der Bedarf nach Refektorin. Der nächste Punkt ist der,
der Testpunkte suchen, das wäre, damit ist gemeint,
dass wir die Stellen suchen, wo Änderungen auch Auswirkungen haben.
Das heißt, die, wo wir prüfen müssen und prüfen können,
einen Test nicht zu einer Verschlechterung führt,
ob ein Test dazu führt, dass das Verhalten unbeeinflusst bleibt.
Wir suchen solche Auswirkungen von Änderungen und versuchen, die zu identifizieren, zu lokalisieren.
Und das ist das, was wir als Testpunkt bezeichnen.
Solche Stellen, wo Auswirkungen, wo sich Änderungen auswirken,
können beispielsweise sein, dass Rückgabewerte irgendwie ausgewertet oder geprüft werden.
Das ist die Stelle, wo ich dann auch dem Rückgabewert
als etwas heranziehen kann, wo ich einen Test dran festmachen kann.
Oder ich übergebe etwas als Parameter, dieser Parameter, den kann ich testen.
Ist es denn das, was ich brauche?
Manchmal haben wir in solcher Legacy-Systeme auch statische Daten,
globale Daten, an denen wir die Veränderungen festmachen können,
um auch zu prüfen, was hat sich denn hier getan?
Ist das Verhalten noch das Gleiche? Oder wir können auch festmachen,
wo, das muss innerhalb der Grenzen bleiben, zum Beispiel in objektorientierten Sprachen,
waren wir häufig solche Sachen wie Private oder gar Konstanten,
wo wir sagen müssen, okay, außerhalb können wir das nicht sehen,
wir müssen da innerhalb bleiben, Das heißt,
wir müssen in irgendeiner Form da die Änderungen abgreifen,
um das zu testen.
Ich gehe jetzt mal nicht darauf ein, dass wir aspektorientierte Programmierungen haben,
wo wir den Code noch verändern oder Templates,
wo wir durch Instanzierung auch noch was haben.
Das wäre noch weitergeht. Und wir wollen,
wenn wir zum Beispiel ermitteln wollen,
was eine Methode für Auswirkungen hat, das wäre hier das,
was sie auf der rechten Seite, auf der,
wo ihr steht, dann wird man natürlich erst mal sehen,
welche Methode geht, um welche Methode geht es denn.
Das wäre auch dieser erste Punkt. Ja,
und falls die einen Rückgabewert haben,
dann könnten wir das auch so machen, okay,
da wo der Aufrufer ist, der den Rückgabewert bekommt,
dass wir dort die Analyse ansetzen.
Falls die Methode irgendwie Felder bearbeitet, also die als Rebute,
meine Instanz, meines Exemplars, dann könnten wir gucken,
wer greift denn da alle auf diese Variablen zu?
Oder wenn wir oberen Unterklassen haben, wer benutzt die?
Gibt es Parameter? Dann können wir schauen, wo kommen die Parameter her?
Was sind das für Werte? Oder eben das,
was wir auf der nächsten Speide schon haben,
wenn es um das Generelles gibt, wo auch immer es abgelegt wird,
ein Array oder ein Feld oder was auch immer,
was bearbeitet wird, dann können wir darauf zugreifen.
Das wäre das, was wir hier als Test Dings beschreiten.
Manchmal sind die Sachen etwas, schreibt das mal hier dran,
manchmal ist es besser eine Ebene höher.
Auf dem Nächsten, nein, das ist falsch geschrieben
Manchmal ist es besser, die Tests auf der Ebene höher anzusiedeln,
zum Beispiel hier, wo der Rückgabewert geprüft wird, weil wir den,
ja, wo der Klient letztlich einer solchen Methode sitzt.
Diese Liste sieht hier relativ einfach aus,
doch es ist bei Legacy-Systemen häufig so,
dass es viele Ursachen und viele Wirkungen gibt,
die irgendwie ineinander spielen, wenn die Übersicht das erfordert,
ist es durchaus möglich, sinnvoll,
sich sogar dazu so ein kleines Modell zu erstellen und Dazu empfiehlt sich so eine Art Ursache-Effekt-Graf.
Diese Ursache-Effekt-Graf sagt also, wenn ich dort was mache,
dann hat das eine Auswirkung auf das,
zum Beispiel auf die Variable oder auf das Feld oder auf dem Hulien oder wie auch immer,
um den Übersicht zu gewahren,
wenn es mehr als so drei,
vier Sachen sind,
die ich
schlecht im Kopf behalten kann
Der nächste Schritt 3 ist eigentlich der Kern dieses ganzen Verfahrens.
Wir wollen es versuchen zu erreichen,
diese kleinen Tests einzuführen, die Entkopplung zu erreichen.
Das ist sozusagen der Kern. Wir werden kurz jetzt
darüber sprechen, uns danach noch weiteren Details zuwenden.
Dazu brauchen wir danach hier eine neue Seite.
Eine Lebeseite, bitte. So,
wie ist sie
Diese Abhängigkeiten wollen wir auflösen und dazu ist erstmal die Frage,
woran erkenne ich denn,
dass es hier Abhängigkeiten gibt
Ich erkenne sie daran, dass es mir schwer fällt,
dafür einen automatisierten Test zu schreiben.
Ich möchte zwei Beispiele bringen. In diesem Test hauen wir
nicht in diesem Testcode, wenn wir dort Objekte erzeugen wollen
und brauchen dafür Zugriffe auf externe Dinge
Das wäre ein Beispiel oder das zweite wäre,
dass wir Methoden in diesem Testcode, in dem Testanisch ausführen wollten.
Wir müssen dazu erst. Wenn das beides zum Beispiel
umfangreiche Vorbereitungen in der Umgebung erfordert
Nein, in der Umgebung soll das heißen.
Dann müssen wir diese Abhängigkeiten auflösen und das sind Codeänderungen für
das Auflösen der Abhängigkeiten. Müssen wir Veränderungen vornehmen,
diese Veränderungen sollen eigentlich durch Tests
irgendwie abgesichert werden
Und hier zeigt sich schon wieder dieser Teufelskreis.
Einerseits wollen wir überhaupt Tests einführen. Andererseits brauchen wir hier
Tests, die um diese Veränderung abzusichern.
Also das ist dann wieder, ich schreibe das durch nochmal hin.
Das ist wieder dieser Teufelskreis, den wir haben.
Wir brauchen also
ein Verfahren
Mit dem wir Code verändern können, mit verringerten Fehlerrisiko,
zum meditierendes Code. So, und dann wäre es
überhaupt möglich. Also das ist aber die eigentliche Herausforderung.
Also das ist ja
ein Problem
Es kann auch passieren, dass ihnen zwar Tests einfallen,
die sie machen können, aber es sind sehr umfangreiche Tests,
die müssen sie erstmal verringern vom Aufwand her,
dann läuft das das Gleiche hinaus.
Und das ist, müssen wir irgendwie in irgendeiner Form beherrschen.
Ja, und um das noch vorzusetzen, sollen wir natürlich
auch noch was zu den Punkten 4 und 5 sagen.
Bei Punkt 4 sind es dann wirklich, ja,
wir schreiben diese Tests, wir machen diese Testautomatisierung,
wir schreiben Unitests, die uns auf Knopfdruck sofort sagen,
ja, Gröder Balken,
nichts passiert und die Sicherheit,
diese Sicherheit bieten
Da gibt es verschiedene Arten. Wir werden über diesen Begriff,
der steht schon hier auf Charakterisierungstests sprechen und dann die wirkliche Änderung durchführen,
Refactoring durchführen. Das ist dann das ganz normale Entwickeln,
aber abgesichert durch Tests, die uns wirklich helfen,
die Sicherheit geben, dass wir hier nichts kaputt machen.
Und das ermöglicht, das sind überhaupt wirklich Evolutionen durchzuführen im
Sinne von Strukturanpassungen. Das ist das, was,
ja, ich hoffe, Sie erinnern sich dran,
im Stage-Model die Phase Evolution von der Phase Patchen,
Flickenteppich unterschied