Schlagwort-Archive: Dojo

IList Dojo Retrospektive

Retrospektive

Image courtesy of loop_oh/


In diesem Artikel führe ich eine Nachbetrachtung zum IList Dojo durch.

Nachdem ich meine fünfteilige Artikelreihe (Links zu den einzelnen Teile am Ende dieses Artikels) beendet hatte, habe ich die Lösung von Stefan Lieser angeschaut. Seine darin beschriebenen Erfahrungen habe ich mit meinen verglichen und seine Umsetzung meiner Umsetzung gegenübergestellt. Dabei geht es mir nicht hauptsächlich darum, ob meine Lösung richtig oder falsch bzw. besser oder schlechter ist. Ich möchte vielmehr betrachten, ob es andere Herangehensweisen und andere Lösungsansätze gibt und weitere Schlüsse aus dem Dojo ziehen können.

Dojo-Start

Der Solution-Aufbau beinhaltet besteht bei beiden aus einem Implementations- und einem Test-Projekt. Auch Stefan Lieser hat sich dafür entschieden, die Test-first-Vorgehensweise anzuwenden. Er verwendet aber nicht Visual T# sondern C# um die Tests zu implementieren. Zudem hat er noch NCrunch eingesetzt, ein Tool das die Tests automatisch im Hintergrund ausführt. Das Resultat wird dabei direkt in Visual Studio im Source Code Editor auf der jeweiligen Zeile angezeigt. Sicher ein Tool, das sich lohnt einmal genauer anzuschauen.

Die erste Methode

Bei den ersten Methoden sind wir auch ähnlich vorgegangen. Wir haben uns beide entschieden, mit Tests zu Count und Add() zu beginnen, um erste Resultate zu haben. Auch den internen Datencontainer haben wir in diesem Schritt eingeführt.

Hier trennen sich unsere Wege

Als nächstes hat sich Stefan Lieser an den Enumerator heran gewagt, den ich erst in Teil 4 implementiert hatte. Ich hatte mich als nächstes auf den Indexer konzentriert, welcher dafür von Stefan Lieser erst zum Schluss implementiert wurde.

Dementsprechend sind dann auch in den nächsten Schritten unterschiedliche Methoden angepackt worden. Stefan Lieser hat auch noch Themen angedacht, die ich nicht bedacht hatte, wie z.B. Performance-Betrachtungen.

Kreuzverhör

Doch wie sieht das Resultat aus?

Machen wir den Kreuztest: Meine Tests führen die Bibliothek von Stefan Lieser aus und seine Tests benutzen meine Library. Im Idealfall würden beide Testsuiten keine Fehler melden, alle Tests wären grün und wir könnten zufrieden Feierabend machen.

Aber wie so oft entspricht die Realität nicht dem Idealfall. In diesem Fall heisst das, dass meine Library in Stefan Liesers Testsuite 6 Fehler verursacht (von insgesamt 31 Tests) und seine Bibliothek in meiner Testumgebung 11 Fehler provoziert (bei einem Total von 39 Tests).

Doch was sind die Gründe für diese Fehler. Haben wir Fehlerfälle vergessen, interpretieren wir die Vorgaben des Interfaces IList<T> anders oder gibt es andere Gründe?

Untersuchung 1: Meine Bibliothek

Meine Library verursacht zwei fehlschlagende Tests, da ich beim Aufruf von RemoveAt() mit einem Index ausserhalb der erlaubten Werte eine ArgumentOutOfRangeException werfe. Die Testfälle erwarten aber eine IndexOutOfRangeException. Laut der Dokumentation von Microsoft soll aber die ArgumentOutOfRangeException geworfen werden, auch wenn die IndexOutOfRangeException wohl passender wäre.

Ein Fehler tritt auf, da ich diesen Test vergessen hatte: Löschen eines Elementes aus der Liste mittels Remove() wenn die Liste noch leer ist. Dieser Aufruf warf bei mir bei der Suche in IndexOf() wegen einer vergessenen Überprüfung eine Exception, statt wie erwartet false zurückzugeben. Dieser Fehler hatte auch zur Folge, dass zwei weitere Tests fehlschlugen, die ebenfalls eine leere Liste verwendeten.

Der letzte Test schlägt fehl, da Stefan Lieser die Methode CopyTo() anders interpretiert als ich. Der zweite Parameter dieser Funktion gibt laut Dokumentation von Microsoft an, ab welcher Position im Array die Daten eingefüllt werden sollen („The zero-based index in array at which copying begins.“). Stefan Lieser hat diesen Parameter so interpretiert, dass dies der Startindex in der LinkedList ist und die Werte vorher ignoriert werden sollen. Hier eine Grafik, die den Unterschied darstellen soll:
CopyTo
Ein kurzer Test des Verhaltens von List<T>’s CopyTo() zeigt das von mir beschriebene Verhalten, dementsprechend entspricht aus meiner Sicht das Verhalten von Stefan Liesers Implementation nicht den Anforderungen des Interfaces. Aber die Dokumentation von IList<T> dürfte in diesem Bereich noch klarer sein, besonders da der Parametername Interpretationen zulässt. Bei der Dokumentation von List<T>’s CopyTo() hat es ein Beispiel, welches das Verhalten ebenfalls aufzeigt.

