2009-06-25 8 views
17

"La principale differenza tra una cosa che potrebbe andare male e una cosa che non si può andare storto è che quando una cosa che non si può andare storto va male di solito si rivela essere impossibile da ottenere in o riparazione. " -Douglas AdamsCome posso gestire una IOException che so non può mai essere lanciata, in modo sicuro e leggibile?

Ho una classe FileItems. Il costruttore FileItems accetta un file e genera un'eccezione (FileNotFoundException) se il file non esiste. Altri metodi di quella classe implicano anche operazioni sui file e quindi hanno la possibilità di lanciare FileNotFoundException. Mi piacerebbe trovare una soluzione migliore. Una soluzione che non richiede che altri programmatori gestiscano tutte queste estremamente improbabili FileNotFoundExceptions.

I fatti della materia:

  1. il file è stato controllato per esistere, ma esiste la possibilità estremamente improbabile che attraverso alcuni importanti colpa della realtà il file potrebbe essere eliminato prima che questo metodo viene chiamato.
  2. Poiché la probabilità che 1 si verifichi è estremamente diversa e irrecuperabile, preferirei definire un'eccezione non controllata.
  3. Il file è già stato trovato esistere, costringendo altri programmatori a scrivere codice e catturare l'opzione FileNotFoundException verificata sembra noiosa e inutile. Il programma dovrebbe fallire completamente a quel punto. Ad esempio c'è sempre la possibilità che un computer possa catch fire, but no one is insane enough to force other programmers to handle that as a checked exception.
  4. Mi imbatto in questo tipo di problema di eccezione di volta in volta e definendo eccezioni non controllate personalizzate ogni volta che si verifica questo problema (la mia vecchia soluzione) è faticoso e si aggiunge a code-bloat.

Il codice attualmente assomiglia a questo

