Risposta diretta
In questo monorepo, tsconfig ha strict, strictTemplates e strictInjectionParameters tutti a true, eppure sopravvivono 54 usi espliciti di ': any' e 46 cast 'as any' su 3884 file TypeScript. Strict mode elimina gli errori di tipo accidentali, non quelli deliberati: any resta una via di fuga disponibile a chi ha fretta, e nessuna opzione del compilatore puo chiuderla del tutto.
"Ho strict: true, quindi il mio codebase è type-safe" è
una frase che sento spesso e che sento raramente verificata su un
progetto reale, di dimensioni reali. Strict mode è necessario. Non è
sufficiente. La differenza tra i due aggettivi vale 100 breach
espliciti di tipo su questo stesso portfolio, tutti passati la
review, tutti compilati senza un solo errore.
Disclaimer di metodo: i numeri vengono da un grep diretto
sul sorgente (grep -ro ": any\b" src/app e
grep -ro "as any\b" src/app), non da un'analisi
teorica. Sono verificabili da chiunque abbia accesso al repository.
Cosa fa davvero strict mode (e cosa non fa)
Il flag strict: true di TypeScript è in realtà un
pacchetto di otto flag distinti (strictNullChecks,
noImplicitAny, strictFunctionTypes e
altri). Questo progetto li ha tutti attivi, più due estensioni
specifiche di Angular: strictTemplates (type-checking
nei template HTML) e strictInjectionParameters
(dependency injection tipizzata).
Quello che strict mode elimina davvero sono gli errori accidentali:
una variabile che il compilatore avrebbe inferito come
any senza noImplicitAny, un accesso a una
proprietà potenzialmente null senza
strictNullChecks. Sono errori che nessuno scrive di
proposito: emergono dall'assenza di vincoli, non da una scelta
esplicita dello sviluppatore.
Quello che strict mode NON elimina è la via di fuga deliberata:
any scritto a mano, o un cast as SomeType
che dice al compilatore "fidati di me" senza che sia vero. Nessun
flag del compilatore può impedire a uno sviluppatore sotto pressione
di scrivere const data: any = response.body per far
compilare il codice e passare oltre.
Il dato: 100 breach espliciti su 3884 file
Il conteggio esatto, a oggi:
- 54 occorrenze di
: any(tipizzazione esplicita ad any). - 46 occorrenze di
as any(cast forzato). - 37 funzioni generiche (
function nome<T>) come confronto: la parte "avanzata" di TypeScript che questo articolo non copre, ma che coesiste con gli stessi 100 breach. - 3884 file TypeScript totali in
src/app.
100 breach su 3884 file è meno del 3% dei file, e molte occorrenze
condividono lo stesso file. Non è un allarme rosso. È la prova
empirica che anche in un codebase disciplinato, con ogni flag di
rigidità disponibile attivo, any non scompare mai del
tutto: si riduce a un residuo misurabile, non a zero.
Dove finiscono davvero gli any: i confini del sistema
Non tutti gli any sono uguali. Guardando dove si
concentrano nel codice, i pattern ricorrenti sono tre:
-
Boundary con librerie esterne non tipizzate o parzialmente
tipizzate. Una libreria di terze parti senza
@typesaggiornati, o con tipi generici troppo larghi per essere utili, spinge versoas anycome via più rapida rispetto a scrivere una dichiarazione di tipo custom per codice che non controlli. -
API browser/SSR ambigue. Codice che deve
funzionare sia server-side (Node/Express) sia client-side
(browser), dove un oggetto globale ha forma diversa nei due
ambienti, a volte finisce per usare
anyinvece di un union type esplicito, soprattutto quando il controlloisPlatformBrowsergià gestisce la logica e il tipo sembra ridondante. -
Deadline sotto pressione. Il caso più onesto e
più comune: un componente che deve andare in produzione, un tipo
complesso da derivare correttamente (generics annidati, mapped
types), e la scelta pragmatica di sbloccare il commit con
anyinvece di passare venti minuti a scrivere il tipo corretto.
Il trade-off: unknown, type guard, o any onesto
Qui arriva la parte controversa dell'articolo. La risposta da
manuale è sempre "usa unknown invece di
any, poi restringi con un type guard". È corretta in
teoria. Nella pratica di un boundary con una libreria esterna,
unknown sposta il costo dello sblocco dal punto di
ingresso a ogni singolo punto di uso a valle, perché ogni accesso a
una proprietà richiede prima un narrowing esplicito.
A volte quel costo vale la pena (dati che attraversano un confine di
sicurezza, input utente, risposte API non fidate). Altre volte no:
un any dichiarato con un commento che spiega perché
(// any: libreria X non ha types, verificato manualmente v2.1)
è più onesto di un as SomeType che finge una garanzia
di tipo che il compilatore non sta realmente verificando. Un cast
bugiardo è peggio di un any dichiarato, perché il
primo dà una falsa sensazione di sicurezza al prossimo sviluppatore
che legge quel codice.
Eliminare gli ultimi any residui ha un rendimento
marginale decrescente: i primi cento probabilmente valgono lo
sforzo, gli ultimi dieci, spesso legati a boundary esterni
genuinamente non tipizzabili, no.
Cosa farei diversamente
Non cambierei la scelta di strict mode al 100%: resta la base
corretta. Quello che introdurrei è un lint rule dedicato
(@typescript-eslint/no-explicit-any in modalità
"warn", non "error") con un commento obbligatorio su ogni
occorrenza che spiega il motivo del boundary, invece di lasciare
che any passi silenzioso in code review solo perché
compila. Il problema non è che esistano 100 any: è che
nessuno dei 100 dichiara perché è lì.
Domande frequenti
Cosa attiva davvero il flag strict di TypeScript?
strict: true attiva un pacchetto di otto flag distinti, tra cui strictNullChecks, noImplicitAny e strictFunctionTypes, che insieme eliminano gli errori di tipo accidentali (variabili inferite come any, accessi a proprietà potenzialmente null) ma non impediscono l'uso deliberato di any scritto esplicitamente dallo sviluppatore.
Perché un progetto con strict mode al 100% ha ancora usi di any?
Perché any resta una via di fuga sempre disponibile al compilatore: nessun flag di rigidità può impedire a uno sviluppatore di scrivere esplicitamente any per far compilare del codice velocemente, specialmente su boundary con librerie esterne non tipizzate o sotto pressione di deadline.
Meglio usare unknown o any quando un tipo non è chiaro?
Dipende dal contesto: unknown è più sicuro perché richiede un narrowing esplicito prima di ogni uso, ma sposta il costo dal punto di ingresso a ogni punto di uso a valle. Su dati che attraversano un confine di sicurezza vale la pena, su boundary genuinamente non tipizzabili un any dichiarato con un commento esplicativo è spesso più onesto.
Perché un cast 'as SomeType' può essere peggio di un any dichiarato?
Perché un cast forzato finge una garanzia di tipo che il compilatore non sta realmente verificando, dando una falsa sensazione di sicurezza al prossimo sviluppatore che legge quel codice, mentre un any dichiarato esplicitamente è almeno onesto sul fatto che quel valore non è verificato.
Vale la pena eliminare tutti gli any residui in un codebase strict?
No, non tutti: eliminare gli any ha un rendimento marginale decrescente. I primi sono spesso facili da correggere, ma gli ultimi residui tendono a essere legati a boundary esterni genuinamente non tipizzabili, dove il costo di eliminarli supera il beneficio reale in sicurezza di tipo.
Commenti
Caricamento commenti...
Accedi per commentare