Untersuchung 2: Stefan Liesers Bibliothek

Bei meinen Tests, losgelassen auf Stefan Liesers Bibliothek, kommen die beiden Diskrepanzen auch wieder zum Vorschein. Die unterschiedlichen Exceptions sind für zwei fehlgeschlagene Tests verantwortlich. Ebenfalls für zwei fehlgeschlagene Tests ist die unterschiedliche Interpretation bei der CopyTo()-Methode verantwortlich.
Die restlichen 7 Tests schlagen Aufgrund fehlender Überprüfung der Parameter fehl. Dies betrifft den Indexer, die Insert()- und die CopyTo()-Methode, wobei bei der letzten ein Test von mir auch nicht optimal ist, da ich Nichts (eine leere Liste) Nirgends (null als array) hin kopieren will.

Fazit

Diese Nachbetrachtung fand ich lehr- und aufschlussreich. Zum einen habe ich in meinem Code noch Fehler entdeckt und dadurch gesehen, dass mir trotz Test-first-Vorgehensweise mögliche Fehlerfälle durch die Lappen gingen. Zum andern wurde mir wieder einmal aufgezeigt, dass die Schnittstellenbeschreibungen unterschiedlich aufgefasst werden können und am besten mit einer öffentlich zugänglichen Beispielimplementation gefestigt werden. Dies trifft natürlich entsprechend auch für APIs und Protokolle zu.

Trotz identischem Start haben sich die Wege getrennt und es sind teilweise unterschiedliche Resultate herausgekommen. Grosse Unterschiede bei der Herangehensweise oder bei den Lösungsansätzen sind mir nicht aufgefallen. Bei den Details gab es aber Unterschiede, die im Einsatz der Bibliothek dann auch zu Problemen führen könnten.

Artikelreihe „LinkedList Dojo mit Visual T#“

LinkedList Dojo mit Visual T# – Teil 5

Karate

Image courtesy of „luigi diamanti“ / FreeDigitalPhotos.net


Von den am Anfang des LinkedList Dojo Teil 4 aufgeführten noch fehlenden Implementationen haben wir erst die GetEnumerator()-Funktionen abgehakt. Packen wir nun die noch fehlenden Funktionen und Properties an. Den in Teil 4 implementierten Enumerator wollen wir nun auch nutzen, um einige von diesen Funktionen zu schreiben.

Die Kopie ist besser als das Original

Nehmen wir uns der CopyTo-Methode an. Als erstes die Tests (ich erlaube mir, gleich zwei Tests zu erstellen).


