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 GameUpdate
objeto, geralmente ele não deve se importar em diferenciar entre GameUpdate
, PlayerGameUpdate
ou 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 GameUpdate
objeto tenha ou não um jogador. Se você está tentando acessar a Player
propriedade de a GameUpdate
, está fazendo algo ilógico.
Idiomas digitados como C # nem sequer permitem que você tente acessar a Player
propriedade a GameUpdate
porque GameUpdate
simplesmente 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.