top of page
  • Writer's picturewackyworld

Testdriven Development (TDD) mit GitHub-Copilot

Updated: Apr 4, 2023

In diesem Beitrag geht es um die testgetriebene Entwicklung mit GitHub-Copilot. Das würde dir vermutlich schon die Überschrift verraten. Was sie dir nicht verrät, ist, wie ich auf diese Kombo gekommen bin, die ich mittlerweile erfolgreich in der Praxis einsetze. Und warum ich glaube, dass diese Kombo den größten Nachteil des TDD abmildern kann. Vorweg erläutere ich den induktiven Fehlschluss, dem viele in Punkto Testing unterliegen und erzähle dir mit den Worten des großen Dijkstra, was Tests wirklich zu leisten imstande sind. Und so viel sei gespoilert: Ich bin ein Riesenfan von Tests und halte sie für unverzichtbar, aber ich habe jetzt schon so viele Codebases gesehen und die wenigsten davon hatten eine Testabdeckung, die höher war als 30 %. Warum ist das so? Entwickeln nicht laut Lebenslauf / Bachelor- / Projekt- / Masterarbeit ausnahmslos alle nach TDD?

Was ist das überhaupt, TDD?

Darauf gehe ich anschließend ein und auf die "Geschmacksrichtungen" des TDD. Allen voran disskutiere ich - mit mir selbst ;) - die Vor- und Nachteile, die mit diesem Designprinzip verbunden sind. Wie üblich bei mir, nehme ich dies betreffend kein Blatt vor den Mund, denn so sehr ich TDD mag, es hat einen Riesennachteil, den ich erst so richtig gespürt habe, als ich Freiberufler wurde. Einige TDD-Dogmatiker/innen werden vielleicht weinen beim Lesen (nicht vor Freude), während Leute mit mehr Adaptionsfähigkeit (Survival-of-the-fittest heißt nicht, dass die überleben werden, die den größten Dickschädel haben, sondern die, am anpassungsfähigsten sind) jubeln werden.

Zum Schluss geht es dann darum, wie man mit GitHub-Copilot einen Ausweg aus dem in der Praxis konsequent geübten TDD-Verzicht finden kann.

So nun aber Vorhang auf!


„Wollten wir die Tests nicht schon längst geschrieben haben, Kurt? Sprint 10 hast du damals gesagt. Jetzt sind wir bei Sprint 110. Ich habe kein gutes Gefühl dabei, dass wir jetzt ein Release ausrollen, bei dem wir kaum was getestet haben. Bis auf das bisschen Rumgefrickel, das wir manuell gemacht haben. Das kannst du ja nicht ernsthaft als Ersatz für Komponententests oder Integrationstests sehen.“. Stille. Drei Sekunden vergingen, bevor Silke weitersprach. „Ich fahr voll ab auf dieses peinliche Schweigen“, sagte Silke. „Du auch? Mensch, Kurt, was ist los?“

„Silke, du hast keine Ahnung von Projektmanagement. Das ist wie ein Tigergehege und ich bin das Gnu.“

Silke konnte sich ein Grinsen nicht verkneifen.

„ Mein Kopf rollt, wenn wir nicht liefern. Unsere Geldgeber interessiert es einen Scheiß, ob wir ne Testabdeckung von 100 Prozent haben.“

Draußen regnete es. Kurt schloss das Bürofenster. Silke glaubte nicht, dass es der Regen war, der ihn dazu bewogen hatte.

„ Die zahlen nur, wenn wir das verdammte Release raushauen. Glaubst du, ich habe da Bock drauf? Glaubst du, ich weiß nicht, dass unsere Nutzer jetzt unsere Tester sind? Wenn du das Problem gelöst kriegst, dann kannst du gerne so viele Tests schreiben wie du lustig bist.“, und sein Tonfall hatte etwas Endgültiges an sich und machte deutlich, dass diese Angelegenheit nicht weiter besprochen werden würde.

Beim Rausgehen drehte er sich noch einmal zu ihr um.

„Manchmal müssen wir schlicht und einfach fest dran glauben, dass man ein Kaninchen aus dem Hut zaubern kann.“.

Sein Lächeln wirkte hölzern.



Na, erinnert dich das an was? Wer hat Recht? Silke oder Kurt? Oder beide? Ein klassisches Dilemma, wie man es aus der Literatur kennt. Oder aus dem Berufsalltag, wenn man in der Softwareentwicklung arbeitet, wie ich.


Ich komme im Laufe des Beitrags noch auf unsere beiden Protagonisten zurück, jetzt wird es aber erst mal psycho-logisch. Der Bindestrich ist gewollt ;)


DER INDUKTIVE FEHLSCHLUSS VON DER FEHLERFREIEN SOFTWARE DURCH EINE HOHE TESTABDECKUNG (KEIN PLÄDOYER FÜR KEINE TESTS!)


