In Java, a cosa servono le eccezioni controllate? [chiuso]


14

Le eccezioni verificate di Java hanno avuto una cattiva stampa nel corso degli anni. Un segno significativo è che è letteralmente l'unica lingua al mondo che li ha (nemmeno altre lingue JVM come Groovy e Scala). Anche importanti librerie Java come Spring e Hibernate non le usano.

Personalmente ho trovato un uso per loro ( nella logica di business tra i livelli), ma per il resto sono eccezioni piuttosto controllate.

Ci sono altri usi che non realizzo?


Tutte le librerie core Java utilizzano le eccezioni verificate in modo abbastanza esteso.
Joonas Pulakka,

3
@Joonas lo so, ed è un dolore!
Brad Cupit,

Risposte:


22

Prima di tutto, come qualsiasi altro paradigma di programmazione, devi farlo bene perché funzioni bene.

Per me il vantaggio delle eccezioni verificate è che gli autori della libreria di runtime Java hanno già deciso per me quali problemi comuni potrei ragionevolmente prevedere di poter gestire al punto di chiamata (al contrario di un catch-print di alto livello- die block) e considerare il prima possibile come gestire questi problemi.

Mi piacciono le eccezioni verificate perché rendono il mio codice più robusto costringendomi a pensare al recupero degli errori il prima possibile.

Per essere più precisi, per me questo rende il mio codice più robusto in quanto mi costringe a considerare strani casi angolari molto presto nel processo invece di dire "Spiacenti, il mio codice non gestisce se il file non esiste ancora" basato su un errore nella produzione, che dovrai quindi rielaborare il codice per gestirlo. L'aggiunta della gestione degli errori al codice esistente può essere un'attività non banale - e quindi costosa - quando si raggiunge la manutenzione piuttosto che eseguirla fin dall'inizio.

È possibile che il file mancante sia fatale e che causi l'arresto anomalo del programma in fiamme, ma poi si prende quella decisione con

} catch (FileNotFoundException e) {
  throw new RuntimeException("Important file not present", e);
}

Ciò mostra anche un effetto collaterale molto importante. Se si racchiude un'eccezione, è possibile aggiungere una spiegazione che si trova nello stack-trace ! Questo è estremamente potente perché è possibile aggiungere informazioni su, ad esempio, il nome del file mancante o i parametri passati a questo metodo o altre informazioni diagnostiche e che le informazioni sono presenti proprio nella traccia dello stack che spesso è l'unica cosa che si quando un programma si è bloccato.

La gente potrebbe dire "possiamo semplicemente eseguire questo nel debugger per riprodurlo", ma ho scoperto che molto spesso gli errori di produzione non possono essere riprodotti in un secondo momento, e non possiamo eseguire debugger in produzione se non per casi molto brutti in cui essenzialmente è in gioco il tuo lavoro.

Più informazioni nella traccia dello stack, meglio è. Le eccezioni verificate mi aiutano a ottenere tali informazioni lì dentro e presto.


EDIT: questo vale anche per i progettisti di biblioteche. Una libreria che uso quotidianamente contiene molte, molte eccezioni verificate che avrebbero potuto essere progettate molto meglio rendendolo meno noioso da usare.


+1. I progettisti di Spring hanno fatto un buon lavoro nel tradurre molte eccezioni di Hibernate in eccezioni non controllate.
Fil

Cordiali saluti: Frank Sommers ha menzionato in questo thread che i software a tolleranza d'errore spesso si basano sullo stesso meccanismo. Forse puoi suddividere la tua risposta nel caso recuperabile e nel caso fatale.
rwong

@rwong, potresti spiegare in modo più dettagliato cosa hai in mente?

11

Hai due buone risposte che spiegano quali eccezioni verificate sono diventate in pratica. (+1 per entrambi.) Ma varrebbe anche la pena di esaminare ciò a cui erano destinati in teoria, perché l'intenzione in realtà vale la pena.

Le eccezioni verificate hanno effettivamente lo scopo di rendere la lingua più sicura. Considera un metodo semplice come la moltiplicazione dei numeri interi. Potresti pensare che il tipo di risultato di questo metodo sarebbe un numero intero, ma, a rigor di termini, il risultato è un numero intero o un'eccezione di overflow. Considerando il risultato intero da solo come tipo di ritorno del metodo non si esprime l'intero intervallo della funzione.

Visto in questa luce, non è del tutto vero dire che le eccezioni verificate non hanno trovato la loro strada in altre lingue. Non hanno semplicemente preso la forma utilizzata da Java. Nelle applicazioni Haskell, è comune utilizzare tipi di dati algebrici per distinguere tra completamento riuscito e non riuscito della funzione. Sebbene questa non sia un'eccezione, di per sé, l'intenzione è molto simile a un'eccezione controllata; è un'API progettata per forzare il consumatore dell'API a gestire entrambi i casi di successo in caso di esito negativo, ad esempio:

data Foo a =
     Success a
   | DidNotWorkBecauseOfA
   | DidNotWorkBecauseOfB

Questo dice al programmatore che la funzione ha altri due possibili risultati oltre al successo.


+1 per le eccezioni verificate relative alla sicurezza del tipo. Non ho mai sentito quell'idea prima, ma ha molto senso.
Ixrec,

11

IMO, le eccezioni verificate sono un'implementazione imperfetta di quella che avrebbe potuto essere un'idea abbastanza decente (in circostanze completamente diverse - ma in realtà non è nemmeno una buona idea nelle circostanze attuali).

La buona idea è che sarebbe bello avere il compilatore verificare che tutte le eccezioni che un programma ha un potenziale da lanciare saranno colte. L'implementazione imperfetta è che per fare ciò, Java ti costringe a gestire l'eccezione direttamente in qualunque classe invoca tutto ciò che viene dichiarato per generare un'eccezione controllata. Una delle idee (e dei vantaggi) più elementari della gestione delle eccezioni è che in caso di errore, consente a strati intermedi di codice che non hanno nulla a che fare con un errore specifico, di ignorare completamente la sua stessa esistenza. Le eccezioni verificate (come implementate in Java) non lo consentono, distruggendo così un vantaggio (principale?) Della gestione delle eccezioni.

In teoria, avrebbero potuto rendere utili le eccezioni verificate applicandole solo a livello di programma - vale a dire, mentre "costruendo" il programma, costruisci un albero di tutti i percorsi di controllo nel programma. Vari nodi nella struttura verrebbero annotati con 1) eccezioni che possono generare e 2) eccezioni che possono essere rilevate. Per assicurarti che il programma fosse corretto, avresti quindi camminato sull'albero, verificando che ogni eccezione lanciata a qualsiasi livello dell'albero fosse catturata a un livello superiore dell'albero.

Il motivo per cui non l' hanno fatto (immagino, comunque) è che farlo sarebbe tremendamente difficile - in effetti, dubito che qualcuno abbia scritto un sistema di costruzione che può farlo per qualsiasi cosa tranne che estremamente semplice (al limite del "giocattolo" ") lingue / sistemi. Per Java, cose come il caricamento dinamico delle classi lo rendono ancora più difficile che in molti altri casi. Peggio ancora (a differenza di qualcosa come C) Java non è (almeno normalmente) compilato / collegato staticamente a un eseguibile. Ciò significa che nulla nel sistema ha davvero la consapevolezza globale di provare a eseguire questo tipo di applicazione fino a quando l'utente finale non inizia effettivamente a caricare il programma per eseguirlo.

Fare applicazione a quel punto è impraticabile per un paio di ragioni. Prima di tutto, anche nella migliore delle ipotesi prolungherebbe i tempi di avvio, che sono già una delle lamentele più grandi e più comuni su Java. In secondo luogo, per fare molto bene, devi davvero rilevare il problema abbastanza presto perché un programmatore lo risolva. Quando l'utente sta caricando il programma, è troppo tardi per fare molto bene - le tue uniche scelte a quel punto sono ignorare il problema o rifiutare di caricare / eseguire il programma, con la possibilità che ci possa essere un'eccezione che non è stato catturato.

Allo stato attuale, le eccezioni verificate sono peggiori di quelle inutili, ma i progetti alternativi per le eccezioni verificate sono persino peggiori. Mentre l'idea di base per le eccezioni verificate sembra buona, in realtà non è molto buona e sembra essere particolarmente inadatta per Java. Per qualcosa come C ++, che è normalmente compilato / collegato in un eseguibile completo con nulla di simile al caricamento di classe di riflessione / dinamica, avresti qualche possibilità in più (anche se, francamente, anche applicandoli correttamente senza lo stesso tipo di disordine che hanno attualmente la causa in Java sarebbe probabilmente al di là dell'attuale stato dell'arte).


La tua prima affermazione iniziale sulla necessità di gestire direttamente l'eccezione è già falsa; puoi semplicemente aggiungere un'istruzione di lancio e quell'istruzione di lancio può essere di un tipo genitore. Inoltre, se davvero non pensi di doverlo gestire, puoi semplicemente convertirlo in RuntimeException. Gli IDE ti aiutano di solito in entrambe le attività.
Maarten Bodewes,

2
@owlstead: Grazie - probabilmente avrei dovuto essere più attento con le parole lì. Il punto non era tanto che in realtà dovevi catturare l'eccezione in quanto era che ogni livello intermedio di codice deve essere consapevole di ogni eccezione che potrebbe fluire attraverso di essa. Per il resto: avere uno strumento che semplifica e velocizza la confusione del codice non cambia il fatto che sta creando confusione nel codice.
Jerry Coffin,

Le eccezioni verificate erano intese per imprevisti - risultati prevedibili e recuperabili diversi dal successo, distinti dai fallimenti - imprevedibili problemi di basso livello / o di sistema. FileNotFound o errore nell'apertura di una connessione JDBC sono stati entrambi considerati contingenze correttamente. Sfortunatamente, i progettisti di librerie Java hanno commesso un errore totale e hanno assegnato tutte le altre eccezioni IO e SQL anche alla classe "controllata"; nonostante questi siano quasi completamente irrecuperabili. literatejava.com/exceptions/…
Thomas W

@owlstead: le eccezioni verificate dovrebbero passare attraverso i livelli intermedi dello stack di chiamate solo se vengono generate nei casi previsti dal codice di chiamata intermedio . IMHO, le eccezioni verificate sarebbero state una buona cosa se i chiamanti dovessero prenderli o indicare se erano previsti o meno ; le eccezioni impreviste dovrebbero essere automaticamente racchiuse in un UnexpectedExceptiontipo derivato da RuntimeException. Nota che "i lanci" e "non si aspetta" non sono contraddittori; se foolancia BozExceptione chiama bar, ciò non significa che si aspetti bardi lanciare BozException.
supercat

10

Sono davvero utili per i fastidiosi programmatori e per incoraggiare la deglutizione delle eccezioni. La metà del punto di eccezione è quella di avere un modo predefinito sano di gestire gli errori e di non dover pensare alla propagazione esplicita di condizioni di errore che non è possibile gestire. (L'impostazione predefinita sana è che se non gestisci mai l'errore da nessuna parte nel tuo codice, hai effettivamente affermato che non può accadere, e ti rimangono un'uscita sana e una traccia dello stack se sbagli.) Sconfitte eccezioni controllate Questo. Hanno molta voglia di propagare esplicitamente gli errori in C.


4
Facile fix: throws FileNotFoundException. Difficoltà:catch (FileNotFoundException e) { throw RuntimeException(e); }
Thomas Eding,

5

Penso che sia giusto dire che le eccezioni verificate sono state un po 'un esperimento fallito. Dove hanno sbagliato è che hanno rotto la stratificazione:

  • Il modulo A potrebbe essere costruito sopra il modulo B, dove
  • Il modulo B potrebbe essere costruito sopra il modulo C.
  • Il modulo C potrebbe sapere come identificare un errore e
  • Il modulo A potrebbe sapere come gestire l'errore.

La bella separazione degli interessati è interrotta, perché ora la conoscenza degli errori che avrebbero potuto essere limitati ad A e C ora deve essere sparsa su B.

C'è una bella discussione sulle eccezioni verificate su Wikipedia.

E Bruce Eckel ha un bell'articolo . Ecco una citazione:

[T] più regole casuali si accumulano sul programmatore, regole che non hanno nulla a che fare con la risoluzione del problema a portata di mano, più lentamente il programmatore può produrre. E questo non sembra essere un fattore lineare, ma esponenziale

Credo per il caso del C ++, se tutti i metodi hanno throws clausola di specifica, allora puoi avere delle ottimizzazioni interessanti. Non credo che Java possa avere questi vantaggi comunque, perché RuntimeExceptionsnon sono controllati. Un moderno JIT dovrebbe essere in grado di ottimizzare il codice comunque bene.

Una caratteristica interessante di AspectJ è la riduzione delle eccezioni: è possibile trasformare le eccezioni verificate in eccezioni non controllate, in particolare per migliorare la stratificazione.

Forse è ironico che Java abbia affrontato tutti questi problemi, quando invece avrebbe potuto verificare null, il che avrebbe potuto essere molto più prezioso .


ahah, non ho mai pensato a quel controllo del nulla come una decisione che non hanno preso ... Ma ho finalmente capito che NON è un problema innato dopo aver lavorato in Objective-C, che consente ai null di avere metodi chiamati.
Dan Rosenstark,

3
il controllo null è un dolore molto più grande - devi SEMPRE controllare - inoltre non spiega il motivo, cosa che fa un'eccezione ben nota.

5

Adoro le eccezioni.

Tuttavia, sono costantemente confuso dal loro uso improprio.

  1. Non utilizzarli per la gestione del flusso. Non vuoi un codice che tenti di fare qualcosa e usi un'eccezione come trigger per fare qualcos'altro invece perché le Eccezioni sono oggetti che devono essere creati e che usano memoria ecc. Solo perché non potresti essere disturbato a scrivere tipo di if () controlla prima di aver effettuato la chiamata. È solo pigro e dà alla gloriosa eccezione una cattiva fama.

  2. Non ingoiarli. Mai. Sotto ogni circostanza. Come minimo assoluto si inserisce qualcosa nel file di registro per indicare che si è verificata un'eccezione. Cercare di rintracciare il codice che ingoia le eccezioni è un incubo. Ancora una volta, maledici inghiottitori di eccezioni per aver discreditato la potente Eccezione!

  3. Tratta le eccezioni con il tuo cappello OO acceso. Crea i tuoi oggetti eccezione con gerarchie significative. Metti insieme la tua logica aziendale e le tue eccezioni. Trovavo che se avessi iniziato su una nuova area di un sistema, avrei trovato la sottoclasse Eccezione entro mezz'ora dalla codifica, quindi in questi giorni comincio scrivendo le classi Eccezione.

  4. Se stai scrivendo frammenti di codice 'frameworky', assicurati che tutte le tue interfacce iniziali siano scritte per lanciare le tue nuove eccezioni ove appropriato ... e assicurati che i tuoi programmatori abbiano del codice di esempio intorno al posto di cosa fare quando il loro codice genera una IOException ma l'interfaccia a cui stanno scrivendo vuole lanciare una "ReportingApplicationException".

Una volta che hai capito come far funzionare le eccezioni per te, non guarderai mai indietro.


2
+1 per buone istruzioni. Inoltre, se del caso, utilizzare initCauseo inizializzare l'eccezione con l'eccezione che l'ha causata. Esempio: si dispone di un'utilità I / O di cui è necessaria un'eccezione solo se la scrittura non riesce. Tuttavia, ci sono diverse ragioni per cui una scrittura potrebbe non riuscire (impossibile ottenere l'accesso, errore di scrittura, ecc.) Lancia l'eccezione personalizzata con la causa in modo che il problema originale non venga ingoiato.
Michael K,

