Riscoprire un'eccezione perde un'astrazione?


12

Ho un metodo di interfaccia che afferma nella documentazione che genererà un tipo specifico di eccezione. Un'implementazione di quel metodo usa qualcosa che genera un'eccezione. Viene rilevata l'eccezione interna e viene generata l'eccezione dichiarata dal contratto di interfaccia. Ecco un piccolo esempio di codice per spiegare meglio. È scritto in PHP ma è piuttosto semplice da seguire.

// in the interface

/**
 * @return This method returns a doohickey to use when you need to foo
 * @throws DoohickeyDisasterException
 */
public function getThatDoohickey();

// in the implementation

public function getThatDoohickey() {

    try {
        $SomethingInTheClass->doSomethingThatThrowsAnException();
    } catch (Exception $Exc) {
        throw new DoohickeyDisasterException('Message about doohickey failure');
    }

    // other code may return the doohickey

}

Sto usando questo metodo nel tentativo di evitare perdite dall'astrazione.

Le mie domande sono: passare l'eccezione interna generata come la precedente eccezione perderebbe l'astrazione? In caso contrario, sarebbe opportuno semplicemente riutilizzare il messaggio dell'eccezione precedente? Se perdesse l'astrazione potresti fornire qualche guida sul perché pensi che lo faccia?

Giusto per chiarire, la mia domanda implicherebbe il passaggio alla seguente riga di codice

throw new DoohickeyDisasterException($Exc->getMessage(), null, $Exc);

Le eccezioni dovrebbero riguardare le cause e non chi li lancia . Se il tuo codice può avere un file_not_found, allora dovrebbe lanciare a file_not_found_exception. Le eccezioni non dovrebbero essere specifiche della libreria.
Mooing Duck,

Risposte:


11

Le mie domande sono: passare l'eccezione interna generata come la precedente eccezione perderebbe l'astrazione? In caso contrario, sarebbe opportuno semplicemente riutilizzare il messaggio dell'eccezione precedente?

La risposta è, dipende".

In particolare, dipende dall'eccezione e dal significato. In sostanza, ridisegnarlo significa che lo stai aggiungendo alla tua interfaccia. Lo faresti se scrivessi il codice che ti stai chiamando, o è solo per evitare di rinominare l'eccezione?

Se la tua eccezione include una traccia fino alla causa principale, nel codice chiamato, che potrebbe perdere dati al di fuori dell'astrazione - ma, in generale, penso che sia accettabile perché l'eccezione è gestita dal chiamante (e nessuno si preoccupa dove si trova proviene da) o mostrato all'utente (e si desidera conoscere la causa principale, per il debug).

Spesso, tuttavia, solo consentire l'eccezione è una scelta di implementazione molto ragionevole e non un problema di astrazione. Basta chiedere "lo lancerei se il codice che sto chiamando non ha funzionato?"


8

A rigor di termini, se l'eccezione 'interno' rivela nulla circa il funzionamento interno di un'implementazione, si sta perdite. Quindi tecnicamente, dovresti sempre prendere tutto e lanciare il tuo tipo di eccezione.

MA.

Un bel paio di argomenti importanti possono essere fatte contro questo. Soprattutto:

  • Le eccezioni sono cose eccezionali ; violano già in molti modi le regole del "normale funzionamento", quindi potrebbero anche violare l'incapsulamento e l'astrazione a livello di interfaccia.
  • Il vantaggio di disporre di una traccia dello stack significativa e di informazioni sugli errori puntuali spesso supera la correttezza di OOP, purché non si perdano informazioni interne sul programma oltre la barriera dell'interfaccia utente (che sarebbe la divulgazione di informazioni).
  • L'avvolgimento di ogni eccezione interna in un tipo di eccezione esterna adatto può portare a un sacco di codice inutile del boilerplate , che vanifica l'intero scopo delle eccezioni (vale a dire, implementare la gestione degli errori senza inquinare il normale flusso di codice con il trattamento degli errori del boilerplate).

