Eu não entendo muito bem o bashing consistente de referências nulas por algumas pessoas da linguagem de programação. O que há de tão ruim neles? Se eu solicitar acesso de leitura a um arquivo que não existe, ficarei perfeitamente feliz em receber uma exceção ou uma referência nula. No entanto, as exceções são consideradas boas, mas as referências nulas são consideradas ruins. Qual é o raciocínio por trás disso?
programming-languages
exceptions
null
davidk01
fonte
fonte
Respostas:
Referências nulas não são "evitadas" mais do que exceções, pelo menos por alguém que eu já conheci ou li. Eu acho que você está entendendo mal a sabedoria convencional.
O que é ruim é uma tentativa de acessar uma referência nula (ou desreferenciar um ponteiro nulo etc.). Isso é ruim porque sempre indica um bug; você nunca faria algo assim de propósito, e se estiver fazendo de propósito, isso é ainda pior, porque torna impossível distinguir o comportamento esperado do comportamento de buggy.
Existem certos grupos marginais por aí que realmente odeiam o conceito de nulidade por algum motivo, mas, como Ed aponta , se você não tiver
null
ounil
então precisará substituí-lo por algo mais, o que pode levar a algo pior que uma falha (como corrupção de dados).Muitas estruturas, de fato, abraçam os dois conceitos; por exemplo, no .NET, um padrão frequente que você verá é um par de métodos, um prefixado pela palavra
Try
(comoTryGetValue
). NoTry
caso, a referência é configurada para seu valor padrão (geralmentenull
) e, no outro caso, uma exceção é lançada. Não há nada errado com nenhuma das abordagens; ambos são usados com freqüência em ambientes que os suportam.Realmente tudo depende da semântica. Se
null
for um valor de retorno válido, como no caso geral de pesquisar uma coleção, retornenull
. Por outro lado, se é não um valor de retorno válido - por exemplo, procurar um registro usando uma chave primária que veio de seu próprio banco de dados - então retornandonull
seria uma má idéia, porque o chamador não será esperando por isso e, provavelmente, não vai verificar isso.É realmente muito simples descobrir qual semântica usar: faz algum sentido que o resultado de uma função seja indefinido? Nesse caso, você pode retornar uma referência nula. Caso contrário, lance uma exceção.
fonte
null
, por isso, realmente, Haskell é bastante irrelevante aqui.null
desempenho persiste tão bem por tanto tempo, e a maioria das pessoas que dizem o contrário parece ser acadêmica com pouca experiência no design de aplicativos do mundo real com restrições como requisitos incompletos ou consistência eventual.A grande diferença é que, se você deixar de fora o código para manipular NULLs, seu código continuará possivelmente travando posteriormente com alguma mensagem de erro não relacionada, onde, como nas exceções, a exceção será gerada no ponto inicial da falha (abertura um arquivo para leitura no seu exemplo).
fonte
Como os valores nulos não são uma parte necessária de uma linguagem de programação e são uma fonte consistente de erros. Como você diz, abrir um arquivo pode resultar em falha, que pode ser comunicada novamente como um valor de retorno nulo ou por meio de uma exceção. Se valores nulos não forem permitidos, existe uma maneira consistente e singular de comunicar a falha.
Além disso, esse não é o problema mais comum com nulos. A maioria das pessoas se lembra de procurar nulo depois de chamar uma função que pode retorná-la. O problema surge muito mais em seu próprio design, permitindo que variáveis sejam nulas em vários pontos na execução do seu programa. Você pode criar seu código de forma que valores nulos nunca sejam permitidos, mas se nulo não fosse permitido no nível do idioma, nada disso seria necessário.
No entanto, na prática, você ainda precisaria de alguma maneira para indicar se uma variável é ou não inicializada. Você teria uma forma de erro em que seu programa não falha, mas continua usando algum valor padrão possivelmente inválido. Sinceramente, não sei o que é melhor. Pelo meu dinheiro, gosto de bater cedo e com frequência.
fonte
null
como valor de retorno: as informações solicitadas não existem. Não há diferença. De qualquer forma, o chamador deve validar o valor de retorno se realmente precisar usar essas informações para algum propósito específico. Seja validar anull
, um objeto nulo ou uma mônada, não faz diferença prática.Tony Hoare, que criou a idéia de uma referência nula em primeiro lugar, chama de erro de um milhão de dólares .
O problema não é sobre referências nulas em si, mas sobre a falta de verificação de tipo adequada na maioria dos idiomas seguros (de outro modo).
Essa falta de suporte, no idioma, significa que os bugs "null-bugs" podem estar ocultos no programa por um longo tempo antes de serem detectados. Essa é a natureza dos bugs, é claro, mas os "bugs nulos" agora são conhecidos por serem evitáveis.
Esse problema está especialmente presente em C ou C ++ (por exemplo) devido ao erro "rígido" que ele causa (uma falha do programa, imediata, sem recuperação elegante).
Em outros idiomas, sempre há a questão de como lidar com eles.
Em Java ou C #, você obteria uma exceção se tentar invocar um método em uma referência nula, e isso pode ser bom. E, portanto, a maioria dos programadores de Java ou C # está acostumada a isso e não entende por que alguém gostaria de fazer o contrário (e rindo do C ++).
No Haskell, você deve fornecer explicitamente uma ação para o caso nulo e, portanto, os programadores do Haskell se gabam de seus colegas, porque eles acertaram (certo?).
É, na verdade, o antigo debate sobre código de erro / exceção, mas desta vez com um valor do Sentinel em vez de código de erro.
Como sempre, o que for mais apropriado realmente depende da situação e da semântica que você está procurando.
fonte
null
é realmente mau, porque subverte o sistema de tipos . (Concedido, a alternativa tem uma desvantagem em sua verbosidade, pelo menos em alguns idiomas.)Você não pode anexar uma mensagem de erro legível por humanos a um ponteiro nulo.
(Você pode, no entanto, deixar uma mensagem de erro em um arquivo de log.)
Em alguns idiomas / ambientes que permitem aritmética do ponteiro, se um dos argumentos do ponteiro for nulo e for permitido no cálculo, o resultado seria um ponteiro inválido e não nulo . (*) Mais poder para você.
(*) Isso acontece muito na programação COM , onde se você tentar chamar um método de interface, mas o ponteiro da interface for nulo, isso resultaria em uma chamada para um endereço inválido que é quase, mas não exatamente, exatamente diferente de zero.
fonte
Retornar NULL (ou zero numérico ou falso booleano) para sinalizar um erro está errado técnica e conceitualmente.
Tecnicamente, você está sobrecarregando o programador com a verificação do valor de retorno imediata do , no ponto exato em que ele é retornado. Se você abrir vinte arquivos em uma linha e a sinalização de erro for feita retornando NULL, o código de consumo deverá verificar cada arquivo lido individualmente e interromper quaisquer loops e construções semelhantes. Esta é uma receita perfeita para código desordenado. Se, no entanto, você optar por sinalizar o erro lançando uma exceção, o código consumidor poderá optar por manipular a exceção imediatamente ou deixá-la subir tantos níveis quanto apropriado, mesmo em chamadas de função. Isso cria um código muito mais limpo.
Conceitualmente, se você abrir um arquivo e algo der errado, retornar um valor (mesmo NULL) estará errado. Você não tem nada para retornar, porque sua operação não foi concluída. Retornar NULL é o equivalente conceitual de "Eu li com sucesso o arquivo e aqui está o que ele contém - nada". Se é isso que você deseja expressar (ou seja, se NULL faz sentido como um resultado real para a operação em questão), então retorne NULL, por todos os meios, mas se desejar sinalizar um erro, use exceções.
Historicamente, os erros eram relatados dessa maneira, porque linguagens de programação como C não possuem manipulação de exceção incorporada à linguagem, e a maneira recomendada (usando saltos longos) é um pouco cabeluda e meio contra-intuitiva.
Há também um lado da manutenção no problema: com exceções, você precisa escrever um código extra para lidar com a falha; caso contrário, o programa falhará cedo e com dificuldade (o que é bom). Se você retornar NULL para sinalizar erros, o comportamento padrão do programa é ignorar o erro e continuar, até que isso acarrete outros problemas futuros - dados corrompidos, segfaults, NullReferenceExceptions, dependendo do idioma. Para sinalizar o erro com antecedência e em voz alta, você precisa escrever um código extra e adivinhar: essa é a parte que fica de fora quando você tem um prazo apertado.
fonte
Como já apontado, muitos idiomas não convertem uma desreferenciação de um ponteiro nulo em uma exceção capturável. Fazer isso é um truque relativamente moderno. Quando o problema do ponteiro nulo foi reconhecido pela primeira vez, as exceções ainda não haviam sido inventadas.
Se você permitir ponteiros nulos como um caso válido, esse é um caso especial. Você precisa de lógica de manipulação de casos especiais, geralmente em muitos lugares diferentes. Isso é complexidade extra.
Seja relacionado a ponteiros potencialmente nulos ou não, se você não usa lançamentos de exceção para lidar com casos excepcionais, deve tratar esses casos excepcionais de outra maneira. Normalmente, toda chamada de função deve ter verificações para esses casos excepcionais, para impedir que a chamada de função seja chamada de forma inadequada ou para detectar o caso de falha quando a função sair. Essa complexidade extra pode ser evitada usando exceções.
Mais complexidade geralmente significa mais erros.
Alternativas ao uso de ponteiros nulos nas estruturas de dados (por exemplo, para marcar o início / fim de uma lista vinculada) incluem o uso de itens sentinela. Eles podem fornecer a mesma funcionalidade com muito menos complexidade. No entanto, pode haver outras maneiras de gerenciar a complexidade. Uma maneira é agrupar o ponteiro potencialmente nulo em uma classe de ponteiro inteligente, para que as verificações nulas sejam necessárias apenas em um local.
O que fazer com o ponteiro nulo quando detectado? Se você não conseguir criar um tratamento de caso excepcional, sempre poderá lançar uma exceção e delegar efetivamente esse tratamento de caso especial ao chamador. E é exatamente isso que alguns idiomas fazem por padrão quando você desrefere um ponteiro nulo.
fonte
Específico para C ++, mas as referências nulas são evitadas porque a semântica nula no C ++ está associada aos tipos de ponteiro. É bastante razoável que uma função de arquivo aberto falhe e retorne um ponteiro nulo; de fato, a
fopen()
função faz exatamente isso.fonte
Depende do idioma.
Por exemplo, o Objective-C permite enviar uma mensagem para um objeto nulo (nulo) sem problemas. Uma chamada para nil retorna nulo também e é considerada um recurso de idioma.
Eu pessoalmente gosto, pois você pode confiar nesse comportamento e evitar todas essas
if(obj == null)
construções aninhadas complicadas .Por exemplo:
Pode ser reduzido para:
Em resumo, torna seu código mais legível.
fonte
Eu não dou a mínima se você retorna um nulo ou lança uma exceção, desde que você o documente.
fonte
GetAddressMaybe
ouGetSpouseNameOrNull
.Uma referência nula geralmente ocorre porque alguma coisa na lógica do programa foi perdida, ou seja: você chegou a uma linha de código sem passar pela configuração necessária para esse bloco de código.
Por outro lado, se você lançar uma exceção para algo, significa que reconheceu que uma situação específica pode ocorrer na operação normal do programa e está sendo tratada.
fonte
Referências nulas geralmente são muito úteis: por exemplo, um elemento em uma lista vinculada pode ter algum sucessor ou nenhum sucessor. Usar
null
para "nenhum sucessor" é perfeitamente natural. Ou então, uma pessoa pode ter um cônjuge ou não - usarnull
para "pessoa que não tem cônjuge" é perfeitamente natural, muito mais natural do que ter algum "não tem cônjuge" - valor especial ao qual oPerson.Spouse
membro poderia se referir.Mas: muitos valores não são opcionais. Em um programa típico de POO, eu diria que mais da metade das referências não pode ser
null
posterior à inicialização ou o programa falhará. Caso contrário, o código teria que estar cheio deif (x != null)
cheques. Então, por que toda referência deve ser anulável por padrão? Realmente deveria ser o contrário: as variáveis devem ser não anuláveis por padrão, e você deve explicitamente dizer "oh, e esse valor também pode sernull
".fonte
Sua pergunta está confusa. Você quer dizer uma exceção de referência nula (que é realmente causada por uma tentativa de dereferência nula)? O motivo claro para não querer esse tipo de exceção é que ela não fornece informações sobre o que deu errado ou mesmo quando - o valor poderia ter sido definido como nulo em qualquer ponto do programa. Você escreve "Se eu solicitar acesso de leitura a um arquivo que não existe, ficarei perfeitamente feliz em receber uma exceção ou uma referência nula" - mas você não deverá estar perfeitamente feliz em obter algo que não indique a causa . Em nenhum lugar da cadeia "foi feita uma tentativa de remover a referência nula", há alguma menção à leitura ou a arquivos não existentes. Talvez você queira dizer que seria perfeitamente feliz em obter nulo como valor de retorno de uma chamada de leitura - isso é um assunto bem diferente, mas ainda não fornece informações sobre o motivo pelo qual a leitura falhou; isto'
fonte