Cosa devo fare con le eccezioni specifiche dell'implementazione?

7

Diciamo che ho un'interfaccia che descrive un semplice servizio

public interface AccountService
{
    public int getUserId(String userName) throws UserNotFoundException;

    //...
}

Ho scritto io stesso l'eccezione UserNotFoundException ed è specifico per questo servizio. Oltre a ciò, qualsiasi altra cosa che potrebbe andare storta è l'implementazione specifica.

Se ho un

public class DatabaseAccountService
{
    @Override
    public int getUserId(String userName) throws UserNotFoundException
    {
        //some stuff that might throw an SQLException
    }
}

La connessione potrebbe essere interrotta, la tabella non potrebbe esistere, ecc. Mentre con FileAccountService invece, il file potrebbe avere problemi di autorizzazione, potrebbe esistere ma essere in un formato non valido, ecc. Potrebbero verificarsi molti problemi specifici di implementazione in entrambi i casi.

Il fatto è che non posso descrivere tutti i possibili problemi di tutte le possibili implementazioni nella mia interfaccia. Ho bisogno in qualche modo di descrivere "cose che sono andate storte e sono specifiche dell'implementazione". Dovrei avvolgere tali eccezioni in RuntimeException o aggiungere un'altra eccezione alla mia firma come OperationFailedException ?

Racchiudere l'eccezione specifica dell'implementazione in UserNotFoundException non è nemmeno un'opzione. Sarebbe bugiardo, potenzialmente dicendo che un utente non esiste mentre il file potrebbe non essere leggibile.

Il problema che vedo con RuntimeException s è che potrebbero non essere catturati dall'utente, forse portare a un arresto anomalo dell'applicazione quando forse c'era qualcosa di meglio da fare.

Il problema che vedo con un OperationFailedException è che è super generico e non aggiunge alcuna informazione ai miei metodi firme.

public interface AccountService
{
    public int getUserId(String userName) throws UserNotFoundException, 
                                                 OperationFailedException; //dumb, any method can fail
}

Inoltre, mi sento come se aggiungere OperationFailedException fosse la cosa migliore da fare, un tipo generico di wrapping di eccezioni esisterebbe nella libreria standard. Tuttavia, per quanto ne so, tale eccezione generica non esiste, suggerendo che ogni eccezione dovrebbe essere il più specifica possibile per aiutare i programmatori.

Ho qualche altra opzione? Qual è il migliore se voglio che qualcuno ecceda tanto dal mio codice che dalla libreria standard Java?

    
posta Winter 06.08.2017 - 04:17
fonte

2 risposte

8

Should I wrap such exceptions into a RuntimeException or add another exception to my signature like OperationFailedException ?

Consiglierei qualcosa come il secondo. Crea un'eccezione più generica per il tuo AccountService . Assegnagli un nome a qualcosa come AccountServiceException o ServiceException e ogni metodo in AccountService lo dichiara come eccezione verificata. Questo è simile alla tua idea di usare OperationFailedException , ma è più specifica della tua applicazione e puoi adattarla a AccountService .

interface AccountService
{
    int getUserId(String userName) throws UserNotFoundException, AccountServiceException;

    Bar foo() throws AccountServiceException;
    //...
}

Ogni livello dovrebbe catturare e gestire o avvolgere le eccezioni dal livello sottostante. Ciò ti separerà dalle eccezioni specifiche dell'implementazione e l'utilizzo di eccezioni controllate assicurerà che i casi di eccezione vengano gestiti e non persi.

I feel like if adding an OperationFailedException was the best thing to do, such generic exception wrapping type would exist in the standard library.

OperationFailedException è esenzialmente solo java.lang.Exception . java.lang.Exception è la classe utilizzata per descrivere un'operazione che ha avuto esito negativo senza fornire ulteriore contesto sul problema. AccountServiceException è molto più descrittivo e può essere catturato senza rilevare eccezioni da altri servizi nell'applicazione. Se passi a Exception o OperationFailedException da tutte le tue classi, la cattura delle eccezioni diventerebbe un incubo.

