Sections

FG&A s.r.l.

Personal tools
You are here: Home Articoli Exception Chaining in Java
Document Actions

Exception Chaining in Java

Il linguaggio Java ha una gestione delle eccezioni molto rigida che obbliga lo sviluppatore a popolare il codice di numerose clausole try...catch. Spesso questi casi vengono gestiti in modo sommario, facendo il catch della classe base Exception per minimizzare gli statement, ma una carenza in questa fase di progettazione può importare il rischio di non ottenere l'adeguato feedback sull'errore generato.

Author: Dario Rota, drnospam@fgasoftware.com

Introduzione

Il linguaggio Java ha una gestione delle eccezioni molto rigida che obbliga lo sviluppatore a popolare il codice di numerose clausole try...catch. Spesso questi casi vengono gestiti in modo sommario, facendo il catch della classe base Exception per minimizzare gli statement, ma una carenza in questa fase di progettazione può importare il rischio di non ottenere l'adeguato feedback sull'errore generato.

Capita infatti di perdere i dettagli di eccezioni che 'salgono' di livello tra le varie chiamate, senza memorizzare la causa vera dell'errore. Al contrario, può succedere che a livello utente venga visualizzata un'eccezione troppo specifica, o che addirittura rischi di svelare dei dettagli d'implementazione.

Quest'articolo descrive la tecnica dell'exception chaining che permette di risolvere entrambi i problemi esposti, permettendo di visualizzare un messaggio d'errore adeguato al contesto ma consentendo allo stesso tempo di risalire alla vera causa che ha generato l'errore.

Checked/unchecked exception

Vengono definite checked le eccezioni che, se lanciate, costringono colui che chiama il metodo a doverle intercettare. Quelle di tipo unchecked, al contrario, non hanno bisogno di essere gestite nella clausola catch. Ecco le due regole generali per l'utilizzo di questo tipo di eccezioni:

  • Usare le checked exception per situazioni di errore non fatale, quindi recuperabili
  • Usare le unchecked exception per errori fatali che devono far interrompere l'esecuzione del programma

In Java quest'ultime sono le RuntimeExcpetion e gli Error.

Exception Translation

Per Exception Translation si intende la tecnica grazie alla quale si maschera un'eccezione appena intercettata in una di diverso tipo, rilanciandola. Questo viene fatto, come già accennato, per i seguenti motivi:

  • Evitare il propagarsi di eccezioni di basso livello che potrebbero rivelare dettagli d'implementazione
  • Proporre dei messaggi d'errore meno specifici e più adatti al contesto dell'utente

Ecco un tipico esempio di ExceptionTranslation:

  try {
    .....
  } catch (ArithmeticException e) {
    throw new MyException("Messaggio d'errore generico.");
  }

Da notare che in questo caso perdiamo il dettaglio dell'eccezione originale.

Cosa si intende per Exception Chaining

L' Exception Translation è senz'altro una tecnica consigliata, ma a volte è utile dover risalire all'eccezione che ha causato il problema (che d'ora in avanti verrà chiamata causa). E' in questi casi che l'exception translation viene migliorata tramite la tecnica chiamata Exception Chaining (letteralmente "concatenamento di eccezioni"). Grazie ad essa la causa viene inglobata nell'eccezione più generica, la quale ha il compito di definire i metodi per restituirla e stamparne lo stack trace completo. Vedremo ora come implementare questa tecnica.

Implementazione

Implementazione generica

In Java, l'implementazione di una chained exception cambia a seconda della versione di jdk usata. Fino alla 1.3 è necessario sviluppare i metodi che resituiscono la causa e stampano lo stack trace, mentre dalla 1.4 in poi l'exception chaining è parte integrante delle classi Java. Inoltre, due dei quattro costruttori che andremo a sviluppare cambieranno a seconda di quale implementazione scegliamo.

Ad ogni modo, un'implementazione generica si basa su questi punti:

  • Derivare la classe da Exception
  • Ridefinirne i costruttori
  • Fornire un metodo per salvare la causa (useremo i costruttori. Non necessario dalla 1.4 in poi)
  • Fornire un metodo per restituire la causa (non necessario dalla 1.4 in poi)
  • Fornire un metodo per stampare lo stack trace completo (non necessario dalla 1.4 in poi)

