Playbook: Die Tracks

Das Playbook für Menschen. Die Skills erledigen die Arbeit; dieses Dokument erklärt, wie ein Run entscheidet, welche Art von Arbeit er macht, und wie du diese Entscheidung steuerst.

Was ein Track ist

Ein Track ist eine von vier Arten von Arbeit, auf die ein Run sich verwenden kann. Jeder Run bearbeitet ein Ticket eines Tracks.

Track Zweck Seine Tickets
Safety Net Das deterministische Tooling einrichten (Testrunner, Coding-Standards, statische Analyse, CI, ein Backward-Compatibility-Check unter einem SemVer-Versprechen), das eine Regression abfängt, bevor strukturelle Arbeit beginnt eines pro Tool, das noch einzurichten ist
Guardrails Weiteres Qualitäts- und Sicherheits-Tooling einrichten, sobald das Safety Net steht (Coverage-Floor, Mess-Erkennung, strengere statische Analyse, …) eines pro Tool, das noch einzurichten ist
Housekeeping Wiederkehrende Wartung und die kleinen Refactoring-Ideen, die unterwegs notiert werden (Playbook) ein stehendes Ticket pro Vorlage
Investigation Strukturelles Refactoring – ein Hotspot, ein Modul, das sich zu vertiefen lohnt die Tickets, die die Suite nach einer Erkundung angelegt hat, und Tickets, die du geschrieben hast

Safety Net und Guardrails folgen dem Tooling-Baum: Jeder Adoptionsschritt ist eine Stufe (Stage) – ein Tool, bis zu einem festgelegten Grad; jedes PHPStan-Level ist eine eigene Stufe. Ein Tool spielt eine Rolle (Role: statische Analyse, Code-Style, Tests, …), und Tools kommen in Packs, die auf ein Projekt zutreffen: jedes Projekt, ein Projekt mit PHP-Code, ein PHP-Projekt, das Twig nutzt, und ein eigenes Pack.

Zu Beginn eines Runs durchsucht die Suite deine offenen Tickets und sortiert sie: Ein Ticket zu einem Tool des Baums gehört zum Track dieses Tools, das offene Housekeeping-Ticket zu Housekeeping, alles andere zu Investigation. Jedes Ticket bekommt außerdem einen Zustand:

  • bearbeitbar – offen, und alles, was es blockiert, ist erledigt;
  • im Review – es hat einen offenen Merge Request;
  • blockiert – es nennt einen Blocker, der nicht erledigt ist;
  • wartend – eine offene Frage an dich steht daran, und noch hat niemand geantwortet.

Wie ein Track gewählt wird

Die Track-Wahl ist ein Entscheidungspunkt. Empfohlen wird der erste Track in der festen Reihenfolge Safety Net, Guardrails, Housekeeping, Investigation, der etwas hat:

Track Er hat etwas, wenn
Safety Net, Guardrails eines seiner Tickets bearbeitbar ist – oder eines seiner Tools nie angeschaut wurde, womit sein Scan fällig ist
Housekeeping sein offenes Ticket ein Fälligkeitsdatum nennt, das erreicht ist, und nicht im Review ist – oder sein Mechanismus noch nicht eingerichtet ist
Investigation immer: Mit offenen Tickets bearbeitet er sie, ohne erkundet er

Safety Net kommt zuerst, solange es unfertig ist. Hat Safety Net nichts Bearbeitbares, liest die Suite, ob es erfüllt ist. Diese Antwort wird aus dem Baum berechnet, nicht beurteilt: Jedes Tool, von dem strukturelle Arbeit abhängt, ist eingerichtet oder abgelehnt.

  • erfüllt → die Empfehlung geht in der Reihenfolge weiter;
  • nicht erfüllt → die Empfehlung ist, den Run zu beenden. Ein autonomer Run endet hier mit „Safety Net is waiting for merges“, gefolgt davon, was im Review ist und was nicht bearbeitet werden kann und warum. In einem interaktiven Run kannst du stattdessen einen anderen Track wählen.

Sobald Safety Net erfüllt ist, wird ein Track, der sich als ohne Bearbeitbares herausstellt, im selben Run übersprungen und der nächste in der Reihenfolge versucht. Ein Aufruf endet also nicht leer, solange ein anderer Track Arbeit hat.

Einen Track selbst nennen

