Sta rilanciando un'eccezione che perde un'astrazione?

11

Ho un metodo di interfaccia che afferma nella documentazione che genererà un tipo specifico di eccezione. Un'implementazione di tale metodo utilizza 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 impedire all'astrazione di fuoriuscire.

Le mie domande sono: passare l'eccezione interna generata come l'eccezione precedente sta perdendo l'astrazione? In caso contrario, sarebbe opportuno riutilizzare semplicemente il messaggio dell'eccezione precedente? Se dovesse fuoriuscire l'astrazione, potresti fornire alcune indicazioni sul perché pensi che sia così?

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

throw new DoohickeyDisasterException($Exc->getMessage(), null, $Exc);
    
posta Charles Sprayberry 31.01.2012 - 04:12
fonte

4 risposte

11

My questions are: Would passing the thrown internal exception as the previous exception be leaking the abstraction? If not, would it be suitable to simply reuse the previous exception's message?

La risposta è: "dipende".

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

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

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

    
risposta data 31.01.2012 - 04:15
fonte
7

In senso stretto, se l'eccezione 'interiore' rivela qualcosa sul funzionamento interno di un'implementazione, tu stai perdendo. Quindi, tecnicamente, dovresti sempre prendere tutto e lanciare il tuo tipo di eccezione.

MA.

Un bel po 'di argomenti importanti possono essere fatti contro questo. In particolare:

  • Le eccezioni sono cose eccezionali ; violano già le regole del "normale funzionamento" in molti modi, quindi potrebbero anche violare l'incapsulamento e l'astrazione a livello di interfaccia.
  • Il vantaggio di avere una significativa traccia dello stack e le informazioni di errore al punto spesso superano la correttezza della OOP, purché non si perdano informazioni sul programma interno la barriera UI (che sarebbe la divulgazione di informazioni).
  • L'avvolgimento di ogni eccezione interna in un adatto tipo di eccezione esterna può portare a un numero di codice boiler inutile, che sconfigge l'intero scopo delle eccezioni (ovvero, implementa la gestione degli errori senza inquinare il normale flusso di codice con errore gestione del boilerplate).

Detto questo, le eccezioni di catching and wrapping spesso hanno senso, se non altro per aggiungere ulteriori informazioni ai tuoi registri, o per evitare la perdita di informazioni interne all'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 attraverso all'utente non filtrato; ma se stai codificando una libreria che implementa qualche protocollo di rete, il bubbling di alcune eccezioni è probabilmente la cosa giusta da fare (dopotutto, se la rete non funziona, c'è poco che puoi fare per migliorare le informazioni o recuperare e continuare ; la conversione di NetworkDownException in FoobarNetworkDownException non ha alcun significato cruft).

    
risposta data 31.01.2012 - 10:04
fonte
6

Prima di tutto, quello che stai facendo qui non sta rilanciando un'eccezione. Rethrowing sarebbe letteralmente lanciare la stessa eccezione, in questo modo:

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

Ovviamente, questo è un po 'inutile. La retrocessione è per le circostanze in cui si desidera rilasciare alcune risorse o registrare l'eccezione o qualsiasi altra cosa si possa desiderare, senza interrompere l'eccezione dall'insorgere dello stack.

Quello che stai facendo è già sostituire le eccezioni tutte , indipendentemente dalla causa, con DoohickeyDisasterException. Quale può o non può essere quello che intendi fare. Ma vorrei suggerire che qualsiasi tempo lo fai, dovresti anche fare come suggerisci e passare l'eccezione originale come eccezione interna, nel caso in cui sia utile nella traccia dello stack.

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

Per rispondere alla domanda se un'eccezione di rethrown (o un'eccezione non rilevata) possa essere un'astrazione che perde, direi che dipende dal codice di chiamata. Se quel codice richiede la conoscenza del framework astratto allora è un'astrazione che perde, perché se sostituisci quella struttura con un'altra, devi cambiare il codice al di fuori della tua astrazione.

Ad esempio, se DoohickeyDisasterException è una classe del framework e il tuo codice chiamante appare così, hai un'astrazione che perde:

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

Se si sostituisce il framework di terze parti con un altro, che utilizza un nome di eccezione diverso, e si devono trovare tutti questi riferimenti e modificarli. Meglio creare la tua classe Exception e avvolgere l'eccezione framework in essa.

Se, d'altra parte, DoohickeyDisasterException è uno dei tuoi, quindi non stai perdendo astrazioni, dovresti preoccuparti di più del mio primo punto.

    
risposta data 31.01.2012 - 10:13
fonte
2

La regola che cerco di seguire è: avvolgere 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ò che questo implica è che se la causa dell'errore è qualcosa di esterno al tuo codice, e non ti aspetti che fallisca, allora fallisci con garbo e riscrivilo. Allo stesso modo, se si tratta di un problema con il codice, quindi avvolgere in un'eccezione (potenzialmente) personalizzata.

Ad esempio, se un file è previsto sul disco, o se un servizio è previsto disponibile, gestirlo con garbo e ripetere l'errore in modo che il consumatore del tuo codice può capire perché quella risorsa non è disponibile. Se hai creato troppi dati per un certo tipo, allora è il tuo errore avvolgere e buttare.

    
risposta data 31.01.2012 - 05:28
fonte

Leggi altre domande sui tag