Nicht selten höre ich, gerade von Berufsanfänger/innen, den Satz, dass man mit Tests die Fehlerfreiheit der (zu erstellenden) Software beweisen könne.

Das ist falsch. Und das sage ich selten, denn oftmals führen mehrere Wege nach Rom, aber diese Aussage ist ein klassischer, induktiver Fehlschluss.


INDUKTIVER FEHLSCHLUSS


WAS BEDEUTET INDUKTION?


Dieses Wort stammt aus dem Lateinischen und bedeutet etwa Herbeiführung oder Veranlassung. Sie wird weithin auch als verallgemeinerndes Denken bezeichnet. Unter ihr versteht man die Ableitung einer allgemeinen Regel durch eine oder mehrere Bedingungen. In dieser Disziplin sind wir Menschen ziemlich gut.

Aber leider ist der induktive Wissenserwerb auch der Unzuverlässigste.

Induktive Lernergebnisse haben statistischen Charakter, da man aus einem begrenzten Faktenvorrat (einer sogenannten Lerndatenmenge) allgemeingültige Regeln gewinnen will. Die Gefahr resultiert hier aus der Tatsache, dass der Faktenvorrat begrenzt ist. Wenn du in Ägypten in einem Hotel arbeitest, und jeden Tag das Verhalten der deutschen Touristen beobachtest, wirst du irgendwann induktiv völlig korrekt zu einer bestimmten Hypothese gelangen. Nämlich zu der, dass es in Deutschland am Strand üblich sei, um 6 Uhr morgens - wie ein Hund, der sein Revier markiert - durch das Platzieren des eigenen Handtuchs seinen Liegeplatz für die nächsten 24 Stunden zu reservieren. Diese Hypothese ist vollkommen zulässig und sie gilt bis zu ihrer Widerlegung. Aber es ist eine Hypothese. Alles induktiv gewonnene Wissen sind letztlich Annahmen.


Ich zitiere mal ein sehr einprägsames Beispiel aus dem genialen Buch von Rolf Dobelli „Die Kunst des klaren Denkens“ (S. 330) [keine Werbung, der ist Spiegel-Beststeller-Autor, der muss nicht mehr von einem kleinen Blogger beworben werden ;)]:



„Eine Gans wird gefüttert. Anfangs zögert das scheue Tier und denkt: Warum füttern mich diese Menschen? Irgendetwas muss doch dahinterstecken. Wochen vergehen, doch jeden Tag kommt der Bauer vorbei und wirft ihr Getreidekörner vor die Füße. Ihre Skepsis lässt allmählich nach. Nach einigen Monaten ist sich die Gans sicher: Die Menschen sind mir zutiefst gutgesinnt. Eine Gewissheit, die sich jeden Tag aufs Neue bestätigt, ja, festigt. Vollends überzeugt von der Güte des Bauern staunt sie, als sie dieser am Weihnachtstag aus ihrem Gehege holt und - schlachtet.”


Die Gans ist dem induktiven Denken zum Opfer gefallen.




WAS BEWEISEN TESTS?



Aus der Korrektheit des Outputs eines Algorithmus für einige Inputs, kann nicht auf die Korrektheit des Outputs desselben Algorithmus für ALLE Inputs geschlossen werden.


Das genau wäre ein induktiver Fehlschluss.


Sind Tests dann nutzlos? Auf gar keinen Fall!


Sie beweisen allerdings nicht die Fehlerfreiheit.