Nenne den Track im Aufruf – „arbeite an Guardrails“, „Investigation“ –, und die Wahl ist beantwortet: Der Run bearbeitet diesen Track, egal was die Reihenfolge empfehlen würde, den Vorrang des Safety Net eingeschlossen. Ein solcher Run bleibt dort. Hat der genannte Track nichts Bearbeitbares, endet er und geht nicht weiter.

Dasselbe gilt für ein Ticket, das du nennst: Der Run bearbeitet dieses Ticket und endet, falls es sich als nicht mehr nötig herausstellt. Unterwegs schaut er sich kein anderes Ticket an.

Wann ein Track gescannt wird

Ein Tooling-Track wird gescannt, wenn dein Tracker noch keine Spur von einem seiner Tools zeigt – kein Ticket dazu, offen oder geschlossen, und keine festgehaltene Ablehnung davon, des Tools als Ganzes oder der Rolle, die es spielt. Bei einem Scan wird jedes Tool an deinem Projekt beurteilt: Ist etwas Echtes und Funktionierendes eingerichtet, das den Zweck des Tools erfüllt, unter welchem Namen auch immer? Ein Projekt, das Laravel Pint nutzt, hat automatisierten Code-Style, also zählt PHP CS Fixer als eingerichtet.

Dann bietet die Suite für jedes Tool, das weder eingerichtet noch abgelehnt ist, ein Ticket an – auch für die blockierten, als blockiert markiert. Der ganze Weg vor dir ist nach einem Scan in deinem Tracker sichtbar. An diesem Entscheidungspunkt legst du alle, einige oder keine an, oder lehnst ein Tool endgültig ab – eine Stufe davon, das ganze Tool oder die Rolle, die es spielt, was auch die übrigen Tools dieser Rolle schließt. Ein Tool, dessen Beurteilung offen blieb – zwei Tools konkurrieren, oder es ist unklar, ob eines den Zweck erfüllt –, wird mit dieser Frage angelegt: Du beantwortest sie dort oder, in einem autonomen Run, an seinem Ticket, das in der Zwischenzeit auf dich wartet.

Danach sind die Tickets der Stand:

  • Ein Tool ohne offenes Ticket gilt als erledigt. In einem gewöhnlichen Run wird nichts erneut beurteilt – außer einem Tool, das die Suite seitdem neu kennt; dafür scannt der nächste Run den Track.
  • Ein Vorschlag, den du weder angelegt noch abgelehnt hast, wird nirgends gespeichert. Sein Tool gilt als erledigt, bis ein späterer Scan es wieder anbietet.
  • Ein Scan läuft erneut, wenn du im Aufruf darum bittest, und als wiederkehrende Housekeeping-Aufgabe, die beide Tooling-Tracks nachprüft – so wird ein Tool bemerkt, das verschwunden ist, oder eines, das der Baum neu dazubekommen hat.

Ein paar Tools werden nur auf deine Anfrage vorgeschlagen: auf manchen Projekten lohnend, auf anderen Rauschen. Semgrep und die Taint-Analyse von Psalm sind zwei davon: Sie finden viel in altem Code, der ohne Guardrails gewachsen ist, und wenig in einem jungen Projekt, das sie von Anfang an hatte. Mutation Testing mit Infection ist ein weiteres: Es führt deine Tests hunderte Male aus und lohnt sich, sobald sie es wert sind, beurteilt zu werden. Ein Projekt, das bereits das eigene Mutation Testing von Pest nutzt, hat es eingerichtet, und Infection wird nicht angeboten. Ihre Tools machen keinen Scan fällig und werden am Ende jedes Scan-Berichts als „auf Anfrage verfügbar“ genannt. Nenne eines im Aufruf („richte Semgrep ein“), und der Run scannt dieses Tool und schlägt es vor wie jedes andere; oder bitte um einen Scan „inklusive der Tools auf Anfrage“. Hat eines erst ein Ticket, wird es wie jedes andere bearbeitet.

Manche Tools können im Moment kein Ticket bekommen: eines, das ein neueres PHP braucht, als dein Projekt zulässt, oder eines, das hinter einer Bedingung wartet, die kein Ticket ändern kann (ein PHP-Tool auf einem Projekt, das kein PHP ist). Der Scan nennt sie als außer Reichweite, mit Begründung. Für ein Tool oberhalb deiner PHP-Version empfiehlt er, eine Ablehnung festzuhalten, die die benötigte PHP-Version nennt; sobald dein Projekt sie erreicht, bietet ein späterer Run an, diese Ablehnung zurückzunehmen.

