Domanda

Le eccezioni non controllate vanno bene se vuoi gestire ogni errore allo stesso modo, ad esempio registrandolo e saltando alla richiesta successiva, visualizzando un messaggio all'utente e gestendo l'evento successivo, ecc.Se questo è il mio caso d'uso, tutto quello che devo fare è catturare qualche tipo di eccezione generale ad alto livello nel mio sistema e gestire tutto allo stesso modo.

Ma voglio riprendermi da problemi specifici e non sono sicuro che il modo migliore per affrontarlo con eccezioni non controllate.Ecco un esempio concreto.

Supponiamo di avere un'applicazione web, creata utilizzando Struts2 e Hibernate.Se si verifica un'eccezione alla mia "azione", la registro e mostro belle scuse all'utente.Ma una delle funzioni della mia applicazione web è la creazione di nuovi account utente, che richiedono un nome utente univoco.Se un utente sceglie un nome che già esiste, Hibernate lancia un file org.hibernate.exception.ConstraintViolationException (un'eccezione non controllata) nelle viscere del mio sistema.Mi piacerebbe davvero risolvere questo particolare problema chiedendo all'utente di scegliere un altro nome utente, invece di fornirgli lo stesso messaggio "abbiamo registrato il tuo problema ma per ora sei ignorato".

Ecco alcuni punti da considerare:

  1. Ci sono molte persone che creano account contemporaneamente.Non voglio bloccare l'intera tabella utente tra un "SELECT" per vedere se il nome esiste e un "INSERT" in caso contrario.Nel caso dei database relazionali, potrebbero esserci alcuni trucchi per aggirare questo problema, ma quello che mi interessa veramente è il caso generale in cui il controllo preliminare di un'eccezione non funziona a causa di una condizione di competizione fondamentale.La stessa cosa potrebbe applicarsi alla ricerca di un file nel file system, ecc.
  2. Data la propensione del mio CTO per la gestione guidata indotta dalla lettura delle colonne tecnologiche in "Inc.", ho bisogno di uno strato di riferimento indiretto attorno al meccanismo di persistenza in modo da poter eliminare Hibernate e utilizzare Kodo, o qualsiasi altra cosa, senza modificare nulla tranne il più basso strato di codice di persistenza.È un dato di fatto, nel mio sistema ci sono diversi livelli di astrazione di questo tipo.Come posso evitare che si diffondano nonostante le eccezioni non controllate?
  3. Uno dei punti deboli dichiarati delle eccezioni controllate è doverle "gestire" in ogni chiamata nello stack, dichiarando che un metodo chiamante le lancia, oppure catturandole e gestendole.Gestirli spesso significa avvolgerli in un'altra eccezione controllata di tipo appropriato al livello di astrazione.Quindi, ad esempio, in un paese con eccezioni controllate, un'implementazione basata su file system del mio UserRegistry potrebbe catturare IOException, mentre un'implementazione del database catturerebbe SQLException, ma entrambi lancerebbero a UserNotFoundException che nasconde l'implementazione sottostante.Come posso trarre vantaggio dalle eccezioni non controllate, risparmiandomi il peso di questo avvolgimento a ogni livello, senza perdere i dettagli di implementazione?
È stato utile?

Soluzione

IMO, le eccezioni di confezionamento (controllate o meno) presentano diversi vantaggi che valgono il costo:

1) Ti incoraggia a pensare alle modalità di fallimento del codice che scrivi.Fondamentalmente, devi considerare le eccezioni che il codice che chiami potrebbe generare e, a tua volta, considererai le eccezioni che genererai per il codice che chiama il tuo.

2) Ti dà l'opportunità di aggiungere ulteriori informazioni di debug nella catena delle eccezioni.Ad esempio, se disponi di un metodo che genera un'eccezione su un nome utente duplicato, potresti racchiudere tale eccezione con uno che includa informazioni aggiuntive sulle circostanze dell'errore (ad esempio, l'IP della richiesta che ha fornito il nome utente duplicato) che non era disponibile per il codice di livello inferiore.La traccia delle eccezioni dei cookie può aiutarti a eseguire il debug di un problema complesso (per me lo è sicuramente).

3) Ti consente di diventare indipendente dall'implementazione dal codice di livello inferiore.Se stai eseguendo il confezionamento delle eccezioni e hai bisogno di sostituire Hibernate con qualche altro ORM, devi solo modificare il codice di gestione di Hibernate.Tutti gli altri livelli di codice continueranno a utilizzare con successo le eccezioni racchiuse e le interpreteranno allo stesso modo, anche se le circostanze sottostanti sono cambiate.Tieni presente che questo si applica anche se Hibernate cambia in qualche modo (es:cambiano le eccezioni in una nuova versione);non è solo per la sostituzione all'ingrosso della tecnologia.

