Cambiamenti che rompono: cosa conta e come rilasciarli
10 min di lettura aggiornato il
Un cambiamento che rompe qualcosa è un cambiamento che un chiamante scritto correttamente non avrebbe potuto sopravvivere. La definizione conta perché la maggior parte delle discussioni su se qualcosa “conta” sono in realtà discussioni su chi lo teneva in modo sbagliato. Se un chiamante ha seguito la vostra documentazione e il vostro cambiamento ha fatto smettere di funzionare il suo codice, il cambiamento rompeva qualcosa. Cosa intendevate voi non c’entra nulla.
Questo è tutto il test. Il resto di questo articolo è ciò che ne deriva: cosa lo fallisce, cosa lo supera, come intercettare un fallimento prima del merge, e cosa fare una volta che sapete di stare rilasciando un cambiamento che rompe qualcosa.
Cosa conta come cambiamento che rompe qualcosa?
Applicate il test al chiamante, non al diff. Un cambiamento rompe qualcosa quando un chiamante che dipendeva solo dal comportamento documentato deve cambiare il suo codice, la sua configurazione o i suoi dati per continuare a funzionare. Rimuovere un campo, rinominare un endpoint, irrigidire la validazione, cambiare un default e cambiare il tipo di un valore si qualificano tutti. Aggiungere un campo opzionale no. Correggere un bug di solito no, con un’eccezione importante più sotto.
| Cambiamento | Rompe qualcosa? | Perché |
|---|---|---|
| Rimuovere o rinominare un campo, endpoint, flag o opzione | Sì | I chiamanti corretti lo referenziano |
| Aggiungere un campo opzionale o un nuovo endpoint | No | Le chiamate esistenti non cambiano |
| Rendere obbligatorio un input opzionale | Sì | Le chiamate che lo omettevano ora falliscono |
| Irrigidire una validazione prima accettata | Sì | Input che funzionavano ora vengono rifiutati |
| Cambiare un valore di default | Sì | I chiamanti che non l’hanno impostato ricevono comportamento nuovo |
| Cambiare un tipo (stringa a numero, valore singolo ad array) | Sì | I parser scritti per il tipo documentato falliscono |
| Riordinare le chiavi di un oggetto | No | A meno che abbiate documentato l’ordine |
| Correggere un bug su cui i chiamanti facevano affidamento | In pratica sì | Vedi la sezione sui contratti accidentali |
| Alzare un rate limit o un tetto di dimensione | No | Nulla che funzionava smette di funzionare |
| Abbassare un rate limit o un tetto di dimensione | Sì | Traffico che andava bene ora viene limitato |
| Cambiare la formulazione di un messaggio d’errore | Dipende | Rompe qualcosa se l’avete documentato o i chiamanti fanno match su di esso |
Cosa non è un cambiamento che rompe qualcosa?
Un cambiamento non rompe nulla quando ogni chiamata che funzionava prima funziona ancora, invariata, e significa ancora la stessa cosa. Aggiungere un nuovo endpoint, aggiungere un parametro opzionale alla richiesta, aggiungere un campo a una risposta, rendere opzionale un input obbligatorio, alzare un limite e migliorare un messaggio d’errore su cui nessuno fa match superano tutti il test. Questi cambiamenti additivi possono andare in un rilascio minor con una voce di changelog ordinaria.
I cambiamenti additivi rompono comunque i chiamanti in tre situazioni. Un client il cui deserializzatore rifiuta i campi sconosciuti fallisce al primo nuovo campo della risposta, quindi documentate fin dall’inizio che i chiamanti devono ignorare i campi che non riconoscono. Un nuovo valore di enum rompe qualsiasi chiamante con uno switch esaustivo (ne parliamo più sotto). E una risposta che cresce può spingere un chiamante oltre un limite di dimensione, un timeout o una larghezza di colonna a cui non aveva mai dovuto pensare.
Quattro righe della tabella meritano uno sguardo più attento, perché è lì che avvengono i disaccordi.
I quattro cambiamenti che rompono qualcosa che i team trascurano
Contratti accidentali. Se la vostra API ha restituito lo stesso campo non documentato per tre anni, un chiamante ci ha costruito sopra. La legge di Hyrum è la versione breve: con abbastanza utenti, ogni comportamento osservabile del vostro sistema sarà dipeso da qualcuno. Ecco perché “era una correzione di bug” non è una difesa. La correzione può essere corretta e rompere comunque qualcosa. Rilasciatela come tale.
Cambiamenti comportamentali senza cambiamento di schema. Il campo è ancora lì, il tipo è lo
stesso, e il valore ora significa qualcosa di diverso. Uno status che prima era active o
inactive e ora restituisce anche suspended rompe ogni chiamante con uno switch esaustivo. Un
timestamp che passa da ora locale a UTC rompe chiunque non abbia letto due volte la documentazione.
Nulla in un diff del file OpenAPI mostra queste cose.
Validazione irrigidita. Iniziate a rifiutare email senza TLD, o spazi finali, o nomi più lunghi di 80 caratteri. Ogni chiamante che inviava esattamente quello ora riceve un 400 per una richiesta che funzionava la settimana scorsa. I cambiamenti di validazione sono i più comuni rilasciati come correzione di “irrigidimento”.
Default cambiati. Nessuno che ha impostato il valore esplicitamente nota nulla. Tutti quelli che non l’hanno fatto, che sono la maggior parte dei chiamanti, ricevono comportamento nuovo senza cambiare una riga. Un default cambiato rompe la maggioranza dei vostri utenti proprio perché non hanno mai visto l’impostazione.
Come si individua un cambiamento che rompe qualcosa prima del rilascio?
Confrontate il contratto della pull request con quello del branch principale, in CI, e fate fallire la build in caso di differenza che rompe qualcosa. Esistono strumenti di diff degli schemi per la maggior parte dei formati di interfaccia, e ciascuno conosce le regole di rottura del proprio formato:
| Interfaccia | Strumento | Cosa confronta |
|---|---|---|
| REST (OpenAPI) | oasdiff | Due specifiche OpenAPI, con un report dei cambiamenti che rompono qualcosa |
| gRPC (Protobuf) | buf breaking | File .proto, a livello wire o di sorgente |
| GraphQL | GraphQL Inspector | Due schemi, segnalando i cambiamenti che rompono e quelli pericolosi |
| Crate Rust | cargo-semver-checks | L’API pubblica contro l’ultima versione pubblicata |
| Pacchetti TypeScript | API Extractor | Un report committato dell’API pubblica del pacchetto |
Questi strumenti intercettano in modo affidabile campi rimossi, operazioni rinominate e tipi cambiati. Non possono vedere i primi due dei quattro tipi sopra, un contratto accidentale o un cambiamento comportamentale, perché nessuno dei due compare in uno schema. Usate lo strumento per fermare quelli ovvi e la domanda di revisione “un chiamante corretto potrebbe accorgersene?” per gli altri. Lo stesso job di CI è il posto naturale per richiedere una voce di changelog, come descritto in far rispettare le voci di changelog in CI, e le modifiche alle API gRPC e Protobuf tratta i casi a livello wire.
Come si segna un cambiamento che rompe qualcosa in un commit?
Con i Conventional Commits, un cambiamento che
rompe qualcosa si segna con un ! prima dei due punti (feat(api)!: remove the legacy export endpoint)
oppure con un footer che inizia con BREAKING CHANGE: seguito da una descrizione. Entrambi
corrispondono a una versione major. Scrivete il footer come prima bozza della voce di changelog,
indicando chi è interessato e cosa deve fare.
Conventional commits e il changelog spiega fin dove vi
porta la convenzione.
La stessa regola vale per le librerie. Una funzione pubblica rimossa, un tipo di parametro ristretto o un valore di ritorno cambiato è una versione major nel versionamento semantico. Le librerie non sempre la rispettano: uno studio su 119.879 aggiornamenti di Maven Central ha rilevato che il 16,6% violava il versionamento semantico, ma solo il 7,9% dei progetti client ne è stato toccato, perché la maggior parte di quei cambiamenti riguardava codice che nessun client chiamava. La rottura si misura sul chiamante.
Come si rilascia un cambiamento che rompe qualcosa?
Lo si rilascia apertamente, con una data, con un percorso. I passi sotto sono in ordine, e l’ultimo è quello che la maggior parte dei team salta: dire alle persone interessate che ciò che stavano aspettando è ora accaduto.
- Decidete se lo è. Usate il test sopra, non il diff. Se due ingegneri non sono d’accordo, rompe qualcosa; il disaccordo è prova che un chiamante avrebbe potuto ragionevolmente fare affidamento sul vecchio comportamento.
- Versionatelo. Sotto versionamento semantico un cambiamento che rompe qualcosa è una versione major. Se gestite un’API datata o versionata, va in una nuova versione e quella vecchia continua a funzionare fino a una data dichiarata. Se non potete versionare, non state rilasciando un cambiamento che rompe qualcosa, state rilasciando un’interruzione con una voce di changelog. Quale schema porta la versione è l’argomento di buone pratiche di versionamento API.
- Scrivete la voce prima che il codice venga mergiato. La voce ha una forma fissa: cosa cambia, chi interessa, cosa devono fare, ed entro quando. Se non potete riempire tutte e quattro, il cambiamento non è pronto. La release notes template mette queste voci per prime, con una data invece di un numero di versione, esattamente per questo.
- Date una scadenza, non un numero di rilascio. “Rimosso in v5” non significa nulla per chi non segue i vostri rilasci. “Smette di funzionare il 1° novembre 2026” significa la stessa cosa per tutti.
- Fornite la migrazione. Un esempio di codice della vecchia chiamata accanto alla nuova. Se il cambiamento è una rinomina, dite entrambi i nomi nella stessa frase. Se è un campo rimosso, dite dove sono andati i dati.
- Annunciatelo ovunque il vecchio comportamento fosse documentato. Il changelog, la pagina docs che descrive l’endpoint, le release notes dell’SDK, e l’header di deprecazione nella risposta se ne avete uno.
- Chiudete il ciclo. Se una cliente ha chiesto il cambiamento, o ha segnalato il bug che vi ha portato, ditele quando viene rilasciato.
Come appare una buona voce per un cambiamento che rompe qualcosa?
Una buona voce nomina il chiamante interessato nella prima riga, dichiara la data, e include la correzione. Eccone una per il caso di validazione irrigidita, nella forma che usiamo:
Gli indirizzi email senza dominio vengono rifiutati dal 1° novembre 2026.
POST /usersePATCH /users/:idattualmente accettano valorialice@localhost. Dal 1° novembre questi restituiscono400 invalid_email. Interessa qualsiasi integrazione che crea utenti da directory interne. Migrazione: inviate un indirizzo completamente qualificato, o omettete il campo e impostatelo dopo. Nessun cambiamento necessario se i vostri indirizzi hanno già un dominio, il che è vero per il 99,4% degli account creati quest’anno.
Dove va questo avviso, e cos’altro dovrebbe accompagnarlo, è il tema di changelog di API.
La percentuale in fondo non è decorazione. Dice alla lettrice se deve preoccuparsi, che è la domanda con cui ha aperto la voce.
Perché non evitarli semplicemente?
Perché l’alternativa è peggiore. Un’API che non rompe mai nulla accumula ogni errore che ha mai fatto: il campo con nome sbagliato, il default sbagliato, il timestamp in ora locale. Ciascuno è una tassa su ogni nuovo chiamante per sempre, per proteggere chiamanti che avrebbero potuto migrare in un pomeriggio. I team con la migliore reputazione di stabilità rompono le cose raramente, secondo un calendario, con un percorso di migrazione e un avviso che ha raggiunto le persone per cui era destinato.
La meccanica di quell’avviso è l’argomento dell’articolo compagno su deprecare un’API. La voce che lo annuncia viene redatta nello stesso modo di qualsiasi altra voce nel feed del changelog: dalla pull request mergiata, trattenuta per un umano, poi pubblicata nel posto dove i chiamanti interessati già leggono.
FAQ
Qual è la differenza tra un cambiamento che rompe qualcosa e uno che non lo fa? Un cambiamento che rompe qualcosa costringe un chiamante corretto a modificare codice, configurazione o dati per continuare a funzionare. Uno che non rompe nulla lascia funzionare ogni chiamata esistente con lo stesso significato, ed è per questo che le aggiunte di solito sono sicure mentre rimozioni, rinomine e regole irrigidite di solito no.
Conta aggiungere un campo obbligatorio? Sì. Ogni chiamata esistente lo omette, quindi ogni chiamata esistente ora fallisce. Aggiungetelo come opzionale con un default sensato, o versionate l’endpoint.
Conta una correzione di bug? Può essere. Se i chiamanti dipendevano dal comportamento con il bug, correggerlo li rompe, qualunque cosa dicesse la documentazione. Trattate qualsiasi correzione che cambia l’output osservabile come un cambiamento che rompe qualcosa, a meno che non possiate dimostrare che nessuno ne dipendeva.
Il versionamento semantico si applica a un’API web? La regola sì: i cambiamenti che rompono qualcosa ottengono una nuova versione major e quella vecchia continua a funzionare per un periodo dichiarato. Il numero spesso vive nell’URL o in un header di data piuttosto che in una versione di pacchetto.
Quanto preavviso è sufficiente? Abbastanza perché un chiamante trovi l’avviso e faccia il lavoro. Novanta giorni è un minimo comune per API pubbliche; più lungo per qualsiasi cosa usata in codice spedito a utenti finali e che non può essere aggiornato da remoto.
Le affermazioni tecniche di questo articolo non sono state verificate in modo indipendente. Se qualcosa non è corretto, faccelo sapere e lo correggeremo.