Wie ein Ticket ausgewählt wird

  • Safety Net und Guardrails folgen der Reihenfolge des Baums. Ein Ticket, das du als priorisiert markiert hast, kommt zuerst.
  • Investigation folgt den Signalen: Sicherheit zuerst, dann Probleme, die schlimmer werden, je länger sie warten, dann alles, was heiß ist, breit genutzt wird, von deinen Tools markiert ist und wenig Risiko trägt. Ein Ticket, das du als priorisiert markiert hast, kommt auch hier zuerst.

Unmittelbar bevor ein Tooling-Ticket bearbeitet wird, wird sein Tool noch einmal beurteilt. Hast du es inzwischen von Hand eingerichtet, wird das Ticket mit einer Notiz geschlossen, die sagt, was den Zweck jetzt erfüllt, und die Auswahl geht zum nächsten. Ein Investigation-Ticket bekommt dieselbe Art von Prüfung: Gibt es das Modul, das es nennt, noch, ist die Reibung noch da?

Ein Tooling-Ticket, das für einen Merge Request zu groß ist

Die Änderung eines Tools kann so viele Dateien berühren, dass niemand sie als eine reviewen könnte. Der Plan schätzt dann die Größe, schneidet die Arbeit in Teile, wo sich Art oder Risiko der Änderung unterscheiden – Umschreibungen, die den Code nur anders ausdrücken, getrennt von Regeln, die ändern, was er tut –, und nennt in einem Satz, warum der Schnitt dort liegt. Jeder Teil ist ein eigener Merge Request, der es wert ist, ohne die anderen gemergt zu werden. Das Ticket bleibt offen, bis der letzte Teil gemergt ist. Arbeit, die das Tool für sein Ziel nicht braucht, kann ein eigenes Ticket werden, das der Run dir zuerst vorschlägt.

Eine PHPStan-Baseline

Ein PHPStan-Level, dessen Baseline noch Befunde enthält, blockiert die Suite nicht stillschweigend. Der Scan schlägt ein Ticket vor, „PHPStan Level N: shrink the baseline“, und das Ticket des nächsten Levels wird davon blockiert. Dieses Ticket nimmt einen Merge Request nach dem anderen auf, jeder entfernt eine Gruppe von Befunden an ihrer Ursache, und schließt mit dem, der die Baseline leert. Ein Merge Request darf einen neuen Baseline-Eintrag hinzufügen, wenn die Baseline insgesamt trotzdem kürzer wird, und nennt ihn. Ein Befund, der ignoriert bleiben soll, oder eine Baseline, die nicht kürzer würde, wird zu einer Ignore-Regel in phpstan.neon – erst, wenn du es erlaubt hast.

Die Limits von PHPMD

PHPMD bekommt keine Baseline. Seine Metrik-Regeln (Komplexität, Länge, Kopplung) haben Limits, und wenn das Tool eingeführt wird, wird jedes Limit auf den schlechtesten Wert gesetzt, den der Code heute hat, mit einem Kommentar, der diesen Wert nennt und wo er steht. Die Limits sinken nur: Ein Merge Request, der die Methode umschreibt, die den schlechtesten Wert setzt, misst erneut, senkt das Limit und aktualisiert den Kommentar, und PHPMD bleibt grün. Das Housekeeping-Ticket senkt ein Limit, unter das der Code schon gekommen ist. Gewollte Ausnahmen werden mit Begründung am Code notiert, nie in eine Baseline. Ein Projekt, das schon eine Baseline hat, bekommt einen Merge Request, der ihre Einträge in Limits und Unterdrückungen überführt und die Datei löscht.

Eine Regel, die zu viel melden würde, um sie auf einmal zu übernehmen, wird nicht aufgegeben. Der erste Merge Request lässt sie weg, markiert als noch nicht übernommen, mit der Anzahl ihrer Befunde, und PHPMD gilt dann als eingeführt: Die Limits halten, und CI führt es aus. Der Rest ist ein eigener zweiter Schritt, ein Ticket, „PHPMD: take in the remaining rules“, abgearbeitet eine Regel pro Merge Request, die Regel mit den wenigsten Befunden zuerst. Eine Regel, die für einen Merge Request zu groß ist, wird Verzeichnis für Verzeichnis übernommen: Ein zweites Ruleset neben dem Haupt-Ruleset enthält nur diese Regel, CI führt es über die schon erledigten Verzeichnisse aus, und der letzte Merge Request für die Regel löscht es und entfernt den Ausschluss der Regel aus der Hauptkonfiguration. Nur du entscheidest, dass eine Regel endgültig abgelehnt ist.

PHP CS Fixer auf einem großen Projekt

PHP CS Fixer kommt in zwei Schritten. Der erste Merge Request fügt das Tool mit einer festgelegten Version des PER Coding Style hinzu, einer kurzen Liste zusätzlicher Regeln, die du ablehnen kannst, und so vielen Regeln, wie ein reviewbarer Merge Request tragen kann, die mit den wenigsten Dateien zuerst. Jede andere Regel wird in der Konfiguration abgeschaltet, mit einem Kommentar, dass sie noch nicht übernommen ist und wie viele Dateien sie berührt. Die Konfiguration liest sich schon wie der Endzustand; die Liste der abgeschalteten Regeln wird nur kürzer. Dieser erste Merge Request reicht, damit Rector beginnen kann.

Der Rest ist ein Guardrails-Ticket, „PHP CS Fixer: take in the remaining rules“, abgearbeitet eine Regel nach der anderen. Eine zweite Konfigurationsdatei neben der Hauptkonfiguration enthält nur die Regel, an der gerade gearbeitet wird, und die schon erledigten Pfade, und CI prüft beide. Jeder Merge Request erweitert sie um einige Verzeichnisse, die am längsten nicht geänderten zuerst, mit der eigenen Ausgabe des Fixers als Änderung. Der letzte für eine Regel löscht die zweite Datei und schaltet die Regel in der Hauptkonfiguration ein. Die Suite liest zuerst deine PHP-Version, damit der Style nie Syntax hinzufügt, die dein ältestes unterstütztes PHP nicht parsen kann. Das Housekeeping-Ticket sucht nach einer neueren Version des PER Coding Style, denn eine festgelegte bewegt sich nie von selbst.

Investigation ohne Tickets

Ohne offenes Investigation-Ticket erkundet der Run den Code: zuerst deine Fokusbereiche, sonst die Stellen, die sich laut Git-Historie immer wieder ändern. Alles, was er findet, wird dir nach Signalstärke geordnet gezeigt. Die Empfehlung ist, die drei stärksten anzulegen – du kannst andere anlegen, so viele du willst, oder keine. Was nicht angelegt wird, wird nicht gespeichert; eine spätere Erkundung stößt vielleicht wieder darauf. Ein autonomer Run legt diese drei an und bearbeitet dann den stärksten.

Hat Investigation offene Tickets, aber keines ist bearbeitbar – alle im Review oder wartend –, ist die Empfehlung, zur Track-Wahl zurückzugehen, statt nach mehr zu suchen, damit sich bei wiederholten autonomen Aufrufen keine neuen Kandidaten hinter ungereviewten stapeln.

Ein Tool ablehnen

Ablehnen hält eine Ablehnung fest: ein geschlossenes Ticket oder eine Datei mit deiner Begründung, an dem Ort, den dein Tracker-Abschnitt nennt. Beim ersten Mal schlägt die Suite einen Ort vor und schreibt deine Antwort in diesen Abschnitt. Ein abgelehntes Tool wird nicht wieder vorgeschlagen.

Tickets, die von einem abgelehnten Tool abhingen, sind ein eigener Entscheidungspunkt. Wo die Abhängigkeit strikt ist, ist die Empfehlung, sie ebenfalls zu schließen; wo das abgelehnte Tool nur als vorher empfohlen galt, ist die Empfehlung, den Blocker zu entfernen und das Ticket zu behalten.

Häufige Fehler

  • Von einem autonomen Run Guardrails oder strukturelle Arbeit erwarten, bevor das Safety Net fertig ist. Der Halt ist gewollt. Merge, was im Review ist, oder nenne den Track, den du willst.
  • Einen Track nennen und erwarten, dass der Run weitergeht. Ein genannter Track ist der ganze Run.
  • „Blockiert“ als Fehlschlag lesen. Das Ticket wartet auf ein anderes Ticket; der Bericht nennt, auf welches.
  • Einen Vorschlag nicht anlegen und erwarten, dass er im nächsten Run wiederkommt. Er kommt mit dem nächsten Scan wieder, nicht vorher.