Se nulos são maus, o que deve ser usado quando um valor pode estar significativamente ausente?

26

Essa é uma das regras que são repetidas várias vezes e que me deixa perplexo.

Os nulos são maus e devem ser evitados sempre que possível .

Mas, mas - da minha ingenuidade, deixe-me gritar - às vezes um valor PODE estar significativamente ausente!

Por favor, deixe-me perguntar isso em um exemplo que vem desse código horrível e anti-padrão no qual estou trabalhando no momento . Este é, em essência, um jogo multiplayer baseado em turnos na web, onde os turnos de ambos os jogadores são executados simultaneamente (como em Pokemon e, ao contrário, no xadrez).

Após cada turno, o servidor transmite uma lista de atualizações para o código JS do lado do cliente. A classe Update:

public class GameUpdate
{
    public Player player;
    // other stuff
}

Essa classe é serializada para JSON e enviada para players conectados.

A maioria das atualizações naturalmente tem um jogador associado a elas - afinal, é necessário saber qual jogador fez qual jogada nesse turno. No entanto, algumas atualizações não podem ter um Player significativamente associado a elas. Exemplo: O jogo foi empatado com força porque o limite de turno sem ações foi excedido. Para essas atualizações, acho que é significativo ter o jogador anulado.

Claro, eu poderia "consertar" esse código utilizando herança:

public class GameUpdate
{
    // stuff
}

public class GamePlayerUpdate : GameUpdate
{
    public Player player;
    // other stuff
}

No entanto, não vejo como isso melhora alguma coisa, por dois motivos:

  • O código JS agora simplesmente receberá um objeto sem o Player como uma propriedade definida, que é a mesma que se fosse nula, pois os dois casos exigem verificação se o valor está presente ou ausente;
  • Se outro campo anulável for adicionado à classe GameUpdate, eu precisaria usar herança múltipla para continuar com esse projeto - mas o MI é maligno por si só (de acordo com programadores mais experientes) e, mais importante, o C # não tê-lo, então eu não posso usá-lo.

Tenho a impressão de que este pedaço deste código é um dos muitos em que um bom programador experiente gritaria de horror. Ao mesmo tempo, não consigo ver como, neste lugar, esse nulo está prejudicando algo e o que deve ser feito.

Você poderia me explicar esse problema?

gaazkam
fonte
2
Esta pergunta é específica para JavaScript? Muitas linguagens têm um tipo opcional para esse propósito, mas não sei se o js possui um (embora você possa criar um) ou se funcionaria com o json (embora essa seja uma parte das verificações nulos desagradáveis, em vez de todo o seu código
Richard Formulário 07/07
9
Essa regra é apenas falsa. Estou um pouco espantado com o fato de alguém subscrever essa noção. Existem algumas boas alternativas para nulo em alguns casos. null é realmente bom para representar um valor ausente. Não permita que regras aleatórias da Internet o impeçam de confiar em seu próprio julgamento fundamentado.
usr
11
@usr - concordo totalmente. Nulo é seu amigo. IMHO, não há uma maneira mais clara de representar um valor ausente. Pode ajudá-lo a encontrar erros, pode ajudar a lidar com condições de erro, pode ajudar a pedir perdão (tentativa / captura). O que há para não gostar ?!
Mergulhador Steve
4
uma das primeiras regras de criação de software é "Sempre verifique nulo". Por que isso é mesmo um problema é incompreensível para mim.
MattE 27/02
11
@ MattE - Absolutamente. Por que eu projetaria um padrão de design quando, na verdade, Null é uma coisa boa a ter em sua caixa de ferramentas básica? O que Graham estava sugerindo, IMHO, me parece um excesso de engenharia.
Mergulhador Steve

Respostas:

20

É melhor retornar muitas coisas do que nulo.

  • Uma sequência vazia ("")
  • Uma coleção vazia
  • Uma mônada "opcional" ou "talvez"
  • Uma função que silenciosamente não faz nada
  • Um objeto cheio de métodos que silenciosamente não fazem nada
  • Um valor significativo que rejeita a premissa incorreta da pergunta
    (que é o que fez você considerar nulo em primeiro lugar)

O truque é perceber quando fazer isso. O nulo é realmente bom em explodir na sua cara, mas apenas quando você o pontilha sem verificar. Não é bom explicar o porquê.

Quando você não precisar explodir e não desejar verificar se é nulo, use uma dessas alternativas. Pode parecer estranho retornar uma coleção se ela tiver apenas 0 ou 1 elementos, mas é realmente bom permitir que você lide silenciosamente com os valores ausentes.

Mônadas soam sofisticadas, mas aqui tudo o que estão fazendo é permitir que você torne óbvio que os tamanhos válidos para a coleção são 0 e 1. Se você os tiver, considere-os.

Qualquer um deles faz um trabalho melhor em tornar clara sua intenção do que nulo. Essa é a diferença mais importante.

Pode ajudar a entender por que você está retornando, em vez de simplesmente lançar uma exceção. Exceções são essencialmente uma maneira de rejeitar uma suposição (elas não são a única maneira). Quando você pergunta o ponto em que duas linhas se cruzam, você assume que elas se cruzam. Eles podem ser paralelos. Você deve lançar uma exceção "linhas paralelas"? Bem, você poderia, mas agora precisa lidar com essa exceção em outro lugar ou deixá-la interromper o sistema.

Se você preferir ficar onde está, pode transformar a rejeição dessa suposição em um tipo de valor que possa expressar resultados ou rejeições. Não é tão estranho assim. Fazemos isso em álgebra o tempo todo. O truque é tornar esse valor significativo para que possamos entender o que aconteceu.

Se o valor retornado puder expressar resultados e rejeições, ele precisará ser enviado para um código que possa lidar com ambos. O polimorfismo é realmente poderoso para usar aqui. Em vez de simplesmente trocar verificações nulas por isPresent()verificações, você pode escrever um código que se comporte bem em ambos os casos. Substituindo valores vazios por valores padrão ou silenciosamente sem fazer nada.

O problema com null é que isso pode significar muitas coisas. Então isso acaba destruindo informações.


No meu experimento de pensamento nulo favorito, peço que você imagine uma máquina complexa Rube Goldbergiana que sinaliza mensagens codificadas usando luz colorida pegando lâmpadas coloridas de uma correia transportadora, enroscando-as em um soquete e ligando-as. O sinal é controlado pelas diferentes cores das lâmpadas colocadas na correia transportadora.

Você foi solicitado a tornar essa coisa horrivelmente cara compatível com a RFC3.14159, que afirma que deve haver um marcador entre as mensagens. O marcador não deve ter luz. Deve durar exatamente um pulso. A mesma quantidade de tempo que uma única luz colorida brilha normalmente.

Você não pode simplesmente deixar espaços entre as mensagens porque a engenhoca aciona alarmes e pára se não conseguir encontrar uma lâmpada para colocar no soquete.

Todo mundo familiarizado com esse projeto estremece ao tocar no mecanismo ou no código de controle. Não é fácil mudar. O que você faz? Bem, você pode mergulhar e começar a quebrar coisas. Talvez atualize esse design hediondo. Sim, você poderia torná-lo muito melhor. Ou você pode conversar com o zelador e começar a coletar lâmpadas queimadas.

Essa é a solução polimórfica. O sistema nem precisa saber que algo mudou.


É muito bom se você conseguir isso. Ao codificar uma rejeição significativa de uma suposição em um valor, você não precisa forçar a pergunta a mudar.

Quantas maçãs você me deve se eu lhe der uma maçã? -1. Porque te devia 2 maçãs de ontem. Aqui, a suposição da pergunta "você me deve" é rejeitada com um número negativo. Embora a pergunta esteja errada, informações significativas são codificadas nesta resposta. Ao rejeitá-lo de maneira significativa, a questão não é forçada a mudar.

No entanto, às vezes, mudar a pergunta é realmente o caminho a percorrer. Se a suposição puder ser mais confiável, ainda que útil, considere fazer isso.

Seu problema é que, normalmente, as atualizações vêm dos jogadores. Mas você descobriu a necessidade de uma atualização que não vem do jogador 1 ou do jogador 2. Você precisa de uma atualização que diga que o tempo expirou. Nenhum jogador está dizendo isso, então o que você deve fazer? Deixar o jogador nulo parece tão tentador. Mas isso destrói informações. Você está tentando codificar o conhecimento em um buraco negro.

O campo do jogador não diz quem acabou de se mudar. Você acha que sim, mas esse problema prova que é uma mentira. O campo player indica a origem da atualização. Sua atualização expirada está chegando no relógio do jogo. Minha recomendação é dar ao relógio o crédito que merece e alterar o nome do playercampo para algo como source.

Dessa forma, se o servidor estiver desligando e precisar suspender o jogo, ele poderá enviar uma atualização que relate o que está acontecendo e se liste como a fonte da atualização.

Isso significa que você nunca deve deixar algo de fora? Não. Mas você precisa considerar como nada pode ser assumido como realmente significando um único valor padrão bom e conhecido. Nada é realmente fácil de usar em excesso. Portanto, tenha cuidado ao usá-lo. Existe uma escola de pensamento que abraça a favor de nada . É chamado de convenção sobre configuração .

Eu gosto disso, mas apenas quando a convenção é claramente comunicada de alguma forma. Não deve ser uma maneira de embaçar os novatos. Mas mesmo se você estiver usando isso, null ainda não é a melhor maneira de fazer isso. Nulo é um nada perigoso . Nulo finge ser algo que você pode usar até usá-lo, então abre um buraco no universo (quebra seu modelo semântico). A menos que abrir um buraco no universo seja o comportamento que você precisa, por que está mexendo com isso? Use outra coisa.

candied_orange
fonte
9
"retorne uma coleção se ela tiver apenas 0 ou 1 elementos" - desculpe, mas acho que não a recebi :( No meu POV, fazer isso (a) adiciona estados inválidos desnecessários à classe (b) requer documentar que a coleção deve ter no máximo 1 elemento (c) não parece resolver o problema de qualquer maneira, pois verificar se a coleção contém seu único elemento antes de acessá-lo é necessário de qualquer maneira, ou então uma exceção será lançada?
gaazkam
2
@gaazkam see? Eu te disse que incomoda as pessoas. No entanto, é exatamente o que você tem feito toda vez que usa a string vazia.
Candied_orange
16
O que acontece quando há um valor válido que passa a ser uma string ou um conjunto vazio?
Blrfl 07/07
5
De qualquer forma, @gaazkam, você não verifica explicitamente se a coleção possui itens. Fazer loop (ou mapear) sobre uma lista vazia é uma ação segura que não tem efeito.
7338 RubberDuck
3
Eu sei que você está respondendo à pergunta sobre o que alguém deve fazer em vez de retornar nulo, mas eu realmente discordo de muitos desses pontos. No entanto, como não estamos específica linguagem e há uma razão para quebrar todas as regras Eu me recuso a downvote
Liath
15

nullnão é realmente o problema aqui. O problema é como a maioria das linguagens de programação atuais lida com informações ausentes; eles não levam esse problema a sério (ou pelo menos não fornecem o apoio e a segurança que a maioria dos terráqueos precisa para evitar as armadilhas). Isso leva a todos os problemas que aprendemos a temer (Javas NullPointerException, Segfaults, ...).

Vamos ver como lidar com esse problema de uma maneira melhor.

Outros sugeriram o Padrão de Objeto Nulo . Embora eu ache que é aplicável às vezes, há muitos cenários em que simplesmente não existe um valor padrão de tamanho único. Nesses casos, os consumidores das informações possivelmente ausentes devem poder decidir por si mesmos o que fazer se essas informações estiverem ausentes.

Existem linguagens de programação que fornecem segurança contra ponteiros nulos (chamada segurança de vácuo) em tempo de compilação. Para dar alguns exemplos modernos: Kotlin, Rust, Swift. Nesses idiomas, o problema da falta de informações foi levado a sério e o uso de null é totalmente indolor - o compilador impedirá que você desreferencie ponteiros nulos.
Em Java, foram feitos esforços para estabelecer as anotações @ Nullable / @ NotNull junto com os plugins do compilador + IDE para alcançar o mesmo efeito. Não foi realmente bem-sucedido, mas está fora do escopo desta resposta.

Um tipo explícito e genérico que denota possível ausência é outra maneira de lidar com isso. Por exemplo, em Java existe java.util.Optional. Pode funcionar:
no nível do projeto ou da empresa, você pode estabelecer regras / diretrizes

  • use apenas bibliotecas de terceiros de alta qualidade
  • sempre use em java.util.Optionalvez denull

A comunidade / ecossistema de idiomas pode ter encontrado outras maneiras de resolver esse problema melhor do que o compilador / intérprete.

marstato
fonte
Você poderia elaborar gentilmente, como os Java Optionals são melhores que os nulos? Enquanto eu leio a documentação , tentar obter um valor do opcional produz uma exceção se o valor estiver ausente; parece que estamos no mesmo lugar: é necessário um cheque e, se esquecermos um, estamos ferrados?
11788 gauckam
3
@gaazkam Você está certo, ele não fornece segurança em tempo de compilação. Além disso, o próprio opcional pode ser nulo, outro problema. Mas é explícito (comparado a variáveis ​​possivelmente sendo comentários nulos / doc). Isso oferece apenas um benefício real se, para toda a base de código, for estabelecida uma regra de codificação. Você não pode definir coisas nulas. Se houver informações ausentes, use Opcional. Isso força explicitação. As verificações nulas usuais tornam-se chamadas paraOptional.isPresent
marstato 07/07
8
@marstato substituindo verificações nulos com isPresent() não é o uso recomendado de Opcional
candied_orange
10

Não vejo mérito na afirmação de que nulo é ruim. Eu verifiquei as discussões sobre isso e elas só me deixam impaciente e irritada.

Nulo pode ser usado errado, como um "valor mágico", quando na verdade significa alguma coisa. Mas você teria que fazer uma programação bastante distorcida para chegar lá.

Nulo deve significar "não existe", que não foi criado (ainda) ou (já) foi ou não foi fornecido se for um argumento. Não é um estado, está faltando tudo. Em uma configuração OO em que as coisas são criadas e destruídas o tempo todo, é uma condição válida e significativa diferente de qualquer estado.

Com null, você é atingido no tempo de execução, onde é atingido no momento da compilação com algum tipo de alternativa nula com segurança de tipo.

Não é inteiramente verdade, posso receber um aviso por não verificar nulo antes de usar a variável. E não é o mesmo. Talvez eu não queira poupar os recursos de um objeto que não tem propósito. E, mais importante, terei que verificar a alternativa da mesma forma para obter o comportamento desejado. Se eu esquecer disso, prefiro obter uma NullReferenceException em tempo de execução do que algum comportamento indefinido / não intencional.

Há um problema a ser observado. Em um contexto multithread, você teria que se proteger contra as condições de corrida atribuindo uma variável local primeiro antes de executar a verificação de nulo.

O problema com null é que isso pode significar muito para muitas coisas. Então isso acaba destruindo informações.

Não, não significa / não significa nada, então não há nada para destruir. O nome e o tipo da variável / argumento sempre dirão o que está faltando, isso é tudo o que há para saber.

Editar:

Estou na metade do vídeo candied_orange vinculado em uma de suas outras respostas sobre programação funcional e é muito interessante. Eu posso ver a utilidade disso em certos cenários. A abordagem funcional que eu vi até agora, com trechos de código voando, normalmente me faz estremecer quando usado em uma configuração OO. Mas acho que tem o seu lugar. Algum lugar. E acho que haverá idiomas que farão um bom trabalho ocultando o que eu considero uma bagunça confusa sempre que o encontrar em C #, idiomas que fornecem um modelo limpo e viável.

Se esse modelo funcional é a sua mentalidade / estrutura, não há realmente nenhuma alternativa para levar adiante contratempos como descrições de erros documentadas e, finalmente, deixar o chamador lidar com o lixo acumulado que foi arrastado ao longo do caminho de volta ao ponto de iniciação. Aparentemente, é assim que a programação funcional funciona. Chame isso de limitação, chame de novo paradigma. Você pode considerar que é um hack para os discípulos funcionais que lhes permite continuar um pouco mais no caminho profano; você pode se deliciar com essa nova maneira de programar que abre novas possibilidades interessantes. Seja como for, parece-me inútil entrar nesse mundo, voltar à maneira tradicional de OO de fazer as coisas (sim, podemos lançar exceções, desculpe), escolher algum conceito,

Qualquer conceito pode ser usado da maneira errada. De onde eu estou, as "soluções" apresentadas não são alternativas para um uso apropriado de null. Eles são conceitos de outro mundo.

Martin Maat
fonte
9
Nulo destrói o conhecimento de que tipo de nada ele representa. Há o tipo de nada que deve ser silenciosamente ignorado. O tipo de nada que precisa interromper o sistema para evitar um estado inválido. O tipo de nada que significa que o programador estragou tudo e precisa corrigir o código. O tipo de nada que significa que o usuário pediu algo que não existe e precisa ser educadamente informado. Desculpe, não. Todos estes foram codificados como nulos. Por isso, se você codificar algum deles como nulo, não ficará surpreso se ninguém souber o que você quer dizer. Você está nos forçando a adivinhar a partir do contexto.
Candied_orange
3
@candied_orange Você pode fazer exatamente o mesmo argumento sobre qualquer tipo opcional: NoneRepresenta….
Deduplicator
3
@candied_orange Eu ainda não entendi o que você quer dizer. Diferentes tipos de nada? Se você usa null quando deveria ter usado um enum, talvez? Existe apenas um tipo de nada. A razão para algo não ser nada pode variar, mas isso não muda o nada nem a resposta apropriada para algo não ser nada. Se você espera um valor em uma loja que não esteja lá e que seja digno de nota, você lança ou registra no momento em que consulta e não obtém nada, não passa nulo como argumento para um método e, nesse método, tenta descobrir por que você ficou nulo. Nulo não seria o erro.
Martin Maat
2
Nulo é o erro, porque você teve a chance de codificar o significado do nada. Sua compreensão do nada não exige tratamento imediato do nada. 0, nulo, NaN, sequência vazia, conjunto vazio, nulo, objeto nulo, não aplicável, exceção não implementada, nada aparece em muitas formas. Você não precisa esquecer o que sabe agora apenas porque não quer lidar com o que sabe agora. Se eu pedir para você pegar a raiz quadrada de -1, você pode lançar uma exceção para me informar que é impossível e esquecer tudo ou inventar números imaginários e preservar o que você sabe.
Candied_orange 8/07
11
@ MartinMaat, você faz parecer que uma função deve gerar uma exceção sempre que suas expectativas são violadas. Isto simplesmente não é verdade. Muitas vezes há casos de esquina para lidar. Se você realmente não consegue ver isso, leia isto #
candied_orange
7

Sua pergunta.

Mas, mas - da minha ingenuidade, deixe-me gritar - às vezes um valor PODE estar significativamente ausente!

Totalmente a bordo com você lá. No entanto, existem algumas advertências em que entrarei em um segundo. Mas primeiro:

Os nulos são maus e devem ser evitados sempre que possível.

Eu acho que essa é uma afirmação bem-intencionada, mas generalizada.

Nulos não são maus. Eles não são um antipadrão. Eles não são algo que deve ser evitado a todo custo. No entanto, os nulos são propensos a erros (da falha em verificar o nulo).

Mas não entendo como a falta de verificação de nulo é de alguma forma a prova de que nulo é inerentemente errado de usar.

Se nulo for inválido, os índices de matriz e ponteiros C ++ também serão. Eles não são. Eles são fáceis de dar um tiro no pé, mas não devem ser evitados a todo custo.

Para o restante desta resposta, vou adaptar a intenção de "nulos são maus" para um mais nítido " nulos devem ser tratados com responsabilidade ".


Sua solução

Embora eu concorde com sua opinião sobre o uso de null como regra global; Não concordo com o uso de null neste caso específico.

Para resumir o feedback abaixo: você está entendendo mal o propósito da herança e do polimorfismo. Embora seu argumento para usar nulo tenha validade, sua "prova" adicional do motivo pelo qual a outra maneira é ruim se baseia no uso indevido de herança, tornando seus pontos um tanto irrelevantes para o problema nulo.

A maioria das atualizações naturalmente tem um jogador associado a elas - afinal, é necessário saber qual jogador fez qual jogada nesse turno. No entanto, algumas atualizações não podem ter um Player significativamente associado a elas.

Se essas atualizações forem significativamente diferentes (quais são), você precisará de dois tipos de atualização separados.

Considere mensagens, por exemplo. Você tem mensagens de erro e mensagens de bate-papo de MI. Mas, embora possam conter dados muito semelhantes (uma mensagem de seqüência de caracteres e um remetente), eles não se comportam da mesma maneira. Eles devem ser usados ​​separadamente, como classes diferentes.

O que você está fazendo agora é diferenciar efetivamente entre dois tipos de atualização com base na nulidade da propriedade Player. Isso equivale a decidir entre uma mensagem IM e uma mensagem de erro com base na existência de um código de erro. Funciona, mas não é a melhor abordagem.

  • O código JS agora simplesmente receberá um objeto sem o Player como uma propriedade definida, que é a mesma que se fosse nula, pois os dois casos exigem verificação se o valor está presente ou ausente;

Seu argumento se baseia em erros de polimorfismo. Se você possui um método que retorna um GameUpdateobjeto, geralmente ele não deve se importar em diferenciar entre GameUpdate, PlayerGameUpdateou NewlyDevelopedGameUpdate.

Uma classe base precisa ter um contrato de trabalho completo, ou seja, suas propriedades são definidas na classe base e não na classe derivada (isto é, o valor dessas propriedades base pode, obviamente, ser substituído nas classes derivadas).

Isso torna seu argumento discutível. Em boas práticas, você nunca deve se importar que seu GameUpdateobjeto tenha ou não um jogador. Se você está tentando acessar a Playerpropriedade de a GameUpdate, está fazendo algo ilógico.

Idiomas digitados como C # nem sequer permitem que você tente acessar a Playerpropriedade a GameUpdateporque GameUpdatesimplesmente não possui a propriedade. O Javascript é consideravelmente mais relaxado em sua abordagem (e não requer pré-compilação), portanto não se preocupa em corrigi-lo e, em vez disso, explode em tempo de execução.

  • Se outro campo anulável for adicionado à classe GameUpdate, eu precisaria usar herança múltipla para continuar com esse projeto - mas o MI é maligno por si só (de acordo com programadores mais experientes) e, mais importante, o C # não tê-lo, então eu não posso usá-lo.

Você não deve criar classes com base na adição de propriedades anuláveis. Você deve criar classes com base em tipos de atualizações de jogos funcionalmente exclusivos .


Como evitar os problemas com null em geral?

Eu acho que é relevante elaborar como você pode expressar significativamente a ausência de um valor. Essa é uma coleção de soluções (ou abordagens ruins) em nenhuma ordem específica.

1. Use null de qualquer maneira.

Essa é a abordagem mais fácil. No entanto, você está se inscrevendo para uma tonelada de verificações nulas e (quando não o faz) solucionando problemas de exceções de referência nula.

2. Use um valor sem sentido não nulo

Um exemplo simples aqui é indexOf():

"abcde".indexOf("b"); // 1
"abcde".indexOf("f"); // -1

Em vez de null, você obtém-1 . Em um nível técnico, esse é um valor inteiro válido. No entanto, um valor negativo é uma resposta sem sentido, pois se espera que os índices sejam números inteiros positivos.

Logicamente, isso só pode ser usado para casos em que o tipo de retorno permite mais valores possíveis do que podem ser retornados de forma significativa (indexOf = números inteiros positivos; int = números inteiros negativos e positivos)

Para tipos de referência, você ainda pode aplicar esse princípio. Por exemplo, se você tiver uma entidade com um ID inteiro, poderá retornar um objeto fictício com seu ID definido como -1, em vez de nulo.
Você simplesmente precisa definir um "estado simulado" pelo qual possa medir se um objeto é realmente utilizável ou não.

Observe que você não deve confiar em valores de sequência predeterminados se não controlar o valor da sequência. Você pode definir o nome de um usuário como "nulo" quando quiser retornar um objeto vazio, mas encontrará problemas quando Christopher Null se inscrever no seu serviço .

3. Lance uma exceção.

Não. Apenas não. Exceções não devem ser tratadas para fluxo lógico.

Dito isto, há alguns casos em que você não deseja ocultar a exceção (referência nula), mas mostra uma exceção apropriada. Por exemplo:

var existingPerson = personRepository.GetById(123);

Isso pode gerar um PersonNotFoundException(ou similar) quando não houver uma pessoa com ID 123.

De certa forma, obviamente, isso é aceitável apenas nos casos em que a não localização de um item impossibilita o processamento posterior. Se não for possível encontrar um item e o aplicativo continuar, você NUNCA deve lançar uma exceção . Não posso enfatizar isso o suficiente.

Flater
fonte
5

A razão pela qual os nulos são ruins não é que o valor ausente seja codificado como nulo, mas que qualquer valor de referência possa ser nulo. Se você tem C #, provavelmente está perdido de qualquer maneira. Não vejo sentido em usar algum opcional lá porque um tipo de referência já é opcional. Mas, para Java, existem anotações @Notnull, que você parece poder configurar no nível do pacote , e para valores que podem ser perdidos, você pode usar a anotação @Nullable.

max630
fonte
Sim! O problema é um padrão sem sentido.
Deduplicator
Discordo. A programação em um estilo que evita nulo leva a que menos variáveis ​​de referência sejam nulas, levando a menos variáveis ​​de referência que não deveriam ser nulas. Não é 100%, mas talvez 90%, o que vale a pena IMO.
Reponha Monica
1

Nulos não são maus, não existe realisticamente nenhuma maneira de implementar nenhuma das alternativas sem, em algum momento, lidar com algo que parece nulo.

Nulos são, no entanto, perigosos . Assim como qualquer outra construção de baixo nível, se você errar, não há nada abaixo para pegá-lo.

Eu acho que existem muitos argumentos válidos contra o null:

  • Que se você já estiver em um domínio de alto nível, existem padrões mais seguros do que usar o modelo de baixo nível.

  • A menos que você precise das vantagens de um sistema de baixo nível e esteja preparado para ser cauteloso, talvez você não deva usar uma linguagem de baixo nível.

  • etc ...

Tudo isso é válido e, além disso, não é necessário escrever esforços para getters e setters, etc., como um motivo comum para não seguir esse conselho. Não é realmente surpreendente que muitos programadores tenham chegado à conclusão de que os nulos são perigosos e preguiçosos (uma combinação ruim!), Mas, como muitos outros conselhos "universais", eles não são tão universais quanto são redigidos.

As pessoas boas que escreveram a língua pensaram que seriam úteis para alguém. Se você entender as objeções e ainda achar que elas são certas para você, ninguém mais saberá melhor.

drjpizzle
fonte
A questão é que "conselho universal" geralmente não é redigido como "universalmente", como as pessoas afirmam. Se você encontrar a fonte original de tais conselhos, eles geralmente falam das advertências que os programadores experientes conhecem. O que acontece é que, quando alguém lê o conselho, eles tendem a ignorar essas advertências e se lembram apenas da parte do "conselho universal"; portanto, quando relacionam esse conselho a outras pessoas, fica muito mais "universal" do que o autor pretendia. .
Nicol Bolas
11
@ NicolBolas, você está certo, mas a fonte original não é o conselho que a maioria das pessoas cita, é a mensagem retransmitida. O ponto que eu queria enfatizar era apenas que muitos programadores foram instruídos a 'nunca fazer X, X é ruim / ruim / o que for' e, exatamente como você diz, falta muitas advertências. Talvez tenham sido descartados, talvez nunca estejam presentes, o resultado final é o mesmo.
drjpizzle
0

Java tem um Optional classe com um ifPresent+ lambda explícito para acessar qualquer valor com segurança. Isso também pode ser feito em JS:

Vejo esta pergunta StackOverflow .

A propósito: muitos puristas acharão esse uso duvidoso, mas acho que não. Recursos opcionais existem.

Joop Eggen
fonte
Uma referência a um java.lang.Optionalser nulltambém não pode?
Deduplicator
@Duplicador Não, Optional.of(null)lançaria uma exceção, Optional.ofNullable(null)daria Optional.empty()- o valor não existe.
Joop Eggen
E Optional<Type> a = null?
Deduplicator
@Duplicator true, mas você deve usar Optional<Optional<Type>> a = Optional.empty();;) - Em outras palavras, em JS (e outros idiomas), essas coisas não serão possíveis. Scala com valores padrão serviria. Java talvez com um enum. JS duvido: Optional<Type> a = Optional.empty();.
Joop Eggen
Então, passamos de um indireto e potencial nullou vazio, para dois mais alguns métodos extras no objeto interposto. Isso é uma conquista e tanto.
Deduplicator
0

A CAR Hoare considerou nulo o seu "erro de bilhão de dólares". No entanto, é importante observar que ele achou que a solução não era "sem nulos em lugar algum", mas "ponteiros não-nulos como padrão e nula somente onde são semanticamente necessários".

O problema com null nas linguagens que repetem seu erro é que você não pode dizer que uma função espera dados não anuláveis; é fundamentalmente inexprimível no idioma. Isso força as funções a incluir muitos clichês desnecessários de manipulação nula, e esquecer uma verificação nula é uma fonte comum de bugs.

Para driblar essa falta fundamental de expressividade, algumas comunidades (por exemplo, a comunidade Scala) não usam null. No Scala, se você deseja um valor anulável do tipo T, adota um argumento do tipo Option[T]e, se deseja um valor não anulável, apenas adota a T. Se alguém passar um nulo para o seu código, ele explodirá. No entanto, considera-se que o código de chamada é o código de buggy. Optioné uma boa alternativa para nulo, porque os padrões comuns de manipulação de nulos são extraídos para funções de biblioteca como .map(f)ou .getOrElse(someDefault).

user311781
fonte