Follow my new blog

Freitag, 27. Januar 2012

TDD im Flow – Teil 2

Was bisher geschah:

Test #2: Ein Worker entnimmt aus mehreren Queues

Der dritte Test in meiner Planung bleibt sinnig. Er führt zu Änderungen am Produktionscode.

image[27]

Den Testcode zu zeigen, lohnt nicht. Er entspricht der Skizze im Bild. Aber hier der Produktionscode:

internal class NotifyingMultiQueue<T>
{
    List<KeyValuePair<string, Queue<T>>> _queues = 
        new List<KeyValuePair<string,Queue<T>>>();
 

    public void Enqueue(T message, string queueName)
    {
        var queue = _queues.Where(nq => nq.Key == queueName)
                           .Select(nq => nq.Value)
                           .FirstOrDefault();
        if (queue == null)
        {
            queue = new Queue<T>();
            _queues.Add(new KeyValuePair<string, Queue<T>>(queueName,
                                                           queue));
        }
        queue.Enqueue(message);
    }


    public bool TryDequeue(string workerId, out T message)
    {
        var queue = _queues[0];
        _queues.RemoveAt(0);

        message = queue.Value.Dequeue();
        return true;
    }
    …

Die “Änderung mit Zukunft” betrifft Enqueue(). Dort werden nun nach Name unterschieden Nachrichten in verschiedene Warteschlangen eingetragen. Dass ich dafür die Warteschlangen in einer Liste statt einem Dictionary organisiere, ist nicht nur einer gewissen Voraussicht geschuldet – ich habe eine Idee, wie ich das Round Robbin Verfahren einfach implementieren kann –, sondern auch dem Wunsch, TryDequeue() für den Moment möglichst einfach zu halten.

Zwar könnte ich Enqueue() noch simpler mit einem Dictionary arbeiten, doch dann könnte TryDequeue() bei der Entnahme nicht durch die Warteschlangen fortschreiten – und sei es auch nur so simpel wie jetzt. In einem Dictionary gibt es keine verlässliche Reihenfolge der Einträge. Ein Fortschreiten durch verschiedene Warteschlangen ist aber nötig, um hier eine neue Äquivalenzklasse anzugehen. Ansonsten ließe sich das Ergebnis dieses Tests auch mit der bisherigen Implementation erreichen.

Test #3: Queuewechsel mit Round Robbin

Jetzt wird es spannend. Nachrichten in separate Queues zu stellen, ist einfach. Sie aber im Round Robbin Verfahren daraus zu entnehmen, das ist schon kniffliger. Das muss ich nun angehen:

image[32]

Dazu müssen auch in den Queues mehrere Nachrichten stehen, weil es ja der Trick beim Round Robbin ist, nicht erst eine Queue abzuarbeiten, sondern für jede Nachricht eine weiter zu rücken.

Oben habe ich mir Gedanken zu einer Datenstruktur gemacht, mit der das möglich ist. Ein hübscher Plan… den ich nun fallen lasse. Nein, den ich schon mit der vorherigen Implementation habe fallen lassen.

Ich baue mir nicht selbst eine verkette Liste von Warteschlangen, sondern nehme eine normale Liste, der ich Queues in ihrer Reihenfolge vorne entnehme und hinten wieder anfüge. So wandern sie im Kreis durch das Fenster des Listenkopfs.

internal class NotifyingMultiQueue<T>
{
    List<KeyValuePair<string, Queue<T>>> _queues =
        new List<KeyValuePair<string,Queue<T>>>();

    …
    public bool TryDequeue(string workerId, out T message)
    {
        var queue = _queues[0];
        _queues.RemoveAt(0);
        _queues.Add(queue);

        message = queue.Value.Dequeue();
        return true;
    }
    …

Um diesen Test “ergrünen zu lassen” ist nur eine weitere Zeile Code in TryDequeue() nötig. “Pointergehansel” wie im Datenmodell ist nicht nötig. Das hatte ich geahnt beim vorherigen Test und deshalb eine Liste statt eines Dictionary für die benannten Queues gewählt.

Hm… man könnte nun argumentieren, dass hier eine Datenstruktur (Liste) zwei Zwecken dient: der Haltung benannter Queues (insb. für Enqueue()) und dem Durchlaufen der Queues in bestimmter Reihenfolge (TryDequeue()). Diesem Hinweis auf das SRP halte ich aber KISS entgegen: für den Moment stellt diese Verquickung kein Problem dar. TryDequeue() wird dadurch nicht komplizierter. Und Enqueue() eigentlich auch nicht. Im Falle des Wechsels der Entnahmestrategie müsste ich ohnehin beide Methoden anfassen.

Noch ein Testszenario entfällt

Beim nächsten Testszenario muss ich schon wieder feststellen, dass ich übers Ziel hinausgeschossen bin. Es zu erfüllen, bedarf keiner Änderung am Code.

image[37]

Das erkenne ich aber erst jetzt, da ich die Lösung besser verstehe und schon Code geschrieben habe. Macht nichts. Ein Test weniger fällt mir leicht ;-)

Weiter geht es im nächsten Teil…

Spendieren Sie mir doch einen Kaffee, wenn Ihnen dieser Artikel gefallen hat...

Keine Kommentare: