38 / 2013

##0° Perf4j é para System.currentTimeMillis() o que log4j é para System.out.println() Ainda não tive a oportunidade de usá-lo. Portanto, fica apenas o hyperlink.

##1° Como sabido, não é elegante exibir detalhes de implementação ao usuário na ocorrência de erros. Exibir mensagens técnicas ou stacktraces não traz qualquer tipo de solução e ainda assusta quem usa o aplicativo.

Visto isso, decidi modelar uma arquitetura para tratamento de exceções.

Semanticamente, uma exceção é um desvio no fluxo de execução do código devido a uma condição específica, ou, mais grosseiramente falando, uma alternativa elegante e fortemente tipada para códigos de retorno.

Tal condição pode fazer parte do requisito funcional da aplicação ou ser uma falha no sistema de computador. Portanto, como premissa, classifico as exceções em dois grupos:

  • Exceções de Aplicação - São os desvios raros, porém esperados, que fazem parte da lógica e do domínio da aplicação e, portanto, publicados e documentados na API. (Certamente, esses desvios poderiam ser implementados utilizando estruturas condicionais (IFs)

  • Erros de Sistema - São os erros que se espera que não aconteça durante o funcionamento normal da aplicação, cujo tratamento e recuperação está fora do alcance do código cliente da API.


Exemplo clássico

Suponha um objeto que representa um banco e possua uma operação de transferência bancária:

interface Banco {
	void transferir(Conta origem, Conta destino, Integer valor);
}

É esperado que a conta de origem não tenha saldo suficiente para executar a operação, gerando um desvio na execução padrão. Uma vez que é esperado e faz parte da lógica, isto é uma exceção de aplicação. Quem usa esta interface pode, por exemplo, solicitar um empréstimo na ocorrência do falha.

Ao mesmo tempo, a operação também pode falhar devido a erros de rede, de escrita em disco, de divisão por zero, entre outras coisas. São eventos que não fazem parte da lógica e cujos códigos clientes, geralmente na ocorrência desses desvios, nada pode fazer para continuar o processamento, sendo, portanto, erros de sistema.

Visto que é preciso retornar se a operação foi realizada com sucesso ou qual tipo falha aconteceu, há duas abordagens:

Código de retorno

Nota: Numa visão mais contemporânea, os códigos inteiros de retorno são representados como enums

Neste caso, é preciso testar o código de retorno para toda chamada ao método. Além disso, eventualmente é necessário propagar tal código para métodos acima. Por indução, no limite, todos os métodos terão CodigosErro como tipo de retorno.

interface Banco {
	CodigosErro transferir(Conta origem, Conta destino, Integer valor);
}

enum CodigosErro {
	SALDO_INSUFICIENTE, ERRO_SISTEMA;
}

Exceção

Já com o uso de exceções, considera-se que a operação irá funcionar na maioria das vezes e incorpora-se na linguagem de programação o suporte a desvios na ocorrência de falhas.

interface Banco {
	void transferir(Conta origem, Conta destino, Integer valor)
		throws SaldoInsuficienteException, SistemaException;
}

Tratamento de exceção

Até aqui, nada de novo. Eu já costumo separar AplicacaoException e SistemaException. Mas o ponto principal desse artigo é o tratamento do falha.

Conforme mostrado, há dois grupos de exceções. O que costuma-se fazer é considerar os dois grupos iguais, exibindo-as numa tela de erro, mostrando sua mensagem, stacktrace e o que mais vier, ou ainda, no melhor dos casos, usar essa tela para erros de sistema e exibir os erros de aplicação em pop-up ou rótulos destacados.

Outros problemas que vejo muito em relação às falhas de aplicação são:

  • Falta de contextualização: Isto ocorre quando a exceção original vai simplesmentes se desviando (subindo) sem transformar-se em novas exceções, ou seja, sem contextualizar as condições de naquele nível de execução. Uma falha num contexto pode não fazer sentido em outro.

  • Uso de classes genéricas: Criar uma classe genérica AplicacaoException e usá-la em todo o sistema impede um tratamento específico para cada tipo de falha, permitindo apenas a interpretação da mensagem pelo usuário final (comentado por João Bulhões)

Proposta

Uma vez que Erros de Sistema não fazem parte da lógica e, portanto, não há tratamento diferenciado, eu criei uma única exceção SystemException para encapsular a exceção original e trafegá-la até seu ponto de tratamento, único para todo aplicativo.

Há um interceptador nos objetos de borda (Façades ou Servicos) do sistema cuja funcionalidade é registrar o erro em log e lançar uma nova exceção com mensagem genérica e sem causa raiz, já que os detalhes dos erros de sistema não devem ser exibidos ao usuário.

Mais além: criei um registro de log por usuário para fácil identificação na ocorrência de chamados. Tecnicamente, uso SiftAppender mais outro interceptador para associar o usuário atual ao contexto de execução.

Quanto aos erros de aplicação, desenhei uma hierarquia de classes representativa e os métodos disparam-na conforme documentados na API, apenas contextualizando (recriando, transformando) o entendimento da falha de acordo com sua lógica.

Mas uma vez, na arquitetura que estou bolando, há uma abstração e baixíssimo acoplamento entre os objetos de serviço (elemento mais externo da camada lógica) e os objetos de apresentação (html, WebService, REST, …) limitando as classes disponíveis nesta última camada. Portanto, o mesmo interceptador para erros de sistema transforma exceções de aplicação em uma nova exceção com a mensagem original e sem causa raiz quando ela transpõe os limites da camada lógica.

UPDATE

Este site compartilha minhas idéias com um diferencial: Os Erros de Sistema são encapsulados como RuntimeException o mais próximo da origem possível. Esta decisão diminui o overhead do código e o torna mais legível. Boa Abordagem. Recomendo!

##2°

Como recriar o ícone ‘Mostrar o Desktop’ no Windows: http://support.microsoft.com/kb/190355