> ## Content Index
> Fetch the complete content index at: https://bitstales.eu/llms.txt
> Use this file to discover other available public pages before exploring further.

# Non sei tu, è il mio catch: la posta del cuore delle eccezioni
- URL: https://bitstales.eu/it/try-catch-quando-non-farlo/
- Published: 2025-09-05T19:12:00.000Z
- Updated: 2025-09-05T19:12:00.000Z
- Description: Catch vuoti, except nudi, retry infiniti, cause perse e return dentro finally: la Dottoressa Finally risponde alle lettere del vostro codice. Perché catch non è sempre la soluzione.
- Author: Vittorio
- Tags: Programming, Error Handling, #it

Benvenuti alla **Posta del cuore del runtime**, la rubrica in cui il vostro codice racconta i suoi problemi di coppia e una professionista prova a rimettere insieme i pezzi. Risponde la **Dottoressa Finally**: trent'anni di esperienza nella gestione delle eccezioni e un'unica certezza, cioè che `catch` non è sempre la risposta giusta.

Perché un `try/catch` è una relazione a tutti gli effetti. C'è qualcuno che lancia un problema, qualcuno che lo riceve, e la domanda di sempre: *ne parliamo adesso o facciamo finta di niente?* Passiamo alle lettere.

## «Lui non mi dice mai niente»

> Cara Dottoressa, il mio collega ha scritto questo tre anni fa. Da allora gli ordini spariscono e nessuno capisce perché. — *Giulia, 31, backend Java*

```java
try {
    ordini.salva(ordine);
} catch (Exception e) {
    // non dovrebbe mai succedere
}
```

Cara Giulia, questa è la relazione in cui i problemi non si affrontano mai. Il database è giù, la connessione è scaduta, il disco è pieno, ma lui risponde «tutto bene, perché?». Il programma continua come se l'ordine fosse stato salvato, l'utente riceve la conferma e il cliente non riceverà mai il pacco. Il problema non è sparito: è stato **rimandato**, e quando tornerà fuori nessuno saprà più dove cercarlo.

In gergo si chiama *Pokémon exception handling*, perché «gotta catch 'em all». Il commento «non dovrebbe mai succedere» è l'equivalente di «ma noi non litighiamo mai»: è vero solo finché nessuno controlla.

**La regola:** se catturate un'eccezione dovete *fare qualcosa*: recuperare davvero, aggiungere contesto e rilanciarla, oppure registrarla e rispondere in modo sensato. Un blocco `catch` vuoto non è gestione dell'errore. È ghosting.

## «È gelosa: intercetta tutte le mie chiamate»

> Dottoressa, il mio worker Python non si ferma più. Né con Ctrl+C, né con `sys.exit()`. Devo usare `kill -9`. — *Marco, 27, data engineer*

```python
while True:
    try:
        elabora_prossimo_batch()
    except:          # "except" nudo: cattura TUTTO
        pass
```

Caro Marco, il tuo `except` è il partner geloso che risponde a tutte le telefonate, comprese quelle dei vigili del fuoco. In Python un `except:` senza tipo cattura `BaseException`, cioè anche `KeyboardInterrupt` (Ctrl+C) e `SystemExit` (`sys.exit()`). In pratica il programma non ti lascia andare via.

La gerarchia delle eccezioni è fatta così proprio per evitarlo: `Exception` raccoglie gli errori "normali", mentre le richieste di chiusura stanno fuori. Dalla versione 3.8 anche `asyncio.CancelledError` deriva da `BaseException`, quindi un `except Exception` non blocca più per sbaglio la cancellazione di un task. Ma un `except BaseException` sì.

In Java vale lo stesso discorso con `catch (Throwable t)`, che cattura anche `OutOfMemoryError` e `StackOverflowError`. Sono situazioni da cui la JVM spesso non può riprendersi in modo affidabile. E se catturate `InterruptedException`, ripristinate il flag. Altrimenti chi voleva fermare il thread non lo saprà mai:

```java
catch (InterruptedException e) {
    Thread.currentThread().interrupt();   // "ho sentito, lo dico anche agli altri"
    return;
}
```

**La regola:** catturate il tipo **più specifico** che sapete gestire. `FileNotFoundError`, non `Exception`. La gelosia non è una strategia di gestione degli errori.

## «Racconta i nostri litigi a tutti»

> Dottoressa, quando qualcosa va storto nel nostro servizio lo stesso stack trace compare quattro volte nei log, a livelli diversi. Durante le reperibilità notturne ci sembra di avere quattro incidenti. — *Il team SRE di un'azienda che preferisce restare anonima*

```java
// repository
catch (SQLException e) { log.error("errore DB", e); throw e; }
// service
catch (SQLException e) { log.error("errore salvataggio", e); throw e; }
// controller
catch (Exception e) { log.error("errore richiesta", e); throw e; }
```

Cari SRE, questa è la coppia che, dopo ogni discussione, la racconta alla madre, al migliore amico, al collega e al barista. Alla fine tutti conoscono il litigio in quattro versioni, e nessuno ha capito come è finito. Ogni livello registra l'errore *e* lo rilancia, quindi un solo problema produce quattro voci nei log, quattro allarmi e un'ora persa a capire che si trattava di una cosa sola.

**La regola:** o gestite un errore, o lo propagate. Non tutte e due le cose. Si registra **una volta sola**, nel punto in cui l'errore viene davvero gestito, che di solito è il confine del sistema: l'handler HTTP, il loop principale, il consumer della coda. Se un livello intermedio ha qualcosa da aggiungere, aggiunga *contesto*, non una riga di log in più.

## «Mi ha lasciata con un messaggio di due parole»

> Dottoressa, nei log trovo solo `RuntimeException: Errore DB`. Nessun dettaglio, nessuna causa. Cosa è successo? — *Francesca, 35, tech lead*

```java
catch (SQLException e) {
    throw new RuntimeException("Errore DB");   // e la causa?
}
```

Cara Francesca, sei stata lasciata con un SMS: «Non funziona. Ciao». L'eccezione originale, con il codice SQL, il vincolo violato e lo stack trace, è stata buttata via. Tradurre un'eccezione in una di livello più alto va benissimo (è il pattern Adapter applicato agli errori), ma la causa va **allegata**. Ogni linguaggio ha il suo modo per farlo:

```java
throw new OrdineNonSalvato("salvataggio ordine " + id, e);   // Java: la causa resta collegata
```

```python
raise OrdineNonSalvato(f"salvataggio ordine {id}") from e      # Python: "The above exception was the direct cause..."
```

```go
return fmt.Errorf("salvataggio ordine %d: %w", id, err)     // Go: %w, poi errors.Is / errors.As
```

In Python esiste anche `raise ... from None`, che nasconde di proposito la causa. È il ghosting consapevole: a volte ha senso, per esempio quando la causa contiene dati sensibili, ma bisogna sapere perché lo si fa.

## «Continuiamo a lasciarci e a tornare insieme»

> Dottoressa, il nostro client riprova all'infinito quando il servizio dei pagamenti non risponde. Ieri il servizio si è ripreso e lo abbiamo subito steso di nuovo. E un cliente ha pagato tre volte. — *Luca, 40, CTO di una startup*

```python
while True:
    try:
        return addebita(carta, importo)
    except Exception:
        continue        # riproviamo, magari stavolta va
```

Caro Luca, questo è il tira e molla con l'ex, versione distribuita. Tre problemi:

1. **Nessuna pausa.** Mille client che riprovano tutti insieme nello stesso istante generano una tempesta di richieste (*thundering herd*) che abbatte il servizio appena si rialza.
2. **Nessuna distinzione.** Un timeout può essere temporaneo. «Carta rifiutata» no. Riprovare un errore definitivo è come richiamare chi ti ha bloccato: non cambia niente, ma fa più rumore.
3. **Nessuna idempotenza.** Se la prima richiesta è arrivata e solo la risposta si è persa, il secondo tentativo addebita di nuovo. Da qui i tre pagamenti.

```python
import random, time

def con_retry(op, tentativi=5, base=0.2):
    for n in range(tentativi):
        try:
            return op()
        except (TimeoutError, ConnectionError):          # solo errori transitori
            if n == tentativi - 1:
                raise
            time.sleep(base * 2 ** n * random.uniform(0.5, 1.5))   # backoff esponenziale + jitter

con_retry(lambda: addebita(carta, importo, idempotency_key=ordine.id))
```

Aggiungete un *circuit breaker*: dopo troppi fallimenti smette di provare per un po', esattamente come l'interruttore di casa. A volte la cosa più sana è prendersi una pausa.

## «Usa le crisi per decidere dove andare a cena»

> Dottoressa, il mio parser controlla se una stringa è un numero provando a convertirla e catturando l'eccezione. Lo fa milioni di volte al secondo. Il profiler dice che passiamo il 40% del tempo a costruire stack trace. — *Andrea, 29, sviluppatore*

Caro Andrea, le eccezioni servono per le situazioni *eccezionali*, non per il traffico ordinario. In Java creare un'eccezione costa: il costruttore chiama `fillInStackTrace()`, che percorre tutto lo stack. In C++ il modello "a costo zero" non costa nulla quando tutto va bene, ma lanciare un'eccezione costa moltissimo, perché lo srotolamento dello stack passa per tabelle e ricerche. È come provocare una crisi di coppia ogni sera per decidere tra pizza e sushi: si arriva a una risposta, ma il prezzo è alto.

Ci sono però differenze culturali. In Python lo stile EAFP (*Easier to Ask Forgiveness than Permission*) è idiomatico: `try: d[k] except KeyError:` è normale, e perfino il ciclo `for` finisce con un'eccezione, `StopIteration`. Ogni linguaggio ha le sue usanze. L'importante è non importare quelle di un altro senza capirle.

## «Ho scoperto che stava con me solo per abitudine»

