DEV Community

loading...

[PT-BR] Be the exception! Dicas para dominar exceções em seus códigos Java

Maximillian Arruda
I'm a Software Engineer who loves to learn and help developers with the best programming practices
Updated on ・8 min read

Esse artigo foi a base da palestra que apresentei no 1o. SouJava Lightning Talks então fique a vontade de conferir, okay?!

Você já se perguntou:

  • Como será que devo criar as exceções para minha as minhas regras de negócio?
  • Será que não estou criando exceções demais?
  • Em relação às exceções das regras de negócios que estou criando: devo extender de RuntimeException ou de Exception?
  • E quanto a lidar com o tratamento de exceções:
  • E as mensagem de erro, estou sendo claro nelas?
  • será que estou logando de maneira adequada a captura de exceções em meus códigos?
  • Será que estou "silenciando" uma exceção de maneira equivocada?
  • Será que estou desenvolvendo meus códigos "orientados à exceções"?

É comum desenvolvedores Java enfrentarem esses dilemas quando se há a necessidade de desenvolver ou lidar com exceções em seus códigos.

Uns lidam com exceções como "erros" da aplicação, outros como "caminhos alternativos" para as regras de negócios em questão e assim por diante. É algo interessante e muitas vezes interpretativa.

Tem desenvolvedores que se utilizam da forma que a linguagem "força" o tratamento das exceções a fim que atingir o seu objetivo, e acho que todos eles estão corretos de acordo com o cada contexto alvo.

Há uns meses atrás tive uma discussões interessantes com outros desenvolvedores e em particular com a comunidade SouJava sobre o assunto. Quero parabenizar a todos pelas ótimas discussões (You rock guys!!!).

Mas uma coisa que temos que admitir: pode não parecer, mas exceções são peças importantes para entender os cenários "excepcionais" que "brotam" em tempo de desenvolvimento e de execução em nossos códigos.

Vamos explorar algumas dessas dicas que possam nos ajudar a modelar e lidar com exceções em nossos códigos Java.

Não conhecer a hierarquia de classes de exceções do Java

Essa é uma dúvida que pode atrapalhar e muito a modelagem do código.
image
Olhando a hierarquia das classes de exceções do Java podemos reconhecer que as classes em vermelho são as consideradas Checked Exceptions e as amarelas são as Unchecked Exception.

Não saber a diferença entre Checked Exceptions e Unchecked Exceptions e quando usá-las

Checked Exceptions são exceções que devem ser usadas para erros recuperáveis ou que sejam requisitos de um regra de negócio importante. Normalmente exceções de seu domínio classificam-se nessa categoria. O compilador da linguagem vai obrigar o tratamento dela sempre que um método que declarativamente a lançar for chamado.

Já as Unchecked Exceptions devem ser utilizadas quando algum cenário "excepcional" é irreversível ou irrecuperável. A princípio não deveríamos capturar esses tipos de exceções. Normalmente utilizam-se "Tratadores de exceções" para esse fim, que geralmente é o de notificar o utilizador da regra que algo aconteceu e não há nada o que fazer para aquela situação.

Capturar toda exceção como Exception

Pra quê criar minhas exceções, vamos utilizar a que tem!

Bom, capturar qualquer exceção (ou todas... hehehe) como Exception pode ser um problema. O mesmo ocorre do lado de quem está escrevendo um método que lança somente exceções do tipo Exception.

try{
    debitAccount.transferTo(creditAccount,amount);
}catch(Exception ex){
    // Ok, mas o que aconteceu?
    // Teremos que checar a mensagem, e torcer para que ela seja útil
    // E talvez analisar a stacktrace
}
Enter fullscreen mode Exit fullscreen mode

No código acima, por utilizar somente a classe Exception, a identificação está prejudicada então se a mensagem de erro não ajudar, a análise do stacktrace será um dos únicos pontos para sua identificação.

Como a classe Exception é uma checked exception então crie suas exceções a partir dela para os cenários queira que o compilador force o tratamento da mesma. Lembre-se de criar suas exceções checadas para casos que devem e que realmente fazem sentido de acordo com seu domínio.

Criar muitas exceções de maneira desordenada

Cuidado pra não criar exceções para tudo! Caso o fizer, provavelmente terá muitas classes para gerenciar e manter então crie somente as exceções necessárias e seja claro nas mensagens.

Imagine em um sistema de transferência de valores entre contas, onde criássemos uma exceção para cada cenário:

try{
    debitAccount.transferTo(creditAccount,amount);
}catch(InvalidAmountException ex){
    // quantia inválida, e agora?
}catch(ExceededAmountException ex){
    // quantia excedida, e agora?
}catch(BalanceNotAvailableException ex){
    // saldo não disponível, e agora?
}catch(DisabledCreditAccountException ex){
    // conta à creditar está desativada, e agora?
}catch(DisabledDebitAccountException ex){
    // conta à debitar está desativada, e agora?
}catch(BlockedCreditAccountException ex){
    // conta à creditar está bloqueada, e agora?
}catch(BlockedDebitAccountException ex){
    // conta à debitar está bloqueada e agora?
}catch(BusinessException ex){
    // alguma outra coisa aconteceu, e agora?
}
Enter fullscreen mode Exit fullscreen mode

