Zum Inhalt springen
Alle Artikel

Achtzehn Wege über das Bull, und der Test sah nur den letzten Pfeil

Eine Regel stand im Code, ausformuliert und begründet. Sie galt für den Schlusspfeil und nicht für die beiden davor. Kein Test bemerkte es, weil er dieselbe Stelle ansah wie die Regel.

4 Min. Lesezeit
  • Tests
  • Eigenschaftstests
  • TypeScript
  • Dart
Aus dem SystemDartile

Eine Zähl-App für Dart behält niemand wegen der Addition. Die behält man wegen der Zahlen daneben: Average, Doppelquote, und der Vorschlag, wie sich der Rest ausmachen lässt. Genau dieser Vorschlag ist die empfindlichste Stelle des Produkts, denn er wird an der Scheibe gegen etwas gehalten, das die meisten Spieler im Kopf haben.

Rechnerisch gibt es auf 40 Punkte über achtzig Wege. Gespielt wird genau einer. Eine Suche, die den ersten Treffer zurückgibt, gibt Unsinn zurück: S1 S1 D19 ergibt genauso 40 wie D20. Also bekommt jeder Weg Kosten, und die Kosten bilden ab, was am Board zählt.

Die Regel stand längst da

Eine davon betrifft das Bull. Der Doppelring ist ein langes, schmales Band, das Bullseye ein winziger Kreis. Deshalb steht in jeder gedruckten Tafel auf 95 die D19 und nicht T15 BULL. Aufs Bull geht man, wenn es sein muss, nicht wenn es rechnerisch passt.

Genau so stand es im Code, in der Funktion für den Schlusspfeil, mit Begründung im Kommentar darüber. Das Bullseye kostet dort 50, das teuerste Doppel 18.

Für die beiden Pfeile davor galt sie nicht

Die Kosten der Vorbereitungspfeile kannten diese Aussage nicht. Dort kostete ein Bull neun, ungefähr so viel wie eine T13, und war damit der billigste Weg zu einem Rest von 40. Das Ergebnis sieht man erst, wenn man es sich ansieht:

RestVorgeschlagenGleich lang, ohne Bull
141T17 BULL D20T20 T19 D12
90BULL D20T18 D18
3325 D4S17 D8
Drei der achtzehn Reste, bei denen der Vorschlag über das Bull lief, obwohl es einen gleich langen Weg daran vorbei gab.

Keiner dieser Wege steht in einer gedruckten Tafel. Wer BULL D20 auf 90 liest, glaubt der App die nächste Zahl auch nicht mehr.

Warum kein Test anschlug

Es gab einen Test für die Sache, und er war kein schlechter. Er lief über alle Reste von 2 bis 170, holte alle Wege, und prüfte: Endet der Vorschlag auf dem Bull, obwohl ein gleich langer Weg auf einem Doppel endet? Das ist eine Eigenschaft und kein Einzelfall, und trotzdem lief er durch.

Aufgefallen ist es beim Durchsehen der Tafel, von Hand, Rest für Rest. Das ist die unangenehme Antwort: Gefunden hat es kein Werkzeug, sondern der Blick auf das Erzeugnis.

Der Fix ist keine größere Strafe

Der naheliegende Weg wäre, dem Bull im Aufbau höhere Kosten zu geben. Er ist falsch. Der Wert müsste größer sein als der größte Abstand zweier Doppel-Ränge, und er müsste bei jeder Änderung an den Gewichten mitwachsen. Eine Zahl, die von drei anderen Zahlen abhängt, ist keine Regel, sondern eine Kalibrierung, die beim nächsten Mal wieder kippt.

Die Aussage gehört deshalb in die Sortierung, vor die Kosten: Gibt es einen gleich langen Weg ohne Bull im Aufbau, kommt er zuerst. Gibt es keinen, bleibt der Weg über das Bull stehen, denn ein Vorschlag ist besser als keiner. Kein Gewicht kann diese Aussage mehr überstimmen.

bewertet.sort((a, b) => {
  if (a.weg.length !== b.weg.length)
    return a.weg.length - b.weg.length;
  // Vor den Kosten, damit kein Gewicht diese Aussage überstimmen kann.
  if (a.bull !== b.bull) return a.bull - b.bull;
  if (a.kosten !== b.kosten) return a.kosten - b.kosten;
  return a.text.localeCompare(b.text);
});
Die Reihenfolge trägt die Regel, nicht die Kostenfunktion.

Was ich daraus mitnehme

Prüft eine Eigenschaft eine Folge von Schritten, dann prüft man sie an jedem Schritt und nicht am letzten. Der neue Test tut genau das: Er sieht auf wuerfe.slice(0, -1), läuft über alle drei Aus-Modi und meldet jeden Rest, bei dem ein Weg ohne Bull dieselbe Länge hätte. Gegengeprobt mit dem alten Stand meldet er die achtzehn.

Und die zweite Lehre ist die ältere: Ein grüner Testlauf ist kein Beleg dafür, dass das Ergebnis stimmt. Er ist ein Beleg dafür, dass die Annahmen im Test zu den Annahmen im Code passen.

Die Tafel liegt inzwischen als eigenes Paket öffentlich, mit beiden Eigenschaftstests: darts-checkout, TypeScript, ohne Abhängigkeiten.

Belege

  • Dartile-Repo, packages/kern/src/checkout.ts: bullImAufbau und die Sortierung in checkoutWege
  • Test: packages/kern/src/checkout.test.ts, „nimmt das Bull nicht als Vorbereitungspfeil“
  • Gezählt über alle Reste von 2 bis 170 in den Modi doppel, master und straight
  • Die Tafel liegt öffentlich als darts-checkout