> Dottoressa, abbiamo un `catch (NullPointerException e)` che "risolve" un crash in produzione. Da allora i dati di alcuni utenti sono sbagliati, ma nessuno sa dire quali. — *Sara, 33, QA*

Cara Sara, un `NullPointerException`, un `IndexError` o un'asserzione fallita non sono eventi della vita da gestire: sono **bug**. Catturarli tiene in piedi un programma il cui stato interno è già sbagliato, e da lì in poi ogni operazione rischia di scrivere dati corrotti. È la relazione che va avanti per inerzia: nessuno ha più fiducia, ma nessuno vuole essere il primo a dirlo.

Ci sono interi ecosistemi costruiti sul principio opposto. Erlang ed Elixir hanno la filosofia *let it crash*: il processo che si trova in uno stato inatteso muore subito, e un *supervisor* ne avvia uno nuovo, pulito. Rust distingue nettamente tra `Result` (un errore previsto, da gestire) e `panic!` (un bug, che si propaga). Su un server, systemd con `Restart=on-failure` e Kubernetes con i suoi restart fanno la stessa cosa a livello di processo.

**La regola:** i bug non si catturano, si correggono. A volte la cosa più sana è lasciarsi: un crash pulito e un riavvio sono meglio di una convivenza con dati corrotti.

## «Se n'è andato senza restituirmi le chiavi»

> Dottoressa, dopo qualche ora il servizio finisce i file descriptor. Ogni volta che una richiesta fallisce, un file resta aperto. — *Paolo, 38, sysadmin*

Caro Paolo, anche quando una relazione finisce male, le chiavi vanno restituite. Il rilascio delle risorse non dipende da come è andata: per questo esistono `finally` e, ancora meglio, i costrutti che lo rendono automatico. `with` in Python, try-with-resources in Java, `defer` in Go, RAII in C++ e Rust:

```python
with open(percorso) as f:        # il file si chiude anche se qualcosa va storto
    dati = analizza(f)
```

Attenzione però a una trappola poco conosciuta: un `return` dentro `finally` fa sparire l'eccezione in corso.

```python
def salva():
    try:
        raise IOError("disco pieno")
    finally:
        return "tutto ok"        # l'IOError sparisce: nessuno lo saprà mai
```

È il "tutto ok" detto con un sorriso mentre la casa brucia. Succede anche in Java. Python ha deciso di intervenire: con la PEP 765, Python 3.14, in uscita il mese prossimo, emetterà un `SyntaxWarning` per `return`, `break` e `continue` che escono da un blocco `finally`.

## Allora quando si fa il catch? (Le coppie che funzionano)

Dopo tante lettere tristi, una buona notizia: il `catch` giusto esiste, ed è quello che ha un **piano**.

- **Ai confini del sistema.** L'handler HTTP che trasforma un'eccezione in una risposta 500 con un identificativo di correlazione, la CLI che stampa un messaggio chiaro ed esce con un codice diverso da zero, l'interfaccia che mostra «riprova più tardi». Qui si registra l'errore, una volta sola.
- **Quando c'è un piano B reale.** Il file di configurazione utente non esiste (`FileNotFoundError`, non `Exception`)? Usate i valori predefiniti. La cache remota non risponde? Leggete dal database.
- **Per tradurre tra livelli**, collegando la causa: `SQLException` diventa `OrdineNonSalvato`, così il livello di dominio non deve conoscere i dettagli SQL.
- **Per aggiungere contesto**: «salvataggio ordine 4812» è molto più utile di «errore».

Alcuni linguaggi hanno scelto di non usare proprio le eccezioni per gli errori previsti. Go usa `if err != nil`: una comunicazione esplicita a ogni passo, un po' noiosa ma onesta. Rust usa `Result` e l'operatore `?`: si dice subito che qualcosa non va, senza drammi, e si passa il problema a chi può risolverlo. Java ha le *checked exception*, una specie di contratto prematrimoniale: le clausole sono scritte nella firma del metodo, e da trent'anni si discute se sia una buona idea. C# e Kotlin, infatti, hanno deciso di farne a meno.

## Il decalogo della coppia sana

1. Catturate solo ciò che sapete gestire.
2. Catturate il tipo più specifico, mai `except:` nudo o `Throwable`.
3. O gestite o propagate: non tutte e due.
4. Registrate l'errore una volta sola, al confine del sistema.
5. Non perdete mai la causa: `from e`, `%w`, `new X(msg, e)`.
6. Riprovate solo gli errori transitori, con backoff, jitter, un limite e l'idempotenza.
7. I bug non si catturano: si correggono.
8. Rilasciate le risorse con `with`, `defer` o RAII, e mai un `return` dentro `finally`.
9. Le eccezioni sono per le eccezioni, non per il controllo del flusso (salvo usanze locali).
10. A volte la cosa più sana è lasciarsi: un crash pulito e un buon supervisor.

Con affetto e uno stack trace completo,  
**la Dottoressa Finally** 💌