Detto questo, la cattura e il confezionamento eccezioni spesso fa ha senso, se non altro per aggiungere ulteriori informazioni ai tuoi log, o per impedire perdite di informazioni interne per l'utente finale. La gestione degli errori è ancora una forma d'arte ed è difficile ridurla a poche regole generali. Alcune eccezioni dovrebbero essere gorgogliate, altre no, e la decisione dipende anche dal contesto. Se stai codificando un'interfaccia utente, non vuoi lasciare nulla all'utente filtrato; ma se stai codificando una libreria che implementa un protocollo di rete, gorgogliare alcune eccezioni è probabilmente la cosa giusta da fare (dopotutto, se la rete è inattiva, c'è poco che puoi fare per migliorare le informazioni o recuperare e continuare ; tradurre NetworkDownExceptionin FoobarNetworkDownExceptionè solo un'innesto insignificante).


6

Prima di tutto, quello che stai facendo qui non è riproporre un'eccezione. Riscrivere sarebbe letteralmente gettare la stessa eccezione, in questo modo:

try {
    $SomethingInTheClass->doSomethingThatThrowsAnException();
} catch (Exception $Exc) {
    throw $Exc;
}

Ovviamente, questo è un po 'inutile. La ricomposizione è per le circostanze in cui si desidera rilasciare alcune risorse o registrare l'eccezione o qualsiasi altra cosa si possa fare, senza fermare l'eccezione dal gorgogliamento dello stack.

Quello che stai già facendo è sostituire tutte le eccezioni, indipendentemente dalla causa, con DoohickeyDisasterException. Quale potrebbe essere o meno ciò che si intende fare. Ma suggerirei che ogni volta che lo fai, dovresti anche fare ciò che suggerisci e passare l'eccezione originale come un'eccezione interna, nel caso in cui sia utile nella traccia dello stack.

Ma non si tratta di astrazioni che perdono, questo è un altro problema.

Per rispondere alla domanda se un'eccezione riproposta (o davvero un'eccezione non rilevata) possa essere un'astrazione che perde, direi che dipende dal tuo codice chiamante. Se quel codice richiede la conoscenza del framework astratto, allora è un'astrazione che perde, perché se si sostituisce quel framework con un altro, è necessario modificare il codice al di fuori della propria astrazione.

Ad esempio, se DoohickeyDisasterException è una classe dal framework e il tuo codice chiamante assomiglia a questo, allora hai un'astrazione che perde:

try {
    $wrapper->getThatDoohickey();
} catch (DoohickeyDisasterException $Exc) {
    echo "The Doohickey is all wrong!";
    echo $Exc->getMessage();
    gotoAHappyPlaceAndSingHappySongs();
}

Se sostituisci il tuo framework di terze parti con un altro, che utilizza un nome di eccezione diverso, devi trovare tutti questi riferimenti e modificarli. Meglio creare la propria classe di eccezione e racchiudervi l'eccezione del framework.

Se, d'altra parte, DoohickeyDisasterException è una delle tue creazioni, allora non stai perdendo astrazioni, dovresti preoccuparti di più del mio primo punto.


2

La regola che cerco di seguire è: avvolgilo se lo hai causato. Allo stesso modo, se un bug dovesse essere creato a causa di un'eccezione di runtime, dovrebbe essere una sorta di DoHickeyException o MyApplicationException.

Ciò implica che se la causa dell'errore è qualcosa di esterno al tuo codice e non ti aspetti che fallisca, allora fallisci con grazia e riprova. Allo stesso modo, se si tratta di un problema con il codice, quindi racchiuderlo in un'eccezione (potenzialmente) personalizzata.

Ad esempio, se si prevede che un file si trovi sul disco o si prevede che sia disponibile un servizio , gestirlo con grazia e ripetere l'errore in modo che l'utente del codice possa capire perché tale risorsa non è disponibile. Se hai creato troppi dati per un certo tipo, allora è il tuo errore per avvolgere e lanciare.

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.