Coding-Kata in der Praxis

von Matthias Streidel | 19. Dezember 2022 | Allgemein, Deutsch, Software Engineering, Tools & Frameworks

Matthias Streidel

Senior Developer

Dieser Artikel baut auf dem ersten Blogpost zum Thema Coding-Kata auf. Er soll durch Beispiele das Thema weiter konkretisieren und damit den Kern der Methode verdeutlichen.

Recap zum Thema Coding-Kata

Das Exerzieren von Coding-Kata ist nichts anderes als das Verwenden eines Werkzeugs (zu Übungszwecken) – nicht mehr, aber auch nicht weniger.

Kaum ein Entwickler hat genug Zeit, Kreativität oder Lust sich jedes Mal eine neue Aufgabe auszudenken, um z.B. in ein neues Framework hineinzuschnuppern. Coding-Kata schafft einen festen Rahmen, in welchem sich die Entwickler frei bewegen können. Einzig der Zweck ist vorgegeben. Insbesondere beim selbstständigen Arbeiten wird dadurch die Gefahr eines Abschweifens vom eigentlichen Ziel minimiert. In einer guten Kata wird der Entwickler gezwungen, zumindest in der einfachsten Form, die elementarsten Grundbausteine der Programmierung zu verwenden (beispielsweise Input erhalten und verarbeiten, for-Schleifen, if-Bedingungen, Output ausgeben). Was mit diesen Grundbausteinen gemeint ist, wird anhand der Kommentare in den Codebeispielen weiter unten erläutert.

Bei Coding-Kata handelt es sich um ein Werkzeug mit mehreren Einsatzzwecken:

  1. Lernen bzw. Üben (z.B. Programmiersprachen, Programmieren allgemein, Zusammenarbeit im Team, TDD, …)
  2. Bewertung von Technologien, Frameworks oder Methodiken
  3. Assessment eines Bewerbers im Interview

Gleichzeitig stellt Coding-Kata ein tolles Werkzeug dar, um sich bestimmte Gewohnheiten anzutrainieren. Dies kann TDD sein, oder aber das Verwenden von Git in der Konsole, einem externen Tool wie Fork oder der Versionsverwaltung der IDE. Erläutert wird letzteres Beispiel später.[1]

Lernen bzw. Üben und Bewertung Technologien/Frameworks oder Methodiken

Die Aspekte Lernen bzw. Üben und Bewertung von Technologien/Frameworks gehen Hand in Hand und werden deshalb gemeinsam behandelt: Bei beidem lernt der Ausführende etwas dazu, und sei es im Rahmen von Projekten, bei denen das Lernen meist niedriger priorisiert ist, als das Ergebnis der Evaluation selbst.

Im folgenden Beispiel nehmen wir an, das Ziel sei das Üben einer Methodik: Test Driven Development (TDD) mithilfe der Kata FizzBuzz. Red, Green, Refactor stellt dabei den Zyklus dar, nach welchem programmiert wird. Es zeigt etwas überspitzt, wie man mit vielen sehr kleinen Schritten langsam immer mehr Funktionalität ergänzt. Die Erklärung des Vorgehens beschränkt sich außerdem auf das ’Fizz‘. ‘Buzz‘ und ‘FizzBuzz‘ werden analog implementiert und getestet.

Der Code zum Beispiel unten kann von Github geklont werden. Anhand der Commits kann der Zyklus Red, Green, Refactor besser nachvollzogen werden

Als Erstes wird folgender Test implementiert:

@Test
fun `that returns the input`() {
	assertThat(sut.fizzBuzz(1)).isEqualTo("1")
}

Dieser wird bei folgender Implementierung fehlschlagen (ROT):

// Grundbaustein 1: Input erhalten
fun fizzBuzz(input: Int): String {
	return ""
}

Der erste Schritt ist getan, ein fehlschlagender Test ist implementiert. Nun wird die Implementierung so geändert, dass der Test gefixt ist (GRÜN):

fun fizzBuzz(input: Int): String {
	return "1"
}

Danach wird der Test erweitert (oder eine weitere Testmethode ergänzt), was aufgrund der noch fehlenden Implementierung, wieder zu fehlschlagenden Testresultaten führt (ROT)[2]:

@Test
fun `that returns all numbers from 1 to 10`() {
	assertThat(sut.fizzBuzz(10))
		.isEqualTo("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]")
}

Beide Tests werden wieder GRÜN mit:

fun fizzBuzz(input: Int): String {
	// Grundbausteine 1 und 2: Verarbeiten des Inputs und Verwendung einer for-loop
	return (1..input).map { it.toString() }.toString()
}

Zum ersten Mal erscheint eine kleine Refaktorisierung sinnvoll: Der erste toString()-Aufruf kann entfernt werden:

fun fizzBuzz(input: Int): String {
	return (1..input)
		.map { it }.toString()
}

Nun beginnt der Zyklus wieder von vorne. Am Ende könnte das Ergebnis wie folgt aussehen:

