Práticas recomendadas para gerenciamento de exceção em Java ou C # [fechado]


117

Estou preso em decidir como lidar com exceções em meu aplicativo.

Muito se meus problemas com exceções vierem de 1) acessar dados por meio de um serviço remoto ou 2) desserializar um objeto JSON. Infelizmente, não posso garantir o sucesso para nenhuma dessas tarefas (corte de conexão de rede, objeto JSON malformado que está fora do meu controle).

Como resultado, se eu encontrar uma exceção, simplesmente a capturo dentro da função e retorno FALSE para o chamador. Minha lógica é que tudo o que o chamador realmente se preocupa é se a tarefa foi bem-sucedida, e não por que não foi bem-sucedida.

Aqui está um exemplo de código (em JAVA) de um método típico)

public boolean doSomething(Object p_somthingToDoOn)
{
    boolean result = false;

    try{
        // if dirty object then clean
        doactualStuffOnObject(p_jsonObject);

        //assume success (no exception thrown)
        result = true;
    }
    catch(Exception Ex)
    {
        //don't care about exceptions
        Ex.printStackTrace();
    }
    return result;
}

Acho que essa abordagem é boa, mas estou muito curioso para saber quais são as melhores práticas para gerenciar exceções (devo realmente fazer uma exceção borbulhar em toda a pilha de chamadas?).

Em resumo das principais questões:

  1. É correto apenas capturar as exceções, mas não colocá-las em bolhas ou notificar formalmente o sistema (por meio de um log ou uma notificação ao usuário)?
  2. Quais são as práticas recomendadas para exceções que não resultam em tudo que exige um bloco try / catch?

Acompanhamento / Editar

Obrigado por todos os comentários, encontramos algumas fontes excelentes sobre gerenciamento de exceções online:

Parece que o gerenciamento de exceções é uma daquelas coisas que variam com base no contexto. Mas o mais importante, deve-se ser consistente em como eles gerenciam exceções dentro de um sistema.

Além disso, tome cuidado com a podridão de código por meio de try / catches excessivos ou não dando respeito a uma exceção (uma exceção é alertar o sistema, o que mais precisa ser avisado?).

Além disso, este é um comentário bem escolhido de m3rLinEz .

Tendo a concordar com Anders Hejlsberg e você, a maioria das pessoas que ligam só se importam se a operação foi bem-sucedida ou não.

A partir deste comentário, surgem algumas questões para pensar ao lidar com exceções:

  • Qual é o ponto dessa exceção sendo lançada?
  • Como faz sentido lidar com isso?
  • O chamador realmente se preocupa com a exceção ou apenas se importa se a chamada foi bem-sucedida?
  • Forçar um chamador a gerenciar uma exceção potencial é normal?
  • Você está sendo respeitoso com as expressões da língua?
    • Você realmente precisa retornar um sinalizador de sucesso como booleano? Retornar booleano (ou um int) é mais uma mentalidade C do que Java (em Java, você apenas trataria da exceção).
    • Siga as construções de gerenciamento de erros associadas ao idioma :)!

Embora o artigo da comunidade oracle esteja em java, é um conselho genérico para uma ampla classe de linguagens. Bom artigo, exatamente o que eu estava procurando.
Bunny Rabbit

Respostas:


61

Parece-me estranho que você queira capturar exceções e transformá-las em códigos de erro. Por que você acha que o chamador prefere códigos de erro em vez de exceções, quando o último é o padrão em Java e C #?

Quanto às suas perguntas:

  1. Você só deve capturar exceções com as quais possa realmente lidar. Apenas detectar exceções não é a coisa certa a fazer na maioria dos casos. Existem algumas exceções (por exemplo, registro e empacotamento de exceções entre threads), mas mesmo para esses casos, você geralmente deve relançar as exceções.
  2. Definitivamente, você não deve ter muitas instruções try / catch em seu código. Novamente, a ideia é capturar apenas exceções com as quais você possa lidar. Você pode incluir um manipulador de exceção de nível superior para transformar qualquer exceção não tratada em algo útil para o usuário final, mas caso contrário, você não deve tentar capturar todas as exceções em todos os lugares possíveis.

E por que alguém iria querer códigos de erro em vez de exceções ... Eu sempre achei estranho que o HTTP ainda use códigos de erro, mesmo que meu aplicativo esteja gerando uma exceção. Por que o HTTP não me permite passar a exceção no estado em que se encontra?
Trejkaz

o melhor que você pode fazer é embrulhar seus códigos de erro em uma mônada
lindenrovio

@Trejkaz Você não deseja retornar os detalhes da exceção aos usuários, porque isso é um risco à segurança. É por isso que os servidores HTML retornam códigos de erro. Também é um problema de localização retornar uma mensagem de erro e provavelmente torna o HTML maior e mais lento para retornar. Tudo isso é porque eu acho que os servidores HTML retornam códigos de erro.
Didier A.

Acho que é melhor dizer: "Você só deve suprimir as exceções com as quais pode realmente lidar."
Didier A.

@didibus Por que você acha que as preocupações com a segurança deveriam criar um inconveniente no desenvolvimento? Nunca disse nada sobre produção. Quanto à localização de mensagens de erro, todo o site já tem esse problema e as pessoas parecem estar enfrentando.
Trejkaz

25

Isso depende da aplicação e da situação. Se você está construindo um componente de biblioteca, você deve borbulhar as exceções, embora elas devam ser agrupadas para serem contextuais com seu componente. Por exemplo, se você está criando um banco de dados Xml e digamos que está usando o sistema de arquivos para armazenar seus dados, e está usando as permissões do sistema de arquivos para proteger os dados. Você não gostaria de criar uma exceção FileIOAccessDenied, pois isso vaza sua implementação. Em vez disso, você envolveria a exceção e lançaria um erro AccessDenied. Isso é especialmente verdadeiro se você distribuir o componente a terceiros.

Quanto a se não há problema em engolir exceções. Isso depende do seu sistema. Se o seu aplicativo pode lidar com os casos de falha e não há benefício em notificar o usuário sobre o motivo da falha, vá em frente, embora seja altamente recomendável que você registre a falha. Sempre achei frustrante ser chamado para ajudar a solucionar um problema e descobrir que eles estavam engolindo a exceção (ou substituindo-a e lançando uma nova, sem definir a exceção interna).

Em geral, eu uso as seguintes regras:

  1. Em meus componentes e bibliotecas, eu só pego uma exceção se pretendo lidar com ela ou fazer algo baseado nela. Ou se eu quiser fornecer informações contextuais adicionais em uma exceção.
  2. Eu uso um try catch geral no ponto de entrada do aplicativo ou o nível mais alto possível. Se uma exceção chegar aqui, eu apenas logo e deixo falhar. Idealmente, as exceções nunca deveriam chegar aqui.

Acho que o seguinte código é um cheiro:

try
{
    //do something
}
catch(Exception)
{
   throw;
}

Um código como este não serve para nada e não deve ser incluído.


@ Josh, bom ponto sobre engolir exceções, mas acredito que haja poucos casos em que é aceitável simplesmente engolir exceções. No último projeto, seu trecho de código foi obrigatório, ele fedia. Registrar todos eles tornou tudo pior, Meu conselho, se você não consegue lidar com a exceção, tente não engoli-la.
smaclell

Combinado como eu disse, tudo depende do aplicativo e do contexto específico. Há momentos em que engulo exceções, embora seja raro, e não consigo me lembrar da última vez que o fiz ;-) Foi provavelmente quando eu estava escrevendo meu próprio logger e a gravação no log, e o log secundário falhou.
JoshBerke

O código serve a um ponto: você pode definir um ponto de interrupção no "lançamento".
Rauhotz

3
Ponto fraco, você pode dizer ao VS para interromper em qualquer exceção lançada ou pode restringi-la e escolher uma exceção específica. No VS2008, há um item de menu sob depuração (você precisará personalizar suas barras de ferramentas para encontrá-lo) Exceções chamadas
JoshBerke

O exemplo do "cheiro de código" tem um efeito colateral definitivo, mesmo nessa forma simples. Se // do somethingincluir qualquer try/finallybloco ao redor do ponto que lança, os finallyblocos serão executados antes do catchbloco. Sem o try/catch, a exceção voará até o topo da pilha sem que nenhum finallybloco seja executado. Isso permite que o manipulador de nível superior decida se deve ou não executar os finallyblocos.
Daniel Earwicker