3
Non voglio essere scortese, ma cosa c'entra questo con le eccezioni CHECKED? :)
palto

Potrebbe essere riscritto per scrivere la propria gerarchia di eccezioni verificate .
Maarten Bodewes,

4

Le eccezioni verificate si trovano anche in ADA.

(Attenzione, questo post contiene convinzioni fermamente convinte che potresti trovare di fronte.)

Ai programmatori non piacciono e si lamentano, o scrivono codici di deglutizione delle eccezioni.

Esistono eccezioni verificate perché le cose possono non solo non funzionare, ma è possibile eseguire un'analisi della modalità / degli effetti e determinarla in anticipo.

Le letture dei file possono fallire. Le chiamate RPC possono fallire. L'IO di rete può fallire. I dati possono essere formattati in modo errato durante l'analisi.

Il "percorso felice" per il codice è facile.

Conoscevo un ragazzo all'università in grado di scrivere un ottimo codice "percorso felice". Nessuno dei casi limite ha mai funzionato. In questi giorni fa Python per un'azienda open source. Disse Nuff.

Se non vuoi gestire le eccezioni verificate, quello che stai veramente dicendo è

While I'm writing this code, I don't want to consider obvious failure modes.

The User will just have to like the program crashing or doing weird things.

But that's okay with me because 

I'm so much more important than the people who will have to use the software

in the real, messy, error-prone world.

After all, I write the code once, you use it all day long.

Quindi le eccezioni verificate non saranno apprezzate dai programmatori, perché significa più lavoro.

Naturalmente, altre persone avrebbero potuto desiderare che il lavoro fosse svolto.

Avrebbero potuto desiderare la risposta giusta anche se il file server non funzionava / la chiavetta USB si spegne.

È una strana convinzione nella comunità di programmatori che dovresti usare un linguaggio di programmazione che ti semplifichi la vita, che ti piace, quando il tuo lavoro è scrivere software. Il tuo lavoro è risolvere il problema di qualcuno, non lasciarti impegnare nell'improvvisazione programmatica del Jazz.

Se sei un programmatore amatoriale (non programmando denaro), sentiti libero di programmare in C # o in qualche altra lingua senza eccezioni verificate. Cavolo, ritaglia l'uomo di mezzo e programma in Logo. Puoi disegnare graziosi motivi sul pavimento con la tartaruga.


6
Bello, ci sei arrivato.
Adam Lear

2
+1 "Nessuno dei casi limite ha mai funzionato. In questi giorni fa Python per un'azienda open source. Nuff ha detto." Classico
NimChimpsky il

1
@palto: Java ti costringe a considerare il percorso non facile. Questa è la grande differenza. Per C #, potresti dire "L'eccezione è documentata" e lavartene le mani. Ma i programmatori sono soggetti a errori. Dimenticano le cose. Quando scrivo il codice, voglio essere ricordato al 100% di eventuali crepe che un compilatore può informarmi.
Thomas Eding,

2
@ThomasEding Java mi costringe a gestire l'eccezione in cui viene chiamato il metodo. È molto raro che io voglia davvero fare qualcosa al riguardo nel codice chiamante e di solito voglio fare una bolla dell'eccezione al livello di gestione degli errori della mia applicazione. Devo quindi rimediare con il wrapping dell'eccezione in un'eccezione di runtime o devo creare la mia eccezione. I programmatori pigri hanno una terza opzione: ignorala perché non mi interessa. In questo modo nessuno sa che si è verificato l'errore e il software ha bug davvero strani. Quel terzo non accade con eccezioni non controllate.
palto

3
@ThomasEding In questo modo si cambierà l'interfaccia di tutto quanto sopra che rivela inutilmente i dettagli di implementazione a ogni livello dell'applicazione. Puoi farlo solo se l'eccezione è qualcosa da cui vuoi recuperare e ha senso nell'interfaccia. Non voglio aggiungere una IOException al mio metodo getPeople perché alcuni file di configurazione potrebbero mancare. Non ha senso.
palto

4

Le eccezioni verificate avrebbero dovuto incoraggiare le persone a gestire gli errori nelle vicinanze. Più lontano dalla fonte, più funzioni sono terminate durante l'esecuzione e maggiore è la probabilità che il sistema sia entrato in uno stato incoerente. Inoltre, è più probabile che tu accada l'eccezione sbagliata per caso.

Purtroppo, le persone hanno teso a ignorare le eccezioni o ad aggiungere specifiche di lancio sempre crescenti. Entrambi mancano il punto di ciò che le eccezioni controllate dovrebbero incoraggiare. Sono come trasformare il codice procedurale per usare molte funzioni di Getter e Setter che presumibilmente lo rendono OO.

In sostanza, le eccezioni verificate stanno tentando di dimostrare un certo grado di correttezza nel codice. È praticamente la stessa idea della digitazione statica, in quanto tenta di dimostrare che certe cose sono corrette prima ancora che il codice venga eseguito. Il problema è che tutta quella prova in più richiede uno sforzo e lo sforzo vale la pena?


2
"Ignorare le eccezioni" è spesso corretto: molte volte la migliore risposta a un'eccezione è quella di bloccare l'app. Per una base teorica per questo punto di vista, leggi la Costruzione software orientata agli oggetti di Bertrand Meyer . Dimostra che se le eccezioni vengono utilizzate correttamente (per le violazioni del contratto), esistono essenzialmente due risposte corrette: (1) lasciare l'app in crash o (2) risolvere il problema che ha causato l'eccezione e riavviare il processo. Leggi Meyer per i dettagli; è difficile riassumere in un commento.
Craig Stuntz,

1
@Craig Stuntz, ignorando le eccezioni intendevo ingoiarle.
Winston Ewert,

Ah, è diverso. Sono d'accordo che non dovresti ingoiarli.
Craig Stuntz,

"Ignorare le eccezioni è spesso corretto - molte volte la migliore risposta a un'eccezione è quella di far arrestare l'app". Dipende molto dall'applicazione. Se si riscontra un'eccezione non rilevata in un'applicazione Web, il peggio che può accadere è che il cliente segnalerà di aver visualizzato un messaggio di errore in una pagina Web e di poter distribuire una correzione di bug in pochi minuti. Se hai un'eccezione durante l'atterraggio di un aereo, faresti meglio a gestirlo e provare a recuperare.
Giorgio,

@Giorgio, molto vero. Anche se mi chiedo se nel caso di un aereo o simile se il modo migliore per recuperare è riavviare il sistema.
Winston Ewert,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.