// Grundbaustein 3: Konditionale Verarbeitung bzw. Verwendung einer if-Bedingung
fun fizzBuzz(input: Int): String {
	return (1..input).map {
		when {
			it % 15 == 0 -> "FizzBuzz"
			it % 3 == 0 -> "Fizz"
			it % 5 == 0 -> "Buzz"
			else -> it
		}
	}.toString()
}

Vor der Implementierung neuer Funktionalität, wie dem Umwandeln von Werten, die durch 3 teilbar sind, wird ein entsprechender Test hinzugefügt oder bestehende Tests angepasst. Dieser schlägt daraufhin natürlich fehl, weil das getestete Verhalten noch nicht implementiert ist (ROT). Danach wird das Verhalten der Methode angepasst, um den Test wieder erfolgreich durchlaufen zu lassen (GRÜN).

Aus eigener Erfahrung: in der Praxis kommt es häufiger vor, dass man den Test gar nicht vor dem Verhalten implementiert, zum Beispiel weil man bestehende Logik anpasst. Es ist besser Test zumindest erst einmal fehlschlagen zu lassen, anstatt gleich die nötigen Anpassungen vorzunehmen.

Trotzdem gilt, ein fehlerhaft programmierter Test, der beispielsweise immer grün ist, kann am leichtesten vermieden werden, indem er vor der zu testenden Logik geschrieben wird. Ansonsten wiegt er nicht nur den Entwickler selbst, sondern auch andere in falscher Sicherheit, mal abgesehen davon, dass er die vorhergesehene Funktionalität gar nicht testet. Ich schreibe meine Tests leider oft auch nicht zuerst, gerade wenn ich explorativ arbeite. Aber ich lasse jeden Test zunächst fehlschlagen! TDD mithilfe von Coding-Kata zu üben, hilft dabei dieses Vorgehen zu verinnerlichen.

Assessment eines Bewerbers im Interview

Einen Bewerber beim Lösen einer Programmieraufgabe zu beobachten kann ungemein helfen, dessen Können und Erfahrung, vor allem aber dessen Vorgehen einzuschätzen. Mit welchen Gedankengängen macht er sich an die Problemstellung heran? Um dies zu bewerten, sollte der Kandidat als Navigator beim Programmieren fungieren, ein Entwickler der interviewenden Firma übernimmt die Rolle als Driver: Somit ist die IDE und deren Konfiguration dem Bewerber egal, nur der Driver muss sich mit der Entwicklungsumgebung auskennen. Nun muss der Navigator außerdem seine Gedankengänge verständlich verbalisieren können und es wird schneller klar, ob ein Verständnis der behandelten Materie existiert.

Beliebte Aufgabenstellungen sind der bereits bekannte FizzBuzz, die Implementierung eines Videoverleihs oder Conway’s Game of Life.

Weiteres Beispiel: Version Control insb. Git

Ein weiteres Ziel von Coding-Kata kann sein, den Umgang mit Git in der IDE oder Konsole zu trainieren. Das geht sowohl alleine als auch im Team und im Entwicklungsalltag kann der Entwickler sich später dann auf den Code konzentrieren, weil der Umgang mit Git in den allermeisten Situationen reine Routine ist. Es ist besser konzentriert die Verwendung von Git zu üben als sich dann im Arbeitsalltag nebenher mit den Feinheiten von Git rumschlagen zu müssen und gleichzeitig Produktivcode einer weiteren Fehlerquelle auszusetzen.

Gruppen-Kata

Genau wie beim selbstständigen Coding-Kata, geht es beim Gruppen-Kata nicht darum, Programmieren zu lernen, sondern mithilfe der Kata einen festen Rahmen zu schaffen. Ein gut eingespieltes Dev-Team ist wertvoll, gerade bei neuen Projekten werden Teams aber geradezu zusammengeworfen. Die Mitglieder müssen sich erst noch aufeinander einspielen. Mithilfe gemeinsam ausgeführter Coding-Kata kann die Team-Interaktion beobachtet werden, um generelle und individuelle Stärken und Schwächen aufzudecken.

Ein weiterer Vorteil solch gemeinsamer Coding-Kata-Sessions ist das direkte Feedback durch andere Teilnehmer. Man sieht wie jemand anderes dieselbe Logik implementieren würde und erhält konstruktive Kritik zu eigenen Code und Vorgehen.

Zum Abschluss

Wer Interesse am Ausprobieren von Coding-Kata hat und etwas anderes als den bekannten FizzBuzz implementieren möchte, findet auf kata-log.rocks eine Sammlung interessanter Kata. Die Seite ermöglicht es auch Themen und Beschränkungen zu filtern, um die bestgeeignete Kata-Aufgabe für den gewünschten Anlass zu finden.

[1]: Die Verwendung von Streams stellt eine ziemlich coole Alternative dar.

[2]: Besser wären in dieser Situation property-based Tests geeignet gewesen.