Perché le persone Java consumano frequentemente eccezioni in modo silenzioso?

Non ho mai fatto alcuna seria codifica Java prima, ma ho imparato la syntax, le librerie e i concetti basati sulle mie competenze esistenti (Delphi & C #). Una cosa che difficilmente capisco è che ho visto così tanto codice che consuma silenziosamente le eccezioni dopo printStackTrace questo modo:

  public void process() { try { System.out.println("test"); } catch(Exception e) { e.printStackTrace(); } } 

C’è un codice simile come questo in quasi tutti gli articoli e progetti Java in cui mi sono imbattuto. Sulla base delle mie conoscenze questo è molto brutto. L’eccezione dovrebbe quasi sempre essere inoltrata al contesto esterno come questo:

  public void process() { try { System.out.println("test"); } catch(Exception e) { e.printStackTrace(); throw new AssertionError(e); } } 

La maggior parte delle volte l’eccezione dovrebbe finire per essere gestita nel ciclo più esterno che appartiene al framework sottostante (Java Swing per esempio). Perché sembra la norma codificare così nel mondo Java? Sono perplesso.

Sulla base del mio background, preferirei rimuovere printStackTrace interamente . Vorrei semplicemente ripensare come RuntimeException aka (o, meglio ancora, AssertionError ) non gestito, quindi catturarlo e registrarlo nel punto più appropriato: il ciclo più esterno del framework.

  public void process() { try { System.out.println("test"); } catch(Exception e) { throw new AssertionError(e); } } 

Ho sempre pensato, è simile al seguente scenario:

“Un uomo viene colpito.

Trattenere il respiro e ha abbastanza forza per prendere un autobus.

10 miglia dopo l’uomo scende dall’autobus, cammina un paio di isolati e muore. “

Quando la polizia arriva al corpo, non hanno la minima idea di cosa è appena successo. Potrebbero avere alla fine ma è molto più difficile.

Meglio è:

“Un uomo viene colpito e muore all’istante, e il corpo giace esattamente dove è appena avvenuto l’omicidio.”

Quando arriva la polizia, tutte le prove sono a posto.

Se un sistema fallisce, è meglio fallire velocemente

Affrontare la domanda:

  1. Ignoranza.
      +
  2. bradipo

MODIFICARE:

Certo, la sezione cattura è utile.

Se qualcosa può essere fatto con l’eccezione, è lì che dovrebbe essere fatto.

Probabilmente questa NON è un’eccezione per il codice dato, probabilmente è qualcosa che è previsto (e nella mia analogia è come una giacca antiproiettile, e l’uomo stava aspettando il tiro in primo luogo).

E sì, la presa potrebbe essere usata per gettare le eccezioni appropriate all’astrazione

Di solito ciò è dovuto all’IDE che offre una “soluzione rapida” utile che avvolge il codice incriminato in un blocco try-catch con quella gestione delle eccezioni. L’idea è che tu in realtà fai qualcosa, ma gli sviluppatori pigri no.

Questa è una ctriggers forma, senza dubbio.

  1. Java ti obbliga a gestire tutte le eccezioni in modo esplicito. Se un metodo che il tuo codice chiama è dichiarato per lanciare FooException e BarException il tuo codice DEVE gestire (o lanciare) quelle eccezioni. L’unica eccezione a questo è RuntimeException , che è silenzioso come un ninja.
  2. Molti programmatori sono pigri (me compreso) ed è molto semplice stampare semplicemente la traccia dello stack.

Questa è una classica discussione sull’uomo di paglia . printStackTrace() è un aiuto per il debug. Se lo vedevi su un blog o su una rivista, era perché lo scrittore era più interessato a illustrare un punto diverso dalla gestione delle eccezioni. Se lo hai visto nel codice di produzione, lo sviluppatore di quel codice era ignorante o pigro, niente di più. Non dovrebbe essere considerato un esempio di pratica comune nel “mondo java”.

Trovo che ci siano spesso 2 motivi per farlo

  1. Il programmatore era pigro
  2. Il programmatore voleva proteggere un punto di ingresso nel componente lì (correttamente o erroneamente)

Non credo che questo sia un fenomeno limitato a Java. Ho visto spesso questo tipo di codifica anche in C # e VB.Net.

In superficie è abbastanza scioccante e sembra terribile. Ma in realtà non è niente di nuovo. Si verifica sempre nelle applicazioni C ++ che utilizzano i valori di ritorno del codice di errore e le eccezioni. La differenza però è che ignorare un valore di ritorno potenzialmente fatale non sembra affatto diverso dal chiamare una funzione che restituisce il nulla.

 Foo* pFoo = ...; pFoo->SomeMethod(); // Void or swallowing errors, who knows? 

Questo codice sembra migliore, ma se SomeMethod () dovesse dire restituire un HResult, sarebbe semanticamente non diverso da ingoiare un’eccezione.

perché Checked Exceptions è un esperimento fallito

(forse printStackTrace () è il vero problema ? 🙂

Devo dire che un po ‘risentito del tono che implica questo tipo di comportamento scorretto di gestione degli errori è qualcosa di fondamentale per i programmatori Java. Certo, i programmatori Java possono essere pigri, proprio come ogni altro programmatore, e Java è un linguaggio popolare, quindi probabilmente vedrai un sacco di codice che ingoia le eccezioni.

Inoltre, come è stato sottolineato altrove, ci sono frustrazioni comprensibili con la dichiarazione forzata di Java delle eccezioni controllate, sebbene personalmente non abbia un problema con questo.

Quello che ho un problema, immagino, è che stai diffondendo un sacco di articoli e frammenti di codice sul web senza preoccuparti di considerare il contesto. La verità è che quando scrivi un articolo tecnico cercando di spiegare come funziona una determinata API o come iniziare con qualcosa, allora è molto probabile saltare alcuni aspetti del codice – la gestione degli errori che non è direttamente in relazione a ciò che stai dimostrando è un candidato probabile per lo smaltimento, soprattutto se è improbabile che l’eccezione si verifichi nello scenario di esempio.

Le persone che scrivono articoli di quella natura devono mantenere un rapporto segnale-rumore ragionevole, e piuttosto equamente, penso, ciò significa che devono presumere che tu conosca alcune nozioni di base sulla lingua in cui ti stai sviluppando; come affrontare correttamente gli errori e un sacco di altre cose. Se trovi un articolo e noti una mancanza di controllo corretto degli errori, allora va bene; assicurati che quando incorpori queste idee (ma ovviamente, mai il codice esatto in sé, giusto?) nel tuo codice di produzione, ti occuperai di tutti quei frammenti che l’autore ha sensibilmente omesso, in un modo che è il più adatto a ciò che stai sviluppando.

Ho un problema con articoli introduttivi di altissimo livello che si soffermano su tali problemi senza mai tornare a loro, ma per favore sii consapevole che non esiste una particolare “mentalità” dei programmatori Java per quanto riguarda la gestione degli errori; Conosco molti dei tuoi amati programmatori C # che non si preoccupano di affrontare tutti i loro problemi.

Una stampa System.out o e.printStackTrace () – che implica l’uso di System.out di solito è una bandiera rossa che significa che qualcuno non si è preoccupato di fare un lavoro diligente. Ad eccezione delle applicazioni Java desktop, la maggior parte delle applicazioni Java è meglio utilizzare la registrazione.

Se la modalità di errore per un metodo è una non operazione, è perfettamente accettabile un’eccezione, indipendentemente dal fatto che si registri la ragione (e l’esistenza) o meno. Più tipicamente, tuttavia, la clausola di cattura dovrebbe prendere una sorta di azione eccezionale.

Riportare un’eccezione è qualcosa che è meglio fare quando si utilizza il fermo per ripulire parte del lavoro a un livello in cui le informazioni necessarie sono ancora disponibili o quando è necessario trasformare l’eccezione in un tipo di eccezione più suscettibile al chiamante.

Viene consumato silenziosamente solo se il blocco di cattura è vuoto.

Per quanto riguarda gli articoli, sono probabilmente più interessanti nel provare qualche altro punto oltre a come gestire le eccezioni. Vogliono solo arrivare dritto al punto e avere il codice più breve ansible.

Ovviamente hai ragione, le eccezioni dovrebbero essere registrate almeno se saranno “ignorate”.

Come altri hanno sottolineato, il motivo per cui lo vedi è per uno dei tre motivi:

  1. Un IDE ha generato il blocco try-catch
    • Il codice è stato copiato e incollato
    • Lo sviluppatore ha inserito lo stacktrace per eseguire il debug, ma non è mai riuscito a gestire correttamente l’eccezione

L’ultimo punto è il meno probabile che si verifichi. Dico questo perché non credo che qualcuno faccia il debug in questo modo. Passare attraverso il codice con un debugger è un modo molto più semplice di eseguire il debug.

La migliore descrizione di cosa dovrebbe essere fatto in un blocco di cattura può essere trovata nel capitolo 9 di Java efficace di Joshua Bloch .

In C # tutte le eccezioni sono eccezioni di runtime, ma in Java sono presenti eccezioni di runtime e eccezioni controllate, che è necessario prendere o dichiarare nei propri metodi. Se chiami un metodo che ha un “tiro” alla fine, devi prendere le eccezioni menzionate lì, o il tuo metodo deve dichiarare anche quelle eccezioni.

Gli articoli Java di solito stampano la traccia dello stack o hanno un commento perché la gestione delle eccezioni è irrilevante per l’argomento dell’articolo. Nei progetti però, si dovrebbe fare qualcosa a riguardo, a seconda del tipo di eccezione.

Dovresti vederlo molto spesso se il programmatore fa bene il suo lavoro. Ignorare l’eccezione è una ctriggers, ctriggers pratica! Ma ci sono alcuni motivi per cui alcuni potrebbero fare questo e le soluzioni più appropriate:

  • “Questo non succederà!” Certo, a volte si “sa” che questa Eccezione non accadrà, ma è ancora più appropriato per ripensare a un’eccezione di runtime con l’Eccezione occorsa come “causa” invece di ignorarla. Scommetto che avverrà in futuro. 😉

  • Codice di prototipazione Se stai semplicemente digitando le tue cose per vedere se funziona, potresti voler ignorare tutte le eccezioni che potrebbero verificarsi. Questo è l’unico caso che faccio qualche presa pigro (Lanciabile). Ma se il codice diventerà qualcosa di utile, includo la corretta gestione delle eccezioni.

  • “Io non so cosa fare!” Ho visto molto codice, in particolare il codice della libreria, che ingoia eccezioni ricorrenti perché in questo livello dell’applicazione non è ansible gestire correttamente. NON FARE QUESTO! Basta rilanciare l’eccezione (aggiungendo una clausola throws nella firma del metodo o avvolgendo l’eccezione in una libreria specifica).

Dovresti sempre inoltrarlo o gestirlo adeguatamente in un contesto reale. Molti articoli e tutorial semplificheranno il loro codice per ottenere i punti più importanti e una delle cose più semplici da semplificare è la gestione degli errori (a meno che ciò che si sta facendo sia scrivere un articolo sulla gestione degli errori :)). Siccome il codice java controllerà la gestione delle eccezioni, mettere un semplice blocco silenzioso (o dichiarazione di registrazione) su on è il metodo più semplice per fornire un esempio funzionante.

Se trovi questo in qualcosa di diverso dal codice di esempio, sentiti libero di inoltrare il codice su TDWTF, anche se ora potrebbero averne troppi esempi 🙂

Temo che la maggior parte dei programmatori Java non sappiano cosa fare con Exceptions, e lo consideriamo quasi sempre un fastidio che rallenta la codifica del caso “nominale”. Naturalmente si sbagliano completamente, ma è difficile convincerli che è importante gestire correttamente le eccezioni. Ogni volta che incontro un tale programmatore (succede spesso) gli do due voci di lettura:

A proposito, sono assolutamente d’accordo sul fatto che sia stupido catturare un’eccezione tipizzata per ripensarla incorporata in una RuntimeException:

  • se la prendi, MANEGGIATURA.
  • in caso contrario, modifica la firma del metodo per aggiungere eventuali eccezioni che potresti / non potresti gestire, in modo che il chiamante abbia la possibilità di farlo da solo.

La combinazione di eccezioni controllate e interfacce porta alla situazione in cui il codice deve gestire le esecuzioni che non vengono mai lanciate. (Lo stesso vale per l’ereditarietà normale, ma è più comune e più facile da spiegare con le interfacce)

Motivo: l’implementazione di un’interfaccia potrebbe non generare eccezioni (controllate) diverse da quelle definite nelle specifiche dell’interfaccia. Per questo motivo, i creatori di un’interfaccia, non sapendo quali metodi di una class che implementano l’interfaccia potrebbero effettivamente aver bisogno di lanciare un’eccezione, potrebbero specificare che tutti i metodi potrebbero generare almeno un tipo di eccezione. Esempio: JDBC, dove viene dichiarato tutto e la nonna per lanciare SQLException.

Ma in realtà, molti metodi di implementazione reale semplicemente non possono fallire, quindi in nessuna circostanza essi lanciano mai un’eccezione. Il codice che chiama questo metodo deve comunque “gestire” l’eccezione e il modo più semplice è quello di ingoiare l’eccezione. Nessuno vuole ingombrare il suo codice con una gestione degli errori apparentemente inutile che non viene mai eseguita.

Come sottolineato, chiamare printStackTrace () non è una gestione veramente silenziosa.

Il motivo per questo tipo di “deglutizione” dell’eccezione è che, se si continua a passare l’eccezione lungo la catena, è comunque necessario gestire l’eccezione da qualche parte o lasciare che l’applicazione si arresti. Quindi, gestirlo al livello che si verifica con un dump di informazioni non è peggiore che gestirlo al livello più alto con un dump di informazioni.

La sua pratica pigra – a dir poco vera e propria.

Solitamente è fatto quando non ti interessa l’eccezione – piuttosto che aumentare il lavoro con le dita.

Non sono d’accordo che rilanciare un’eccezione controllata sia un’idea migliore. La cattura significa manipolazione; se devi rilanciare, non dovresti prenderlo. Aggiungerei la clausola throws alla firma del metodo in quel caso.

Direi che avvolgere un’eccezione controllata in una non controllata (ad esempio, il modo in cui Spring avvolge l’SQLException selezionata in un’istanza della sua gerarchia non controllata) è accettabile.

La registrazione può essere considerata gestione. Se l’esempio è stato modificato per registrare la traccia dello stack usando log4j invece di scrivere sulla console, sarebbe accettabile? Non molto di un cambiamento, IMO.

Il vero problema è ciò che è considerato una procedura di recupero eccezionale e accettabile. Se non riesci a recuperare dall’eccezione, il meglio che puoi fare è segnalare l’errore.

Si prega di non racchiudere mai, mai, mai un’eccezione controllata in un’eccezione non controllata.

Se ti ritrovi ad affrontare eccezioni che non pensi dovresti, allora il mio consiglio è che probabilmente stai lavorando al livello sbagliato di astrazione.

Elaborerò: le eccezioni controllate e deselezionate sono due animali molto diversi. Le eccezioni controllate sono simili al vecchio metodo di restituzione dei codici di errore … sì, sono un po ‘più doloroso da gestire dei codici di errore, ma hanno anche dei vantaggi. Le eccezioni non controllate sono errori di programmazione e errori di sistema critici … eccezioni eccezionali in altre parole. Quando si cerca di spiegare che cosa è un’eccezione, molte persone si trovano in un casino completo perché non riconoscono la differenza in questi due casi molto diversi.

Perché non hanno ancora imparato questo trucco:

 class ExceptionUtils { public static RuntimeException cloak(Throwable t) { return ExceptionUtils.castAndRethrow(t); } @SuppressWarnings("unchecked") private static  X castAndRethrow(Throwable t) throws X { throw (X) t; } } class Main { public static void main(String[] args) { // Note no "throws" declaration try { // Do stuff that can throw IOException } catch (IOException ex) { // Pretend to throw RuntimeException, but really rethrowing the IOException throw ExceptionUtils.cloak(ex); } } } 

Il vero punto di eccezione è semplificare la gestione degli errori e separarlo dal rilevamento degli errori. Ciò è in contrasto con la rappresentazione di errori in base ai codici di errore, in cui il codice di gestione degli errori è sparsi ovunque e ogni chiamata che può fallire deve essere controllata per il codice di ritorno.

Se l’eccezione rappresenta un errore (che è la maggior parte dei casi), di solito il modo più ragionevole per gestirlo è di uscire e lasciare la gestione ad un livello superiore. È necessario prendere in considerazione l’ipotesi di un’eccezione diversa se viene aggiunta qualche semantica significativa, ad esempio, questo errore è un errore di sistema insolito / un problema temporaneo (di rete) / questo è un errore lato client o server ecc.

Tra tutte le strategie di gestione degli errori, il più ignorante si nasconde o semplicemente stampa un messaggio di errore e procede come se nulla fosse accaduto.


I Sun People volevano che il codice fosse più esplicito e costringevano i programmatori a scrivere quali eccezioni potevano essere generate da quale metodo. Sembrava essere la mossa giusta – chiunque saprà cosa aspettarsi in cambio di una chiamata di metodo dato il suo prototipo (potrebbe restituire un valore di questo tipo o lanciare un’istanza di una delle classi specificate (o è sottoclass)).

Ma come si è scoperto con molti programmatori Java ignoranti, ora trattano la gestione delle eccezioni come se si trattasse di un bug / “feature” linguistico che necessitava di un workaround e di un codice di scrittura nel modo peggiore o quasi peggiore ansible:

  • L’errore viene gestito subito nel contesto non adatto a decidere cosa fare con esso.
  • Viene visualizzato o ignorato in modo silenzioso e l’elaborazione continua anche quando un ulteriore codice non ha alcuna possibilità di funzionare correttamente.
  • Il chiamante del metodo non può distinguere se è terminato con successo o meno.

Come scrivere il “modo giusto” di?

  • Indicare ogni class base di eccezioni che può essere generata nell’intestazione del metodo. AFAICR Eclipse può farlo automaticamente.
  • Rendi significativa la lista di lancio nel prototipo del metodo. Le liste lunghe sono inutili e “lanciare l’eccezione” è pigro (ma utile quando non si preoccupa molto delle eccezioni).
  • Quando si scrive la “strada sbagliata” semplice “lanciare Exception” è molto meglio e richiede meno byte di “try {…} catch (Exception e) {e.printStackTrace ();}”.
  • Risolvi l’eccezione incatenata se necessario.

If you want your exception to be handled outside the scope of the current method you don’t need to to catch it actually, instead you add ‘throws’ statement to the method signature.

The try/catch statements that you’ve seen are only in the code where programmer explicitly decided to handle the exception in place and therefore not to throw it further.

I think developers also try to consider the importance of “doing the right thing” in a particular context. Often times for throw away code or when propagating the exception upwards wouldn’t buy anything because the exception is fatal, you might as well save time and effort by “doing the wrong thing”.

You would usually swallow an exception when you cannot recover from it but it is not critical. One good example is the IOExcetion that can be thrown when closing a database connection. Do you really want to crash if this happens? That’s why Jakarta’s DBUtils have closeSilently methods.

Now for checked exception that you cannot recover but are critical (usually due to programming errors), don’t swallow them. I think exceptions should be logged the nearest as possible to the source of the problem so I would not recommend removing the printStackTrace() call. You will want to turn them into RuntimeException for the sole purpose of not having to declare these exceptions in you business method. Really it doesn’t make sense to have high level business methods such as createClientAccount() throws ProgrammingErrorException (read SQLException), there is nothing you can do if you have typos in your sql or accessing bad indexes.

From experience, Swallowing an exception is harmful mostly when it’s not printed. It helps to bring attention if you crash, and I’ll do that deliberately at times, but simply printing the exception and continuing allows you to find the problem and fix it, and yet usually doesn’t negatively effect others working on the same codebase.

I’m actually into Fail-Fast/Fail-HARD, but at least print it out. If you actually “Eat” an exception (which is to truly do nothing: {}) It will cost someone DAYS to find it.

The problem is that Java forces you to catch a lot of stuff that the developer knows won’t be thrown, or doesn’t care if they are. The most common is Thread.sleep(). I realize there are holes that might allow threading issues here, but generally you know that you are not interrupting it. Periodo.

There can be many reasons why one would use catch Exception. In many cases it is a bad idea because you also catch RuntimeExceptions – and well you don’t know in what state the underlying objects will be after this happens ? That is always the difficult thing with unexpected conditions: can you trust that the rest of the code will not fail afterwards.

Your example prints the stacktrace so at least your will know what the root cause might have been. In bigger software projects it is a better idea to log these things. And lets hope that the log component does not throw exceptions either our you might end up in an infinite loop (which will probably kill your JVM).

If you have a checked exception and you don’t want to handle it in a method, you should just have the method throw the exception. Only catch and handle exceptions if you are going to do something useful with it. Just logging it is not very useful in my book as users rarely have time to be reading logs looking for exceptions or know what to do if an exception is thrown.

While wrapping the exception is an option, I would not suggest you do this unless; you are throwing a different exception to match an exist interface or there really is no way such an exception should be thrown.

BTW: If you want to re throw a checked exception you can do this with

 try { // do something } catch (Throwable e) { // do something with the exception Thread.currentThread().stop(e); // doesn't actually stop the current thread, but throws the exception/error/throwable } 

Note: if you do this, you should make sure the throws declaration for the method is correct as the compiler is unable to do this for you in this situation.

because it is a best practice. I thought everybody knew.
but the cold truth is that nobody really understands how to work with exceptions. The C error handing style made so much more sense.