Developer Experience
15 gennaio 20253 min read

TypeScript strict mode: non si torna indietro

Come abbiamo migrato un codebase di 50k righe a strict mode e perché ogni nuovo progetto parte da lì.

lintedhub

redazione tecnica
TypeScript
DX
Best Practices
Migration

TypeScript strict mode: non si torna indietro

"Strict mode è troppo restrittivo." L'abbiamo sentito. L'abbiamo pure pensato. Poi abbiamo fatto la migrazione e non torneremmo mai indietro.

Cosa include strict mode

Prima di tutto, chiariamo cosa significa "strict": true:

{
  "compilerOptions": {
    "strict": true
    // Equivale a:
    // "noImplicitAny": true,
    // "noImplicitThis": true,
    // "alwaysStrict": true,
    // "strictBindCallApply": true,
    // "strictNullChecks": true,
    // "strictFunctionTypes": true,
    // "strictPropertyInitialization": true,
    // "useUnknownInCatchVariables": true
  }
}

La flag più importante? strictNullChecks. Da sola vale la migrazione.

Il problema con nullable types

Senza strict mode:

function getUser(id: string) {
  return users.find(u => u.id === id);
}

const user = getUser("123");
console.log(user.name); // Runtime: Cannot read property 'name' of undefined

TypeScript non si lamenta. Il bug esplode in produzione.

Con strict mode:

function getUser(id: string) {
  return users.find(u => u.id === id);
}

const user = getUser("123");
console.log(user.name); // Error: 'user' is possibly 'undefined'

Il compilatore ti forza a gestire il caso:

const user = getUser("123");
if (user) {
  console.log(user.name); // OK
}

// O con optional chaining
console.log(user?.name);

// O con assertion (quando sei sicuro)
console.log(user!.name); // Usare con cautela

La strategia di migrazione

Abbiamo migrato 50k righe di codice in 3 settimane. Ecco come.

Fase 1: Preparazione

{
  "compilerOptions": {
    "strict": false,
    "noImplicitAny": true  // Inizia da qui
  }
}

noImplicitAny da solo trova tonnellate di problemi. Sistemali prima.

Fase 2: Null checks incrementali

{
  "compilerOptions": {
    "noImplicitAny": true,
    "strictNullChecks": true  // Il big one
  }
}

Aspettati molti errori. È normale. Procedi file per file.

Fase 3: Il resto

{
  "compilerOptions": {
    "strict": true  // Full send
  }
}

A questo punto, gli errori rimanenti sono pochi.

Pattern utili post-migrazione

Type guards

function isUser(obj: unknown): obj is User {
  return (
    typeof obj === "object" &&
    obj !== null &&
    "id" in obj &&
    "name" in obj
  );
}

const data = await fetchSomething();
if (isUser(data)) {
  console.log(data.name); // TypeScript sa che è User
}

Assertion functions

function assertUser(obj: unknown): asserts obj is User {
  if (!isUser(obj)) {
    throw new Error("Invalid user object");
  }
}

const data = await fetchSomething();
assertUser(data);
console.log(data.name); // OK dopo l'assertion

NonNullable utility type

type MaybeUser = User | null | undefined;
type DefinitelyUser = NonNullable<MaybeUser>; // User

I numeri

Dopo la migrazione:

  • Bug in produzione: -60% (sì, davvero)
  • Tempo di code review: -20% (meno da controllare)
  • Onboarding: più veloce (il codice è auto-documentante)

Conclusione

Strict mode non è "troppo restrittivo". È esattamente restrittivo quanto serve per catturare bug prima che raggiungano gli utenti.

Ogni nuovo progetto dovrebbe partire con "strict": true. Per progetti esistenti, la migrazione richiede sforzo ma ripaga rapidamente.

Il type system è il tuo alleato. Lascialo fare il suo lavoro.

Hai un progetto in mente?

Trasformiamo scelte tecniche complesse in sistemi in produzione.

Se stai valutando stack, architetture o integrazioni AI, parliamone. Niente pitch: una conversazione tecnica.

Parliamone