GeSHi Error: GeSHi could not find the language tsharp (using path /home/rolandba/public_html/blog/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Dank dem Enumerator ist die Implemetation der Funktion fast schon geschenkt:

1
2
3
4
5
6
7
public void CopyTo(T[] array, int arrayIndex)
{
    foreach (T item in this)
    {
        array[arrayIndex++] = item;
    }
}

Und noch ein paar Test für die Fehlerfälle:


GeSHi Error: GeSHi could not find the language tsharp (using path /home/rolandba/public_html/blog/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Und die dazugehörige Implementation mit den Überprüfungen:

1
2
3
4
5
6
7
8
9
10
public void CopyTo(T[] array, int arrayIndex)
{
    if (array == null) throw new ArgumentNullException("array");
    if (arrayIndex  array.Length) throw new ArgumentException("The number of elements in the source collection is greater than the available space from arrayIndex to the end of the destination array.");

    foreach (T item in this)
    {
        array[arrayIndex++] = item;
    }
}

Ich möchte da noch etwas hinzufügen

Unter anderem fehlt noch die Funktion Insert. Auch hierzu zuerst wieder die Tests.


GeSHi Error: GeSHi could not find the language tsharp (using path /home/rolandba/public_html/blog/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Auch hier wieder die Implementation der Funktion, welche die Tests erfüllt.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void Insert(int index, T item)
{
    if (index == Count)
    {
        Add(item);
        return;
    }

    LinkedListElement element = new LinkedListElement();
    element.Element = item;

    if(index == 0)
    {
        element.Next = first;
        first = element;
        return;
    }

    LinkedListElement priorIndex = GetAt(index - 1);
    element.Next = priorIndex.Next;
    priorIndex.Next = element;
}

Wiederum benötigen wir noch Tests für die Fehlerfälle:


GeSHi Error: GeSHi could not find the language tsharp (using path /home/rolandba/public_html/blog/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Diese führen dann zu der folgenden Implementation der Insert-Funktion.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public void Insert(int index, T item)
{
    if (index  Count) throw new ArgumentOutOfRangeException();

    if (index == Count)
    {
        Add(item);
        return;
    }

    LinkedListElement element = new LinkedListElement();
    element.Element = item;

    if(index == 0)
    {
        element.Next = first;
        first = element;
        return;
    }

    LinkedListElement priorIndex = GetAt(index - 1);
    element.Next = priorIndex.Next;
    priorIndex.Next = element;
}

Weg damit

Für die RemoveAt-Funktion implementiere gleich alle Tests auf einmal.


GeSHi Error: GeSHi could not find the language tsharp (using path /home/rolandba/public_html/blog/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Dann schaue ich, dass alle Tests grün werden.

1
2
3
4
5
6
7
8
9
10
11
12
public void RemoveAt(int index)
{
    if (index = Count) throw new ArgumentOutOfRangeException();
    if (index == 0)
    {
        first = first.Next;
        return;
    }

    LinkedListElement priorIndex = GetAt(index - 1);
    priorIndex.Next = priorIndex.Next.Next;
}

Und wenn wir schon am entfernen sind packen wir die Remove-Funktion auch noch an. Wie gewohnt zuerst die Tests:


GeSHi Error: GeSHi could not find the language tsharp (using path /home/rolandba/public_html/blog/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Der letzte Test überprüft dabei, dass das erste Auftreten eines Elementes entfernt wird. In der dazu passenden Implementation können wir von bereits bestehenden Funktionen profitieren:

1
2
3
4
5
6
7
8
public bool Remove(T item)
{
    int pos = IndexOf(item);
    if (pos &lt; 0) return false;

    RemoveAt(pos);
    return true;
}

Bitte nicht berühren!

Für das letzte Property kann man sicherlich darüber streiten, ob ein Test nötig ist. Aber der Vollständigkeit halber auch hier zuerst der Test.


GeSHi Error: GeSHi could not find the language tsharp (using path /home/rolandba/public_html/blog/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Und das implementierte Property IsReadOnly:


GeSHi Error: GeSHi could not find the language tsharp (using path /home/rolandba/public_html/blog/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Somit ist die Klasse LinkedList fertig implementiert. Doch bevor wir uns wieder dem Tagesgeschäft zuwenden wollen wir noch einen Blick zurück werfen und schauen, was wir gelernt haben und wo noch Potential für Verbesserungen besteht.

Retrospektive

Als erstes stellt sich die Frage: Haben wir das Ziel erreicht?
Das dürfen wir mit gutem Gewissen mit ja beantworten, wir haben eine funktionstüchtige Klasse implementiert, die durch Tests abgesichert ist.

Was aber noch wichtiger ist: Was haben wir auf dem Weg zum Ziel gelernt?
Der Test-First-Ansatz hat mir gut gefallen, auch wenn ich mich manchmal zurückhalten musste, gleich mit der Implementation vorzupreschen, ohne den Test geschrieben zu haben. Einige wenige Male habe ich schon eine zusätzliche Überprüfung eingebaut, bevor ich diese durch einen Test nötig gemacht hatte. Aber wenn es sich um eine einzelne Überprüfung handelt und man den Test gleich nachliefert ist das aus meiner Sicht legitim. Wir wollen ja nicht päpstlicher als der Papst sein…
Was den Einsatz von Visual T# für die Tests anbelangt bin ich nicht so begeistert. Einige Sachen gefallen mir (z.B. die klare Trennung zwischen Vorbereitung, Ausführung und Überprüfung), aber ich frage mich, ob sich der Aufwand lohnt, eine neue Sprache dafür zu lernen. Mit Unit Tests in C# kann man die Tests ebenfalls schreiben und man muss dann nicht andauernd zwischen den beiden Sprachen wechseln. Vielleicht habe ich mich aber auch noch zu wenig mit Visual T# auseinander gesetzt und es verbergen sich noch interessante Konzepte darin, welche die Arbeit mit Unit-Tests vereinfachen oder verbessern. Was aber an Visual T# noch verbessert werden muss ist die Stabilität. Es ist mir einige Male abgestürzt und manchmal hat es unerklärliche Kompilierfehler gemeldet, die mit einem Neustart des Visual Studios behoben werden konnten.
Den Aufwand, die Klasse komplett zu implementieren, hatte ich etwas unterschätzt. Anhand der Properties und Methoden, die IList verlangt, habe ich die Dauer in etwa geschätzt, dabei aber nicht beachtet, dass ich noch einen Enumerator implementieren muss. Dass ich die Blog-Artikel parallel dazu geschrieben habe hat einen zusätzlichen Sprachwechsel erfordert, so dass ich immer zwischen C#, T# und Deutsch wechseln musste. Hier hat mir der Test-First-Ansatz geholfen, bei den einzelnen Schritten fokussiert zu bleiben und nicht in einer Sprache zu weit vorzupreschen und die anderen Sprachen dann mühsam (und fehleranfällig) „nachzuziehen“.
Das einfache Beispiel erlaubt es auch, einmal ein grobes Verhältnis zwischen Test-Code und Produktiv-Code zu schätzen. Sowohl bei der LinkedList als auch beim LinkedListEnumerator ist der Test-Code etwas doppelt so lang wie der Produktiv-Code. Das Verhältnis der Anzahl Test-Methoden gegenüber der Anzahl Properties und Methoden in den beiden Klassen ist dabei noch grösser (circa 2,5 mal so viele Test-Methoden wie produktive Methoden und Properties). Kurzfristig denkende Manager werden sich bei solchen Zahlen vermutlich die Haare raufen, wenn zwei drittel des Codes nicht im produktiven System verwendet wird. Wenn aber dadurch die Fehler, die den Kunden bei seiner Arbeit mit unserer Software behindern, verhindert oder zumindest stark reduziert werden können, ist dieser „unproduktive“ Code schlussendlich ebenso wertvoll wie der Produktiv-Code.

Den entwickelten Code der LinkedList will ich niemandem Vorenthalten. Was für einen produktiven Code noch fehlt sind die XML-Kommentare.

Die Beispiel-Lösung von Stephan Lieser ist in der Juni-Ausgabe erschienen.

Fazit
Test-First-Prinzip erfolgreich angewandt und als gut befunden, die Unit Tests werde ich aber wohl weiterhin mit C# schreiben.

Hat euch dieses Dojo gefallen? Habt ihr ähnliche Erfahrungen mit Visual T# gemacht? Gerne erwarte ich eure Kommentare.

LinkedList Dojo mit Visual T# – Teil 4

Karate

Image courtesy of „luigi diamanti“ / FreeDigitalPhotos.net


Im vierten Teil von “LinkedList Dojo mit Visual T#” nehmen wir uns den im Teil 3 noch fehlenden Implementierungen an. Folgende Methoden werfen immer noch die NotImplementedException:

  • Insert(int index, T item)
  • RemoveAt(int index)
  • CopyTo(T[] array, int arrayIndex)
  • IsReadOnly
  • Remove(T item)
  • GetEnumerator() in den Varianten „Generics“ und „Non-Generics“

Wagen wir und gleich an den wahrscheinlich grössten Brocken, die zwei GetEnumerator()-Methoden. Die Implementierung der Methoden wird dabei nicht das schwierige und aufwändige sein, sonder der Enumerator, der zurückgegeben wird.

Erstellen wir also eine neue Klasse für den Enumerator und im Testprojekt die zugehörigen Tests.

Als erstes die zwei Tests, die den Enumerator verlangen:


GeSHi Error: GeSHi could not find the language tsharp (using path /home/rolandba/public_html/blog/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Für den nicht generischen Enumerator muss unsere Liste auf das nicht generische Interface gecastet werden, bevor die Funktion aufgerufen werden kann.

Um die Test grün werden zu lassen benötigen wir nun die Implementation des Enumerators. Ich habe mich dabei entschieden, eine eigene Klasse auf der selben Ebene wie die Liste zu erstellen und keine verschachtelte Klasse.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
namespace RolandBaer.LinkedList
{
    public class LinkedListEnumerator : IEnumerator
    {
        public T Current
        {
            get { throw new NotImplementedException(); }
        }

        public void Dispose()
        {
            throw new NotImplementedException();
        }

        object System.Collections.IEnumerator.Current
        {
            get { throw new NotImplementedException(); }
        }

        public bool MoveNext()
        {
            throw new NotImplementedException();
        }

        public void Reset()
        {
            throw new NotImplementedException();
        }
    }
}

Jetzt noch die beiden GetEnumerator-Methoden implementieren:

1
2
3
4
5
6
7
8
9
public IEnumerator GetEnumerator()
{
    return new LinkedListEnumerator();
}

System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
    return GetEnumerator();
}

Da das generische Interface das nicht-generische beinhaltet kann in der nicht-generischen Methode auf die generische Methode zurückgegriffen werden. So habe ich schon eine Redundanz verhindert.

Durchnummerieren!

Viel sinnvolles können wir aber mit der Implemetation des Enumerators noch nicht anfangen. Schreiben wir deshalb ein paar Tests, die den Enumerator testen.

Als erstes testen wir, ob MoveNext false zurückliefert, wenn wir eine leere Liste haben. Wie wir die Liste übergeben, haben wir noch gar nicht überlegt, aber wenn keine Liste bekannt ist muss sicher false zurückgegeben werden.


GeSHi Error: GeSHi could not find the language tsharp (using path /home/rolandba/public_html/blog/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Um den Test grün werden zu lassen müssen wir MoveNext anpassen.

1
2
3
4
public bool MoveNext()
{
    return false;
}

Wie gesagt, die Liste muss dem Enumerator auf irgend eine Art bekannt gemacht werden. Ich bevorzuge in solchen Fällen den Konstruktor, da ich so nicht ein zusätzliches Attribut einführen muss. Der Compiler verhindert auch, dass ich vergesse, die Liste anzugeben wenn der Default-Konstruktor fehlt.

Zuerst aber wieder der Test, der das ganze nötig macht.


GeSHi Error: GeSHi could not find the language tsharp (using path /home/rolandba/public_html/blog/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Der erste Test ist der schon vorher eingeführte Test, erweitert um den Parameter im Konstruktor. Der zweite Test ist der neue Test, welcher einen Zugriff auf die Liste verlangt. Im LinkedListEnumerator muss der Konstruktor und eine Variable für die Liste hinzugefügt werden und MoveNext wird angepasst:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class LinkedListEnumerator : IEnumerator
{
    LinkedList list;

    public LinkedListEnumerator(LinkedList list)
    {
       this.list = list;
    }

    public bool MoveNext()
    {
        return list.Count &gt; 0;
    }

    // (...)
}

GetEnumerator in der LinkedList muss auch noch angepasst werden, wir übergeben dem Konstruktor einfach this und schon kompiliert wieder alles. Und die Test sind auch wieder alle grün.

Den zweiten Test passe ich nun noch an, so dass eine vernünftige Implementation von MoveNext nötig wird.


GeSHi Error: GeSHi could not find the language tsharp (using path /home/rolandba/public_html/blog/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Für die Implementierung sind noch ein paar Umbauarbeiten nötig, doch zuerst die neue Version von MoveNext:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private LinkedListElement current;

public bool MoveNext()
{
    if(list.Count == 0) return false;

    if (current == null)
    {
        current = list.GetAt(0);
        return true;
    }

    current = current.Next;
    return current != null;
}

Neben der oben gezeigten neuen Membervariable in der Klasse LinkedListEnumerator verschieben wir auch die Klasse LinkedListElement, welche früher eine geschachtelte Klasse war, in den normalen Namespace und deklarieren sie als internal. Die Methode GetAt(int) deklarieren wir auch als internal, so dass der Enumerator sie verwenden kann.

Dank der Unit-Tests gehen wir bei diesen Umbauarbeiten kein Risiko ein, was wir auch jederzeit überprüfen können.

Und ja, ich habe wieder zu viel implementiert, also noch einen allgemeineren Test, um alle Abläufe durchzugehen.


GeSHi Error: GeSHi could not find the language tsharp (using path /home/rolandba/public_html/blog/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Faster, Faster!

Den Rest des Enumerators erledigen wir im Schnellzugtempo. Ein paar Tests und nur noch seht wenig zu implementieren.


GeSHi Error: GeSHi could not find the language tsharp (using path /home/rolandba/public_html/blog/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Und hier die dazugehörigen Implementationen. Für Dispose habe ich keinen Test geschrieben, da Dispose auch nichts macht. Wenn man mit Sicherheit verhindern will, dass noch die NotImplementedException geworfen wird, kann man einen Test schreiben, es würde aber spätestens auffallen, wenn man die Liste ein erstes mal mit foreach verwendet.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public T Current
{
    get
    {
        if (current == null) { throw new InvalidOperationException(); }
        return current.Element;
    }
}

object System.Collections.IEnumerator.Current
{
    get
    {
        return Current;
    }
}

public void Dispose()
{
}

public void Reset()
{
    current = null;
}

Die Tests benötigen noch ein Refactoring und der Konstruktor des Enumerators sollte nicht zugänglich sein, da nur die Liste den passenden Enumerator erstellen soll. Nach den Änderungen sieht dann die Testklasse wie folgt aus.


GeSHi Error: GeSHi could not find the language tsharp (using path /home/rolandba/public_html/blog/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Den Enumerator haben wir nun, die restlichen Funktionen fehlen aber immer noch. Wir nehmen uns diesen im fünften Teil an.

LinkedList Dojo mit Visual T# – Teil 3

Karate

Image courtesy of „luigi diamanti“ / FreeDigitalPhotos.net


Dies ist der dritte Teil von „LinkedList Dojo mit Visual T#“. In dieser Übungsaufgabe implementieren wir eine verknüpfte Liste nach dem Test-First Ansatz mit Hilfe von Visual T#.

Am Ende von Teil 2 habe ich ja bereits angekündigt, dass als Erstes ein Refactoring ansteht. Dabei werden wir nicht nur die LinkedList überarbeiten sondern auch die dazugehörigen Tests (ohne natürlich die Test-Funktionalitäten zu ändern).

Was riecht hier so streng?

Code Smell: Suchschleifen

Im Indexer haben wir zwei mal dieselbe Suchschleife, einmal im get- und einmal im set-Block. Diese Schleife extrahieren wir in eine eigene Methode, die wir dann aufrufen.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public T this[int index]
{
    get
    {
        if (index &gt;= Count) throw new ArgumentOutOfRangeException();

        LinkedListElement temp = GetAt(index);

        return temp.Element;
    }
    set
    {
        if (index &gt;= Count) throw new ArgumentOutOfRangeException();

        LinkedListElement temp = GetAt(index);

        temp.Element = value;
    }
}

private LinkedListElement GetAt(int index)
{
    LinkedListElement temp = first;

    for (int i = 0; i &lt; index; i++)
    {
        temp = temp.Next;
    }
    return temp;
}

Wieder alle Tests ausführen und beruhigt sehen, dass immer noch alle Tests funktionieren.

Code Smell: Initialisierung in Tests

In jedem Test wird die Liste neu erzeugt. Dies kann in eine Methode ausgelagert werden, den sogenannten testcontext. Der Codeabschnitt vor dem runtest entspricht dabei dem TestInitialize- bzw. dem SetUp-Attribut, der Codeabschnitt nach dem runtest dem TestCleanup- bzw. dem TearDown-Attribut beim Microsoft Unit Test Tool bzw. bei NUnit.
Das sieht dann wie folgt aus (nur ein Test als Beispiel).

testclass LinkedListTest for LinkedList
{
    LinkedList sut;
       
    testcontext
    {
        test
        {
            sut = new LinkedList ();
            runtest;
        }
    }
       
    test Count get
    {
        runtest int count = sut.Count;

        assert count == 0;
    }
    (...)
}

Die beiden schlimmsten Code Smells sind behoben, aber die zwei Schleifen in Count und Add riechen auch noch ein wenig. Ich erlaube mir aber, die Nase noch ein bisschen zuzuklemmen und noch etwas Funktionalität hinzuzufügen.

Leeren Sie Ihren Kopf

Als erstes möchte ich die Liste leeren können, so dass ich wieder neue Elemente hinzufügen kann.


GeSHi Error: GeSHi could not find the language tsharp (using path /home/rolandba/public_html/blog/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Der assert-Ausdruck vor dem runtest ist eine kleine Zusatzsicherheit, dass die Liste auch Elemente enthält bevor wir sie löschen. Das ist erlaubt und auch vernünftig, da so auch wirklich das getestet wird, was man testen will. Der Test schlägt natürlich fehl, da Clear noch die Standard-Implementation enthält, die eine Exception wirft. Lassen wir den Test grün werden.

1
2
3
4
public void Clear()
{
    first = null;
}

Ganz nach dem Motto nach uns die Sintflut hängen wir einfach das erste Element der verketteten Liste ab und wir sind die Liste los. Der Garbage Collector kann sich dann um die Überreste kümmern.

Haben Sie ..?

Da dies so einfach war fügen wir noch eine Funktionalität mehr hinzu, die Methode Contains(). Wiederum zuerst den Test, der dann fehlschlägt:


GeSHi Error: GeSHi could not find the language tsharp (using path /home/rolandba/public_html/blog/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Und die einfachste Implementation, die den Test erfüllt:

1
2
3
4
public bool Contains(T item)
{
    return true;
}

Ein zweiter Test soll den Fall abdecken, dass das Element nicht in der Liste vorhanden ist:


GeSHi Error: GeSHi could not find the language tsharp (using path /home/rolandba/public_html/blog/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Natürlich kann die Überprüfung auch mit


GeSHi Error: GeSHi could not find the language tsharp (using path /home/rolandba/public_html/blog/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

erfolgen.

Hier die (nun sinnvolle) Implementierung, die den neuen Test erfüllt, ohne die anderen Tests fehlschlagen zu lassen.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public bool Contains(T item)
{
    LinkedListElement temp = first;

    if (EqualityComparer.Default.Equals(temp.Element, item))
        return true;
    while (temp.Next != null)
    {
        temp = temp.Next;
        if (EqualityComparer.Default.Equals(temp.Element,item))
            return true;
    }

    return false;
}

Das Konstrukt in der if-Abfrage ist dem Umstand geschuldet, dass unsere LinkedList sowohl mit Value- als auch mit Reference-Typen funktionieren soll. Weiter Infos sind z.B. bei StackOverflow zu finden.

Bei der Implementation dieser Funktion habe ich gemerkt, dass noch nicht beide Randbedingungen überprüft werden. Also fügen wir noch einen zusätzlichen Test hinzu:


GeSHi Error: GeSHi could not find the language tsharp (using path /home/rolandba/public_html/blog/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Dieser Test ist von Anfang an grün, wir haben unsere Implementation also richtig gemacht.

Wo finde ich ..?

Verwandt mit Contains() scheint IndexOf() zu sein. Nehmen wir uns diese Implementation auch noch gleich vor. Wie immer zuerst der Test. Wir übernehmen gleich die drei Tests von Contains() und passen sie an:


GeSHi Error: GeSHi could not find the language tsharp (using path /home/rolandba/public_html/blog/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Natürlich schlagen alle drei Tests fehl. Nehmen wir nun auch den Code von Contains() und passen ihn für IndexOf() an:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public int IndexOf(T item)
{
    int pos = 0;
    LinkedListElement temp = first;

    if (EqualityComparer.Default.Equals(temp.Element, item))
        return pos;
    while (temp.Next != null)
    {
        temp = temp.Next;
        pos++;
        if (EqualityComparer.Default.Equals(temp.Element, item))
            return pos;
    }

    return -1;
}

Mit minimalen Anpassungen sind wieder alle Tests grün. Aber wir haben wieder einen Code Smell, den wir auch gleich beheben:

1
2
3
4
public bool Contains(T item)
{
    return IndexOf(item) &gt; -1;
}

Contains() überprüft nur noch, ob IndexOf() einen gültigen Index zurück gibt. Und dieses Refactoring hat nichts kaputt gemacht, es sind immer noch alle Tests grün.

Immer noch warten einige Methoden auf ihre Implementation. Diesen werden wir uns im vierten Teil annehmen.

LinkedList Dojo mit Visual T# – Teil 2

Karate

Image courtesy of „luigi diamanti“ / FreeDigitalPhotos.net


Dies ist der zweite Teil von „LinkedList Dojo mit Visual T#“. In dieser Übungsaufgabe implementieren wir eine verknüpfte Liste nach dem Test-First Ansatz mit Hilfe von Visual T#.

Greifen Sie ruhig zu

Im ersten Teil haben wir die Funktion Add() und das Property Count implementiert. Nun wollen wir überprüfen, ob die Daten auch richtig abgelegt wurden. Dazu benötigen wir den Index-Operator (manchmal auch Indexer genannt) this[int index].

Als erstes wiederum ein Test, um eine erste Implementation zu erzwingen.


GeSHi Error: GeSHi could not find the language tsharp (using path /home/rolandba/public_html/blog/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Und hier die zum Test passende Implementation.

1
2
3
4
5
6
7
8
9
10
11
public T this[int index]
{
    get
    {
        return first.Element;
    }
    set
    {
        throw new NotImplementedException();
    }
}

Schon beim Schreiben des Codes ist die nächste Frage aufgetaucht: Wie soll sich die Liste verhalten, wenn noch kein Element vorhanden ist oder wenn ein Index verwendet wird, der grösser ist als die Anzahl der Elemente in der Liste?
Die Dokumentation von IList<T> verrät es uns (im Bereich Exceptions):

ArgumentOutOfRangeException:
index is not a valid index in the IList<T>.

Dies hat zwei weitere Tests zur Folge:


GeSHi Error: GeSHi could not find the language tsharp (using path /home/rolandba/public_html/blog/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Und dazu natürlich noch die passende Implementation, so dass die Tests auch wieder grün werden.

1
2
3
4
5
6
7
8
9
10
public T this[int index]
{
    get
    {
        if (index &gt;= Count) throw new ArgumentOutOfRangeException();
        return first.Element;
    }
    set
    (...)
}

Gezielter Zugriff

Natürlich funktioniert der Indexer bisher nur beim ersten Element richtig. Fügen wir also einen Test hinzu, um den wahlfreien Zugriff zu ermöglichen.


GeSHi Error: GeSHi could not find the language tsharp (using path /home/rolandba/public_html/blog/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Fehlt noch die Implementation des Setters, als erstes wieder der Test.

test this[int index] set
{
    int value1, value2;  
    LinkedList sut = new LinkedList ();
    for(int i = 0; i = Count) throw new ArgumentOutOfRangeException();

        LinkedListElement temp = first;

        for (int i = 0; i &lt; index; i++)
        {
            temp = temp.Next;
        }

        temp.Element = value;
    }
}

OK, bei der Implementation habe ich etwas geschummelt, ich habe das Test-First Prinzip missachtet und bereits die Überprüfung, ob der übergebene index gültig ist, eingebaut. Hier noch der Test, um die Überprüfung auch zu rechtfertigen.

 test this[int index] set
{
    LinkedList sut = new LinkedList ();
    sut.Add(5);
    sut.Add(8);
           
    runtest sut[2] = 3;

    assert thrown ArgumentOutOfRangeException;
}

Was mich aber mehr stört als die kleine Prinzip-Verletzung sind die Redundanzen im Code, am offensichtlichsten im zuletzt implementierten Indexer. Deshalb starten wir den dritten Teil mit einem Refactoring der bisher implementieren Funktionalitäten und Tests.

LinkedList Dojo mit Visual T# – Teil 1

Karate

Image courtesy of „luigi diamanti“ / FreeDigitalPhotos.net


In der Mai-Ausgabe der dotnetpro war die Dojo-Aufgabe (mehr Infos zu den Dojo-Aufgaben) eine verkettete Liste zu implementieren. Eine Aufgabe, bei der keine Ressourcenzugriffe (Datenbank, Filesystem …) und keine Abhängigkeiten das Testen erschweren. Also ein ideales Tummelfeld, um Unit Tests zu verwenden und dabei Visual T# einzusetzen (Einstieg in Visual T#). Dabei versuche ich nach dem Test-First Prinzip vorzugehen.

Aller Anfang ist leicht

Die Aufgabenstellung beschreibt die Anforderungen und die „Architektur“ der Liste schon relativ präzise, so dass ich mir erlaube gleich Visual Studio zu starten, ohne erst eine Skizze zu Papier zu bringen.

Die Visual Studio Solution enthält keine Überraschungen. Als erstes Project die Klassenbibliothek mit der zu erstellenden verketteten Liste (LinkedList) und als zweites das Visual T# Testprojekt.

Als erstes erfüllen wir die Anforderung, dass unsere LinkedList das Interface IList<T> implementiert. Die Methodenrümpfe erstellt uns Visual Studio auf Mausklick, so dass unser Projekt bereit für die ersten Test-Implementationen ist.

Erster Test

Als ersten Test erstellen wir die Überprüfung, dass eine leere Liste beim Abfragen der Anzahl der Elemente null zurück gibt.


GeSHi Error: GeSHi could not find the language tsharp (using path /home/rolandba/public_html/blog/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Die einfachste Implementierung, die diesen Test erfüllt sieht so aus:

1
2
3
4
public int Count
{
    get { return 0; }
}

Noch nicht wirklich eine sinnvolle Implementierung aber aus dem Test-First Ansatz bzw. dem YAGNI-Prinzip richtig.

Ein Fuss vor den Andern

Für den nächsten Schritt müssen wir etwas mehr investieren. Wenn wir ein Element in der Liste haben möchten müssen wir zuerst das Element hinzufügen.


GeSHi Error: GeSHi could not find the language tsharp (using path /home/rolandba/public_html/blog/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Da wir noch keine Implementation für Add() haben wirft dieser Test eine NotImplementedException. Fügen wir also die Implementation hinzu und bauen auch das Property Count so um, dass der Test erfogreich ist. Dabei implementieren wir (vielleicht ein bisschen entgegen dem YAGNI- und KISS-Prinzip) auch gleich den internen Container, da dieser ja als Anforderung verlangt wird.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class LinkedList< T > : IList< T >
{
    private class LinkedListElement
    {
        public T Element { get; set; }
        public LinkedListElement Next { get; set; }
    }

(...)

    public void Add(T item)
    {
        first = new LinkedListElement() { Element = item };
    }

    public int Count
    {
        get { if (first == null) return 0; return 1; }
    }

(...)

    private LinkedListElement first;
}

Dieser Code erfüllt die in den Tests definierten Anforderungen, aber da fehlt noch einiges. Machen wir also den nächsten Schritt, noch ein Element hinzufügen und die Anzahl überprüfen.


GeSHi Error: GeSHi could not find the language tsharp (using path /home/rolandba/public_html/blog/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Kleine Anmerkung zu den Tests: Auch wenn es vielleicht den Anschein macht, ich habe keine Tests geändert sondern neue hinzugefügt. Bei T# dürfen mehrere Tests die selbe Signatur (in diesem Fall ‚test Count get‘) haben, das Testprojekt enthält jetzt also drei Tests.

Unbegrenztes Wachstum

Nun ist es Zeit, bei Add() und Count eine allgemeine Implementation einzufügen, die für beliebig viele (im Rahmen der Schnittstelle, Count ist z.B. vom Typ int) Elemente funktioniert.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public void Add(T item)
{
    if (first == null)
    {
        first = new LinkedListElement() { Element = item };
    }
    else
    {
        LinkedListElement temp = first;
        while(temp.Next != null)
        {
            temp = temp.Next;
        }
        temp.Next = new LinkedListElement { Element = item };
    }
}

public int Count
{
    get
    {
        if (first == null) return 0;

        int count = 1;
        LinkedListElement temp = first;
        while (temp.Next != null)
        {
            temp = temp.Next;
            count++;
        }
        return count;
    }
}

Vertrauen ist gut, Kontrolle ist besser

Mit einem weiteren Test überprüfen wir die allgemeine Gültigkeit der Implementation.


GeSHi Error: GeSHi could not find the language tsharp (using path /home/rolandba/public_html/blog/wp-content/plugins/codecolorer/lib/geshi/) (code 2)

Somit haben wir einen Stand erreicht, bei dem wir die Liste befüllen können und die Werte scheinbar auch gespeichert werden. Um dies aber überprüfen zu können müssen wir eine Möglichkeit implementieren, auf die gespeicherten Werte wieder zugreifen zu können. Diese Funktionalität werden wir im zweiten Teil realisieren.