Questo è lo stesso schema usato altrove nel JRE. SQLException e JAXBException sono due esempi di eccezioni specifiche per il dominio in cui sono definite, ma generate da molti metodi in quel dominio.

    
risposta data 06.08.2017 - 05:06
fonte
3

Secondo me, un progetto API dovrebbe essere guidato dai suoi potenziali utenti. Quindi mi occuperò del punto di vista di un utente (chiamante). Sono ben consapevole che questo può portare a dibattiti ...

  1. Non mi piace scrivere elenchi di eccezioni complete nella mia clausola throws . Non è pigrizia - non voglio forzare il mio chiamante ad affrontare 20 tipi di eccezioni diversi che sono per lo più irrilevanti per lui.

  2. Non mi piace catturare, avvolgere e rilanciare le eccezioni, solo perché le firme del metodo mi costringono a farlo. Questo non è solo brutto per me, ma porta anche a avvolgere molti livelli di eccezione attorno a quello originale, rendendo i registri difficili da leggere.

  3. Se alcune delle mie chiamate al metodo falliscono, normalmente non ho un modo alternativo per rispettare il mio contratto, quindi devo solo notificare al mio chiamante che non potrei fare il mio lavoro. Qui preferisco semplicemente lasciare che l'eccezione mi sia passata attraverso il mio chiamante. Per me, questo è il più grande vantaggio delle eccezioni rispetto allo stile dei linguaggi di tipo "assegno per difetto" vecchio stile come C

  4. Se alcune delle mie chiamate al metodo falliscono e ho un modo alternativo per quel passo, ci proverò, non importa perché il primo modo abbia fallito.

  5. Quindi di solito non mi interessa il tipo di eccezione che ho ricevuto. È importante per la registrazione ma non per me come chiamante.

  6. A scopo di registrazione, a volte ha senso aggiungere informazioni all'eccezione ricevuta (ad esempio gli argomenti di chiamata che hanno portato all'eccezione). Quindi circonderò tutto il mio corpo del metodo con try-catch e lanciando un'eccezione wrapper con le informazioni di contesto visibili nel suo toString () e l'eccezione originale come causa.

Che diavolo significa per la tua domanda?

  • Avere a che fare con più eccezioni non correlate da una chiamata al metodo è un no-go per me, perché mi costringe a intraprendere il percorso 1. o 2., che non mi piace. Quindi UserNotFoundException e AccountServiceException dovrebbero avere una classe base comune, almeno.

  • Se ogni servizio nella tua applicazione ha il suo albero delle classi di eccezioni non correlato, devo ancora gestire molti tipi di eccezioni nel mio metodo (perché sicuramente il mio metodo non solo chiamerà AccountService), quindi è ancora lo stesso problema.

  • Preferirei avere una classe di eccezione root comune ApplicationException da cui derivano tutti i tipi di applicazioni specifiche (non so se hai il controllo su tutta l'applicazione in modo che tu possa farlo). Ciò mi consente di far passare le eccezioni al mio chiamante con un breve elenco throws , che tende a diventare throws ApplicationException più in alto nei livelli software. E questa classe base può supportare cose come i testi di notifica utente, contenenti informazioni di contesto e così via.

  • Se non è possibile introdurre una classe di base di eccezione comune per l'applicazione, mi piacerebbe ottenere RuntimeExceptions (o sottoclassi specifiche) perché supporta il mio caso d'uso al 95% (lascia che l'eccezione ripple attraverso) . Non temere "che potrebbero non essere catturati dall'utente, magari portando a un arresto anomalo dell'applicazione". È un errore di programmatore errato se le attività di livello superiore della tua applicazione si bloccano quando si ottiene una RuntimeException (ad esempio NullPointerException) e non si intraprendono azioni appropriate.

Quindi sceglierei una classe base ApplicationException utilizzabile in tutte le sottoclassi dell'applicazione (preferita) o RuntimeException.

    
risposta data 06.08.2017 - 17:30
fonte

Leggi altre domande sui tag