Ouvi dizer que a inclusão de referências nulas nas linguagens de programação é o "erro do bilhão de dólares". Mas por que? Claro, eles podem causar NullReferenceExceptions, mas e daí? Qualquer elemento do idioma pode ser uma fonte de erros se usado incorretamente.
E qual é a alternativa? Suponho que, em vez de dizer isso:
Customer c = Customer.GetByLastName("Goodman"); // returns null if not found
if (c != null)
{
Console.WriteLine(c.FirstName + " " + c.LastName + " is awesome!");
}
else { Console.WriteLine("There was no customer named Goodman. How lame!"); }
Você poderia dizer o seguinte:
if (Customer.ExistsWithLastName("Goodman"))
{
Customer c = Customer.GetByLastName("Goodman") // throws error if not found
Console.WriteLine(c.FirstName + " " + c.LastName + " is awesome!");
}
else { Console.WriteLine("There was no customer named Goodman. How lame!"); }
Mas como isso é melhor? De qualquer forma, se você esquecer de verificar se o cliente existe, você recebe uma exceção.
Suponho que um CustomerNotFoundException seja um pouco mais fácil de depurar do que um NullReferenceException por ser mais descritivo. Isso é tudo o que existe?
language-design
exceptions
pointers
null
Tim Goodman
fonte
fonte
Respostas:
nulo é mau
Há uma apresentação na InfoQ sobre este tópico: Referências nulas: o erro de bilhões de dólares de Tony Hoare
Tipo de opção
A alternativa da programação funcional é usar um tipo de opção , que pode conter
SOME value
ouNONE
.Um bom artigo O padrão "Option" que discute o tipo de opção e fornece uma implementação para Java.
Também encontrei um relatório de bug para Java sobre esse problema: Adicione tipos de opções agradáveis ao Java para evitar NullPointerExceptions . O recurso solicitado foi introduzido no Java 8.
fonte
String
tipo não é realmente uma string. É umString or Null
tipo. Os idiomas que aderem totalmente à idéia dos tipos de opção não permitem valores nulos em seu sistema de tipos, garantindo que, se você definir uma assinatura de tipo que exijaString
(ou qualquer outra coisa), ela deverá ter um valor. OOption
tipo existe para fornecer uma representação no nível de tipo de coisas que podem ou não ter um valor, para que você saiba onde deve lidar explicitamente com essas situações.O problema é que, em teoria, qualquer objeto pode ser nulo e lançar uma exceção quando você tenta usá-lo, seu código orientado a objetos é basicamente uma coleção de bombas não explodidas.
Você está certo de que a manipulação de erros graciosa pode ser funcionalmente idêntica às
if
instruções de verificação nula . Mas o que acontece quando algo que você se convenceu não poderia ser um nulo é, de fato, um nulo? Kerboom. Aconteça o que acontecer a seguir, estou disposto a apostar que 1) não será gracioso e 2) você não gostará.E não descarte o valor de "fácil de depurar". O código de produção maduro é uma criatura louca e extensa; qualquer coisa que lhe dê mais informações sobre o que deu errado e onde você pode economizar horas de escavação.
fonte
public Foo doStuff()
não significa "fazer coisas e retornar o resultado Foo" significa "fazer coisas e retornar o resultado Foo, OU retornar nulo, mas você não tem idéia se isso vai acontecer ou não". O resultado é que você precisa anular a verificação EM TODA PARTE PARA ter certeza. É possível remover nulos e usar um tipo ou padrão especial (como um tipo de valor) para indicar um valor sem sentido.Existem vários problemas com o uso de referências nulas no código.
Primeiro, geralmente é usado para indicar um estado especial . Em vez de definir uma nova classe ou constante para cada estado, como normalmente são feitas especializações, o uso de uma referência nula está usando um tipo / valor com perda generalizado em massa .
Segundo, o código de depuração se torna mais difícil quando uma referência nula aparece e você tenta determinar o que o gerou , qual estado está em vigor e sua causa, mesmo que você possa rastrear seu caminho de execução upstream.
Terceiro, referências nulas introduzem caminhos de código adicionais a serem testados .
Quarto, quando referências nulas são usadas como estados válidos para parâmetros e valores de retorno, a programação defensiva (para estados causados pelo design) exige que mais verificações de referências nulas sejam feitas em vários locais ... apenas por precaução.
Em quinto lugar, o tempo de execução do idioma já está executando verificações de tipo quando realiza uma pesquisa de seletor na tabela de métodos de um objeto. Portanto, você está duplicando o esforço verificando se o tipo de objeto é válido / inválido e solicitando que o tempo de execução verifique o tipo de objeto válido para chamar seu método.
Por que não usar o padrão NullObject para tirar proveito da verificação do tempo de execução, para que ele invoque métodos NOP específicos para esse estado (conforme a interface do estado regular), além de eliminar toda a verificação extra de referências nulas em toda a sua base de código?
Isso envolve mais trabalho , criando uma classe NullObject para cada interface com a qual você deseja representar um estado especial. Mas pelo menos a especialização é isolada para cada estado especial, em vez do código no qual o estado pode estar presente. IOW, o número de testes é reduzido porque você tem menos caminhos de execução alternativos em seus métodos.
fonte
[self.navigationController.presentingViewController dismissViewControllerAnimated: YES completion: nil];
tempo de execução, ele será tratado como uma sequência de não operações, a visualização não será removida da tela e você poderá ficar sentado na frente do Xcode coçando a cabeça por horas tentando descobrir o porquê.Nulos não são tão ruins, a menos que você não os esteja esperando. Você deve especificar explicitamente no código que espera nulo, que é um problema de design de linguagem. Considere isto:
Se você tentar chamar métodos em a
Customer?
, deverá receber um erro em tempo de compilação. O motivo pelo qual mais idiomas não fazem isso (IMO) é que eles não permitem que o tipo de variável seja alterado dependendo do escopo em que está. Se o idioma puder lidar com isso, o problema poderá ser resolvido inteiramente dentro do diretório sistema de tipos.Há também a maneira funcional de lidar com esse problema, usando os tipos de opção e
Maybe
, mas não estou tão familiarizado com ele. Eu prefiro assim, porque o código correto teoricamente só precisa de um caractere adicionado para compilar corretamente.fonte
GetByLastName
retornos nulos se não foram encontrados (em vez de gerar uma exceção) - só consigo ver se ele retornaCustomer?
ouCustomer
.Maybe
- AMaybe Customer
éJust c
(ondec
é aCustomer
) ouNothing
. Em outros idiomas, é chamadoOption
- veja algumas das outras respostas sobre esta questão.Customer.FirstName
é do tipoString
(ao contrário deString?
). Esse é o benefício real - apenas variáveis / propriedades que possuem um valor significativo não devem ser declaradas como anuláveis.Há muitas respostas excelentes que cobrem os sintomas infelizes de
null
, então eu gostaria de apresentar um argumento alternativo: nulo é uma falha no sistema de tipos.O objetivo de um sistema de tipos é garantir que os diferentes componentes de um programa "se encaixem" adequadamente; um programa bem digitado não pode "sair dos trilhos" para um comportamento indefinido.
Considere um dialeto hipotético de Java, ou qualquer que seja a sua linguagem de tipo estaticamente preferida, em que é possível atribuir a sequência
"Hello, world!"
a qualquer variável de qualquer tipo:E você pode verificar variáveis assim:
Não há nada impossível nisso - alguém poderia criar uma linguagem assim, se quisesse. O valor especial não precisa ser
"Hello, world!"
- poderia ter sido o número 42, a tupla(1, 4, 9)
ou, digamosnull
,. Mas por que você faria isso? Uma variável do tipoFoo
deve conter apenasFoo
s - esse é o objetivo do sistema de tipos!null
não éFoo
mais do que"Hello, world!"
é. Pior,null
não é um valor de nenhum tipo e não há nada que você possa fazer com isso!O programador nunca pode ter certeza de que uma variável realmente contém ae
Foo
nem o programa; para evitar um comportamento indefinido, ele deve verificar as variáveis"Hello, world!"
antes de usá-las comoFoo
s. Observe que fazer a verificação de string no snippet anterior não propaga o fato de que foo1 é realmente umFoo
-bar
provavelmente terá sua própria verificação também, apenas por segurança.Compare isso com o uso de um tipo
Maybe
/Option
com correspondência de padrões:Dentro da
Just foo
cláusula, você e o programa sabem com certeza que nossaMaybe Foo
variável realmente contém umFoo
valor - essas informações são propagadas na cadeia de chamadas ebar
não precisam fazer nenhuma verificação. Como esseMaybe Foo
é um tipo distintoFoo
, você é forçado a lidar com a possibilidade de que ele possa conterNothing
, para nunca ser pego de surpresa por aNullPointerException
. Você pode raciocinar sobre seu programa com muito mais facilidade e o compilador pode omitir verificações nulas, sabendo que todas as variáveis do tipoFoo
realmente contêmFoo
s. Todo mundo ganha.fonte
my Int $variable = Int
, ondeInt
está o valor nulo do tipoInt
?this type only contains integers
ao contráriothis type contains integers and this other value
, você vai ter um problema de cada vez que unicamente quer inteiros.??
,?.
e assim por diante) para coisas que linguagens como Haskell implementar funções como biblioteca. Eu acho que é muito mais arrumado.null
/Nothing
propagation não é "especial" de forma alguma, por que deveríamos ter que aprender mais sintaxe para tê-lo?(Jogando meu chapéu no ringue para uma pergunta antiga;))
O problema específico com null é que ele quebra a digitação estática.
Se eu tiver um
Thing t
, o compilador pode garantir que posso ligart.doSomething()
. Bem, a menos quet
seja nulo no tempo de execução. Agora todas as apostas estão encerradas. O compilador disse que estava tudo bem, mas descobri muito mais tarde quet
NÃO de fatodoSomething()
. Portanto, em vez de poder confiar no compilador para capturar erros de tipo, tenho que esperar até o tempo de execução para capturá-los. Eu poderia muito bem usar Python!Portanto, de certa forma, null introduz a digitação dinâmica em um sistema estaticamente digitado, com resultados esperados.
A diferença entre dividir por zero ou logaritmo negativo etc. é que quando i = 0, ainda é um int. O compilador ainda pode garantir seu tipo. O problema é que a lógica aplica mal esse valor de uma maneira que não é permitida pela lógica ... mas se a lógica faz isso, é praticamente a definição de um bug.
(O compilador pode capturar alguns desses problemas, BTW. Como sinalizar expressões como
i = 1 / 0
no momento da compilação. Mas você realmente não pode esperar que o compilador siga uma função e garanta que todos os parâmetros sejam consistentes com a lógica da função)O problema prático é que você faz muito trabalho extra e adiciona verificações nulas para se proteger em tempo de execução, mas e se você esquecer uma? O compilador impede você de atribuir:
Então, por que deveria permitir a atribuição a um valor (nulo) ao qual interromperá a des-referência
s
?Ao misturar "sem valor" com referências "digitadas" no seu código, você está sobrecarregando os programadores. E quando as NullPointerExceptions acontecem, rastreá-las pode ser ainda mais demorado. Em vez de confiar na digitação estática para dizer "isso é uma referência a algo esperado", você está deixando o idioma dizer "isso pode muito bem ser uma referência a algo esperado".
fonte
*int
identifica umint
, ouNULL
. Da mesma forma em C ++, uma variável do tipo*Widget
identifica aWidget
, uma coisa cujo tipo deriva deWidget
, ouNULL
. O problema com Java e derivados como C # é que eles usam o local de armazenamentoWidget
como significante*Widget
. A solução não é fingir que um*Widget
não deve ser capaz de aguentarNULL
, mas sim ter umWidget
tipo de local de armazenamento adequado .Um
null
ponteiro é uma ferramentanão é um inimigo. Você apenas deve usá-los da maneira certa.
Pense apenas em quanto tempo leva para encontrar e corrigir um erro típico de ponteiro inválido , em comparação com a localização e a correção de um erro nulo de ponteiro. É fácil verificar um ponteiro
null
. É uma PITA verificar se um ponteiro não nulo aponta ou não para dados válidos.Se ainda não estiver convencido, adicione uma boa dose de multithreading ao seu cenário e pense novamente.
Moral da história: não jogue as crianças na água. E não culpe as ferramentas pelo acidente. O homem que inventou o número zero há muito tempo era bastante esperto, já que naquele dia você poderia nomear o "nada". O
null
ponteiro não está tão longe disso.EDIT: Embora o
NullObject
padrão pareça ser uma solução melhor do que as referências que podem ser nulas, ele apresenta problemas por conta própria:Uma referência que mantém um
NullObject
deve (de acordo com a teoria) não faz nada quando um método é chamado. Assim, erros sutis podem ser introduzidos devido a referências erroneamente não atribuídas, que agora são garantidas como não nulas (yehaa!), Mas executam uma ação indesejada: nada. Com um NPE, é quase óbvio onde está o problema. Com umNullObject
comportamento que de alguma forma (mas errado), apresentamos o risco de que os erros sejam detectados tarde demais. Isso não é acidentalmente semelhante a um problema de ponteiro inválido e tem consequências semelhantes: A referência aponta para algo que parece dados válidos, mas não introduz erros de dados e pode ser difícil de localizar. Para ser sincero, nesses casos, eu preferiria sem piscar um NPE que falha imediatamente, agora,sobre um erro lógico / de dados que de repente me atinge mais tarde na estrada. Lembre-se do estudo da IBM sobre o custo dos erros em função de qual estágio eles são detectados?A noção de não fazer nada quando um método é chamado a
NullObject
não é válida, quando um getter de propriedade ou uma função é chamada, ou quando o método retorna valores viaout
. Digamos que o valor de retorno é umint
e decidimos que "não fazer nada" significa "retornar 0". Mas como podemos ter certeza de que 0 é o "nada" certo a ser (não) retornado? Afinal, eleNullObject
não deve fazer nada, mas, quando solicitado por um valor, tem que reagir de alguma forma. Falhar não é uma opção: usamos oNullObject
para impedir o NPE, e certamente não o trocaremos contra outra exceção (não implementada, dividida por zero, ...), não é? Então, como você não devolve nada corretamente quando precisa devolver alguma coisa?Não posso ajudar, mas quando alguém tenta aplicar o
NullObject
padrão a todos os problemas, parece mais uma tentativa de reparar um erro cometendo outro. É sem dúvida uma solução boa e útil em alguns casos, mas certamente não é a bala mágica para todos os casos.fonte
null
existir? É possível fazer ondas de mão em praticamente qualquer falha de idioma com "não é um problema, apenas não se engane".Maybe T
que pode conter umNothing
ou umT
valor. O principal aqui é que você é forçado a verificar se umMaybe
realmente contém umT
antes de poder usá-lo e fornecer um curso de ação, caso contrário. E como você não permitiu ponteiros inválidos, agora você nunca precisa verificar nada que não seja umMaybe
.t.Something()
? Ou ele tem alguma maneira de saber que você já fez a verificação e de não fazê-lo novamente? E se você fez a verificação antes de passart
para este método? Com o tipo Option, o compilador sabe se você já fez a verificação ou não, examinando o tipo det
. Se for uma opção, você não a verificou, enquanto se for o valor desembrulhado, você o terá.Referências nulas são um erro porque permitem código não sensorial:
Existem alternativas, se você alavancar o sistema de tipos:
Geralmente, a idéia é colocar a variável em uma caixa, e a única coisa que você pode fazer é desempacotá-la, preferencialmente recrutando a ajuda do compilador como proposta para Eiffel.
Haskell tem isso do zero (
Maybe
), em C ++ você pode aproveitar,boost::optional<T>
mas ainda pode ter um comportamento indefinido ...fonte
Maybe
não está embutido no núcleo da linguagem, a sua definição só acontece de ser no prelúdio padrão:data Maybe t = Nothing | Just t
.T
divididos por outros valores do tipoT
, você limitaria a operação a valores do tipoT
divididos por valores do tipoNonZero<T>
.Tipos opcionais e correspondência de padrões. Como eu não conheço C #, aqui está um código em uma linguagem fictícia chamada Scala # :-)
fonte
O problema com os nulos é que as linguagens que lhes permitem forçar você a programar defensivamente contra ele. É preciso muito esforço (muito mais do que tentar usar blocos de defesa defensivos) para garantir que
Então, de fato, os nulos acabam sendo uma coisa cara de se ter.
fonte
O problema em que grau sua linguagem de programação tenta provar a correção do seu programa antes de executá-lo. Em um idioma estaticamente digitado, você prova que possui os tipos corretos. Ao passar para o padrão de referências não anuláveis (com referências anuláveis opcionais), você pode eliminar muitos dos casos em que o nulo é passado e não deveria ser. A questão é se o esforço extra no tratamento de referências não anuláveis vale o benefício em termos de correção do programa.
fonte
O problema não é muito nulo, é que você não pode especificar um tipo de referência não nulo em muitas linguagens modernas.
Por exemplo, seu código pode parecer
Quando as referências de classe são repassadas, a programação defensiva incentiva você a verificar todos os parâmetros para nulo novamente, mesmo que você tenha verificado nulos imediatamente, especialmente ao construir unidades reutilizáveis em teste. A mesma referência pode ser facilmente verificada quanto a nulos 10 vezes na base de código!
Não seria melhor se você pudesse ter um tipo anulável normal quando receber a coisa, lidar com nulo de vez em quando e depois passá-lo como um tipo não anulável para todas as suas pequenas funções e classes auxiliares sem verificar nulo todas as Tempo?
Esse é o objetivo da solução Option - não permitir ativar estados de erro, mas permitir o design de funções que implicitamente não podem aceitar argumentos nulos. Se a implementação "padrão" dos tipos é ou não anulável ou não anulável é menos importante do que a flexibilidade de ter as duas ferramentas disponíveis.
http://twistedoakstudios.com/blog/Post330_non-nullable-types-vs-c-fixing-the-billion-dollar-mistake
Também está listado como o recurso # 2 mais votado para C # -> https://visualstudio.uservoice.com/forums/121579-visual-studio/category/30931-languages-c
fonte
None
cada etapa do caminho.Nulo é mau. No entanto, a falta de um nulo pode ser um mal maior.
O problema é que, no mundo real, você costuma ter uma situação em que não possui dados. Seu exemplo da versão sem nulos ainda pode explodir - ou você cometeu um erro lógico e esqueceu de procurar Goodman ou talvez Goodman tenha se casado quando você verificou e quando a procurou. (Isso ajuda a avaliar a lógica se você descobrir que Moriarty está observando cada pedaço do seu código e tentando enganá-lo.)
O que faz a pesquisa de Goodman quando ela não está lá? Sem um nulo, você deve retornar algum tipo de cliente padrão - e agora você está vendendo coisas para esse padrão.
Fundamentalmente, tudo se resume a saber se é mais importante que o código funcione, não importa o que ou que funcione corretamente. Se um comportamento impróprio é preferível a nenhum comportamento, você não deseja nulos. (Para um exemplo de como essa pode ser a escolha certa, considere o primeiro lançamento do Ariane V. Um erro não detectado / 0 fez com que o foguete fizesse uma curva difícil e a autodestruição disparou quando se desfez devido a isso. estava tentando calcular que, na verdade, não servia mais a nenhum propósito no instante em que o booster estivesse aceso - teria feito órbita apesar da rotina de devolver o lixo.)
Pelo menos 99 vezes em 100, eu escolheria usar nulos. Eu pularia de alegria ao notar a versão do eu deles, no entanto.
fonte
null
é a única (ou mesmo preferível) maneira de modelar a ausência de um valor.Otimize para o caso mais comum.
Ter que verificar se há nulo o tempo todo é tedioso - você deseja apenas se apossar do objeto Customer e trabalhar com ele.
No caso normal, isso deve funcionar bem - você procura, pega o objeto e o usa.
No caso excepcional , em que você (aleatoriamente) procura um cliente pelo nome, sem saber se esse registro / objeto existe, você precisa de alguma indicação de que isso falhou. Nessa situação, a resposta é lançar uma exceção RecordNotFound (ou deixar o provedor SQL abaixo fazer isso por você).
Se você estiver em uma situação em que não sabe se pode confiar nos dados que chegam (o parâmetro), talvez porque foram inseridos por um usuário, você também pode fornecer o 'TryGetCustomer (name, out customer)' padronizar. Referência cruzada com int.Parse e int.TryParse.
fonte
Exceções não são avaliadas!
Eles estão lá por uma razão. Se o seu código estiver com problemas, existe um padrão genial, chamado exceção , e isso indica que algo está errado.
Ao evitar o uso de objetos nulos, você está ocultando parte dessas exceções. Não estou cravando no exemplo do OP, em que ele converte a exceção de ponteiro nulo em exceção bem digitada; isso pode realmente ser bom, pois aumenta a legibilidade. No entanto, quando você usa a solução do tipo Option , como @Jonas apontou, está ocultando exceções.
Do que no seu aplicativo de produção, em vez de ser lançada uma exceção ao clicar no botão para selecionar a opção vazia, nada acontece. Em vez de ponteiro nulo, a exceção seria lançada e provavelmente obteríamos um relatório dessa exceção (como em muitos aplicativos de produção, temos esse mecanismo) e do que poderíamos corrigi-lo.
Tornar seu código à prova de balas, evitando exceções, é uma péssima idéia, fazer você codificar à prova de balas, corrigindo exceções, e esse é o caminho que eu escolheria.
fonte
None
a algo que não é uma Option a um erro em tempo de compilação e, da mesma forma, usar umOption
como se tivesse um valor sem primeiro verificar se é um erro em tempo de compilação. Erros de compilação certamente são mais agradáveis do que as exceções em tempo de execução.s: String = None
, precisa dizers: Option[String] = None
. E ses
é uma opção, você não pode dizers.length
, no entanto, pode verificar se ela possui um valor e extrair o valor ao mesmo tempo, com a correspondência de padrões:s match { case Some(str) => str.length; case None => throw RuntimeException("Where's my value?") }
s: String = null
, mas esse é o preço da interoperabilidade com o Java. Geralmente, proibimos esse tipo de coisa em nosso código Scala.Nulls
são problemáticos porque devem ser verificados explicitamente, mas o compilador não pode avisar que você se esqueceu de procurá-los. Somente análises estáticas demoradas podem lhe dizer isso. Felizmente, existem várias boas alternativas.Tire a variável fora do escopo. Com muita frequência,
null
é usado como marcador de posição quando um programador declara uma variável muito cedo ou a mantém por muito tempo. A melhor abordagem é simplesmente livrar-se da variável. Não o declare até que você tenha um valor válido para inserir nele. Essa não é uma restrição tão difícil quanto você imagina, e torna seu código muito mais limpo.Use o padrão de objeto nulo. Às vezes, um valor ausente é um estado válido para o seu sistema. O padrão de objeto nulo é uma boa solução aqui. Evita a necessidade de verificações explícitas constantes, mas ainda permite representar um estado nulo. Não é "ocultar uma condição de erro", como algumas pessoas afirmam, porque nesse caso um estado nulo é um estado semântico válido. Dito isto, você não deve usar esse padrão quando um estado nulo não for um estado semântico válido. Você deve apenas tirar sua variável do escopo.
Use uma opção Talvez /. Primeiro, isso permite que o compilador avise que você precisa verificar se há um valor ausente, mas faz mais do que substituir um tipo de verificação explícita por outro. Usando
Options
, você pode encadear seu código e continuar como se seu valor existisse, não precisando realmente verificar até o último minuto absoluto. No Scala, seu código de exemplo seria semelhante a:Na segunda linha, onde estamos construindo a mensagem incrível, não fazemos uma verificação explícita se o cliente foi encontrado e a beleza é que não precisamos . Não é até a última linha, que pode ser muitas linhas mais tarde, ou mesmo em um módulo diferente, onde nós nos preocupar explicitamente com o que acontece se o
Option
éNone
. Não apenas isso, se tivéssemos esquecido de fazê-lo, não teria o tipo verificado. Mesmo assim, é feito de uma maneira muito natural, sem umaif
declaração explícita .Compare isso com a
null
, onde o tipo verifica muito bem, mas é necessário fazer uma verificação explícita em todas as etapas ou todo o aplicativo explodir em tempo de execução, se você tiver sorte durante um caso de uso que seus testes de unidade exercem. Simplesmente não vale a pena o aborrecimento.fonte
O problema central do NULL é que ele torna o sistema não confiável. Em 1980, Tony Hoare, no jornal dedicado ao seu Prêmio Turing, escreveu:
A linguagem ADA mudou muito desde então, no entanto, esses problemas ainda existem em Java, C # e em muitas outras linguagens populares.
É dever do desenvolvedor criar contratos entre um cliente e um fornecedor. Por exemplo, em C #, como em Java, você pode usar
Generics
para minimizar o impacto daNull
referência, criando somente leituraNullableClass<T>
(duas Opções):e depois use-o como
ou use duas opções de estilo com métodos de extensão C #:
A diferença é significativa. Como cliente de um método, você sabe o que esperar. Uma equipe pode ter a regra:
Se uma classe não for do tipo NullableClass, sua instância não deve ser nula .
A equipe pode fortalecer essa idéia usando Design por contrato e verificação estática no momento da compilação, por exemplo, com pré-condição:
ou para uma string
Essa abordagem pode aumentar drasticamente a confiabilidade do aplicativo e a qualidade do software. Design by Contract é um caso da lógica Hoare , que foi preenchida por Bertrand Meyer em seu famoso livro de construção de software orientado a objetos e na linguagem Eiffel em 1988, mas não é usado de maneira inválida na criação de software moderna.
fonte
Não.
A falha de um idioma em contabilizar um valor especial como
null
é o problema do idioma. Se você observar idiomas como Kotlin ou Swift , que forçam o escritor a lidar explicitamente com a possibilidade de um valor nulo a cada encontro, não há perigo.Em idiomas como o em questão, o idioma permite
null
ser um valor de qualquer tipo -ish , mas não fornece construções para reconhecer isso posteriormente, o que leva a que as coisas sejamnull
inesperadas (especialmente quando passadas para você de outro lugar) e assim você obtém falhas. Esse é o mal: deixar você usarnull
sem fazer você lidar com isso.fonte
Basicamente, a idéia central é que os problemas de referência de ponteiro nulo sejam capturados no tempo de compilação, em vez do tempo de execução. Portanto, se você estiver escrevendo uma função que utiliza algum objeto e não deseja que ninguém a chame com referências nulas, o tipo system deve permitir que você especifique esse requisito para que o compilador possa usá-lo para garantir que a chamada para sua função nunca compile se o chamador não atender aos seus requisitos. Isso pode ser feito de várias maneiras, por exemplo, decorando seus tipos ou usando tipos de opção (também chamados tipos anuláveis em alguns idiomas), mas obviamente isso é muito mais trabalhoso para os designers de compiladores.
Eu acho que é compreensível por que, nas décadas de 1960 e 70, o designer de compiladores não foi all-in para implementar essa idéia porque as máquinas eram limitadas em recursos, os compiladores precisavam ser relativamente simples e ninguém sabia realmente o quão ruim isso seria. anos abaixo da linha.
fonte
Eu tenho uma objeção simples contra
null
:Ele quebra completamente a semântica do seu código, introduzindo ambiguidade .
Muitas vezes, você espera que o resultado seja de um determinado tipo. Isso é semanticamente correto. Digamos, você pediu ao banco de dados um usuário com um certo valor
id
, espera que o resultado seja de um determinado tipo (=user
). Mas, e se não houver usuário desse id? Uma (ruim) solução é: agregarnull
valor. Portanto, o resultado é ambíguo : ou é umuser
ou énull
. Masnull
não é o tipo esperado. E é aí que o cheiro do código começa.Ao evitar
null
, você torna seu código semanticamente claro.Sempre há maneiras de contornar
null
: refatoração para coleçõesNull-objects
,optionals
etc.fonte
Se for semanticamente possível que um local de armazenamento de referência ou tipo de ponteiro seja acessado antes que o código seja executado para calcular um valor para ele, não há uma escolha perfeita sobre o que deve acontecer. Ter esses locais como padrão para referências de ponteiro nulo que podem ser lidas e copiadas livremente, mas que apresentarão falhas se forem desferenciadas ou indexadas, é uma opção possível. Outras opções incluem:
A última opção pode ter algum apelo, especialmente se um idioma incluir tipos anuláveis e não anuláveis (pode-se chamar os construtores de matriz especiais para qualquer tipo, mas somente é necessário chamá-los ao criar matrizes de tipos não anuláveis), mas provavelmente não seria viável na época em que ponteiros nulos foram inventados. Das outras opções, nenhuma parece mais atraente do que permitir que ponteiros nulos sejam copiados, mas não desreferenciados ou indexados. A abordagem 4 pode ser conveniente ter como opção e deve ser bastante barata de implementar, mas certamente não deve ser a única opção. Exigir que os ponteiros, por padrão, apontem para algum objeto válido em particular é muito pior do que ter ponteiros como padrão para um valor nulo que pode ser lido ou copiado, mas não desreferenciado ou indexado.
fonte
Sim, NULL é um design terrível , no mundo orientado a objetos. Em poucas palavras, o uso NULL leva a:
Verifique esta publicação no blog para obter uma explicação detalhada: http://www.yegor256.com/2014/05/13/why-null-is-bad.html
fonte