Blog
Architekturaussagen brauchen getrennte Belege
Bevor ich behaupte, eine Architektur funktioniere, benenne ich die Art des Belegs.
Ein Entwurf beschreibt Absicht
Ein Diagramm oder ein schriftlicher Vertrag hilft mir, Zuständigkeiten und erwartete Grenzen zu durchdenken. Er ist wertvoll, weil Widersprüche vor der Umsetzung sichtbar werden können, nicht weil er die Übereinstimmung der Umsetzung beweist. Ich trenne beabsichtigtes von beobachtetem Verhalten. Bei einem Vorschlag frage ich, welche Annahmen gelten müssen und welche Teile noch Entscheidungen statt Tatsachen sind.
Die Absicht schriftlich festzuhalten hat einen konkreten Nutzen: Es trennt Entscheidungen von Annahmen. Ein Zuständigkeitsbild kann festhalten, wem eine Warteschlange gehört, wer wiederholen darf und was mit einer Nachricht geschieht, die zweimal scheitert. Solche Entscheidungen kann ich ablehnen, bevor Code existiert, was deutlich günstiger ist, als sie in einer Prüfung zu entdecken.
Dasselbe Dokument altert. Wenn Umsetzung und Beschreibung auseinandergehen, ist der Unterschied eine Frage und kein Beweis, dass der Code falsch ist; vielleicht ist die Beschreibung nur älter. Ich halte die Notiz bei der Änderung, die sie anpasst, damit später erkennbar bleibt, welche Aussage wann gelten sollte.
claims:
- statement: "a malformed request is rejected"
level: exercised
command: "bun test src/requests"
- statement: "the packaged service starts with the documented config"
level: observed
command: "./scripts/smoke.sh --config config/local.yaml"
- statement: "requests are routed to the owning module"
level: specified
reference: "docs/routing.md"Tests und Builds beantworten engere Fragen
Ein gezielter Test kann zeigen, dass ein ausgewähltes Verhalten für bestimmte Eingaben eintrat. Ein erfolgreicher Build kann zeigen, dass Teile gut genug zusammenpassen, um ein Ergebnis zu erzeugen. Keines von beiden belegt ungeprüfte Wechselwirkungen oder den vorgesehenen Start und die richtige Konfiguration. Ich kennzeichne Aussagen deshalb als beschrieben, getestet oder gebaut. So wird ein bestandener Check nicht unbemerkt zum Beleg für eine andere Frage.
Ein Test beantwortet die Frage, die seine Eingaben stellen, und kaum mehr. Ich versuche, die Behauptung im Testnamen zu nennen, weil eine grüne Testsuite oft als allgemeine Zusage gelesen wird. Prüft ein Test einen Pfad durch eine Wiederholungsregel, passt die Kennzeichnung getestet zu diesem Pfad; für die Regel als Ganzes passt sie nicht.
Ein Build ist eine andere Art von Beleg. Er zeigt, dass sich die Teile zu einem Ergebnis zusammenfügen, und ein wiederholbarer Build ergänzt, dass das Ergebnis zu den Quellen passt. Über Konfiguration und Start sagt er weiterhin nichts, weshalb ich gebaut von beobachtet getrennt halte, auch wenn die Pipeline grün ist.
Betrieb braucht eigene Beobachtung
Das Verhalten zur Laufzeit hängt von Bedingungen ab, die ein Entwurf nie vollständig abbildet. Eine wichtige Aussage würde ich auf der Ebene prüfen, auf der sie relevant ist: das passende Verhalten beobachten, die Bedingungen festhalten und nicht Beobachtetes nennen. Auch eine Laufzeitprüfung ist kein dauerhafter Beweis; Bedingungen ändern sich. Hilfreich ist eine sichtbare Belegkette, damit spätere Fehler auf die Ebene verweisen, die neu untersucht werden muss.
Wenn ich das laufende System beobachte, halte ich die Bedingungen zusammen mit dem Ergebnis fest: welche Revision, welche Konfiguration, welche Eingaben und was ausgelassen wurde. Ohne diese Notizen lässt sich eine erfolgreiche Beobachtung schwer wiederholen und später schwer beurteilen. Mit ihnen kann jemand entscheiden, ob derselbe Beleg nach einer Änderung der Umgebung noch gilt.
Mir geht es um eine gekennzeichnete Kette. Eine wichtige Behauptung soll auf die Ebene verweisen, die sie stützt, und auf keine stärkere. Scheitert später etwas, sagt mir die Kennzeichnung, wo ich zuerst nachsehe, und sie verhindert, dass ein bestandener Check stillschweigend für eine Frage einsteht, die er nie gestellt hat.
#!/usr/bin/env bash
set -euo pipefail
level="${1:?usage: run-evidence.sh <level> <command>}"
shift
echo "level: $level"
echo "command: $*"
# Keep the conditions next to the result, so a passing run
# cannot be read as evidence for a wider claim.
"$@" 2>&1 | tee "evidence-$level-$(date +%Y%m%d-%H%M%S).log"