Na minha opinião, há uma diferença entre retornar NULL, retornar algum resultado vazio (por exemplo, a string vazia ou uma lista vazia) e lançar uma exceção.
Eu normalmente tomo a seguinte abordagem. Considero uma função ou método chamada f (v1, ..., vn) como a aplicação de uma função
f : S x T1 x ... x Tn -> T
onde S é o "estado do mundo" T1, ..., Tn são os tipos de parâmetros de entrada e T é o tipo de retorno.
Primeiro tento definir esta função. Se a função for parcial (ou seja, existem alguns valores de entrada para os quais não está definida), retorno NULL para sinalizar isso. Isso ocorre porque quero que a computação termine normalmente e me diga que a função que solicitei não está definida nas entradas fornecidas. O uso, por exemplo, de uma string vazia como valor de retorno é ambíguo, pois pode ser que a função esteja definida nas entradas e a string vazia seja o resultado correto.
Eu acho que a verificação extra para um ponteiro NULL no código de chamada é necessária porque você está aplicando uma função parcial e é tarefa do método chamado informar se a função não está definida para a entrada especificada.
Prefiro usar exceções para erros que não permitem realizar o cálculo (ou seja, não foi possível encontrar nenhuma resposta).
Por exemplo, suponha que eu tenha uma classe Customer e queira implementar um método
Customer findCustomer(String customerCode)
para procurar um cliente no banco de dados do aplicativo por seu código. Nesse método, eu
- Retorne um objeto da classe Customer se a consulta for bem-sucedida,
- Retorne nulo se a consulta não encontrar nenhum cliente.
- Lance uma exceção se não for possível conectar-se ao banco de dados.
As verificações extras para nulo, por exemplo
Customer customer = findCustomer("...");
if (customer != null && customer.getOrders() > 0)
{
...
}
fazem parte da semântica do que estou fazendo e não apenas os "pulo" para tornar o código melhor lido. Não acho que seja uma boa prática simplificar a semântica do problema em questão apenas para simplificar o código.
Obviamente, como a verificação de nulo ocorre com muita frequência, é bom que o idioma suporte alguma sintaxe especial para ele.
Também consideraria usar o padrão Null Object (como sugerido por Laf), desde que eu possa distinguir o objeto nulo de uma classe de todos os outros objetos.