9

Eu gostaria de recomendar outra boa fonte sobre o assunto. É uma entrevista com os inventores do C # e Java, Anders Hejlsberg e James Gosling, respectivamente, sobre o tópico Exceção Verificada do Java.

Falha e exceções

Existem também ótimos recursos na parte inferior da página.

Tendo a concordar com Anders Hejlsberg e você, a maioria das pessoas que ligam só se importam se a operação foi bem-sucedida ou não.

Bill Venners : Você mencionou questões de escalabilidade e controle de versão em relação às exceções verificadas. Você poderia esclarecer o que você quer dizer com essas duas questões?

Anders Hejlsberg : Vamos começar com o controle de versão, porque os problemas são muito fáceis de ver aí. Digamos que eu crie um método foo que declare que lança as exceções A, B e C. Na versão dois de foo, quero adicionar vários recursos e agora foo pode lançar a exceção D. É uma alteração importante para mim adicione D à cláusula throws desse método, porque o chamador existente desse método quase certamente não tratará essa exceção.

Adicionar uma nova exceção a uma cláusula throws em uma nova versão quebra o código do cliente. É como adicionar um método a uma interface. Depois de publicar uma interface, para todos os fins práticos, ela é imutável, porque qualquer implementação dela pode ter os métodos que você deseja adicionar na próxima versão. Portanto, você precisa criar uma nova interface. Da mesma forma, com as exceções, você teria que criar um método totalmente novo chamado foo2 que gerasse mais exceções, ou teria que capturar a exceção D no novo foo e transformar o D em A, B ou C.

Bill Venners : Mas você não está quebrando o código deles nesse caso, mesmo em uma linguagem sem exceções verificadas? Se a nova versão de foo vai lançar uma nova exceção que os clientes devem pensar em manipular, seu código não está quebrado apenas pelo fato de que eles não esperavam essa exceção quando escreveram o código?

Anders Hejlsberg : Não, porque em muitos casos, as pessoas não se importam. Eles não vão lidar com nenhuma dessas exceções. Há um manipulador de exceção de nível inferior em torno de seu loop de mensagem. Esse manipulador apenas exibirá um diálogo que diz o que deu errado e continuará. Os programadores protegem seu código escrevendo try finally's anywhere, portanto, eles retrocederão corretamente se ocorrer uma exceção, mas não estão realmente interessados ​​em lidar com as exceções.

A cláusula throws, pelo menos da maneira como é implementada em Java, não necessariamente força você a lidar com as exceções, mas se você não lidar com elas, ela o força a reconhecer precisamente quais exceções podem passar. Ele requer que você capture exceções declaradas ou as coloque em sua própria cláusula throws. Para contornar esse requisito, as pessoas fazem coisas ridículas. Por exemplo, eles decoram cada método com "throws Exception". Isso anula completamente o recurso, e você acabou de fazer o programador escrever mais lixo gobbledy. Isso não ajuda ninguém.

EDIT: Adicionados mais detalhes sobre o converstaion


Obrigado por isso! Eu atualizei minha pergunta com informações sobre sua resposta!
AtariPete

Parece que o Sr. Hejlsberg está justificando o tratamento de exceções do Pokémon. Um dos grandes problemas com o design de exceções em Java e C # é que muitas informações são codificadas no tipo de uma exceção, enquanto as informações que deveriam ser armazenadas em instâncias de exceção não estão disponíveis de maneira consistente. As exceções devem se propagar na pilha de chamadas até que todas as condições anormais representadas por elas tenham sido resolvidas; infelizmente, o tipo de exceção - mesmo se reconhecida - faz pouco para indicar se uma situação foi resolvida. Se uma exceção não for reconhecida ...
supercat

... a situação é ainda pior. Se o código chamar FetchDatae lançar uma exceção de um tipo inesperado, ele não terá como saber se essa exceção significa simplesmente que os dados estão indisponíveis (nesse caso, a capacidade do código de sobreviver sem ele os "resolveria") ou se isso significa que a CPU está em chamas e o sistema deve fazer um "desligamento de segurança" na primeira oportunidade. Parece que Hejlsberg está sugerindo que o código deve assumir o primeiro; talvez seja a melhor estratégia possível, dadas as hierarquias de exceção existentes, mas parece nojenta mesmo assim.
supercat

