Argumentos contra a supressão de erros

35

Encontrei um código como este em um de nossos projetos:

    SomeClass QueryServer(string args)
    {
        try
        {
            return SomeClass.Parse(_server.Query(args));
        }
        catch (Exception)
        {
            return null;
        }
    }

Tanto quanto eu entendo, suprimir erros como esse é uma prática ruim, já que ele destrói informações úteis da exceção do servidor original e faz com que o código continue quando realmente deveria terminar.

Quando é apropriado suprimir completamente todos os erros como este?

    
por user626528 23.02.2016 / 11:22
fonte

7 respostas

52

Imagine o código com milhares de arquivos usando várias bibliotecas. Imagine todos eles são codificados assim.

Imagine, por exemplo, que uma atualização do seu servidor faz com que um arquivo de configuração desapareça; e agora tudo que você tem é um rastreamento de pilha é uma exceção de ponteiro nulo quando você tenta usar essa classe: como você resolveria isso? Pode levar horas, quando pelo menos apenas registrar o rastreio da pilha bruta do arquivo não encontrado [caminho do arquivo] pode ajudá-lo a resolver momentaneamente.

Ou ainda pior: uma falha em uma das bibliotecas que você usa depois de uma atualização que faz seu código falhar mais tarde. Como você pode rastrear isso de volta para a biblioteca?

Mesmo sem um tratamento robusto de erros, basta fazer

throw new IllegalStateException("THIS SHOULD NOT HAPPENING")

ou

LOGGER.error("[method name]/[arguments] should not be there")

pode poupar horas.

Mas há alguns casos em que você pode realmente querer ignorar a exceção e retornar null assim (ou não fazer nada). Especialmente se você integrar com algum código legado mal projetado e essa exceção é esperada como um caso normal.

Na verdade, ao fazer isso, você deve se perguntar se realmente está ignorando a exceção ou "manipulando adequadamente" suas necessidades. Se retornar nulo estiver "manipulando adequadamente" sua exceção nesse caso específico, faça-o. E adicione um comentário porque esta é a coisa certa a fazer.

As práticas recomendadas são coisas a seguir na maioria dos casos, talvez 80%, talvez 99%, mas você sempre encontrará um caso extremo em que elas não se aplicam. Nesse caso, deixe um comentário porque você não está seguindo a prática para os outros (ou mesmo para você mesmo) que lerão seu código meses depois.

    
por 23.02.2016 / 11:37
fonte
14

Existem casos em que esse padrão é útil - mas eles são normalmente usados quando a exceção gerada nunca deveria ter sido (ou seja, quando exceções são usadas para comportamento normal).

Por exemplo, imagine que você tenha uma classe que abre um arquivo, armazene-a no objeto retornado. Se o arquivo não existir, você pode considerar que, para não ser um caso de erro, retornará null e permitirá que o usuário crie um novo arquivo. O método file-open pode lançar uma exceção aqui para indicar que nenhum arquivo está presente, portanto, captá-lo silenciosamente pode ser uma situação válida.

No entanto, não é frequente você querer fazer isso, e se o código estiver cheio de um padrão como esse, você gostaria de lidar com isso agora. No mínimo, eu esperaria que uma captura tão silenciosa para escrever uma linha de log dizendo que isso é o que aconteceu (você tem traçado, certo) e um comentário para explicar esse comportamento.

    
por 23.02.2016 / 12:05
fonte
7

Isso é 100% dependente do contexto. Se o chamador de tal função tiver o requisito para exibir ou registrar erros em algum lugar, então é obviamente um absurdo. Se o chamador ignorasse todos os erros ou mensagens de exceção, não faria muito sentido retornar a exceção ou sua mensagem. Quando o requisito é fazer com que o código só termine sem exibir qualquer razão, então uma chamada

   if(QueryServer(args)==null)
      TerminateProgram();

provavelmente seria suficiente. Você deve considerar se isso tornará difícil encontrar a causa do erro pelo usuário - se for esse o caso, essa é a forma incorreta de tratamento de erros. No entanto, se o código de chamada se parece com isso

  result = QueryServer(args);
  if(result!=null)
      DoSomethingMeaningful(result);
  // else ??? Mask the error and let the user run into trouble later

então você deve ter um argumento com o desenvolvedor durante a revisão do código. Se alguém implementar tal forma de tratamento sem erro apenas porque está com preguiça de descobrir os requisitos corretos, então este código não deve entrar em produção, e o autor do código deve ser ensinado sobre as possíveis conseqüências de tal preguiça. .

    
por 23.02.2016 / 11:37
fonte
5

Nos anos que passei programando e desenvolvendo sistemas, existem apenas duas situações em que achei o padrão em questão útil (em ambos os casos, a supressão continha também o registro da exceção lançada, não considero captura simples e null de retorno como uma boa prática).

As duas situações são as seguintes:

1. Quando a exceção não foi considerada um estado excepcional

Isto é quando você faz uma operação em alguns dados, que podem lançar, você sabe que pode lançar, mas você ainda quer que seu aplicativo continue em execução, porque você não precisa dos dados processados. Se você os receber, é bom, se não, também é bom.

Alguns atributos opcionais de uma classe podem ter em mente.

2. Quando você está fornecendo uma nova (melhor, mais rápida?) Implementação de uma biblioteca usando uma interface já usada em um aplicativo

