As exceções devem representar condições que provavelmente o código de chamada imediato não estará preparado para manipular, mesmo que o método de chamada possa. Considere, por exemplo, o código que está lendo alguns dados de um arquivo, pode legitimamente assumir que qualquer arquivo válido terminará com um registro válido e não é necessário extrair nenhuma informação de um registro parcial.
Se a rotina de dados de leitura não usasse exceções, mas simplesmente informasse se a leitura foi bem-sucedida ou não, o código de chamada teria que se parecer com:
temp = dataSource.readInteger();
if (temp == null) return null;
field1 = (int)temp;
temp = dataSource.readInteger();
if (temp == null) return null;
field2 = (int)temp;
temp = dataSource.readString();
if (temp == null) return null;
field3 = temp;
gastando três linhas de código para cada peça útil de trabalho. Por outro lado, se readInteger
lançar uma exceção ao encontrar o final de um arquivo, e se o chamador puder simplesmente passar a exceção, o código se tornará:
field1 = dataSource.readInteger();
field2 = dataSource.readInteger();
field3 = dataSource.readString();
Muito mais simples e com aparência mais clara, com ênfase muito maior no caso em que as coisas funcionam normalmente. Observe que, nos casos em que o chamador imediato estaria esperando manipular uma condição, um método que retorna um código de erro geralmente será mais útil do que aquele que lança uma exceção. Por exemplo, para totalizar todos os inteiros em um arquivo:
do
{
temp = dataSource.tryReadInteger();
if (temp == null) break;
total += (int)temp;
} while(true);
versus
try
{
do
{
total += (int)dataSource.readInteger();
}
while(true);
}
catch endOfDataSourceException ex
{ // Don't do anything, since this is an expected condition (eventually)
}
O código que pede os números inteiros está esperando que uma dessas chamadas irá falhar. Ter o código usando um loop sem fim que será executado até que isso aconteça é muito menos elegante do que usar um método que indique falhas por meio de seu valor de retorno.
Como as turmas geralmente não sabem quais condições seus clientes esperarão ou não, é sempre útil oferecer duas versões de métodos que podem falhar de maneiras que alguns chamadores esperariam e outras pessoas que não ligam. Isso permitirá que esses métodos sejam usados de maneira limpa com os dois tipos de chamadores. Note também que mesmo os métodos "try" devem lançar exceções se surgirem situações que o chamador provavelmente não esteja esperando. Por exemplo, tryReadInteger
não deve lançar uma exceção se encontrar uma condição de fim de arquivo limpa (se o responsável pela chamada não estivesse esperando isso, o chamador teria usado readInteger
). Por outro lado, provavelmente deveria lançar uma exceção se os dados não pudessem ser lidos, por exemplo, o cartão de memória que o continha estava desconectado. Embora esses eventos devam ser sempre reconhecidos como uma possibilidade, é improvável que o código de chamada imediata esteja preparado para fazer qualquer coisa útil em resposta; certamente não deve ser relatado da mesma maneira que seria uma condição de fim de arquivo.