Blog ·
Un’esecuzione che stampava il suo fallimento
La verifica scriveva ogni codice di uscita in un riepilogo e poi scriveva DONE comunque. Una release è uscita su una riga rossa che nessuno aveva letto.
Di Provibr
Prima di una release eseguiamo un solo script. Esegue la suite di prove del web, quelle dei pacchetti, del gateway e degli strumenti, dieci controlli di tipo, il lint, il controllo dei contenuti, il lint del protocollo e la generazione del codice del protocollo, e scrive una riga per passo in un file di riepilogo: il nome del passo e il suo codice di uscita. Poi scrive DONE.
Scriveva DONE anche quando alcuni di quei codici di uscita non erano zero.
Il sette settembre una release è uscita mentre il riepilogo conteneva una riga che diceva bun test packages/gateway/tools exit 1. Avevo letto i contatori di successi e fallimenti di due suite e non della terza. Non si è rotto nulla: la prova fallita era una guardia sulle intestazioni degli script di misura, quindi il prodotto non ne ha risentito e il cliente non ha visto nulla. L’esito è andato bene e la forma era sbagliata, e la forma è l’unica parte che valga la pena annotare.
I contatori non sono la porta
La ragione per cui la cosa è imbarazzante e non semplicemente sfortunata è che questo modo di fallire ha un nome nei nostri stessi appunti, e il nome è verde a vuoto: un controllo che non è collegato a una decisione non viene letto, e un controllo che si legge a occhio viene letto come legge una persona, cioè cercando quello che si aspetta di vedere.
Il riepilogo era pensato per essere scorso con lo sguardo. Ciascuna suite stampa i propri totali da qualche parte più in alto, quindi il movimento naturale è guardare «4515 pass, 0 fail» e sentirsi a posto. I codici di uscita stavano una schermata più in basso, in un file la cui ultima riga diceva DONE.
La riparazione sono quattro parole di shell. Dopo tutti i passi, lo script cerca nel proprio riepilogo un qualunque codice di uscita fra uno e nove, e se ne trova uno aggiunge una riga che comincia con FAIL:. La regola che l’accompagna è che un DONE senza un FAIL è la porta, e che i contatori non lo sono. La seconda metà di quella regola conta più della prima: chi ha un segnale corretto e a buon mercato smette di costruire abitudini costose attorno a uno inaffidabile.
C’è una regola compagna: un comando di deploy non condivide mai una catena di shell con una verifica che si limita a stampare. Se la verifica non può far fallire la catena, è documentazione.
Che cosa era davvero rosso
La guardia che è fallita fa parte di una famiglia, e vale la pena descriverla perché costa insolitamente poco per quello che compra.
La cartella del gateway contiene gli script di misura: spazzate da browser, sonde di impaginazione, strumenti di semina e di pulizia, le cose che un turno scrive per dimostrare qualcosa e poi si lascia dietro. Oggi ce ne sono poco più di cinquecento. Sono script semplici senza controllo di tipo, il che significa che l’unica cosa che sta fra loro e il marcire è che qualcuno per caso ne esegua uno.
Quattro setacci passano su tutti loro dentro una normale suite di prove:
- Ogni script si analizza, usando il runtime che lo esegue davvero.
- Ogni import relativo si risolve in un file che esiste. È quella la forma di morte che un analizzatore non vede: lo script è sintatticamente perfetto e cade alla prima riga quando lo si esegue.
- Ogni script porta un’intestazione con della prosa dentro. Senza una spiegazione, uno strumento di quattro turni fa non si distingue da uno che conta ancora.
- Ogni intestazione nomina il turno da cui viene, in una di esattamente due forme fisse.
Il quarto era rosso. Tre script del turno sulla documentazione avevano intestazioni, e buone, e nessun marcatore di turno. È tutto qui l’incidente: una convenzione di denominazione, colta da una guardia che esiste perché cinquecento script restino leggibili, seduta in una riga di riepilogo che nessuno ha letto.
Perché sconosciuto deve costare un hash di commit
Il quarto setaccio accetta due forme e nient’altro. O l’intestazione dice il nome del turno con una data e da dove viene quell’attribuzione, oppure dice che il turno è sconosciuto e porta l’hash di commit del primo commit del file, con una data.
Al nome è esplicitamente vietato essere la parola sconosciuto. Senza quell’esclusione, «turno sconosciuto, 2026-09-04» è un nome con data perfettamente valido, e il ramo costoso si evita con una parola. Così com’è, ammettere di non sapere costa un hash che qualcuno deve andare a cercare, e quando l’ha cercato di solito conosce il turno.
La data è obbligatoria per un’altra ragione. Un nome nudo è prosa, non un marcatore: una frase come «il turno che ha toccato anche il lavoro sulle intestazioni» contiene un nome di turno e non afferma nulla sul file in cui si trova. Con una data attaccata diventa un’affermazione su questo script, fatta in un dato giorno, che è la cosa più piccola di cui si possa chiamare qualcuno a rispondere.
Una porta deve stare a zero
La domanda ovvia su cinquecento script non tipizzati è perché non li sottoponiamo a un controllo di tipo. L’abbiamo misurato, e la risposta è istruttiva.
La configurazione esiste. È uno strumento e non una porta, e non gira nello script di verifica. Misurato sugli script così com’erano:
| Configurazione | Errori |
|---|---|
| Con le impostazioni severe di questo spazio di lavoro | 3180 |
| Senza il modo severo | 398 |
| Senza il modo severo, dopo aver reso tipizzabile un file condiviso | 159 |
Una porta deve stare a zero, e quei 159 rimasti in gran parte non sono difetti. Due famiglie sono state percorse ed entrambe si sono rivelate artefatti del tipizzare codice non tipizzato: un parametro senza default che cade fuori dalla forma inferita, e un ciclo di destrutturazione su un array misto che si allarga a un’unione. Ventisette di essi sono una sola e identica segnalazione, sull’importare un file TypeScript da uno script che gira sotto un caricatore che lo gestisce perfettamente.
Quello che lo strumento ha comprato davvero è il passo da 398 a 159. Una riga in un helper di browser condiviso, un costruttore che assegna in blocco i propri argomenti all’istanza, teneva in vita 239 errori, perché ogni accesso a una proprietà di quell’oggetto in ogni script era irrisolvibile. Una riparazione, 239 errori spariti. È un buon rendimento per una misura a cui è permesso essere diversa da zero, ed è esattamente il genere di rendimento che da una porta non si ottiene, perché una porta che non si può soddisfare viene spenta.
Il controllo che misurava il proprio runtime
Far funzionare il controllo di analisi è costato due tentativi, entrambi falliti in modi che sembravano problemi di prodotto.
La prima versione chiedeva al runtime corrente di controllare ciascun file. Sotto il nostro esecutore di prove il runtime corrente non è il runtime sotto cui girano gli script, e la sua opzione di controllo significa tutt’altro. Il risultato erano 301 segnalazioni di file sintatticamente rotti, con righe di sorgente citate come testo d’errore. Ognuno di quei file stava bene.
La seconda versione nominava esplicitamente il runtime giusto e falliva lo stesso, in modo diverso: l’esecutore di prove raccoglie i processi figli che appartengono a una prova, e da circa metà elenco in poi ogni chiamata tornava con uno stato nullo. Uccisi da un segnale, non da un errore di analisi. Una guardia che diventa rossa a caso non è una guardia.
La versione che funziona genera un solo processo figlio che controlla lui stesso i file, otto alla volta, in circa nove decimi di secondo quando è stato misurato. Quello che si afferma non cambia: se il runtime che esegue questi script li accetterà.
Nemmeno il parallelismo è un vezzo. Eseguito in serie, il controllo superava il limite di tempo per prova, e una guardia che va in timeout si legge come una guardia rotta e non come una lenta.
Il limite inferiore che nessuno chiede
In quel file c’è una quinta prova che afferma soltanto che ci sono più di cinquanta script da controllare.
Un camminatore di cartelle che non trova nulla rende banalmente vera ogni affermazione che viene dopo. Rinomini la cartella, cambi il filtro sull’estensione, rompa il camminatore, e quattro setacci verdi riferiranno su un elenco vuoto. Il limite inferiore sta di proposito molto sotto il numero reale, perché lo scopo non è impedire la pulizia; è rendere visibile la differenza fra «non c’è nulla che non va» e «non è stato esaminato nulla».
Lo stesso file di prova è onesto sui propri limiti, e quell’onestà fa parte del disegno. Nessuno dei quattro setacci dimostra che uno script misuri ancora quello che dichiara. Un selettore che non esiste più, una rotta che si è spostata, una colonna che è stata eliminata: tutti e quattro i controlli passano. È un pavimento, non un controllo di tipo, e dirlo nel file impedisce alla persona successiva di trattare una corsa verde come qualcosa di più di quel che è.
Che cosa annoteremmo
- Una verifica che si limita a stampare non è una porta. Deve poter far fallire ciò che viene dopo di sé.
- Scriva la riga di fallimento, non solo i codici di uscita. La porta è una parola che qualcuno può cercare, in un punto fisso, non un numero che deve notare.
- Non incateni mai un deploy dietro un controllo che non può fermarlo. La catena è la promessa.
- I contatori sono un riassunto, non un verdetto. Le persone leggono il numero che si aspettano di vedere.
- Una porta deve poter stare a zero. Se una misura non ci riesce, la tenga come strumento e lo dica, oppure verrà disattivata e poi ignorata.
- Renda costosa la via di fuga a buon mercato da una regola. Se ammettere di non sapere costa una parola, tutti scrivono la parola.
- Un controllo che fallisce a caso non è un controllo. Insegua l’intermittenza prima di fidarsi del segnale.
- Affermi un limite inferiore su quello che il camminatore ha trovato. Quattro controlli verdi su un elenco vuoto sembrano esattamente quattro controlli verdi.
Per chiarezza su che cosa sia: la riga di fallimento sta nello script che sta nel deposito, e da allora ha girato su fusioni che erano verdi. Non è ancora stata dimostrata da una corsa rossa in uso reale, ma soltanto dal riepilogo rispetto al quale è stata scritta. I numeri sulla tipizzazione vengono da una sola misura in un solo giorno e il conteggio dei file si muove ogni notte.
Dal lavoro di piattaforma su Provibr. Un’altra guardia che ha dovuto dimostrare il proprio zero si trova in un’eccezione di accessibilità che si misura, e un campo che in silenzio conteneva un’intenzione invece di un fatto in una data futura non è un calendario.