Imagine que você tenha um aplicativo usando algum tipo de biblioteca antiga, que não emitiu exceções, mas retornou null de erro. Então você criou um adaptador para esta biblioteca, praticamente copiando a API original da biblioteca, e está usando essa nova interface (ainda não lançada) em seu aplicativo e lidando com o null você mesmo.

Uma nova versão da biblioteca vem, ou talvez uma biblioteca completamente diferente oferecendo a mesma funcionalidade, que, em vez de retornar null s, gera exceções e você deseja usá-la.

Você não deseja vazar as exceções para seu aplicativo principal, portanto, você as suprime e registra no adaptador que você criou para quebrar essa nova dependência.

O primeiro caso não é um problema, é o comportamento desejado do código. Na segunda situação, no entanto, se em todos os lugares o valor de retorno null do adaptador de biblioteca realmente significar um erro, a refatoração da API para lançar uma exceção e capturá-la em vez de verificar null pode ser (e normalmente o código é ) uma boa ideia.

Eu pessoalmente uso a supressão de exceção apenas para o primeiro caso. Eu usei apenas para o segundo caso, quando não tínhamos o orçamento para fazer o resto do aplicativo funcionar com as exceções em vez de null s.

    
por 23.02.2016 / 13:02
fonte
-1

Embora pareça lógico dizer que os programas devem apenas capturar exceções que eles sabem como manipular, e possivelmente não sabem como lidar com exceções que não foram previstas pelo programador, tal afirmação ignora o fato de que muitas operações podem falhar em um número quase ilimitado de maneiras que não têm efeitos colaterais e que, em muitos casos, o tratamento adequado para a grande maioria dessas falhas será idêntico; os detalhes exatos do fracasso serão irrelevantes e, consequentemente, não importa se o programador os antecipou.

Se, por exemplo, o propósito de uma função é ler um arquivo de documento em um objeto adequado e criar uma nova janela de documento para mostrar esse objeto ou relatar ao usuário que o arquivo não pode ser lido, tente carregar um arquivo de documento inválido não deve travar o aplicativo - ele deve mostrar uma mensagem indicando o problema, mas deixar o resto do aplicativo continuar funcionando normalmente, a menos que, por algum motivo, a tentativa de carregar o documento tenha corrompido o estado de outra coisa no sistema.

Fundamentalmente, o tratamento adequado de uma exceção geralmente dependerá menos no tipo de exceção do local onde foi lançada; se um recurso é protegido por um bloqueio de leitura / gravação e uma exceção é lançada um método que adquiriu o bloqueio para leitura, o comportamento adequado deve geralmente ser para liberar o bloqueio desde que o método não pode ter feito nada para o recurso. Se uma exceção é lançada enquanto o bloqueio é adquirido para por escrito, o bloqueio deve ser invalidado com freqüência, pois o recurso protegido pode estar em um estado inválido; se a primitiva de bloqueio não tiver um estado "invalidado", deve-se adicionar um sinalizador para rastrear tal invalidação. Liberar o bloqueio sem invalidá-lo é ruim porque outro código pode ver o objeto protegido em um estado inválido. Deixar a fechadura pendurada, no entanto, também não é uma solução adequada. A solução correta é invalidar o bloqueio para que qualquer tentativa pendente ou futura de aquisição falhe imediatamente.

Se um recurso de invalidação for abandonado antes de tentar usá-lo, não há motivo para desativar o aplicativo. Se um recurso invalidado for crítico para a operação continuada de um aplicativo, o aplicativo precisará ser desativado, mas a invalidação do recurso provavelmente fará com que isso aconteça. O código que recebeu a exceção original geralmente não tem como saber qual situação se aplica, mas se invalida o recurso, pode garantir que a ação correta acabará sendo tomada em ambos os casos.

    
por 24.02.2016 / 08:56
fonte
-2

Engolir esse tipo de erro não é particularmente útil para ninguém. Sim, o chamador original pode não se importar com a exceção, mas alguém pode .

Então o que eles fazem? Eles adicionarão código para lidar com a exceção para ver qual tipo de exceção eles estão recebendo de volta. Ótimo. Mas, se o manipulador de exceções for deixado, o chamador original não recuperará mais o valor nulo e algo ocorrerá em outro lugar.

Mesmo se você estiver ciente de que algum código upstream poderia lançar um erro e, assim, retornar null, seria seriamente inerte você não tentar pelo menos desativar a exceção no código de chamada IMHO.

    
por 23.02.2016 / 12:41
fonte
-3

Eu vi exemplos em que bibliotecas de terceiros tinham métodos potencialmente úteis, exceto que eles lançavam exceções em alguns casos que deveriam funcionar para mim. O que posso fazer?

  • Implemente exatamente o que preciso de mim mesmo. Pode ser difícil em um prazo.
  • Modifique o código de terceiros. Mas então eu tenho que procurar por conflitos de mesclagem com cada atualização.
  • Escreva um método wrapper para transformar a exceção em um ponteiro nulo comum, um booleano falso ou qualquer outra coisa.

Por exemplo, o método de biblioteca

public foo findFoo() {...} 

retorna o primeiro foo, mas se não houver nenhum, lança uma exceção. Mas o que eu preciso é um método para retornar o primeiro foo ou um nulo. Então eu escrevo este aqui:

public foo myFindFoo() {
    try {
        return findFoo() 
    } 
    catch (NoFooException ex) {
        return null;
    }
}

Não é legal. Mas às vezes a solução pragmática.

    
por 23.02.2016 / 20:38
fonte