Eu concordo que lançar Exceptin é ridículo, porque quase tudo pode lançar uma exceção, você deve apenas assumir que isso pode acontecer. Mas quando você especifica exceções como throws A, B, C, é apenas uma anotação, para mim, deve ser usado como um bloco de comentário. É como dizer, ei cliente da minha função, aqui vai uma dica, talvez você queira lidar com A, B, C, pois esses erros podem acontecer ao me usar. Se você adicionar D no futuro, não será um grande problema se não estiver sendo tratado, mas é como adicionar uma nova documentação, ah, a propósito, agora também seria útil capturar D.
Didier A.

8

As exceções verificadas são um assunto controverso em geral, e em Java em particular (mais tarde tentarei encontrar alguns exemplos para aqueles que são a favor e contra).

Como regra geral, o tratamento de exceções deve ser algo em torno dessas diretrizes, sem uma ordem específica:

  • Para fins de manutenção, sempre registre as exceções para que, quando começar a ver bugs, o log o ajude a apontar o local onde o bug provavelmente começou. Nunca saia printStackTrace()ou algo parecido, as chances são de que um de seus usuários obterá um desses rastreamentos de pilha eventualmente e não terá nenhum conhecimento sobre o que fazer com ele.
  • Pegue as exceções que você pode controlar, e apenas essas, e lidar com elas , não apenas jogue-as na pilha.
  • Sempre capture uma classe de exceção específica e, geralmente, você nunca deve capturar o tipo Exception, pois é muito provável que engula exceções importantes.
  • Nunca (nunca) pegue Errors !! , significando: Never catch Throwables como Errors são subclasses do último. Errors são problemas que você provavelmente nunca será capaz de lidar (por exemplo OutOfMemory, ou outros problemas JVM)

Em relação ao seu caso específico, certifique-se de que qualquer cliente que chame seu método receberá o valor de retorno adequado. Se algo falhar, um método de retorno booleano pode retornar falso, mas certifique-se de que os locais em que você chama esse método são capazes de lidar com isso.


As exceções verificadas não são um problema em C # porque ele não as possui.
cletus

3
Imho, às vezes é bom detectar erros: Lembro-me de um aplicativo Java que consome muita memória que escrevi. Peguei o OutOfMemory-Ex e mostrei uma mensagem ao usuário de que ele está sem memória, que ele deveria fechar outros programas e disse a ele como iniciar o jvm com mais espaço de heap atribuído. Acho que ajudou.
Lena Schimmel

5

Você só deve capturar as exceções com as quais pode lidar. Por exemplo, se você estiver lidando com a leitura em uma rede e o tempo de conexão expirar e você obtiver uma exceção, pode tentar novamente. No entanto, se você está lendo em uma rede e obtém uma exceção IndexOutOfBounds, você realmente não pode lidar com isso porque você não (bem, neste caso você não vai) saber o que causou isso. Se você vai retornar falso ou -1 ou nulo, certifique-se de que seja para exceções específicas. Não quero que uma biblioteca que estou usando retorne um falso em uma leitura de rede quando a exceção lançada é que o heap está sem memória.


3

As exceções são erros que não fazem parte da execução normal do programa. Dependendo do que o programa faz e de seus usos (ou seja, um processador de texto versus um monitor cardíaco), você desejará fazer coisas diferentes quando encontrar uma exceção. Trabalhei com código que usa exceções como parte da execução normal e é definitivamente um cheiro de código.

Ex.

try
{
   sendMessage();

   if(message == success)
   {
       doStuff();
   }
   else if(message == failed)
   {
       throw;
   }
}
catch(Exception)
{
    logAndRecover();
}

Este código me faz vomitar. IMO, você não deve se recuperar de exceções, a menos que seja um programa crítico. Se você lançar exceções, coisas ruins estão acontecendo.


2

Todos os itens acima parecem razoáveis ​​e, muitas vezes, seu local de trabalho pode ter uma política. Em nosso local definimos os tipos de Exceção: SystemException(não marcada) e ApplicationException(marcada).

