Tipicamente e basicamente, existem duas formas de lidar com isso no Java: com exceções ou sem exceções. Simples assim.
É possível usar, tradicionalmente, um código de erro no retorno, ou retornar um valor nulo ou equivalente, que é a melhor opção (e única) para linguagens que não possuem estruturas de Exceção.
Ou, a cada parâmetro inválido ou problema na computação de um resultado, lançar uma Exceção apropriada a ser tratada (ou não) pelo chamador.
Tá, e daí? Quais são as diferenças práticas?
Bem, claramente o uso de Exceções enriquece o tratamento, já que podem ser personalizadas e enquanto objetos podem carregar diversas informações sobre o erro. Por exemplo, uma mesma exceção de acesso a um SGBD pode carregar, além da mensagem de erro, o código de erro SQL, a instrução submetida ao banco, a versão do banco, etc.
Por outro lado, sendo objetos, criar e lançar Exceções tem um custo de processador, para criar, inicializar e destruir o objeto, e memória, para armazená-lo. Ou seja, usar Exceções implica em um decréscimo de performance.
Não, não sou contra o uso de Exceções, não me entenda mal. Como pensador de projetos de sistemas quero colocar que devemos avaliar os prós e contras de diversas abordagens e, baseado nesses prós e contras, tomar decisões.
Neste caso, temos (mais) um problema de "cobertor curto": maior riqueza de informação (bom), maior uso de recursos (cpu, mem, disk, ou seja, ruim), menor riqueza de informação (ruim), menor uso de recursos (bom). Enfim, é um dilema.
Para ilustrar uma diferença de performance no uso ou não de Exceções criei um pequeno programa para calcular volume. Ele simplesmente multiplica três inteiros, desde que sejam positivos e aí está o tratamento de erro, zero ou números negativos não são permitidos. O código-fonte está aqui: ExPerfTest.java
Na primeira abordagem, um parâmetro zero ou negativo invalida o cálculo e sempre retorna 0 como volume. Ou seja, se eu chamar volume(10, -20, 30) ele retorna 0, não lança Exceções. No meu Notebook, a execução deste método 100 mil vezes com valor inválido é realizada em 8 milissegundos em média. A seguir está o método:
public static int volume(int a, int b, int c) { return a < 0 ? 0 : b < 0 ? 0 : c < 0 ? 0 : a * b * c; }Na segunda abordagem, um parâmetro zero ou negativo invalida o cálculo e lança uma Exceção. Ou seja, se eu chamar volume(10, -20, 30) ele cria (new) e lança (throw) um objeto da classe Exception. No meu Notebook, a execução deste método 100 mil vezes com valor inválido é realizada em 190 milissegundos em média. A seguir está o método:
public static int volume2(int a, int b, int c) throws Exception { if (a < 0 || b < 0 || c < 0) throw new Exception("número inválido"); return a * b * c; }Claro, tenho que deixar claro que, neste projeto, há uma sobrecarga da própria instrumentação (em especial o Late Binding da Interface). Na prática esses tempos são menores, mas aproximadamente na mesma proporção, o que não muda o fato de Exceções reduzirem a performance. O projeto inteiro (no NetBeans) pode ser baixado neste link: exception-perf-test.zip
Finalizando, é importante deixar claro que a redução de performance é visível em várias chamadas ao método. Executando o mesmo método com e sem exceções apenas 3 vezes a diferença é praticamente inexistente ( < 1ms ). Estes dados indicam que métodos de acesso aleatório ou pontual podem lançar exceções sem problemas. Para métodos que são acessados frequentemente ou recorrentemente, e provavelmente podem receber dados inválidos, pode ser escolhido uma implementação sem Exceção.
Cada caso é um caso, mas só um lembrete: Exceções são para situações Excepcionais. Se a Exceção acontece com frequência, ela não é Excepcional, concorda?
Quero agradecer ao Everton e Cristiano pela conversa em aula e que me levou a criação deste Post, obrigado.
Abraços,
Márcio Torres
Nenhum comentário:
Postar um comentário