4) Ti incoraggia a utilizzare diverse classi di eccezioni per rappresentare situazioni diverse.Ad esempio, potresti avere una DuplicateUsernameException quando l'utente tenta di riutilizzare un nome utente e una DatabaseFailureException quando non puoi verificare la presenza di nomi utente duplicati a causa di una connessione DB interrotta.Questo, a sua volta, ti consente di rispondere alla tua domanda ("come posso recuperare?") in modi flessibili ed efficaci.Se ricevi una DuplicateUsernameException, puoi decidere di suggerire un nome utente diverso all'utente.Se ricevi un'eccezione DatabaseFailureException, puoi lasciarla bollire fino al punto in cui viene visualizzata una pagina "inattivo per manutenzione" all'utente e inviarti un'e-mail di notifica.Una volta che hai eccezioni personalizzate, hai risposte personalizzabili - e questa è una buona cosa.

Altri suggerimenti

Mi piace riconfezionare le eccezioni tra i "livelli" della mia applicazione, quindi ad esempio un'eccezione specifica del DB viene riconfezionata all'interno di un'altra eccezione che è significativa nel contesto della mia applicazione (ovviamente lascio l'eccezione originale come membro così Non ostruisco l'analisi dello stack).

Detto questo, penso che un nome utente non univoco non sia una situazione sufficientemente "eccezionale" da giustificare un lancio.Utilizzerei invece un argomento di ritorno booleano.Senza sapere molto della tua architettura, è difficile per me dire qualcosa di più specifico o applicabile.

Vedere Modelli per generazione, gestione e gestione degli errori

Dal modello Dominio diviso ed Errori tecnici

Un errore tecnico non dovrebbe mai causare la generazione di un errore di dominio (mai il twain dovrebbe soddisfare).Quando un errore tecnico deve causare il fallimento dell'elaborazione aziendale, dovrebbe essere avvolto come SystemError.

Gli errori di dominio dovrebbero sempre iniziare da un problema di dominio ed essere gestiti dal codice di dominio.

Gli errori del dominio dovrebbero passare "perfettamente" attraverso i confini tecnici.Può darsi che tali errori debbano essere serializzati e ricostituiti perché ciò accada.Proxy e facciate dovrebbero assumersi la responsabilità di farlo.

Gli errori tecnici devono essere gestiti in punti particolari nell'applicazione, come i confini (vedere il registro al limite di distribuzione).

