Tratamento de erros é algo que não é visto com o suficiente cuidado pela maioria dos desenvolvedores de software. Com frequência, quando estou dando consultorias, encontro software sem tratamento de erro, com um tratamento de erro inútil ou ruim. Infelizmente a maioria dos desenvolvedores assume que o software deve funcionar e acabou. Acham-se os melhores desenvolvedores do planeta, já que não criam bugs, e esquecem que há problemas que acontecem mesmo com um software sem bugs. A memória acaba, a conexão com o banco de dados ou com um serviço cai, uma permissão é negada, o ambiente de instalação não é suportado, o navegador onde roda a aplicação não suporta a versão de Javascript que você está usando, e por aí vai. Outros não sabem como fazer o tratamento de erro, só sabem que devem tratar erro de alguma forma, e fazem tratamentos de erros inúteis, que mais atrapalham do que ajudam.

Vamos começar primeiro entendendo o que é um erro. O .Net já usa um nome para definir erros que não deixa margens à duplicidade: Exception, ou “exceção” no português. Exceção, segundo o dicionário Aulete Digital é:

  1. Não correspondência a uma regra.
  2. O que não confirma uma regra ou generalização.

Ou seja, exceções são coisas que acontecem e que não fazem parte de uma regra. A palavra regra ajuda bem a entender o significado. Não é uma regra que seu código tenha bugs (eu espero), assim como não é uma regra que a conexão do banco de dados caia, e não é uma regra que o IIS seja reiniciado enquanto a aplicação está rodando. Todos esses casos são exceções. Quando essas exceções acontecem nosso código precisa saber lidar com elas.

Agora que já está claro o que é um erro, temos que entender quando lançar uma exceção. Devemos ter claro o seguinte: se algo que aconteceu no software não for uma exceção, mas uma regra, como uma regra de negócios, por exemplo, isso não deve ser tratado como um erro, com uma Exception sendo lançada. Assim, por exemplo, quando o usuário digitar um CPF inválido, em vez de lançar uma CPFInvalidoException, você devolve algum objeto que informe que o CPF é inválido. Afinal, digitar um CPF inválido é uma regra de negócio que deve ser tratada, não é um bug ou condição excepcional.

 

Exceções no .Net são objetos perigosos, eles podem derrubar a aplicação se não forem tratados, e além disso têm um custo altíssimo de performance. Sempre que for lançar uma exceção avalie se está fazendo isso para deixar claro que há um bug ou condição inesperada.

Há uma regra muito clara para saber se você deve lançar uma exceção ou não: lance erros quando seu método não conseguir fazer o que tem que fazer. Se seu método deve abrir uma conexão e não conseguir, lance um erro. Se o seu método deve dar um desconto a um cliente, e o cliente não existir e portanto o desconto não puder ser dado, lance um erro. Mas não lance um erro se estiver buscando um cliente e ele não existir, afinal em buscas o objeto buscado pode realmente não ser encontrado, e isso é legal.

E como tratar as exceções de verdade? Certamente não é assim:

static void Main(string[] args)
{
    try
    {
        var minhaClasse = new MinhaClasse();
        minhaClasse.FazAlgo();
    }
    catch (Exception)
    {
        throw;
    }
}

Esse código tem diversos problemas:

  1. Todo o código do método Main está envolto em um blog de Try Catch Finally (TCF). Será que instanciar a classe “MinhaClasse” pode gerar um erro? Talvez não, e então essa linha tinha que estar fora do TCF. Já vi empresas onde todos os métodos estão envoltos em TCF, como se tudo pudesse causar erro. Isso causa uma tremenda confusão no código, além de uma queda na performance. Problema: preguiça de checar onde podem acontecer exceções.
  2. No catch a exceção sendo tratada é a System.Exception. Isso significa que qualquer exceção que aconteça vai ser tratada. Mas será que a classe “MinhaClasse”, que está dentro do try, não gera uma exceção mais específica? Se sim, então esta exceção deveria ser tratada. Problema: preguiça de checar quais exceções são lançadas pelo método FazAlgo, ou talvez o método seja tão complicado, tenha tantas dependências, e não tenha tratamento de erros, que isso não seja claro.
  3. Se realmente for o caso de tratar qualquer exceção, então um simples “catch {}” resolveria, não precisava de um “catch (Exception) {}”.
  4. A exceção não é efetivamente tratada, já que o código simplesmente lança a exceção de volta no throw.

Mas dá para ficar pior. Esse código é pior:

static void Main(string[] args)
{
    try
    {
        var minhaClasse = new MinhaClasse();
        minhaClasse.FazAlgo();
    }
    catch (Exception ex)
    {
        throw ex;
    }
}

Esse código é pior porque ele esconde a exceção. Vejam minha janela de edição Visual Studio:

O erro acontece na linha 15, ao chamar FazAlgo. Se eu rodar o primeiro código, da primeira listagem, tenho o seguinte Stack Trace:

at ConsoleApplication1.MinhaClasse.FazAlgo() in F:\ConsoleApplication1\Program.cs:line 50
at ConsoleApplication1.Program.Main(String[] args) in F:\ConsoleApplication1\Program.cs:line 21
at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()

Rodando da segunda maneira, o Stack Trace é esse:

at ConsoleApplication1.Program.Main(String[] args) in F:\ConsoleApplication1\Program.cs:line 21
at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()

Deixei em destaque a diferença. Note que na segunda maneira de trabalhar, fazendo “throw ex”, o erro, que aconteceu na minha linha 50, no método “FazAlgo” da classe “MinhaClasse”, é escondido. Temos somente o erro na linha 21, que é a linha de saída do método Main. Ou seja, em vez de ajudar, atrapalhamos, porque sem tratamento nenhum de erro o resultado seria parecido com o primeiro caso, teríamos claro onde o erro realmente ocorreu: no método “FazAlgo”.

Vamos arrumar então? Arrumando todos os problemas o código fica assim:

static void Main(string[] args)
{
    var minhaClasse = new MinhaClasse();
    try
    {
        minhaClasse.FazAlgo();
    }
    catch (MinhaException ex)
    {
        Console.WriteLine("A MinhaException aconteceu./nDetalhes:{0}/nTentar de novo?", ex.Message);
        if (Console.ReadLine() == "s")
            Main(args);
    }
}

Agora realmente temos a exceção sendo tratada. Se acontecer uma System.Exception a aplicação cai, mas talvez seja exatamente o que eu espero que aconteça. Uma System.Exception significa que aconteceu uma exceção, algo não esperado, e a aplicação pode conter um estado inválido, e não deve mais continuar. Talvez cair seja a opção correta. De qualquer forma, isso deve ser analisado, e não simplesmente tratar System.Exception sempre.

Há mais uma coisa faltando: log dos erros. Uma aplicação profissional que se presa loga suas exceções. Aplicação sem log de erros não é uma aplicação profissional, é amadora. Não fazer um log de erros é falta de profissionalismo. Quando a aplicação falhar em produção, como você vai saber se não tiver um log? Vai pedir um dump e analisar no WinDBG, aquela ferramenta super fácil de usar? Facilite sua vida, logue seus erros.

Mais uma dica: é uma boa prática tratar as exceções no mínimo nas fronteiras da aplicação. Isso significa tratar na interface gráfica, tratar nas camadas de serviço, e em qualquer outra onde exista uma fronteira entre sua aplicação e outra. Se você não fizer isso, vai ter desagradáveis surpresas. Aplicação caindo toda hora, yellow screen of death no ASP.Net, e por aí vai. Com Silverlight, se você devolve um erro da camada de serviço WCF para a camada Silverlight vai receber um belo erro “Not Found” de volta no Silverlight, independentemente do erro. Tudo o que você precisa para uma boa depuração, não é?

A politica de tratamento de erros é algo que deve ser definido logo no começo do desenvolvimento de uma aplicação. Definir onde os erros serão tratados, como serão tratados, onde serão logados, que exceções serão definidas, entre outros assuntos devem ser avaliados o quanto antes, preparando uma estrutura mínima de tratamento de erros. Uma boa opção é usar o Enterprise Library, que já possui o Exception Handling Application Block para ajudar na tarefa. Este bloco de aplicação já me poupou centenas (talvez milhares) de linhas de código (baixe a versão 4.1 da EntLib aqui), sem falar na dor de cabeça. Se você não conhece este bloco sugiro avaliá-lo o quanto antes. Mais ou menos… assim que terminar de ler este post.

Resumindo:

  • Se você não vai tratar a exceção, ou seja, fazer alguma coisa com ela, não coloque TCF no método.
  • Use um framework para te ajudar, como o EntLib.
  • Trate erros nas camadas de fronteira.
  • Logue seus erros.
  • Nunca, nunca, nunca faça “throw ex”.

Boa caçada às exceptions!