Concordamos que SystemExceptioné improvável que s sejam recuperáveis ​​e serão tratados uma vez no topo. Para proporcionar ainda mais contexto, os nossos SystemExceptions são exteneded para indicar onde ocorreu, por exemplo RepositoryException, ServiceEception, etc.

ApplicationExceptions podem ter um significado comercial como InsufficientFundsExceptione devem ser tratados pelo código do cliente.

Sem um exemplo concreto, é difícil comentar sua implementação, mas eu nunca usaria códigos de retorno, eles são um problema de manutenção. Você pode engolir uma Exceção, mas precisa decidir por que e sempre registrar o evento e o rastreamento de pilha. Por último, como seu método não tem outro processamento, ele é bastante redundante (exceto para encapsulamento?), Portanto, doactualStuffOnObject(p_jsonObject);poderia retornar um booleano!


1

Depois de pensar um pouco e examinar seu código, parece-me que você está simplesmente relançando a exceção como um booleano. Você poderia apenas deixar o método passar por essa exceção (você nem mesmo precisa capturá-la) e lidar com ela no chamador, já que é aí que isso importa. Se a exceção fará com que o chamador tente novamente esta função, o chamador deve ser o único capturando a exceção.

Às vezes, pode acontecer que a exceção que você está encontrando não faça sentido para o chamador (ou seja, é uma exceção de rede); nesse caso, você deve envolvê-la em uma exceção específica do domínio.

Se, por outro lado, a exceção sinaliza um erro irrecuperável em seu programa (ou seja, o resultado final dessa exceção será o encerramento do programa), eu pessoalmente gosto de tornar isso explícito capturando-o e lançando uma exceção de tempo de execução.


1

Se você for usar o padrão de código em seu exemplo, chame-o de TryDoSomething e capture apenas exceções específicas.

Considere também o uso de um Filtro de Exceção ao registrar exceções para fins de diagnóstico. VB tem suporte de idioma para filtros de exceção. O link para o blog de Greggm tem uma implementação que pode ser usada em C #. Filtros de exceção têm melhores propriedades para depuração em relação a captura e relançamento. Especificamente, você pode registrar o problema no filtro e permitir que a exceção continue a se propagar. Esse método permite anexar um depurador JIT (Just in Time) para ter a pilha original completa. Um relançamento corta a pilha no ponto em que foi relançada.

Os casos em que TryXXXX faz sentido são quando você está envolvendo uma função de terceiros que lança casos que não são verdadeiramente excepcionais ou são simples e difíceis de testar sem chamar a função. Um exemplo seria algo como:

// throws NumberNotHexidecimalException
int ParseHexidecimal(string numberToParse); 

bool TryParseHexidecimal(string numberToParse, out int parsedInt)
{
     try
     {
         parsedInt = ParseHexidecimal(numberToParse);
         return true;
     }
     catch(NumberNotHexidecimalException ex)
     {
         parsedInt = 0;
         return false;
     }
     catch(Exception ex)
     {
         // Implement the error policy for unexpected exceptions:
         // log a callstack, assert if a debugger is attached etc.
         LogRetailAssert(ex);
         // rethrow the exception
         // The downside is that a JIT debugger will have the next
         // line as the place that threw the exception, rather than
         // the original location further down the stack.
         throw;
         // A better practice is to use an exception filter here.
         // see the link to Exception Filter Inject above
         // http://code.msdn.microsoft.com/ExceptionFilterInjct
     }
}

Se você usa um padrão como TryXXX ou não, é mais uma questão de estilo. A questão de capturar todas as exceções e engoli-las não é uma questão de estilo. Certifique-se de que exceções inesperadas possam se propagar!


Adoro o padrão TryXXX em .net.
JoshBerke

1

Eu sugiro seguir suas dicas da biblioteca padrão para a linguagem que você está usando. Não posso falar em C #, mas vamos dar uma olhada em Java.

Por exemplo, java.lang.reflect.Array tem um setmétodo estático :

static void set(Object array, int index, Object value);

A maneira C seria

static int set(Object array, int index, Object value);