E se utilizarmos essa outra sintaxe:

try{
    debitAccount.transferTo(creditAccount,amount);
}catch(InvalidAmountException 
            | ExceededAmountException 
            | BalanceNotAvailableException 
            | DisabledCreditAccountException 
            | DisabledDebitAccountException
            | BlockedCreditAccountException 
            | BlockedDebitAccountException 
            | BusinessException ex){
    // quantia inválida ou
    // quantia excedida ou
    // saldo não disponível ou
    // conta à creditar está desativada ou
    // conta à debitar está desativada ou
    // conta à creditar está bloqueada ou
    // conta à debitar está bloqueada ou
    // alguma outra coisa aconteceu, e agora?
}
Enter fullscreen mode Exit fullscreen mode

Nada agradável, não é?

Que tal reduzir nossa lista de exceções:

image

Assim ficará mais sucinta o seu tratamento:

try{
    debitAccount.transferTo(creditAccount,amount);
}catch(BalanceNotAvailableException ex){
    // saldo não disponível, e agora?
}catch(DeactivatedAccountException ex){
    // uma ou ambas estão desativadas, e agora?
}catch(BlockedAccountException ex){
    // uma ou ambas estão bloqueadas, e agora?
}
Enter fullscreen mode Exit fullscreen mode

Claro, não existe "bala de prata". Será preciso utilizar mensagens bem claras para fornecer um bom contexto para identificar o ocorrido. Em nosso exemplo, mensagens claras serão necessárias, especialmente as exceções DeactivatedAccountException e BlockedAccountException. Ambas não deixa claro se a debitAccount ou creditAccount ou ambas causaram o cenário em questão.

Não utilizar mensagens claras nas exceções

Acredito que é unânime essa dica: sempre use mensagens claras em suas exceções. Isso ajudará bastante a encontrar e entender o erro.
Normalmente é utilizado um arquivos de propriedades para armazenar essas mensagens de erros, fornecendo assim talvez uma maneira de exibir a mensagem na camada de apresentação de maneira internacionalizada.

Silenciar exceções

Outra abordagem muito equivocada e muito comum que ocorre é no tratamento de exceções que o chamador "acham desnecessário" realizar o tratamento, simplesmente ignorando o fato da ocorrência do lançamento de exceção.

Por exemplo, você pode talvez ter visto algo assim:

Connection  connection = null;
try{
        connection = datasource.getConnection();
        // executando uma regra incrível aqui
}catch(SQLException exception){
        handleSQLException(exception);
}finally{
    if( connection != null){
        try{
            connection.close();
        }catch(SQLException ex){}
    }
}
Enter fullscreen mode Exit fullscreen mode

Não finalizar/fechar recursos abertos no contexto que ocorreu a exceção

Um dos fatores de leaks em aplicações java é o não fechamento correto de recursos como conexão de banco de dados, streams ou qualquer outro componente que tem por necessidade serem fechados ao finalizar o bloco de código que eles foram abertos ou gerados.
Utilize o bloco finally para fechar os recursos ou componentes necessários.

Então, não faça isso:

Connection  connection = null;
try{
        connection = datasource.getConnection();
        // executando uma regra incrível aqui
}catch(SQLException exception){
        handleSQLException(exception);
}
Enter fullscreen mode Exit fullscreen mode

Meu conselho é fazer algo assim:

Connection  connection = null;
try{
        connection = datasource.getConnection();
        // executando uma regra incrível aqui
}catch(SQLException exception){
        handleSQLException(exception);
}finally{
    if( connection != null){
        try{
            connection.close();
        }catch(SQLException ex){
            throw new IllegalStateException("algo grave aconteceu ao fechar a conexão",ex);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

A fim de ajudar a lidar com esse problema, a partir do Java 7 foi adicionado o try-with-resources statement, onde é possível utilizar quando precisarmos trabalhar com qualquer objeto que implemente a interface java.lang.AutoCloseable, que inclui todos os objetos que implementam java.io.Closeable:

try(Connection  connection = datasource.getConnection()){
        // executando uma regra incrível aqui
}catch(SQLException exception){
        handleSQLException(exception);
}
Enter fullscreen mode Exit fullscreen mode

Assim os componentes e recursos abertos dentro do try() serão fechados ao finalizar o bloco.

Nota: Uma instrução try-with-resources pode ter blocos catch e finally, exatamente como uma instrução try comum. Em uma instrução try-with-resources, qualquer catch ou finally block é executado depois que os recursos declarados são fechados.

Silenciar exceções

Outra abordagem muito equivocada e muito comum que ocorre é no tratamento de exceções que o chamador "acham desnecessário" realizar o tratamento, simplesmente ignorando o fato da ocorrência do lançamento de exceção.

Mas não se ilude, talvez a aplicação entrou em um estado inconsistente e isso ninguém quer, não é?

Um outro exemplo:

try{
        CurrencyUnit usd = Monetary.getCurrency("USD");
        Money coffee = Money.of(5, usd);

        Account maxAccount = bank.getAccount(123);
        Account otavioAccount = bank.getAccount(333);

        maxAccount
            .transferTo(otavioAccount)
            .amount(coffee)
            .commit();

}catch(BalanceNotAvailableException exception){
        // É impossível saber o que aconteceu aqui sem realizar um debug! 
}
Enter fullscreen mode Exit fullscreen mode

É impossível saber o que aconteceu aqui sem realizar um debug! (Bom, o Otávio infelizmente vai saber... ☹️).

Então, sempre trate ou log essas exceções (talvez em um nível WARN ou ERROR).

Perder o rastreamento da pilha

Quem sou eu pra ditar regras, não é? Mas vamos olhar o exemplo abaixo.

try{
        CurrencyUnit usd = Monetary.getCurrency("USD");
        Money coffee = Money.of(5, usd);

        Account maxAccount = bank.getAccount(123);
        Account otavioAccount = bank.getAccount(333);

        maxAccount
            .transferTo(otavioAccount)
            .amount(coffee)
            .commit();

}catch(BalanceNotAvailableException exception){
        // Assim perderemos a causa raiz da exceção que é: 
    // o Max não vai poder para o café pro Otávio dessa vez!
        throw new ExceededAmountException();
}
Enter fullscreen mode Exit fullscreen mode

Para quem desenvolveu pode parecer nada significante, mas ignorar a causa raiz irá causar um desconforto para quem estiver dando o suporte caso a exceção ocorra.

Se a mensagem não for clara, será necessário navegar até o ponto do código que ocorreu a exceção.

Assim perderemos a causa raiz da exceção que é: o Max não conseguiu pagar o café pro Otávio dessa vez!

Conclusão

Trabalhar com exceções em Java é um assunto um tanto quanto polêmico e interessante. Tem uns que abominam, outros que se favorecem da funcionalidade e até pode ser divisor de águas quando se tenta conhecer outras linguagens (como por exemplo o Golang).

Na verdade o que interessa é resolver o problema da melhor maneira possível, digo, sempre olhando os prós e os contras de cada abordagem, pensando não só do ponto de vista de quem desenvolve mas também do ponto de vista de manutenção.

Deixo abaixo alguns artigos que utilizei que são referências ótimas para todos sobre o tema!

Espero ter "lançado" em sua mente a vontade de explorar mais sobre esse tema e assim torço para que você continue a "capturar " e aprender!

Esse artigo foi a base da palestra que apresentei no 1o. SouJava Lightning Talks então fique a vontade de conferir!!!

Deixe suas dicas, dúvidas e "exceções", quero dizer, sugestões aqui!

Abraços e até a próxima!

Referências:

Discussion (10)

Collapse
hbelmiro profile image
Helber Belmiro

Muito bom o artigo. Com muitas exceções, acontece o problema citado no artigo. E com poucas, o cliente da API não consegue ter tratamentos específicos para cada erro. O mais difícil pra mim é encontrar esse equilíbrio.

Collapse
dearrudam profile image
Maximillian Arruda Author

Verdade!!! Você foi direto ao ponto!!! O equilíbrio entre esses pontos é muito difícil encontrar... acredito que não só o equilíbrio nesse aspecto, mas também pra tudo... muitos só focam-se nos prós e muito poucos nos contras e vice-versa pra utilizar uma abordagem ou biblioteca ou framework... equilíbrio é a chave mesmo!!! Obrigado por comentar!!! Valeu Helber!!!!

Collapse
boaglio profile image
Fernando Boaglio

Realmente achar o equilíbrio é complicado, eu acho que um bom critério de quantidade de exceptions pode ser aproximadamente a mesma quantidade de erros que o usuário vai receber no front end. Eu sei que cada caso é um caso, mas acho que na média esse critério faz sentido.

Collapse
dearrudam profile image
Maximillian Arruda Author

Essa métrica faz muito sentido sim!!! Obrigado pelo comentário @boaglio !!! Aprendendo muito!!! :)

Collapse
psanrosa13 profile image
Paula Santana

Muito bom! Aliás essa thread do Telegram do SouJava foi sensacional né?!Artigo com linguajar simples e de fácil entendimento! Obrigada pelo material que produziu!

Collapse
dearrudam profile image
Maximillian Arruda Author

Verdade, essa thread rendeu mesmo!!!! foi o que me deu um gás pra fazer um artigo desse!!! Obrigado mesmo Paula!!! Você é referência!!!! Uma honra!!!! :)

Collapse
boaglio profile image
Fernando Boaglio

Muito bom Max!

Collapse
dearrudam profile image
Maximillian Arruda Author

Muito obrigado Fernando!!! Vc é outra referência pra mim !!! Valeu mesmo!!!

Collapse
gleisser profile image
Gleisser

Otimo artigo! muito bom!

Collapse
dearrudam profile image
Maximillian Arruda Author • Edited

Que bom que gostou!!! Muito obrigado!!! Abraços!!!