Postado na(s) categoria(s) Arquitetura pelo Giovanni Bassi em 14 de setembro de 2009 às 08:35 | Tags:

Comentários


Brazil Fabio Moggi
setembro 14. 2009 12:46
Fabio Moggi
Giovanni, o blog está cada vez melhor ;)

no site


setembro 14. 2009 13:46
Israel Aece
Boas Giovanni,

Utilizar essa técnica funciona bem quando você conhece a classe e garante que ela não irá disparar exceções no construtor. (sim, sei que isso é uma prática ruim, quando não é bem escrito).

Mas as próprias classes do .NET FX fazem isso. Como exemplo, veja a classe FileStream, que dispara uma UnauthorizedAccessException/SecurityException caso o usuário que esteja na aplicação não possua privilégio para manipular o arquivo.

Neste caso, a criação do objeto também deverá estar protegido:

FileStream fs = null;
try
{
    fs = new FileStream("Arquivo.txt", FileMode.Open);
}
catch (UnauthorizedAccessException)
{
    Console.WriteLine("Voce nao tem acesso");
}
catch (Exception ex)
{
    Console.WriteLine("Problemas: {0}", ex.Message);
}
finally
{
    if (fs != null)
        fs.Dispose();
}

http://www.israelaece.com/http://www.israelaece.com/


setembro 14. 2009 13:49
Giovanni Bassi
Oi Israel,
É isso aí. Se há essa possibilidade, temos que tratá-la. No meu exemplo eu assumi que não havia.

http://unplugged.giggio.net/http://unplugged.giggio.net/


Brazil Alessandro
setembro 14. 2009 14:07
Alessandro
Giggio,
em ASP.NET MVC,
criar um interceptor para capturar e logar os erros nos controllers seria uma boa opção (seguindo o conselho de tratar nas fronteiras)?

Att,
Alessandro

no site


setembro 14. 2009 14:11
Giovanni Bassi
Alessandro, sem dúvida!

http://unplugged.giggio.net/http://unplugged.giggio.net/


setembro 14. 2009 15:18
Rafael Noronha
Giovanni,

Deixo a dica para o pessoal trabalhando com asp.net dar uma conferida no ELMAH, um excelente framework de log de erros.
http://code.google.com/p/elmah/

Melhor que um bom framework? Um bom framework que você não precisou escrever. =D

http://rafanoronha.net/http://rafanoronha.net/


setembro 14. 2009 15:35
Wellington
Olá Giovanni, Valeu pelas dicas cara!
Seus posts continuam ajudando muito,
sempre tive muitas dúvidas para tratar erros...
vou procurar pela dica do Alessandro (interceptor)
e do Elmah para usar nas fronteiras da aplicação.

Valeu mesmo!

Wellington

http://unplugged.giggio.net/unplugged/post/Como-tratar-erros.aspxhttp://unplugged.giggio.net/unplugged/post/Como-tratar-erros.aspx


Brazil well
setembro 14. 2009 15:40
well
Valeu Giovanni, muito bom esse post!


Wellington

no site


Brazil Alessandro
setembro 14. 2009 20:07
Alessandro
Giovani,
no caso de uma exception em um serviço de domínio, por exemplo, eu preciso fazer com que esta exception passe pela entidade de domínio que chamou o serviço e chegue até a interface do usuário para informá-lo e ou logar o erro.
Qual a melhor maneira de repassar exceptions para camadas superiores como no caso acima?

Att,
Alessandro

no site


setembro 14. 2009 20:43
Giovanni Bassi
Alessandro, Se for só para logar o erro no serviço, então você coloca um TCF, loga, e throw, ou dá um throw em outra exceção, com a original na innerexception.
Na interface você vai ter que definir que tipo de informação vai exibir ao usuário quando tiver uma exceção. Um TCF pega o erro e exibe o que você achar melhor.

http://unplugged.giggio.net/http://unplugged.giggio.net/


setembro 15. 2009 00:18
Felipe Fujiy
Já vi casos piores:

try{}catch(Exception ex)
{throw new Exception();}

Absurdo, não?

Sei que há casos onde devemos lançar uma nova exception, mas não um Exception(base) e ainda mais sem o InnerException.

Quanto o catch{} ja ouvi falar que é ruim,pois pega umas exceptions SEH, não lembro o que é, mas é algo do Windows

http://blog.fujiy.net/http://blog.fujiy.net/


setembro 15. 2009 14:07
Prodis
@Felipe, você não viu nada ainda (risos).

