sexta-feira, 6 de dezembro de 2013

Propagação Deficiente - Antipattern de exceção

Para entender melhor este antipattern, é necessário entender o que compõe uma exceção.

Exceção Exemplo #1
ApplicationException: Erro ao carregar configuracoes
    at LeitorConfiguracoes.lerArq(LeitorConfiguracoes.java:404)
    at DeamonAplicacao.iniciar(DeamonAplicacao.java:376)
    at java.lang.Thread.run(Thread.java:722)
Caused by: java.lang.NullPointerException
    at LeitorConfiguracoes.parser(LeitorConfiguracoes.java:120)
    at LeitorConfiguracoes.lerArq(LeitorConfiguracoes.java:386)
    ... 2 more
Identificando os componentes da exceção:
  • Classe: ApplicationException
  • Mensagem: Erro ao carregar configuracoes
  • Stack Trace: desde LeitorConfiguracoes.lerArq, até java.lang.Thread.run
  • Caused by: é a nova exceção a partir da linha "Caused by" (inclusive)
Lembrando que a mensagem, e o "caused by" não são obrigatórios.
Veja o outro exemplo abaixo:
Exceção Exemplo #2
java.util.NoSuchElementException
    at LeitorConfiguracoes.parser(LeitorConfiguracoes.java:124)
    at LeitorConfiguracoes.lerArq(LeitorConfiguracoes.java:386)
    at DeamonAplicacao.iniciar(DeamonAplicacao.java:376)
    at java.lang.Thread.run(Thread.java:722)
Identificando os componentes da exceção:
  • Classe: java.util.NoSuchElementException
  • Mensagem: não tem (ela deveria estar na frente do nome da classe da exceção)
  • Stack Trace: desde LeitorConfiguracoes.parser, até java.lang.Thread.run
  • Caused by: não tem
Com base nos exemplos acima, agora mostro o erro mais comum, quando se trata de exceção:
catch (Exception e) {
    log.error(e.getMessage());
}
Ou
catch (Exception e) {
    throw new ApplicationException(e.getMessage());
}
Para a Exceção Exemplo #1, dos 4 elementos que tem a exceção, a expressão "e.getMessage()" representa apenas "Erro ao carregar configuracoes". Ela seria logada da seguinte forma:

17:52:37 [ERROR] Erro ao carregar configuracoes
Somente com esta informação, como é possível fazer um trobbleshooting eficiente? A classe, a Stack Trace e o Caused By são MUITO importantes. A mensagem é apenas um fragmento da exceção.
Para a Exceção Exemplo #2, é muito pior. A exceção não tem mensagem. E apenas encontraríamos uma linha perdida no log com a seguinte aparência.
17:52:37 [ERROR] null
Uma linha assim não ajudaria em nada, praticamente.
Então lembre-se:

  • Em um log de erro, o primeiro parâmetro é uma mensagem, e o segundo parâmetro a exceção (com isso, todos os componentes da exceção são logados)
  • Se for encapsular e lançar a nova exceção, em geral, existe um construtor da exceção onde o primeiro parâmetro é uma mensagem, e o segundo parâmetro é a exceção (parâmetro cause)

catch (Exception e) {
    log.error("Erro na operacao", e);
}
Ou
catch (Exception e) {
    throw new ApplicationException("Erro na aplicacao", e);
}

Captura e Ignora - Antipattern de exceção

Em qualquer sistema, as exceções sempre irão existir, e por motivos diversos:
  • a importação de um arquivo com um dado não esperado, 
  • uma sobrecarga no sistema que provocou um timeout em uma operação, 
  • uma tabela que esqueceram de atualizar no banco, 
  • o disco que encheu, 
  • um arquivo com permissão de somente-leitura,
  • um possível bug,
  • etc etc etc.
Estas ações são indesejáveis e, por mais exaustivamente que se teste o sistema, você nunca terá a garantia que estará livre dos problemas acima. Você dificilmente conseguirá sempre realizar testes para todas estas condições e tantas outras não listadas.
Mas o que temos que fazer afinal?
Tratar a exceção da melhor maneira possível, informando a exceção com precisão no log, e passando uma mensagem precisa ao usuário. Mas as vezes encontramos exatamente o contrário, trechos de código que captura a exceção, e segue em frente como se nada tivesse acontecido.
Não faça isso com sua exceção
Abaixo estão alguns exemplos do que NÃO se deve fazer:
catch (IOException e) {
  return null;
}
Ou
catch (IOException e) { }
Com isso, além de simplesmente retornar nulo (ou não fazer nada), ao invés de manipula-la, ou relançar a exceção, você joga fora de vez qualquer informação referente ao problema que gerou a exceção.
Outras maneiras de esconder a exceção:
catch (IOException e) {
    log.error("Erro", e);
    return null;
}
Ou
catch (IOException e) {
    e.printStackTrace();
    return null;
}
Ou

catch (IOException e) {
    e.printStackTrace(); // Ou log.error("Erro", e);
}
De maneira similar as situações anteriores, a única diferença é que a exceção, pelo menos, foi logada, mas o fluxo de execução continua normalmente. Isto nem sempre está errado, mas muitas vezes o programador não insere este código nos pontos apropriados. Ao invés de retornar nulo ou seguir com o fluxo de execução, a exceção deveria ser tratada.