Reescrever uma exceção vazando uma abstração?


12

Eu tenho um método de interface que afirma na documentação que lançará um tipo específico de exceção. Uma implementação desse método usa algo que gera uma exceção. A exceção interna é capturada e a exceção declarada pelo contrato de interface é lançada. Aqui está um pequeno exemplo de código para melhor explicar. Está escrito em PHP, mas é bastante simples de seguir.

// 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

}

Estou usando esse método na tentativa de impedir que a abstração vaze.

Minhas perguntas são: Passar a exceção interna lançada como a exceção anterior vazaria a abstração? Caso contrário, seria adequado simplesmente reutilizar a mensagem da exceção anterior? Se houvesse vazamento da abstração, você poderia fornecer algumas orientações sobre por que acha que isso acontece?

Apenas para esclarecer, minha pergunta envolveria a alteração para a seguinte linha de código

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

As exceções devem ser sobre causas, não sobre quem as lança . Se o seu código pode ter um file_not_found, ele deve lançar um file_not_found_exception. Exceções não devem ser específicas da biblioteca.
Mooing Duck

Respostas:


11

Minhas perguntas são: Passar a exceção interna lançada como a exceção anterior vazaria a abstração? Caso contrário, seria adequado simplesmente reutilizar a mensagem da exceção anterior?

A resposta é: "depende".

Especificamente, depende da exceção e do que isso significa. Em essência, relançar novamente significa que você está adicionando isso à sua interface. Você faria isso se escrevesse o código para o qual está se chamando ou isso é apenas para evitar a renomeação da exceção?

Se sua exceção incluir um rastreamento até a causa raiz, no código chamado, que pode vazar dados para fora da abstração - mas, geralmente, acho que isso é aceitável porque a exceção é tratada pelo chamador (e ninguém se importa onde veio) ou mostrado ao usuário (e você deseja conhecer a causa raiz da depuração.)

Freqüentemente, porém, apenas permitir a exceção é uma escolha muito razoável de implementação, e não um problema de abstração. Basta perguntar "eu jogaria isso se o código que estou chamando não o fizesse?"


8

A rigor, se a exceção "interna" revelar algo sobre o funcionamento interno de uma implementação, você está vazando. Portanto, tecnicamente, você sempre deve pegar tudo e lançar seu próprio tipo de exceção.

MAS.

Alguns argumentos importantes podem ser feitos contra isso. Mais notavelmente:

  • Exceções são coisas excepcionais ; eles já violam as regras da 'operação normal' de várias maneiras; portanto, também podem violar o encapsulamento e a abstração no nível da interface.
  • O benefício de ter um rastreamento de pilha significativo e informações de erro pontuais geralmente supera a correção do OOP, desde que você não vaze informações internas do programa além da barreira da interface do usuário (que seria a divulgação de informações).
  • O agrupamento de todas as exceções internas em um tipo de exceção externa adequado pode levar a muitos códigos clichê inúteis , o que anula todo o objetivo das exceções (a saber, implemente o tratamento de erros sem poluir o fluxo de código regular com o tratamento de erros).

Dito isto, capturar e agrupar exceções geralmente faz sentido, apenas para adicionar informações adicionais aos seus logs ou para evitar o vazamento de informações internas para o usuário final. O tratamento de erros ainda é uma forma de arte e é difícil resumir algumas regras gerais. Algumas exceções devem ser borbulhadas, outras não, e a decisão também depende do contexto. Se você estiver codificando uma interface do usuário, não deseja deixar nada passar para o usuário; mas se você estiver codificando uma biblioteca que implementa algum protocolo de rede, é provável que fazer algumas exceções seja o ideal (afinal, se a rede estiver inoperante, pouco poderá ser feito para melhorar as informações ou recuperar e continuar ; traduzir NetworkDownExceptionpara FoobarNetworkDownExceptioné apenas uma crosta sem sentido).


6

Primeiro de tudo, o que você está fazendo aqui não é relançar uma exceção. Relembrar seria literalmente lançar a mesma exceção, assim:

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

Obviamente, isso é um pouco inútil. A releitura é para as circunstâncias em que você deseja liberar alguns recursos ou registrar a exceção ou o que mais desejar, sem impedir que a exceção borbulhe na pilha.

O que você já está fazendo é substituir todas as exceções, independentemente da causa, por uma DoohickeyDisasterException. O que pode ou não ser o que você pretende fazer. Mas gostaria de sugerir que qualquer vez que você fizer isso, você também deve fazer como você sugere e passar a exceção original como uma exceção interna, apenas no caso é útil no rastreamento de pilha.

Mas não se trata de abstrações com vazamentos, é outra questão.

Para responder à questão de saber se uma exceção repetida (ou uma exceção não capturada) pode ser uma abstração com vazamento, eu diria que depende do seu código de chamada. Se esse código requer conhecimento da estrutura abstraída, é uma abstração com vazamento, porque se você substituir essa estrutura por outra, precisará alterar o código fora da abstração.

Por exemplo, se DoohickeyDisasterException for uma classe da estrutura e seu código de chamada estiver assim, você terá uma abstração com vazamento:

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

Se você substituir sua estrutura de terceiros por outra, que usa um nome de exceção diferente, você precisará encontrar todas essas referências e alterá-las. Melhor criar sua própria classe Exception e incluir a exceção da estrutura.

Se, por outro lado, DoohickeyDisasterException for de sua autoria, então você não está vazando abstrações, você deve se preocupar mais com o meu primeiro ponto.


2

A regra pela qual tento seguir é: envolva-a se você a causou. Da mesma forma, se um bug fosse criado devido a uma exceção de tempo de execução, deveria ser algum tipo de DoHickeyException ou MyApplicationException.

O que isso implica é que, se a causa do erro é algo externo ao seu código e você não espera que ele falhe, falhe normalmente e repita o processo. Da mesma forma, se houver algum problema com o seu código, envolva-o em uma exceção (potencialmente) personalizada.

Por exemplo, se um arquivo é esperado para estar no disco, ou um serviço é esperado para estar disponível, então lidar com isso graciosamente e re-lançar o erro para que o consumidor do seu código pode descobrir por que esse recurso não está disponível. Se você criou muitos dados para algum tipo, é seu erro quebrar e lançar.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.