La quantità di informazioni sul contesto passata con l'errore dipenderà da quanto ciò sarà utile per la diagnosi e la gestione successive (capire una strategia alternativa).È necessario chiederti se lo stack traccia di una macchina remota sia completamente utile all'elaborazione di un errore di dominio (sebbene la posizione del codice dell'errore e i valori delle variabili in quel momento possa essere utile)

Quindi, avvolgi l'eccezione di ibernazione al limite dell'ibernazione con un'eccezione di dominio non controllata come "UniqueUsernameException" e lascia che arrivi fino al suo gestore.Assicurati di Javadoc l'eccezione generata anche se non è un'eccezione controllata!

Dato che attualmente stai utilizzando l'ibernazione, la cosa più semplice da fare è semplicemente verificare l'eccezione e inserirla in un'eccezione personalizzata o in un oggetto risultato personalizzato che potresti aver impostato nel tuo framework.Se vuoi abbandonare l'ibernazione in un secondo momento assicurati di racchiudere questa eccezione in un solo posto, il primo posto in cui rilevi l'eccezione dall'ibernazione, questo è il codice che probabilmente dovrai modificare comunque quando effettui un cambio, quindi se il problema è in un posto, quindi le spese generali aggiuntive sono quasi pari a zero.

aiuto?

Sono d'accordo con Nick.L'eccezione che hai descritto non è realmente "un'eccezione imprevista", quindi dovresti progettare il tuo codice di conseguenza tenendo conto delle possibili eccezioni.

Inoltre consiglierei di dare un'occhiata alla documentazione di Microsoft Enterprise Library Blocco gestione eccezioni ha una bella descrizione dei modelli di gestione degli errori.

Puoi rilevare eccezioni non controllate senza doverle racchiudere.Ad esempio, quanto segue è Java valido.

try {
    throw new IllegalArgumentException();
} catch (Exception e) {
    System.out.println("boom");
}

Quindi nella tua azione/controller puoi avere un blocco try-catch attorno alla logica in cui viene effettuata la chiamata Hibernate.A seconda dell'eccezione è possibile visualizzare messaggi di errore specifici.

Ma immagino che oggi potrebbe essere Hibernate e domani il framework SleepLongerDuringWinter.In questo caso devi far finta di avere il tuo piccolo framework ORM che avvolge il framework di terze parti.Ciò ti consentirà di racchiudere qualsiasi eccezione specifica del framework in eccezioni più significative e/o verificate di cui sai come dare un senso migliore.

  1. La domanda non è realmente correlata al rapporto tra check e check.dibattito incontrollato, lo stesso vale per entrambi i tipi di eccezioni.

  2. Tra il punto in cui viene lanciata la ConstraintViolationException e il punto in cui vogliamo gestire la violazione visualizzando un simpatico messaggio di errore c'è un gran numero di chiamate al metodo sullo stack che dovrebbero interrompersi immediatamente e non dovrebbero preoccuparsi del problema.Ciò rende il meccanismo delle eccezioni la scelta giusta invece di riprogettare il codice dalle eccezioni ai valori restituiti.

  3. In effetti, utilizzare un'eccezione non controllata invece di un'eccezione controllata è una soluzione naturale, poiché vogliamo davvero che tutti i metodi intermedi sullo stack di chiamate siano ignorare l'eccezione e non gestirlo .

  4. Se vogliamo gestire la "violazione del nome univoco" solo visualizzando un simpatico messaggio di errore (pagina di errore) all'utente, non è realmente necessaria una DuplicateUsernameException specifica.Ciò manterrà basso il numero di classi di eccezione.Possiamo invece creare una MessageException che può essere riutilizzata in molti scenari simili.

    Appena possibile catturiamo la ConstraintViolationException e la convertiamo in una MessageException con un bel messaggio.È importante convertirlo presto, quando potremo essere sicuri che sia davvero il "vincolo del nome utente univoco" ad essere violato e non qualche altro vincolo.

    Da qualche parte vicino al gestore di livello superiore, gestisci semplicemente MessageException in un modo diverso.Invece di "abbiamo registrato il tuo problema ma per ora sei risolto" visualizza semplicemente il messaggio contenuto in MessageException, senza traccia dello stack.

    La MessageException può accettare alcuni parametri aggiuntivi del costruttore, come una spiegazione dettagliata del problema, l'azione successiva disponibile (annulla, vai a una pagina diversa), l'icona (errore, avviso)...

Il codice potrebbe assomigliare a questo

// insert the user
try {
   hibernateSession.save(user);
} catch (ConstraintViolationException e) {
   throw new MessageException("Username " + user.getName() + " already exists. Please choose a different name.");
}

In un posto completamente diverso c'è un gestore di eccezioni top

try {
   ... render the page
} catch (MessageException e) {
   ... render a nice page with the message
} catch (Exception e) {
   ... render "we logged your problem but for now you're hosed" message
}

@Jan Controllare e non controllare è una questione centrale qui.Metto in dubbio la tua supposizione (n. 3) secondo cui l'eccezione dovrebbe essere ignorata nei frame intermedi.Se lo faccio, mi ritroverò con una dipendenza specifica dell'implementazione nel mio codice di alto livello.Se sostituisco Hibernate, i blocchi catch in tutta la mia applicazione dovranno essere modificati.Tuttavia, allo stesso tempo, se rilevo l'eccezione a un livello inferiore, non otterrò molti vantaggi dall'utilizzo di un'eccezione non controllata.

Inoltre, lo scenario qui è che voglio rilevare un errore logico specifico e modificare il flusso dell'applicazione richiedendo nuovamente all'utente un ID diverso.La semplice modifica del messaggio visualizzato non è sufficiente e la capacità di mappare messaggi diversi in base al tipo di eccezione è già incorporata nelle servlet.

@erikson

Giusto per aggiungere cibo ai tuoi pensieri:

Anche selezionato rispetto a non selezionato lo è discusso qui

L'utilizzo di eccezioni non controllate è conforme al fatto che vengono utilizzate IMO come eccezione causato dal chiamante della funzione (e il chiamante può trovarsi diversi livelli sopra quella funzione, da qui la necessità che altri frame ignorino l'eccezione)

Per quanto riguarda il tuo problema specifico, dovresti rilevare l'eccezione non controllata ad alto livello e incapsularla, come detto da @Kanook nella tua eccezione, senza visualizzare lo stack di chiamate (come menzionato da @Jan Soltis)

Detto questo, se la tecnologia sottostante cambia, ciò avrà effettivamente un impatto su quelli catch() già presenti nel tuo codice e questo non risponde al tuo ultimo scenario.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top