CodeGym /Corsi /JAVA 25 SELF /Catene di eccezioni (Exception Chaining)

Catene di eccezioni (Exception Chaining)

JAVA 25 SELF
Livello 24 , Lezione 2
Disponibile

1. Introduzione

In questa lezione analizzeremo un’importante tecnica di gestione delle eccezioni — le catene di eccezioni (exception chaining). Questa tecnica consente di non perdere le informazioni sulla causa principale dell’errore, anche quando «incapsuli» un’eccezione dentro un’altra.

Nelle applicazioni reali capita spesso che l’errore si verifichi in profondità nello stack delle chiamate — ad esempio, durante il lavoro con il database, il file system o la rete. Supponiamo che tu abbia un metodo che accede al database e può lanciare SQLException. Ma a livello di business logic non vuoi «sporcare» il codice con dettagli tecnici e preferisci lanciare una tua eccezione, ad esempio UserManagementException.

Cosa succede se lanci semplicemente una nuova eccezione?

try {
    // qualcosa con il database
} catch (SQLException e) {
    throw new UserManagementException("Errore durante la gestione degli utenti");
}

Problema:
In questo caso si perde l’informazione su cosa sia successo nel database (e lo stack delle chiamate!). Nel log vedrai solo UserManagementException e non saprai quale sia stata la causa.

2. Soluzione: incapsulare l’eccezione originale (chaining)

Java consente di «incapsulare» un’eccezione dentro un’altra, passando l’eccezione originale come causa (cause) al costruttore della nuova eccezione. Questo si chiama catena di eccezioni.

Come si fa?

La maggior parte delle eccezioni standard e personalizzate ha un costruttore che accetta un secondo parametro — Throwable cause:

public UserManagementException(String message, Throwable cause) {
    super(message, cause);
}

Utilizzo:

try {
    // qualcosa con il database
} catch (SQLException e) {
    throw new UserManagementException("Errore durante la gestione degli utenti", e);
}

Ora, se guardi lo stack delle chiamate (printStackTrace()), vedrai sia la tua eccezione sia l’intera catena fino alla causa principale!

3. Come ottenere la causa di un’eccezione

Ogni oggetto di tipo Throwable ha il metodo getCause(), che restituisce l’eccezione originale (oppure null, se non c’è).

Esempio:

try {
    // ...
} catch (UserManagementException e) {
    Throwable cause = e.getCause();
    if (cause != null) {
        System.out.println("Causa principale: " + cause);
    }
    e.printStackTrace();
}

A cosa serve?

  • Per il debugging: vedi non solo «cosa è andato storto» al livello superiore, ma anche dove esattamente si è verificato l’errore in profondità nello stack.
  • Per il logging: puoi scrivere nel log l’intera catena di errori.
  • Per trasferire informazioni tra i layer dell’applicazione: il layer di business può «incapsulare» un’eccezione tecnica nella propria, senza perdere i dettagli.

4. Esempio: catena di eccezioni in un’applicazione reale

Supponiamo di avere un metodo che carica un utente dal database:

public User loadUser(String username) throws UserManagementException {
    try {
        // Codice che può lanciare SQLException
        // ...
    } catch (SQLException e) {
        throw new UserManagementException("Impossibile caricare l'utente: " + username, e);
    }
}

Dove UserManagementException è la tua eccezione personalizzata:

public class UserManagementException extends Exception {
    public UserManagementException(String message, Throwable cause) {
        super(message, cause);
    }
}

Cosa succede in caso di errore?

  • Nei log saranno visibili sia la tua eccezione sia la SQLException originale con tutti i dettagli.
  • Se necessario, puoi accedere alla causa principale tramite getCause().

5. Come appare lo stack delle chiamate con una catena di eccezioni

Esempio di output:

UserManagementException: Impossibile caricare l'utente: vasya
    at UserService.loadUser(UserService.java:15)
    ...
Caused by: java.sql.SQLException: Connection refused
    at ...

Qui si vede tutto: l’intera catena delle chiamate, dove si è verificato l’errore di business e quale eccezione tecnica è stata la causa.

6. Pratica: implementiamo una catena di eccezioni

Passo 1. Creiamo la nostra eccezione:

public class UserManagementException extends Exception {
    public UserManagementException(String message) {
        super(message);
    }
    public UserManagementException(String message, Throwable cause) {
        super(message, cause);
    }
}

Passo 2. Usiamo la catena:

try {
    // qualcosa di rischioso
} catch (SQLException e) {
    throw new UserManagementException("Errore durante il lavoro con il DB", e);
}

Passo 3. Gestione al livello più alto:
Al livello più alto del programma intercettiamo la nostra eccezione personalizzata e stampiamo il messaggio di errore insieme all’intera catena delle cause.

public class Main {
    public static void main(String[] args) {
        try {
            runUserManagement();
        } catch (UserManagementException e) {
            System.err.println("Si è verificato un errore: " + e.getMessage());
            // Stampiamo la catena delle cause
            Throwable cause = e.getCause();
            while (cause != null) {
                System.err.println("Causa: " + cause.getMessage());
                cause = cause.getCause();
            }
        }
    }

    private static void runUserManagement() throws UserManagementException {
        try {
            // simulazione di errore del DB
            throw new SQLException("Nessuna connessione al DB");
        } catch (SQLException e) {
            throw new UserManagementException("Errore durante il lavoro con il DB", e);
        }
    }
}

7. Errori tipici nell’uso delle catene di eccezioni

Errore n. 1: lanci una nuova eccezione senza causa.

catch (SQLException e) {
    throw new UserManagementException("Errore", /* nessuna causa! */);
}

Male: si perde l’informazione sulla causa principale.

Errore n. 2: non hai implementato il costruttore con la causa nella tua eccezione.
Se nella tua classe di eccezione non c’è un costruttore con Throwable cause, non potrai passare la causa — dovrai aggiungerlo manualmente.

Errore n. 3: intercetti e silenzi l’eccezione senza propagarla.

catch (SQLException e) {
    // Ci limitiamo a fare logging e taciamo
}

Male: l’errore si «perde», il programma continua a funzionare in modo scorretto.

try {
    userService.loadUser("vasya");
} catch (UserManagementException e) {
    System.err.println("Errore: " + e.getMessage());
    if (e.getCause() != null) {
        System.err.println("Causa principale: " + e.getCause());
    }
    e.printStackTrace();
}
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION