Por exemplo, suponha que eu tenho uma classe Member
, que possui lastChangePasswordTime:
class Member{
.
.
.
constructor(){
this.lastChangePasswordTime=null,
}
}
cujo lastChangePasswordTime pode estar ausente de forma significativa, porque alguns membros nunca podem alterar suas senhas.
Mas, de acordo com Se nulos são maus, o que deve ser usado quando um valor pode estar significativamente ausente? e https://softwareengineering.stackexchange.com/a/12836/248528 , não devo usar null para representar um valor significativamente ausente. Então, eu tento adicionar um sinalizador booleano:
class Member{
.
.
.
constructor(){
this.isPasswordChanged=false,
this.lastChangePasswordTime=null,
}
}
Mas acho que é bastante obsoleto porque:
Quando isPasswordChanged é false, lastChangePasswordTime deve ser nulo e a verificação de lastChangePasswordTime == null é quase idêntica à verificação de isPasswordChanged é false, portanto, prefiro verificar lastChangePasswordTime == null diretamente
Ao alterar a lógica aqui, posso esquecer de atualizar os dois campos.
Nota: quando um usuário altera as senhas, registro o horário da seguinte forma:
this.lastChangePasswordTime=Date.now();
O campo booleano adicional é melhor do que uma referência nula aqui?
std::optional
ouOption
. Em outros idiomas, você pode ter que criar um mecanismo apropriado, você mesmo pode recorrer anull
algo semelhante, porque é mais idiomático.lastChangePasswordTime
pode ser um ponteiro não inicializado lá, e compará-lo a qualquer coisa seria um comportamento indefinido. Não é uma razão realmente convincente para não inicializar o ponteiro paraNULL
/ emnullptr
vez disso, especialmente não no C ++ moderno (onde você não usaria um ponteiro), mas quem sabe? Outro exemplo seria linguagens sem ponteiros, ou talvez com um mau suporte para ponteiros. (FORTRAN 77 vem à mente ...)Respostas:
Não vejo por que, se você tem um valor significativamente ausente,
null
não deve ser usado se você for deliberado e cuidadoso.Se seu objetivo é cercar o valor anulável para impedir que ele seja referenciado acidentalmente, sugiro criar o
isPasswordChanged
valor como uma função ou propriedade que retorna o resultado de uma verificação nula, por exemplo:Na minha opinião, fazendo assim:
isPasswordChanged
valor mencionado.A maneira como você persiste os dados (presumivelmente em um banco de dados) seria responsável por garantir que os nulos sejam preservados.
fonte
isPasswordChanged
assim como você pode verificar se é nulo. Não vejo nada ganho aqui.nulls
não são maus. Usá-los sem pensar é. Este é um caso em quenull
é exatamente a resposta correta - não há data.Observe que sua solução cria mais problemas. Qual é o significado da data que está sendo definida como algo, mas o
isPasswordChanged
é falso? Você acabou de criar um caso de informações conflitantes que precisa capturar e tratar especialmente, enquanto umnull
valor tem um significado claramente definido e inequívoco e não pode estar em conflito com outras informações.Portanto, não, sua solução não é melhor. Permitir um
null
valor é a abordagem correta aqui. As pessoas que afirmam quenull
sempre são más, não importa o contexto, não entendem o porquênull
.fonte
null
existe porque Tony Hoare cometeu o erro de um bilhão de dólares em 1965. As pessoas que afirmamnull
ser "más" estão simplificando demais o tópico, é claro, mas é bom que as linguagens modernas ou os estilos de programação estejam se afastandonull
, substituindo com tipos de opções dedicados.DateTime
campo é um ponteiro. Todo o conceito denull
só faz sentido para ponteiros!Dependendo da sua linguagem de programação, pode haver boas alternativas, como um
optional
tipo de dados. Em C ++ (17), isso seria std :: opcional . Pode estar ausente ou ter um valor arbitrário do tipo de dados subjacente.fonte
Optional
no java; o significado de um nuloOptional
depende de você ...Optional<Date> lastPasswordChangeTime = null;
, mas eu não desistiria apenas por causa disso. Em vez disso, eu instilaria uma regra de equipe muito firme, de que nenhum valor opcional jamais pode ser atribuídonull
. Opcional, você compra muitos recursos interessantes para desistir facilmente. Você pode facilmente atribuir valores padrão (orElse
ou com uma avaliação lenta :)orElseGet
, lançar erros (orElseThrow
), transformar valores de forma nula e segura (map
,flatmap
) etc.isPresent
quase sempre é um cheiro de código, na minha experiência, e um sinal bastante confiável de que os desenvolvedores que estão usando esses opcionais estão "perdendo o objetivo". De fato, basicamente não oferece nenhum benefícioref == null
, mas também não é algo que você deva usar com frequência. Mesmo que a equipe seja instável, uma ferramenta de análise estática pode estabelecer a lei, como um linter ou FindBugs , que pode capturar a maioria dos casos.null
porque a linguagem é 100% compatível com Java, mas ninguém a usa eOption[T]
está em todo o lado, com excelente suporte à programação funcional comomap
,flatMap
correspondência de padrões e assim por diante. muito, muito além das== null
verificações. Você está certo que, em teoria, um tipo de opção parece pouco mais do que um ponteiro glorificado, mas a realidade prova que esse argumento está errado.Corretamente usando null
Existem diferentes maneiras de usar
null
. A maneira mais comum e semanticamente correta é usá-lo quando você pode ou não ter um único valor. Nesse caso, um valor é igualnull
ou é significativo como um registro do banco de dados ou algo assim.Nessas situações, você geralmente o usa dessa maneira (em pseudo-código):
Problema
E tem um problema muito grande. O problema é que, quando você invoca,
doSomethingUseful
o valor pode não ter sido verificadonull
! Caso contrário, o programa provavelmente falhará. E o usuário pode nem ver boas mensagens de erro, ficando com algo como "erro horrível: valor desejado, mas foi nulo!" (após a atualização: embora possa haver ainda menos erros informativos comoSegmentation fault. Core dumped.
, ou pior ainda, nenhum erro e manipulação incorreta em null em alguns casos)Esquecer de escrever verificações
null
e lidar comnull
situações é um bug extremamente comum . É por isso que Tony Hoare, que inventou,null
disse em uma conferência de software chamada QCon London em 2009 que ele cometeu o erro de um bilhão de dólares em 1965: https://www.infoq.com/presentations/Null-References-The-Billion-Dollar- Erro-Tony-HoareEvitando o problema
Algumas tecnologias e linguagens tornam
null
impossível verificar o esquecimento de maneiras diferentes, reduzindo a quantidade de bugs.Por exemplo, Haskell tem a
Maybe
mônada em vez de nulos. Suponha que esseDatabaseRecord
seja um tipo definido pelo usuário. Em Haskell, um valor do tipoMaybe DatabaseRecord
pode ser igualJust <somevalue>
ou igual aNothing
. Você pode usá-lo de diferentes maneiras, mas não importa como o use, não poderá aplicar alguma operaçãoNothing
sem conhecê-lo.Por exemplo, esta função chamada
zeroAsDefault
retornax
paraJust x
e0
paraNothing
:Christian Hackl diz que C ++ 17 e Scala têm seus próprios caminhos. Portanto, você pode tentar descobrir se o seu idioma tem algo parecido e usá-lo.
Os nulos ainda são amplamente utilizados
Se você não tem nada melhor, o uso
null
é bom. Apenas continue atento. Declarações de tipo em funções o ajudarão de alguma maneira.Além disso, isso pode parecer pouco progressivo, mas você deve verificar se seus colegas querem usar
null
ou algo mais. Eles podem ser conservadores e podem não querer usar novas estruturas de dados por alguns motivos. Por exemplo, suporte a versões mais antigas de um idioma. Tais coisas devem ser declaradas nos padrões de codificação do projeto e discutidas adequadamente com a equipe.Na sua proposta
Você sugere o uso de um campo booleano separado. Mas você precisa verificá-lo de qualquer maneira e ainda pode esquecer de verificá-lo. Portanto, não há nada ganho aqui. Se você pode esquecer outra coisa, como atualizar os dois valores a cada vez, é ainda pior. Se o problema de esquecer de verificar
null
não for resolvido, não faz sentido. Evitarnull
é difícil e você não deve fazê-lo de uma maneira que a torne pior.Como não usar null
Finalmente, existem maneiras comuns de usar
null
incorretamente. Uma dessas maneiras é usá-lo no lugar de estruturas de dados vazias, como matrizes e seqüências de caracteres. Uma matriz vazia é uma matriz adequada como qualquer outra! É quase sempre importante e útil para estruturas de dados, que podem se ajustar a vários valores, para poder ficar vazio, ou seja, tem 0 comprimento.Do ponto de vista da álgebra, uma string vazia para strings é semelhante a 0 para números, ou seja, identidade:
A string vazia permite que as strings em geral se tornem um monóide: https://en.wikipedia.org/wiki/Monoid Se você não conseguir, não é tão importante para você.
Agora vamos ver por que é importante programar com este exemplo:
Se passarmos uma matriz vazia aqui, o código funcionará bem. Isso não fará nada. No entanto, se passarmos
null
aqui, provavelmente teremos um erro com um erro do tipo "não é possível fazer um loop nulo, desculpe". Poderíamos envolvê-lo,if
mas isso é menos limpo e, novamente, você pode esquecer de verificá-loComo lidar com null
O que
doSomethingAboutIt()
deveria estar fazendo e, principalmente, se deveria lançar uma exceção é outra questão complicada. Em resumo, depende senull
foi um valor de entrada aceitável para uma determinada tarefa e o que é esperado em resposta. Exceções são para eventos que não eram esperados. Não irei mais além nesse tópico. Essa resposta já é longa.fonte
horrible error: wanted value but got null!
é muito melhor do que o mais típicoSegmentation fault. Core dumped.
...Error: the product you want to buy is out of stock
.null
ondenull
não é permitido é um erro de programação, ou seja, um bug no código. Um produto que está fora de estoque faz parte da lógica comercial comum e não um bug. Você não pode e não deve tentar traduzir a detecção de um bug em uma mensagem normal na interface do usuário. Falha imediata e não execução de mais código é o curso de ação preferido, especialmente se for uma aplicação importante (por exemplo, uma que execute transações financeiras reais).null
completamente, mesmo do fundo.Além de todas as respostas muito boas fornecidas anteriormente, eu acrescentaria que toda vez que você for tentado a deixar um campo nulo, pense com cuidado se for um tipo de lista. Um tipo anulável é equivalente a uma lista de 0 ou 1 elementos, e geralmente isso pode ser generalizado para uma lista de N elementos. Especificamente, neste caso, você pode considerar
lastChangePasswordTime
uma lista depasswordChangeTimes
.fonte
Pergunte a si mesmo: qual comportamento requer o campo lastChangePasswordTime?
Se você precisar desse campo para um método IsPasswordExpired () para determinar se um Membro deve ser solicitado a alterar sua senha de vez em quando, eu definiria o campo para o momento em que o Membro foi criado inicialmente. A implementação IsPasswordExpired () é a mesma para membros novos e existentes.
Se você tiver um requisito separado de que os membros recém-criados tenham que atualizar suas senhas, eu adicionaria um campo booleano separado chamado passwordShouldBeChanged e o definiria como verdadeiro na criação. Eu mudaria a funcionalidade do método IsPasswordExpired () para incluir uma verificação para esse campo (e renomearia o método para ShouldChangePassword).
Faça suas intenções explícitas no código.
fonte
Primeiro, nulo ser mau é dogma e, como de costume, funciona melhor como orientação e não como teste de aprovação / não aprovação.
Em segundo lugar, você pode redefinir sua situação de uma maneira que faça sentido que o valor nunca possa ser nulo. InititialPasswordChanged é um booleano definido inicialmente como false; PasswordSetTime é a data e hora em que a senha atual foi definida.
Observe que, embora isso tenha um pequeno custo, agora você pode calcular SEMPRE quanto tempo se passou desde a última vez que uma senha foi definida.
fonte
Ambos são 'seguros / sãos / corretos' se o chamador verificar antes do uso. A questão é o que acontece se o chamador não verificar. Qual é o melhor, algum sabor de erro nulo ou usando um valor inválido?
Não existe uma única resposta correta. Depende do que você está preocupado.
Se as falhas forem realmente ruins, mas a resposta não for crítica ou tiver um valor padrão aceito, talvez seja melhor usar um booleano como sinalizador. Se usar a resposta errada é um problema pior do que travar, usar o null é melhor.
Para a maioria dos casos "típicos", falhas rápidas e forçar os chamadores a verificar são a maneira mais rápida de codificar corretamente, e, portanto, acho que nulo deve ser a opção padrão. Eu não colocaria muita fé nos evangelistas "X é a raiz de todo mal"; eles normalmente não anteciparam todos os casos de uso.
fonte