public Iterator getFileItemsIterator() { 
    try{ 
     Scanner sc = new Scanner(this.fileWhichIsKnowToExist); 
     return new specialFileItemsIterator(sc);   
     } catch (FileNotFoundException e){ //can never happen} 

    return null; 
} 

Come posso fare questo meglio, senza definire un FileNotFoundException incontrollato personalizzato? C'è un modo per lanciare un checkedException su uncheckException?

+2

I principali errori della realtà avvengono sorprendentemente spesso. –

+0

I guasti della realtà hanno rotto il mio software perfetto per tutta la settimana. Ho provato a presentare alcune segnalazioni di bug ma sembra che anche il loro server jira sia inattivo http://jira.reality.net/ –

risposta

46

Il solito schema per affrontare questo è exception chaining. Basta avvolgere la FileNotFoundException in una RuntimeException:

catch(FileNotFoundException e) { 
    throw new RuntimeException(e); 
} 

Questo modello non è applicabile solo quando un'eccezione non può avvenire nella situazione specifica (come la vostra), ma anche quando si dispone di alcun mezzo o intenzione di gestire davvero il eccezione (come un errore del collegamento al database).

Edit: Attenzione di questo anti-modello simile di aspetto, che ho visto in natura troppo spesso:

catch(FileNotFoundException e) { 
    throw new RuntimeException(e.getMessage()); 
} 

In questo modo, si butta via tutte le informazioni importanti in originale stacktrace, che spesso rende difficile il rintracciamento dei problemi.

Un'altra modifica: Come Thorbjørn Ravn Andersen fa giustamente notare nella sua risposta, non fa male di indicare il motivo per cui si sta concatenamento l'eccezione, sia in un commento o, meglio ancora, come il messaggio di eccezione:

catch(FileNotFoundException e) { 
    throw new RuntimeException(
     "This should never happen, I know this file exists", e); 
} 
+3

+1 sull'anti-pattern. questo mi fa impazzire. – akf

+0

+1, non pensavo che il costruttore RuntimeException abbia preso un'eccezione a causa di tutto il codice che ho da allora che passa il messaggio. Grazie mille. –

+1

Ora vai a visitare dailywtf.com per uno screenshot di una finestra di dialogo che dice "Questo non dovrebbe mai accadere, so che questo file esiste" –

11

È possibile convertire un'eccezione controllata in deselezionata raggruppandola in una RuntimException. Se l'eccezione viene catturata più in alto nello stack ed emessa usando printStackTrace(), verrà visualizzata anche la pila per l'eccezione originale.

try { 
    // code... 
} 
catch (IOException e) { 
    throw new RuntimeException(e); 
} 

Questa è una buona soluzione, che non si dovrebbe esitare a utilizzare in queste situazioni.

7

Se la propria posizione è così improbabile e deve solo terminare il programma, utilizzare un'eccezione di runtime esistente, anche RuntimeException stessa (se non IllegalStateException).

1

si sono probabilmente meglio gettare l'eccezione superclasse e più generico IOException in qualsiasi punto nel codice che coinvolge la lettura o la scrittura del file.

Il file può esistere quando il costruttore di vostra classe viene eseguito, ma che non garantisce che:

  1. Essa esiste quando i metodi vengono chiamati
  2. E 'scrivibile/leggibile
  3. Un altro thread non lo fa l'accesso e in qualche modo compromettere il tuo stream
  4. la risorsa non va via nel bel mezzo della lavorazione

ecc.

Invece di reinventare la ruota, direi semplicemente ri-lanciare IOException ovunque le classi JDK/java.io ti obbligano a farlo.

Anche io per una classe di odio che lancia Eccezioni dal loro costruttore - Mi piacerebbe sbarazzarsi di questi se fossi in te.

1

Ho fatto un po 'di ricerca su google e ho trovato questo globo di codice. E 'un po' più flessibile di un approccio mi pensa

Complimenti di questo article

class SomeOtherException extends Exception {} 

public class TurnOffChecking { 
    private static Test monitor = new Test(); 
    public static void main(String[] args) { 
    WrapCheckedException wce = new WrapCheckedException(); 
    // You can call f() without a try block, and let 
    // RuntimeExceptions go out of the method: 
    wce.throwRuntimeException(3); 
    // Or you can choose to catch exceptions: 
    for(int i = 0; i < 4; i++) 
     try { 
     if(i < 3) 
      wce.throwRuntimeException(i); 
     else 
      throw new SomeOtherException(); 
     } catch(SomeOtherException e) { 
      System.out.println("SomeOtherException: " + e); 
     } catch(RuntimeException re) { 
     try { 
      throw re.getCause(); 
     } catch(FileNotFoundException e) { 
      System.out.println(
      "FileNotFoundException: " + e); 
     } catch(IOException e) { 
      System.out.println("IOException: " + e); 
     } catch(Throwable e) { 
      System.out.println("Throwable: " + e); 
     } 
     } 
    monitor.expect(new String[] { 
     "FileNotFoundException: " + 
     "java.io.FileNotFoundException", 
     "IOException: java.io.IOException", 
     "Throwable: java.lang.RuntimeException: Where am I?", 
     "SomeOtherException: SomeOtherException" 
    }); 
    } 
} ///:~ 
10

Considerare utilizzando il modulo

throw new RuntimeException("This should never happen", e); 

invece. Questo ti permette di trasmettere un significato al maintainer per seguirti, sia durante la lettura del codice, ma anche DOVREBBE che l'eccezione sia stata lanciata in qualche strano scenario.

MODIFICA: questo è anche un buon modo per passare eccezioni attraverso un meccanismo che non prevede tali eccezioni. Per esempio. se hai un iteratore "ottieni più righe dal database", l'interfaccia di Iterator non ti permette di lanciare e.g. un FileNotFoundException in modo da poterlo avvolgere in questo modo. Nel codice USING the iterator, è possibile rilevare l'errore runtimee ed esaminare l'escpetion originale con getCause(). MOLTO utile quando si attraversano percorsi di codice legacy.

1

Ho riscontrato lo stesso problema.

Nel caso più semplice si informa l'utente dell'errore e si suggerisce di ripetere o annullare l'operazione.

In generale, il flusso di lavoro è una sequenza di azioni (I/O incluso), in cui ogni azione "presuppone" che il precedente sia riuscito.

L'approccio che ho scelto è quello di creare un elenco di azioni di "roll-back". Se il flusso di lavoro ha esito positivo, vengono ignorati. Se si verifica un'eccezione, eseguo i rollback e presenta un'eccezione all'utente.

Questo:

  • mantiene l'integrità dei dati
  • permette di codificare più facile

funzione tipica è come:

returntype func(blah-blah-blah, Transaction tr) 
{ 
    // IO example 
    Stream s = null; 
    try 
    { 
     s = new FileStream(filename); 
     tr.AddRollback(File.Delete(filename)); 
    } 
    finally 
    { 
     if (s != null) 
      s.close(); 
    } 
} 

Utilizzo Tipico è:

Transaction tr = new Transaction(); 
try 
{ 
    DoAction1(blah1, tr); 
    DoAction2(blah2, tr); 
    //... 
} 
catch (Exception ex) 
{ 
    tr.ExecuteRollbacks(); 
    // queue the exception message to the user along with a command to repeat all the actions above 
} 

Questo è un po po 'più complicato nel mondo reale, perché

  • a volte è necessario per ottenere un po' di serrature prima di azioni di esecuzione
  • codice di rollback dovrebbe essere eccezioni in silenzio stesso (per istanza)

Ma mi sono già abituato a questo approccio, ora le mie applicazioni sono più stabili.

0

Non caricare l'intera applicazione con un RuntimeException quando si verifica una condizione di errore improbabile. RuntimeException deve essere riservato per gli errori del programmatore e probabilmente non è causato dall'errore del programmatore IOException.

Invece, incapsula l'eccezione di livello inferiore in un'eccezione di livello superiore e rethrow. Quindi gestisci l'eccezione di livello superiore nella catena di chiamate.

Ad esempio:

class SomeClass { 

    public void doActions() { 
    try { 
     someAction(); 
    } catch (HigherLevelException e) { 
     notifyUser(); 
    } 

    someOtherUnrelatedAction(); 
    } 

    public void someAction() throws HigherLevelException { 
    try { 
     // user lower-level abstraction to do our bidding 
    } catch(LowerLevelException e) { 
     throw new HigherLevelException(e); 
    } 
    } 

    public void someOtherUnrelatedAction() { 
    // does stuff 
    } 
} 

Molto probabilmente lo stack di chiamate che ha generato l'eccezione si esibiva qualche compito nella vostra applicazione. Invece di forzare il crash dell'intera applicazione con un RuntimeException capire cosa fare quando il problema si verifica durante quell'attività. Ad esempio, stavi cercando di salvare un file? Non arrestarti in modo anomalo, ma informa l'utente che si è verificato un problema.

Problemi correlati