... com o valor de retorno sendo um indicador de sucesso. Mas você não está mais no mundo C.

Depois de abraçar as exceções, você deve descobrir que isso torna seu código mais simples e claro, movendo o código de tratamento de erros para longe de sua lógica central. Procure ter muitas instruções em um único trybloco.

Como outros observaram - você deve ser o mais específico possível no tipo de exceção que detecta.


este é um comentário muito válido, seja respeitoso com a linguagem e como ela tradicionalmente gerencia tais questões. Não traga uma mentalidade C para um mundo Java.
AtariPete

0

Se você capturar uma exceção e retornar falso, deve ser uma exceção muito específica. Você não está fazendo isso, você está pegando todos eles e retornando falsos. Se eu obtiver uma MyCarIsOnFireException, quero saber imediatamente! Posso não me importar com o resto das exceções. Portanto, você deve ter uma pilha de manipuladores de exceção que digam "uau uau, algo está errado aqui" para algumas exceções (relançar, ou capturar e relançar uma nova exceção que explica melhor o que aconteceu) e apenas retornar falso para os outros.

Se este é um produto que você lançará, você deve registrar essas exceções em algum lugar, isso o ajudará a ajustar as coisas no futuro.

Edit: Quanto à questão de envolver tudo em um try / catch, acho que a resposta é sim. As exceções devem ser tão raras em seu código que o código no bloco catch é executado tão raramente que nem atinge o desempenho. Uma exceção deve ser um estado em que sua máquina de estado quebrou e não sabe o que fazer. Pelo menos relançar uma exceção que explica o que estava acontecendo no momento e tem a exceção capturada dentro dela. "Exceção no método doSomeStuff ()" não é muito útil para quem precisa descobrir por que ele quebrou durante as férias (ou em um novo emprego).


Não se esqueça de que a configuração de um bloco de exceção também custa ...
devstuff

0

Minha estratégia:

Se a função original retornou void, eu altero para retornar bool . Se ocorrer uma exceção / erro, retorne falso , se tudo estiver bem, retorne verdadeiro .

Se a função deve retornar algo, quando a exceção / erro ocorrer, retorne nulo , caso contrário, o item retornável.

Em vez de bool, uma string pode ser retornada contendo a descrição do erro.

Em todos os casos, antes de retornar qualquer coisa, registre o erro.


0

Algumas respostas excelentes aqui. Eu gostaria de acrescentar que se você acabar com algo como postou, pelo menos imprima mais do que o rastreamento da pilha. Diga o que você estava fazendo no momento e Ex.getMessage (), para dar ao desenvolvedor uma chance de lutar.


eu concordo inteiramente. Eu só fiz Ex.printStackTrace (); como um exemplo de algo que eu estava fazendo na captura (ou seja, não jogando novamente).
AtariPete

0

Os blocos try / catch formam um segundo conjunto de lógica embutido no primeiro (principal) conjunto, como tal, eles são uma ótima maneira de eliminar códigos ilegíveis e difíceis de depurar.

Ainda assim, usados ​​razoavelmente, eles fazem maravilhas na legibilidade, mas você deve seguir duas regras simples:

  • use-os (com moderação) no nível inferior para detectar problemas de manipulação da biblioteca e transmiti-los de volta para o fluxo lógico principal. A maior parte do tratamento de erros que desejamos deve vir do próprio código, como parte dos próprios dados. Por que fazer condições especiais, se os dados de retorno não são especiais?

  • use um grande manipulador no nível superior para gerenciar qualquer ou todas as condições estranhas que surgem no código que não são detectadas em um nível inferior. Faça algo útil com os erros (logs, reinicializações, recuperações, etc).

Além desses dois tipos de tratamento de erros, todo o resto do código no meio deve estar livre e livre de código try / catch e objetos de erro. Dessa forma, ele funciona de maneira simples e esperada, não importa onde você o use ou o que faça com ele.

Paulo.


0

Posso chegar um pouco atrasado na resposta, mas o tratamento de erros é algo que sempre podemos mudar e evoluir ao longo do tempo. Se você quiser ler algo mais sobre esse assunto, escrevi um post no meu novo blog sobre o assunto. http://taoofdevelopment.wordpress.com

Boa codificação.

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.