Der berühmte Dijkstra (um hier weitere Abschweifungen zu vermeiden, verweise ich bezüglich dieses Pioniers der Informationstechnologie auf Wikipedia: https://de.wikipedia.org/wiki/Edsger_W._Dijkstra)




Durch Tests kann man stets nur die Anwesenheit, nicht aber die Abwesenheit von Bugs beweisen.

Aber das reicht aus. Gerade am Anfang der Entwicklung sind Komponententests (Unit-Tests) unerlässlich.


⚠️ **WARNING (HARDCORE NERDISH)** ⚠️


Wer sich dafür interessiert, wie man die Richtigkeit einer (Haskell-)Methode mittels mathematischer Beweisverfahren ermitteln kann, dem sei folgende Quelle (als Sprungbrett für weitere Artikel zu dem Thema) empfohlen:



TDD


DEFINITION


Was ist TDD, Test Driven Development?


Bei TDD wird der Test vor der Implementierung geschrieben. Das hat natürlich zur Folge, dass er zunächst nicht bestanden wird. Dies wird als Test-First bezeichnet und darum ist TDD keine Test-, sondern eine Designstrategie. Denn wird der Test zuerst geschrieben, so wird die Schnittstelle der zu testenden Komponente bereits benutzt, bevor sie tatsächlich existiert. Der Entwickler (m/w) bekommt frühestmöglich Feedback, ob das Design auch verwendbar sein wird.


PHASEN


[1.] CODE RED

Einen Test schreiben, der zunächst fehlschlägt.


[2.] CODE GREEN

Code schreiben, der den **Test mit den einfachsten möglichen Mitteln besteht**.


[3.] REFACTOR

Den neuen Code durch Refactoring glattpolieren, bspw. statt der hardcodierten Rückgabe der Zahl 7 (als Summe von 3 und 4) wird a + b returniert. Manche (Geschmacksrichtung "Triangulation", siehe weiter unten) halten diesen Step in der ersten "Iteration" für verfrüht und gestatten Abstraktionen erst nach einem weiteren Test. Das hängt stark von der Geschmacksrichtung ab, zu der wir noch kommen werden.



WOMIT ANFANGEN?


Du wirst dich jetzt vielleicht fragen: Wenn ich bei Greenfield beginne, so muss ich ja meine Tests nach den Anforderungen ausrichten. Mit der Umsetzung welcher Anforderungen sollte ich beginnen?


Du solltest die die Anforderung vornehmen, deren Umsetzung die geringste Entourage (Begriff "ausgeliehen" vom Anti-Pattern "Entourage" - https://ivanbrygar.com/2020/09/02/entourage-anti-pattern-and-stairway-pattern/ - last visit: 2022/12/04 - 15:56) mit sich bringt, sprich mit den wenigsten Abhängigkeiten verbunden ist.


GESCHMACKSRICHTUNGEN BEIM TDD


TRIANGULATION


Bei der Triangulation verallgemeinern wir den Code erst, wenn wir zwei oder mehr Tests geschrieben haben.


Daraus ergibt sich folgender Ablauf (die Beispiele sind Originalcode einer Mob-Programming-Code-Kata-Session = Mehrere Coder/innen lösen zusammen eine Kata, es war die berühmte Arabic-to-Roman-Numbers-Conversion-Kata - https://codingdojo.org/kata/RomanNumerals/).


Übrigens sind Code-Katas, am besten noch in der Gruppe gelöst, die beste Art, ohne Deadline-Druck, TDD zu üben.


Kleiner Tipp für Instruktor/innen: Mach gleich am Anfang der Code-Kata klar, dass es hier nicht ums gewinnen oder verlieren und schon gar nicht darum geht, wer den "coolsten Code" schreibt. Das nimmt besonders Anfänger/innen die Angst davor, sich vor der Gruppe zu blamieren.


Aber nun zum Ablauf:


[1.] Zuerst schreiben wir einen Test


[TestMethod]

[DataRow(1, "I")]

public void ConvertReturnsRomanNumberForSingleDigits(int number, string expected)

{

string result = this.sut.Convert(number);

result.Should().Be(expected);

}


sut dürfte bekannt sein, wenn nicht, Subject of test, also die zu testende Klasse. Wir erwarten bei der numerischen Eingabe 1 den string-Rückgabewert "I".


[2.] Dann machen wir die kleinstmögliche Änderung am Programmcode, damit er diesen Test erfüllt, z.B. geben wir nur den erwarteten Wert hartkodiert zurück.



public class ArabicToRoman

{

public string Convert(int number)

{

return "I";

}

}


"Warum denn keine "If"-Abfrage?", magst du dich jetzt fragen, wenn du noch kein so verstaubter Hase bist wie ich. Der Produktivcode ist für den Test "geblackboxt". Der Test will - dem Grundprinzip der Informatik folgend, EVA (Eingabe, Verarbeitung, Ausgabe) - bei einem Eingabewert von einer numerischen 1 einen Ausgabewert in Form einer Zeichenkette mit dem Inhalt "I" erhalten. Ihn interessiert nur E und A, was bei V, also der Verarbeitung passiert, ist dem Test sowas von egal. Und das passiert hier. Nicht mehr und nicht weniger. Dass auch "I" bei allen anderen Eingaben außer 1 returniert wird, mag irritierend sein, aber lediglich für Menschengehirne. Eine If-Clause wäre hier überflüssig. Und was überflüssig ist, gehört nicht in eine professionelle Codebase (YAGNI-Prinzip).


[3.] Dann schreiben wir (mindestens!) noch einen Test.


Wir haben damals gleich mehrere "Triangulationstests" geschrieben.



[TestMethod]

[DataRow(1, "I")]

[DataRow(2, "II")]

[DataRow(3, "III")]

[DataRow(4, "IV")]

[DataRow(5, "V")]

[DataRow(6, "VI")]

[DataRow(7, "VII")]

[DataRow(8, "VIII")]

[DataRow(9, "IX")]

public void ConvertReturnsRomanNumberForSingleDigits(int number, string expected)

{

string result = this.sut.Convert(number);

result.Should().Be(expected);

}


Und im Code immer nur plump, die jeweilige Ziffer zurückgegeben.


[4.] Erst jetzt abstrahieren wir die Implementierung


Das war der Code bei dem wir aufgehört haben. Eine **rekursive** Lösung, die auch bei mehrstelligen Ziffern funktionierte. Aber soweit ich mich entsinne, sind wir nicht ganz fertig geworden, also wer Fehler findet, kann sie behalten.




Diese Technik ist nützlich, wenn du dir nicht sicher bist, ob der generische Code, den du schreiben willst (also der höher abstrahierte), korrekt ist. Zwei oder mehr Assertions können dir mehr Vertrauen geben.



FAKE IT TIL YOU MAKE IT


Auch bei “Fake It” machen wir nach dem ersten Test nur die kleinstmögliche Änderung am Programmcode, um den einen Test zu erfüllen. Im Unterschied zur Triangulation schreiben wir kein zweites Testbeispiel, sondern verallgemeinern (= more abstraction) den Code im Refaktorisierungsschritt.


Also, in Steps:

[1.] Zuerst schreiben wir einen Test.

[2.] Dann machen wir die kleinste Änderung am Programmcode, damit dieser den Test erfüllt, z.B. durch Rückgabe des erwarteten Werts (hartkodiert).

[3.] Jetzt abstrahieren wir die Implementierung durch Refaktorisierung.


OFFENSICHTLICHE IMPLEMENTIERUNG


Bisher bestand die Implementierung nach dem ersten Test daraus, dass wir den erwarteten Rückgabewert als Konstante zurückgeben.


Aber müssen wir das tun, wenn doch ganz klar ist, wie die Implementierung aussieht?


Natürlich nicht. Wenn vollkommen klar ist, wie die Implementierung aussehen soll, coden wir sie einfach runter.



[Test] public void Sum_3plus4_Returns7()

{

Assert.That(Plus(3, 4), Is.EqualTo(7));

}

// Und dann gleich:

public int Plus(int a, int b)

{ return a + b;

}


VOR- UND NACHTEILE DES TDD


NACHTEILE


Ich fange mal mit dem - mit Riesenabstand - größten Nachteil an.


Den habe ich damals als Arbeitnehmer nicht so gespürt und hatte daher immer Verständnis, wenn TDD-Dogmatiker vehement darauf bestanden hatten, testgetrieben zu entwickeln. Heute, als Freiberufler und kleiner Unternehmer, sehe ich das anders. Und habe im Übrigen immer weniger Verständnis für starrsinnig-dogmatische Entscheidungen, deshalb auch das Beispiel mit Kurt und Silke am Anfang.


Am schlimmsten sind immer diese Haben-Wir-schon-immer-so-gemacht-Binärdenker/innen. Im Kopf ploppt bei mir dann immer Sokrates auf:

Nur der ist weise, der weiß, dass er es nicht ist.

Aber zurück zum größten Nachteil:


TDD kostet Zeit und damit Geld.


Setze ich strikt auf TDD, muss ich hier von Anfang an mehr Zeit einkalkulieren - trotz inzwischen fast vierjähriger TDD-Praxis (ein Gruß geht an die aus meiner Sicht beste Clean-Code-Kaderschmiede dieser Welt "M&M Software GmbH", dort habe ich gelernt, richtig gute Tests zu schreiben und nach TDD zu entwickeln; den Wert dieser Technik habe ich aber erst später kapiert) würde ich diesen Mehraufwand zwischen 10 und 30% ansetzen.


Nehmen wir einen fiktiven Stundensatz von 100 Euro und ein Projekt mit 100 Personentage (= 100 x 8 h = 800 Stunden). Nettopreis wäre hier 80.000 Euro. Ohne TDD! Nehme ich jetzt - zur Kalkulation mit TDD - den Median von 10 und 30 = 20 % und schlage diesen auf den Preis drauf, liege ich bei 96.000 Euro und / oder bei 120 Euro Stundensatz!


Soweit die Theorie.


In der Praxis konkurriere ich mit meist gleich mehreren Marktteilnehmer/innen, deren Qualitätsansprüche an die eigene Software ich - nach der Sichtung etlicher Codebases ohne auch nur einen einzigen Unit-Test - nur erahnen kann.


Nicht jeder Kunde richtet sich alleine nach der Preisführerschaft. Aber auch den "Nicht-Preissensitiven" kann ich keinen Aufschlag von 20 % mit der Begründung TDD verkaufen. Das kann ich überhaupt niemandem verticken und das habe ich erst begriffen, als ich selbst ein kleiner Unternehmer geworden bin.


Und von einem gebauchpinselten Ego kann ich meiner Familie und mir am Ende des Monats nichts kaufen.


Und den erheblich besseren Code, den ich durch TDD zweifelsohne erhalte?


Würde ich so lange bleiben wie ein Angestellter, dann würde ich in den meisten Fällen die erstaunten Gesichter noch sehen. Die Gesichter derer, die am Nutzen von TDD gezweifelt haben. Würde sie leise tuscheln hören: "Naja, der Code von unserer TDD-Nervensäger, der hat ja echt um die Hälfte weniger Bugs. Irgendwas scheint ja dran zu sein, an dieser Technik.".


Aber so lange bleibe ich meist nicht als als Scannerpersönlichkeit, die Projekte mit nicht allzu langen Laufzeiten bevorzugt.


So sehe ich den Code nie wieder. Den Kunden zwar gelegentlich schon, aber ich habe überwiegend Konzerne als Kunden und die haben Hunderte, wenn nicht Tausende von Projekten.


Warum also auf TDD bestehen, wenn es nicht gewertschätzt wird?


Und was passiert in Projekten, die über Projektvermittler -/innen (PV) laufen?

Da geben die PV das Angebot ab. Bin ich dann aus dem Schneider? Nein. Denn wenn ich als Freelancer Jens langsamer bin als Freelancer Kuddel, der nicht mal weiß, wie Unit-Test geschrieben wird, dann stehe ich sehr schnell auf der "Watchlist" des PO / Projektmanagers (m/w). Und dann heißt es "Warum ist Kuddel immer schneller als du?"


Auch hier wieder: Das gilt nicht für alle Kunden. Bei meinem jetzigen Kunden (Stand: 2022/12/04 - 23:59, EnBW) wird sehr auf Qualität geachtet und ich kann problemlos nach TDD vorgehen.


Alle anderen Nachteile, wie sie unter anderen unter den Begriff des Test Induced Damage zu subsumieren sind, will ich hier nicht weiter ausführen, sonst würde der Beitrag den Rahmen sprengen. Eines nur zum "Test induced damage": Ja, es ist was dran, dass sicherlich mehr Tests geschrieben werden als benötigt, weil man auch Getter- / Setter und sonstigen Nonsens testet, den man beim nachgelagerten Testschreiben auslassen würde. Und wie bereits erwähnt: YAGNI! Außerdem bedeutet mehr Code auch mehr Blut für die Bug-Vampire. Es ist also sehr wahrscheinlich, dass die Testcodebase auch befallen wird, von Bugs, wenn...ja, wenn man hier nicht mit der gleichen Sorgfalt (Clean Code und Co.) vorgeht, wie beim Schreiben des Produktivcodes. Auf der anderen Seite ist es so, dass ein Teil des Aufwands, den Tests mit sich bringen, im Verhältnis zum Produktionscode abnimmt, wenn die Codebasis wächst.


Manche bringen noch vor, dass TDD Übung erfordere. Stimmt. Genauso wie Coden überhaupt, wie Laufen (als 2jähriger war ich darin noch nicht so gut und mit 2 Promille bin ich darin auch nicht mehr gut), wie Gitarre-Spielen, wie Weitsprung, wie....


ABER, GIBTS DA NICHT WAS VON GITHUB (UND NICHT NUR VON RATIOPHARM)?



Wäre es nicht schön, wenn wir den Geschwindigkeitsnachteil irgendwie ausgleichen könnten? Einen Usain Bolt des Codens beiziehen könnten?

Dazu kommen wir gleich.


VORTEILE


Das sind für mich die Wichtigsten:

  • kein ungetesteter Code

  • saubere/testbare Architektur durch TDD als Designstrategie

  • keine/wenig Redundanzen durch gnadenloses rechtzeitiges Refaktorisieren

  • In seinem Buch »Test-driven Development by Example« vergleicht Kent Beck diese Arbeitsweise mit dem Heraufziehen eines Eimers aus einem Brunnen, bei dem die Kurbelwelle mit Zähnen ausgestattet ist, die beim Loslassen einrasten. Genau dies verspricht das Test-driven Development: zu jeder Zeit "clean code that works", also jederzeit ein so gut wie releasefähiges Projekt

  • kein unnötiger Code auf Vorrat

  • Konzentration auf das Wesentliche

  • Man hat eine boolesche Metrik für die Erfüllung der Anforderungen: die Tests werden bestanden oder nicht.

  • Das Refactoring, also das Aufräumen im Code ohne Funktionsänderung, führt zu weniger Fehlern; weil man dabei in kleinen Schritten vorgeht und stets entlang bestandener Tests, entstehen dabei wenige neue Fehler, und sie sind besser lokalisierbar.

  • Weil einfach und ohne großen Zeitaufwand getestet werden kann, arbeiten die Programmierer die meiste Zeit an einem korrekten System und also mit Zutrauen und konzentriert auf die aktuelle Teilaufgabe hin. (Keine „Durchquerung der Wüste“, kein „Alles hängt mit allem zusammen“)

  • Der Bestand an Unit-Tests dokumentiert das System zugleich. Man erzeugt nämlich zugleich eine „ausführbare Spezifikation“ - was das Softwaresystem leisten soll, liegt in Form sowohl lesbarer wie auch jederzeit lauffähiger Tests vor.

  • TDD führt in der Tendenz zu Programmcode, der stärker modularisiert ist sowie leichter zu ändern und zu erweitern (Open-Closed-Pinciple).

[UPDATE: 2023/04/04 - 17:00] In dem neuesten (2022 veröffentlicht) Werk von Robert C. Martin „Clean Craftsmanship - Best Practices, Standards und Ethik für die Softwareentwicklung" widmet er mehrere Kapitel dem TDD. Im zweiten Kapitel zeigt er auf beeindruckende und zugleich charmante Weise, wie er beim Versuch, für seinen Sohn einen „Primzahl-Checker“ zu schreiben, durch striktes Einhalten der TDD-Triangulation einen Algorithmus aus dem Code-Fels gemeißelt hat, der dem Sieb des Erasthotenes entsprach. Und das ganze komplett "freestyle". Im Gegenteil: Er zeigt sogar, dass ein vorher entworfenes Design in Form eines Klassendiagramms im Fall des Primzahlencheckers durch das Freimeißeln obsolet geworden ist.

Im dritten Kapitel gelingt ihm das auch für Bubble-Sort. Einige Seiten später auch für Quicksort. Hier allerdings musste ich schmunzeln. Kam mir etwas vor wie die aus dem nichts hervorsprudelnden Ideen für mathematische Beweise, die jeder, der / die mal eine Vorlesung zum Thema „mathematisches Beweisen“ behandelt hat, nur zu gut kennt. Immerhin muss man Uncle Bob zugute halten, dass er nach der Quicksort-Entdeckung einen wichtigen Satz hat fallen gelassen:

„Das bedeutet, dass das Erkennen von Weggabelungen und die Wahl des richtigen Wegs manchmal sehr wichtig sein kann. In diesem Fall führte uns ein Weg zu einem ziemlich schlechten Algorithmus und der andere zu einem sehr guten Algorithmus.“

(Robert C. Martin, vor Kap. 3.3. „Steckenbleiben“).

Die Fähigkeit, dies zu erkennen, erfordert schon fortgeschrittenes Wissen im Bereich TDD, so Uncle Bob. Das sehe ich auch so. Aber dieses Herausmeißeln von Algorithmen vergleiche ich als Hobby-Autor immer gerne mit dem von Stephen King beschriebenen Freilegen des Kerns einer Geschichte. Der weltberühmte Schriftsteller gehört ja zu den Autoren, die ihre Romane nicht planen, sondern drauflosschreiben. (Was man bei jemanden, der sein Leben lang Bestseller geschrieben hat so drauflosschreiben nennt...). Robert C. Martin macht deutlich, dass man dieses „Draufloscoden“ auch mit TDD als Sicherheitsnetz wagen kann. Etwas, das gerade UML-Muffel freuen wird.



GIT-HUB-COPILOT ALS TDD-PILOT


Die Lösung zur Überwindung des Zeitproblems als größter Nachteil beim Coden ist GitHub Copilot. Das ist der Usain Bolt des Codens. Aber auch nur unter ganz bestimmten Voraussetzungen.


Die erste wäre: Diese Methodik ist nichts für Junior/innen. Anfänger/innen sollten auf keinen Fall mit Git-Gub-Copilot anfangen oder meldest du deinen 6jährigen Sohn nach der ersten Fahrt auf seinem Kettcar gleich zur Rallye Paris-Dakar an? Am besten noch unter Mitnahme des Ironman auf Hawaii vorher, weil er ja zum Gartenpool gerannt ist, 3 Runden drin geplanscht hat und anschließend mit seinem Kinderfahrrad bis zu Opa gedüst ist.




Ich komme gleich noch dazu, warum das brandgefährlich ist. Also nicht das Kettcarfahren unter glühender Saharasonne, sondern das "Zugänglichmachen" dieser Methodik für Anfänger/innen.


GITHUB-COPILOT UND VISUAL STUDIO (ALLEINE): KEIN GUTES DUO


GitHub und damit auch GitHub gehört zu Microsoft (https://news.microsoft.com/announcement/microsoft-acquires-github/ - last visit: 2022/12/05 - 22:38). Wissen vielleicht einige nicht. Warum erwähne ich das?


Weil Git-Hub-Copilot von den meisten Visual-Studio-Nutzer/innen grottenschlecht bewertet wird.





Und Visual Studio (im Folgenden VS) stammt ebenfalls von Microsoft und ist für uns .NET-Entwickler/innen das was für den Maurer die Maurerkelle ist. Oder für die Friseurin die Schere.

Das ist unser Haupthandwerkszeug.

Ich muss immer schmunzeln, wenn ich Visual Studio in Projektbeschreibungen als Requirement lese. Wäre das gleiche, als wenn ich eine Köchin frage, ob sie schon mal nen Kochtopf gesehen hätte.


Aber zurück zu den schlechten Bewertungen.


Sie sind zurecht vergeben worden. Meine Entwickler-Kolleg/innen haben schlichtweg Recht.


Denn in VS läuft Git-Hub-Copilot gefühlt 10 Mal schlechter als bspw. in Rider. Oder in Visual Studio Code.


Und hier bin ich mal wieder superglücklich, dass ich mir als Freiberufler aussuchen kann, mit welcher Maurerkelle, ich die Wand hochziehe. Hauptsache, sie steht am Ende stabil dar. Und nicht wie das erste Haus im Märchen von den 3 Schweinchen und dem Wolf. Und wie wir das stabil kriegen? Du ahnst es...mit TDD natürlich ;)


Also, wenn du vorhast, meinen TDD-mit-GitHub-Copilot-Ansatz auszuprobieren, empfehle ich dir folgende Kombi:

- VS in der Community-Version

- ABER ACHTUNG: Lizenzfalle! Wenn du für als Arbeitnehmer für ein Unternehmen arbeitest, solltest du das beachten:

"Ein „Unternehmen“ ist jede Organisation und ihre verbundenen Unternehmen, die gemeinsam (a) mehr als 250 PCs oder Nutzer oder (b) eine Million US-Dollar (oder den Gegenwert in anderen Währungen) Jahresumsatz hat. „Verbundene Unternehmen" bezeichnet die juristischen Personen, die (über Mehrheitsbesitz) einen beherrschenden Einfluss auf ein anderes Unternehmen ausüben können oder unmittelbar oder mittelbar kontrolliert werden."

D.h., wenn dein Unternehmen die vorbezeichneten Kriterien erfüllt, darfst du Community NICHT verwenden. Dann musst du PRO oder ENTERPRISE lizensieren (lassen).

UND

- Rider von Jetbrains || Visual Studio Code

ODER:

nur Rider von Jetbrains || Visual Studio Code


Ich selbst darf als Freelancer VS in der Community-Version nutzen und das schwerfällige Tool schleppe ich nur mit, weil das beste Addon für TDD, "NCrunch", nur für VS existiert. Zudem nutze ich als Softwarearchitekt auch "NDepenD", das ebenfalls nur für VS erhältlich ist.


Wie ich genau vorgehe in der Kombi, dazu gleich mehr.


Vorher komme ich zur Beantwortung der Frage...


WAS IST GITHUB-COPILOT?


Ich bin ein Freund davon, immer erst die Schöpfer/innen bzw. deren Quelle zu nennen:

https://github.com/features/copilot - last visit: 2022/12/05 - 22:39.


Ich halte übrigens nichts davon, in einem Blogbeitrag dessen Inbetriebnahme zu zeigen. Ist erstens supereinfach und zweitens vermutlich bereits überholt, nachdem meine digitale Tinte getrocknet ist. Zumal alles in der Doku (siehe "Mutterquelle") steht.



Mein Kollege und ich haben GitHub-Copilot Copi getauft. Zwar ist er geschlechtslos, Copi und nicht etwa der Kollege, der ist ein ganzer Kerl ;), aber für uns Männer ist Copi ein Mann. Falls du eine Frau bist, nenn sie Copine und denk dir die weibliche Form der Artikel und Pronomen im Folgenden an die Stelle der männlichen Varianten. Oder zeig mich bei Alice Schwarzer an.


Aber was ist Copi nun?


Eines ist er auf keinen Fall: Unumstritten.


Das will ich hier keinesfalls unter den Teppich kehren. Aus Platz- und Zeitgründen folgt hier aber nur eine stichwortartige Aufzählung der Kritikpunkte:


- Zurecht sind viele Entwickler/innen stocksauer, dass deren Code von Copi geklaut wurde (https://winfuture.de/news,132836.html - last visit: 2022/12/06 - 23:21, "GitHub Copilot: Entwickler reichen Sammelklage gegen Microsoft ein"). Hier müssen meine Ex-Kolleg/innen, die Jurist/innen, eine höchstrichterliche Entscheidung fällen und / oder im UrhG (vgl. §§ 69a ff. UrhG) nachbessern.

- Darauf gehe ich später noch einmal in anderem Gewand ein: https://t3n.de/news/github-copilot-sicherheit-studie-1403211/ - last visit: 2022/12/06 - 23:25 "Experiment: GitHub Copilot erzeugt in 40 Prozent der Fälle unsicheren Code".


Aber was ist Copi denn nun?


"GitHub Copilot is a cloud-based artificial intelligence tool developed by GitHub and OpenAI to assist users of Visual Studio Code, Visual Studio, Neovim, and JetBrains integrated development environments (IDEs) by autocompleting code.[1] Currently available by subscription to individual developers, the tool was first announced by GitHub on 29 June 2021, and works best for users coding in Python, JavaScript, TypeScript, Ruby, and Go.[2]" (https://en.wikipedia.org/wiki/GitHub_Copilot - last visit: 2022/12/06 - 23:27).



Wenn du mehr wissen willst, dürfte es ein Leichtes sein, dies aus den vorgenannten Quellen zu extrahieren.


VORGEHEN: TDD MIT GITHUB-COPILOT


Ich schreibe Tests in Visual Studio und dann lasse ich Copi den Produktivcode in Rider schreiben, sofern er dazu in der Lage ist.


Viele überschätzen hier Copi. Ich arbeite ja mit C# und Python (und diversen anderen Sprachen) und der Unterschied ist hier schon gravierend. Genauso groß wie zwischen Visual Studio und Rider. In Python, der "Leib- und Magensprache" der AI-Coder/innen, kriegt Copi gefühlt 70 % mehr hin als in C#. Hier muss ich außer bei simplen Standardproblemen, meist vorlegen. Aber, wenn Copi genug von meiner "Codebase klauen" kann, ist es ein guter Kollege und trägt erheblich zum Geschwindigkeitsgewinn bei.


Wenn es allerdings klappt und es sich nicht um zu individuelle Stellen handelt, dann habe ich einen gigantischen Geschwindigkeitsvorteil, den mir kein(e) 10-X-Developer(in) der Welt streitig machen kann. Außer vielleicht Stephen Kings Rasenmähermann.



🚧CAUTION TRAP!🚧


ICH WIEDERHOLE MICH: Für Anfänger/innen ist das auf keinen Fall zu empfehlen, denn Copi hat von der Masse gelernt. Siehe oben bei Kritik ("Experiment: GitHub Copilot erzeugt in 40 Prozent der Fälle unsicheren Code").


Da waren sog. "10x-Coder/innen" dabei, also richtige gute Kolleg/innen, genauso wie Leute, die gerade mit dem Coden angefangen haben. D.h. der Code ist nicht brillant, sondern allenfalls Durchschnitt. Demzufolge muss Copis Code genauso gereviewed werden wie der von lebendigen Kolleg/innen.


Ich habe das auch schmerzlich lernen müssen, denn simple Fehler erkennt Copi natürlich von selbst (Copis Adern bestehen aus reiner Mathematik - zu der auch die Aussagenlogik und die Prädikatenlogik zählen). Ich hatte mir durch blindes Vertrauen auf die Korrektheit von Copis Code einen richtig fiesen Multi-Threading-Bug eingefangen, der nur "sporadisch" auftrat (bei Concurrency muss immer die Dimension Zeit einbezogen werden). Bis ich den entdeckt hatte, war viel Wasser die Wupper runtergelaufen. Und ich habe natürlich nur in meinem Code gesucht, am Anfang zumindest.


Zur Fehlbarkeit von AI-basierten System, siehe meine letzte Podcastfolge:


Hätte ich den Fehler durch Unit-Tests entdecken können? Ja. Warum habe ich ihn nicht entdeckt? Weil ich Copi habe den Test schreiben lassen - den einzigen, danach nie wieder... ;) In meiner Anfangszeit mit Copi musste auch ich noch viel lernen, wie ich mit meinem neuen digitalen Hausgenossen umzugehen habe.


Was sagt uns das? Schreibe die Tests immer selbst, zumindest bei C#.


Bei Python schreibe ich zwar auch Tests, hier habe ich aber die Erfahrung gemacht, dass Copi hier erheblich besser abschneidet.


Ich höre zudem schon die Unkenrufe: Wie faul muss man sein, dass man den Code nicht selbst schreibt?


Choose a lazy person to do a hard job. Because a lazy person will find an easy way to do it.

(Bill Gates)


Meist sind diese Unkenrufer/innen auch dieselben, die stets betonen, dass wir das immer schon so gemacht hätten.


Ich verdiene fünfstellig im Monat, weil ich so einen Schwachsinn immer ignoriert habe und weil mir die Meinung der Geistig-Unbeweglichen schon immer am Allerwertesten vorbei ging.


Kann schief gehen mit GitHub-Copilot und TDD, klar, kann aber auch ne richtige Effizienzrakte werden.


Ein Freund von mir hat immer gesagt:


"Wer nichts risikiert, trinkt auch keinen Champagner".

Recht hat er.


Und was ist mit Silke und Kurt, wer hat nun Recht?


Beide.


Kurt muss jetzt erst mal den Karren aus dem Dreck ziehen, er trägt aber auch die Verantwortung dafür, dass er drin steckt. Insofern sollte er nach Beruhigung der "Gläubiger" (Anteilseigner, Finanziers) auf Silke hören und schnellstmöglich Tests schreiben (lassen).

1 comment
bottom of page