In ogni caso, se abbiamo deciso che la nostra sarà un'eccezione di tipo checked, deriviamo la nostra classe da Exception:

  public class LibraryException extends Exception

Se invece avessimo optato per un'eccezione unchecked, avremmo dovuto derivare da RuntimeException:

  public class LibraryException extends RuntimeException

Prima di Java 1.4

Se vogliamo creare un'eccezione di tipo chained, prima della versione 1.4 l'implementazione è un pò laboriosa.

Per prima cosa, ridefiniamo i costruttori aggiungendone due

Default constructor. Richiamiamo il costruttore di default della classe padre.

  public LibraryException() {
    super();
  }

Questo costruttore è lievemente diverso in quanto riceve il messaggio d'errore tipico e chiama il relativo costruttore della classe base.

  public LibraryException(String message) {
    super(message);
  }

Questo costruttore ci permette di creare la nostra eccezione a partire da un'altra eccezione, che verrà inglobata come data member.

  public LibraryException(Throwable cause) {
    this.cause = cause;
  }

Qui invece, oltre a ricevere la causa, riceviamo un messaggio aggiuntivo che descrive l'eccezione.

  public LibraryException(Throwable cause, String message) {
    super(message);
    this.cause = cause;
  }

E' ora il momento di creare il metodo che ci restituirà la causa. Per convenzione lo chiameremo getCause, visto che da Java 1.4 si chiamerà così.

 public Throwable getCause() {
    return cause;
  }

Ridefiniamo ora i metodi che ci permetteranno di stampare lo stack trace completo dell'eccezione.

  public void printStackTrace() {
    super.printStackTrace();
    if (cause != null) {
      cause.printStackTrace();
    }
  }

Questo metodo stampa lo stack trace su un PrintStream

  public void printStackTrace(PrintStream s) {
    super.printStackTrace(s);
    if (cause != null) {
      s.println("Causato da:");
      cause.printStackTrace(s);
    }
  }

Questo metodo stampa lo stack trace su un PrintWriter

  public void printStackTrace(java.io.PrintWriter pw) {
    super.printStackTrace(pw);
    if (cause != null) {
      pw.println("Causato da:");
      cause.printStackTrace(pw);
    }
  }

Da Java 1.4 in poi

Da Java 1.4 in poi il compito dello sviluppatore è facilitato. L'exception chaining è infatti diventato parte delle classi d'eccezioni Java. La classe base Throwable dispone di un costruttore che riceve un'eccezione, la causa viene salvata e non c'è quindi bisogno di aggiungere un data member. I due costruttori che ricevono la causa andranno modificati per chiamare correttamente i costruttori della classe padre. Il metodo getCause e i tre printStackTrace spariscono. I primi due costruttori rimangono invariati, cambiano invece i due costruttori che ricevono la causa:

  public EnhancedLibraryException(Throwable cause) {
    super(cause);
  }

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

Come vedete,  è bastato ridefinire i costruttori per creare una chianed exception completa.

Conclusioni

L'exception chaining può senz'altro essere utile in situazioni dove serve un debug completo sulla causa che genera un'eccezione. Ipotizzando di creare una classe base chiamata ChainedException, possiamo usarla in ogni nostro progetto per creare un sistema d'eccezioni funzionale. Se invece volessimo creare un'eccezione tipica del progetto (MyProjectException) ma che sfrutti comunque i vantaggi dell'exception chaining, non dobbiamo far altro che creare la nuova classe ed ereditare da ChainedException, ovviamente ridefinendone i quattro costruttori.

Fonti e bibliografia

Effective Java Joshua Bloch Addison Wesley Capitolo 8 Item 40 / 43

Chained Exceptions in Java By Richard G. Baldwin http://www.developer.com/java/article.php/10922_1431531_1

Download codice completo

Il seguente archivio ZIP contiene il codice discusso in questo articolo sotto forma di progetto eclipse completo. Vedere i commenti nelle classi per ulteriori dettagli