Ignora l'eccezione quando si chiude una risorsa come un file?

4

Da efficace Java 2e di Joshua Bloch,

An example of the sort of situation where it might be appropriate to ignore an exception is when closing a FileInputStream. You haven’t changed the state of the file, so there’s no need to perform any recovery action, and you’ve already read the information that you need from the file, so there’s no reason to abort the operation in progress. Even in this case, it is wise to log the exception, so that you can investigate the matter if these exceptions happen often.

È saggio ignorare sempre quell'eccezione, come suggerito. Il codice sarebbe quindi,

FileInputStream stream = null;
try {
    stream = new FileInputStream("foo.txt");
    // do stuff
} finally {
    if (null != stream) {
        try {
            stream.close();
        } catch (IOException e) {
            LOG.error("Error closing file. Ignoring this exception.", e);
        }
    }

che è molto più dettagliato di

try (FileInputStream stream = new FileInputStream("foo.txt")) {
    // do stuff
}

Ma la verbosità extra vale la pena ignorare l'eccezione dalla chiusura del file? È meglio ignorare l'eccezione poiché non c'è niente da fare? Lo stesso approccio dovrebbe essere utilizzato con risorse diverse da un file, come una connessione al database?

    
posta Max 30.12.2017 - 12:52
fonte

8 risposte

1

La terza edizione di Effective Java contiene una nuova sezione con il titolo "Preferisci provare con risorse per provare finalmente". In esso, Bloch scrive: "Utilizza sempre try-with-resources preferibilmente per provare finalmente a lavorare con le risorse che devono essere chiuse" e fornisce una spiegazione che include i seguenti 3 punti:

  • Un'eccezione nel blocco finally nasconderà l'eccezione nel blocco try, esattamente come user949300 descrive nella sua risposta.

  • try-finally è difficile da leggere se utilizzato con più di una risorsa.

  • try-finally è spesso usato in modo errato. "In effetti, due terzi degli usi del metodo close nelle librerie Java erano errati nel 2007."

Quindi per rispondere alla domanda, Bloch sembra pensare che valga la pena provare con le risorse.

    
risposta data 05.03.2018 - 20:01
fonte
3

Il motivo, che, come ricordo che Bloch spiega, è che l'eccezione interessante è quella originale generata dal blocco try . Qualsiasi eccezione generata nel blocco finally nasconderà l'eccezione originale.

Immagina che l'ur-Exception sia perché il File si trova su una rete e la rete non funziona. Questa è l'Eccezione più informativa che vuoi lanciare. Ma il problema riguarda anche close() e non vuoi che la seconda eccezione "entri nel modo".

Nota questa spiegazione era nella V1 del suo libro ...

Aggiunto Il seguente codice dimostra il punto:

try {
  throw new Exception("in try, here is the ur-Exception");
}
finally {
  throw new Exception("in finally");
}

Il secondo, "finalmente" Eccezione, è quello che vedrai, nascondendo l'Eccezione originale.

    
risposta data 31.12.2017 - 18:15
fonte
1

In generale, dovresti aspettarti che il tuo programma venga eseguito senza mai lanciare un'eccezione.

Pertanto, quando viene lanciata un'eccezione, è insolito che tu voglia ignorarla.

Tuttavia, è normale che non si desideri arrestare il programma. Quindi le persone mettono il trucco di alto livello e non bloccano nulla per fermare cose a cui non hanno pensato di bloccare completamente le loro applicazioni.

In questo caso però vuoi davvero registrare l'eccezione a cui non avevi pensato, in modo da poter aggiornare il tuo codice per gestirlo correttamente.

Nell'esempio FileInputStream, sì, non è davvero chiaro il motivo per cui ciò provocherebbe un'eccezione (forse il file è stato cancellato?) o cosa si farebbe se ciò accadesse.

Potenzialmente suppongo che se la chiusura non riesce a completare con successo si potrebbe avere una perdita di memoria. Quindi se ne sta succedendo molto vorrai saperlo.

    
risposta data 30.12.2017 - 14:44
fonte
1

Per motivi di discussione, non riesco a immaginare come questa operazione possa fallire, e cosa potrebbe andare storto se fallisce.

Pertanto, non farei altro che l'accesso alla produzione. Assicurati inoltre che l'app venga eseguita in un punto di interruzione durante l'esecuzione su un computer per sviluppatori. Se questo dovesse fallire, voglio saperlo e perché è successo, così posso capire più correttamente come gestirlo.

    
risposta data 31.12.2017 - 16:42
fonte
1

then and now, very sometimes. Yes. But always? By God - never!

Prima nota: dovresti fare attenzione a generalizzare eccessivamente una regola da un esempio preciso.

Confronto

void close(Closable target) {
    try {
        target.close();
    } catch (IOException e) {
        ???
    }
}

Non abbiamo abbastanza informazioni per respingere questa eccezione come non importante. Ci sono molti situazioni eccezionali che impediscono la chiusura di una risorsa.

void close(FileInputStream target) {
    try {
        target.close();
    } catch (IOException e) {
        ???
    }
}

Qui abbiamo il contesto aggiuntivo in cui stiamo leggendo i dati (non ci si preoccupa di essere corrotti) e che il chiamante ha già ottenuto i dati previsti (il chiamante sta richiamando close() ). In altre parole, sebbene ci troviamo in una situazione eccezionale, le circostanze non indicano che il chiamante non sarà in grado di garantire la sua esplicita postcondition .

Non dobbiamo confondere un caso in cui maturiamo valore aziendale ma perdiamo un handle di file con un caso in cui vi sono indicazioni di perdita effettiva dei dati.

Personalmente non mi vergognerei troppo di verbosity ; puoi usare un altro Closable per fare il lavoro

try( ClosePolicy closer = new LoggingClosePolicy(new FileInputStream(...))) {
    FileInputStream stream = closer.get();
    // ...
}

che ritengo sia un po 'meglio nella scala che indica l'intenzione: la strategia predefinita per le eccezioni durante la chiusura non è soddisfacente, quindi la stiamo sostituendo.

Dopo aver eliminato i detriti, penso che tu possa iniziare a rispondere alla tua domanda iniziale: dovremmo sempre ...? È un'eccezione controllata, quindi non possiamo ignorare passivamente la possibilità.

Vedo due possibilità; se il metodo locale non può stabilire la sua post-condizione di fronte all'eccezione, allora penso che sia necessario propagarlo in qualche modo.

Ma penso che sia molto più comune che il metodo locale sia in grado di stabilire le parti osservabili della condizione della posta, nel qual caso il log-and-move-on sembra un modo ragionevole per accumula valore commerciale quando le circostanze eccezionali non richiedono una riparazione immediata.

    
risposta data 31.12.2017 - 17:45
fonte
1

Si noti che questo problema non è limitato al linguaggio Java o ai linguaggi con eccezioni. È possibile affrontare esattamente lo stesso problema durante la programmazione in C. Verificate il valore restituito da fclose ()? O da close ()?

Ho visto entrambi gli approcci: controllare e non controllare. Personalmente, provo a codificare i finalizzatori in modo che non restituiscano un codice di errore, ignorando silenziosamente tutti gli errori. In alcuni casi, quando sono interessato a catturare i bug, non riesco presto a fare grandi cose, chiamando abort () se vedo che una precondizione importante viene violata. Il nucleo scarto lasciato dietro spiega tutto. Inutile dire che nel programma finale la funzione abort () non viene mai chiamata. Non utilizzare abort () se ad es. una connessione di rete o qualche risorsa esterna non riesce; è solo per le precondizioni!

Nel caso di file, probabilmente non fallirai chiudendolo. Tuttavia, nel caso di alcuni flussi di rete, potrebbe esserci una condizione di errore.

Inoltre, potresti prendere in considerazione la registrazione dell'eccezione e quindi ignorarla in silenzio. Se visualizzi molte di tali eccezioni nei log, puoi disattivare la registrazione per i trasgressori delle dimensioni del registro più grandi.

    
risposta data 31.12.2017 - 18:02
fonte
1

Per me c'è una sola regola sulle eccezioni:

Se un metodo non adempie al suo contratto, genera un'eccezione.

Ora ci sono aspetti espliciti e impliciti del contratto di un metodo. Il contratto esplicito tipicamente (si spera) si correla con il suo nome. Parti di contratto implicite possono lasciare il file system nello stesso stato di prima della chiamata al metodo, non creare perdite di risorse e così via. È una decisione di progettazione, che ha impliciti compiti che un determinato metodo ha.

Qual è il contratto del tuo metodo?

  • es. leggere alcuni dati da un file di configurazione.

Qual è l'effetto di un errore durante la chiusura dello stream?

  • Il file è stato letto correttamente, quindi il contratto esplicito è stato soddisfatto.
  • La mancata chiusura di un FileInputStream comporta una perdita di risorse. Per un singolo file di configurazione letto una sola volta nel tempo di esecuzione dell'applicazione, questo non rappresenta un problema, quindi in genere non viola il contratto implicito.
  • La chiusura di un fileInputStream potrebbe (in base all'ambiente: sistema operativo, file system, ...) lasciare un blocco sul file, impedendo la modifica successiva con altri processi. Poiché i file di configurazione in genere non vengono modificati spesso (e nel nostro caso richiedono il riavvio dell'applicazione quando modificati), anche questo è un comportamento accettabile del nostro metodo di lettura.

Quindi sì, in questo caso leggendo un singolo file di configurazione, registrerei l'eccezione con un livello WARN e restituirò senza eccezioni.

Ma se il metodo fosse la funzione fileCopy() di un'applicazione di gestione file, non chiudere il file sorgente con il rischio di lasciare un blocco sul file violerebbe sicuramente il contratto implicito, quindi è necessaria un'eccezione.

    
risposta data 01.01.2018 - 14:25
fonte
-1

Bloch propone di spazzare via la spazzatura sotto il tappeto e nasconderlo all'utente fingendo che non ci sia, il che sono certo che un programmatore (o un programma) onesto non farà mai. Dovrei fare in modo che il programma fallisca e si inserisca davvero in modo grave al fine di rilevare tutti i possibili errori alle prime occorrenze. Il recupero degli errori è un altro problema, però.

Sembra anche che ci sia una ridondanza nel codice: se crei lo stream all'esterno del blocco try , non dovrai controllare se è null in finally perché sarà sempre stato creato .

    
risposta data 30.12.2017 - 20:10
fonte

Leggi altre domande sui tag