Eu já peguei casos assim:
try
{
  //código
}
catch (Exception)
{

}

Muito útil para quando você quer arrebentar um sistema.

http://prodis.pro.br/http://prodis.pro.br/


Brazil Alessandro de Souza
setembro 15. 2009 18:20
Alessandro de Souza
Giggio,
quando uso um TCF e no C eu chamo o throw; a mesma exception é repassada pra cima? É assim que funciona?

Att,
Alessandro

no site


setembro 15. 2009 23:33
Giovanni Bassi
Putz, Prodis, já vi assim também. É o "programador coveiro", que quer enterrar o erro...

Alessandro, é isso mesmo. Ao dar só "throw;", ele lança de volta a exceção inicial.

http://unplugged.giggio.net/http://unplugged.giggio.net/


setembro 16. 2009 16:50
Bruno Feliciano
Isso não é nada... Já vi um sistema (numa famosa empresa pública de Processamento de Dados do Estado de São Paulo) que o sistema foi desenvolvido de uma forma que lugares aonde teriam um simples If / Else eram tratados com TCF com catchs genéricos (por exemplo ao invés de verificar se um valor era nulo pra saber oq fazer esperava-se dar uma  NullException e no catch estava o codigo que seria o Else...

Isso é tão absurdo que eu talvez não acreditasse se não tivesse (infelizmente) realmente visto...

http://twitter.com/brunao05http://twitter.com/brunao05


Brazil Guilherme de Carvalho
setembro 17. 2009 12:01
Guilherme de Carvalho
Muito interessante o assunto, realmente é algo pouco tratado, creio até que poderia ser um artigo na .net magazine quem sabe.

Mas no caso de tratamento de erros ao acessar bancos, seria interessante fazer assim(VB):

Dim conexaoPgSQL as new NpgsqLConnnection(stringConexao)
Dim comandoPgSQL as new NpgsqlCommand
Try
   comandoPgSQL.Connection = conexaoPgSQL
   conexaoPgSQL.Open()
   ...
   ...
   ...
Catch exPgSQL as NpgsqlException
   conexaoPgSQL.Close()
Catch ex as Exception
   conexaopgSQL.Close()
End Try

Ou não seria necessário o segundo Catch (Exception), poderia também colocar dentro dos catch algo para criar log's de erro?

no site


setembro 18. 2009 00:00
Giovanni Bassi
Guilherme, nem um nem outro.
Coloca no final do TCF:

Finally
    If ConexaopgSQL IsNot Nothing Then
        ConexaopgSQL.Close()
    End If
End Try

(e eu ainda lembro VB!)

http://unplugged.giggio.net/http://unplugged.giggio.net/


Brazil Vinicius
setembro 25. 2009 11:58
Vinicius
Suponha que eu tenha uma regra no meu sistema que me impeça de remover uma entidade que tenha algum relacionamento com alguma outra entidade do sistema. Devo tratar isso no metodo de exclusao e disparar uma exception caso ocorra ou devo tratar na aplicacao e se for o caso nem chamar a exclusao e mostrar uma mensagem amigavel pro usuario?

Voce comentou do cpf, nao sei se encaixa na minha duvida,

no site

Comentar


(Vai mostrar seu Gravatar)

  Country flag

biuquote
  • Comentário
  • Pré-visualização
Loading



Quem é Giovanni Bassi

Giovanni Bassi Sou uma pessoa apaixonada por tecnologia e especificamente por .Net. Sou consultor independente especialista em .Net, focado em arquitetura e melhores práticas. Tenho dezenas de artigos publicados na .Net Magazine, revista da qual sou editor técnico. Ministro palestras e cursos de vez em quando, e quando dá tempo eu respiro um pouco. Mais detalhes nesta página.

Busca

Selos

Eu vou ao TechEd Brasil 2010, e você?

MVP

MCPD

MCSD

.Net Magazine

Abaixo ao if!

Calendário

«  julho 2010  »
seteququsedo
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678
Ver detalhamento de posts no calendário

Blogs interessantes

    OPMLDownload OPML file

    Postagens recentes

    Comentários recentes

    Disclaimer / Aviso
    As opiniões colocadas neste blog são minhas e pessoais e não expressam necessariamente as opiniões de meus empregadores, pareceiros e amigos. Da mesma forma, os comentários feitos por leitores do blog não expressam a minha opinião.

    © Copyright 2010 .Net Unplugged
    Log in