Tipos anuláveis ​​são preferíveis a números mágicos?


22

Ultimamente tenho tido um pouco de debate com um colega de trabalho. Estamos usando especificamente o C #, mas isso pode se aplicar a qualquer idioma com tipos anuláveis. Digamos, por exemplo, que você tenha um valor que represente um máximo. No entanto, esse valor máximo é opcional. Argumento que um número anulável seria preferível. Meu colega de trabalho favorece o uso de zero, citando precedentes. É verdade que coisas como soquetes de rede costumam usar zero para representar um tempo limite ilimitado. Se eu escrevesse um código que lida com soquetes hoje, eu pessoalmente usaria um valor anulável, pois acho que isso representaria melhor o fato de que NÃO há tempo limite.

Qual representação é melhor? Ambos exigem uma verificação de condição para o valor que significa "nenhum", mas acredito que um tipo anulável transmite a intenção um pouco melhor.


6
Se um número for usado, coloque-o em uma constante, não diretamente no código.
Renato Dinhani

@ RenatoDinhaniConceição que não pode ser uma regra geral. Caso contrário, você acabará codificando tudo.
perfil completo de Simon Bergot

Respostas:


24

Considerar:

  • Língua,

  • Estrutura,

  • Contexto.

1. Idioma

Usar ∞ pode ser uma solução para o máximo.

  • JavaScript, por exemplo, tem um infinito. C # não¹.

  • Ada, por exemplo, tem intervalos. C # não.

Em C #, existe int.MaxValue, mas você não pode usá-lo no seu caso. int.MaxValueé o número inteiro máximo, 2.147.483.647. Se no seu código, você tem um valor máximo de algo, como uma pressão máxima aceita antes que algo exploda, usar 2.147.483.647 não faz sentido.

2. Enquadramento

O .NET Framework é bastante inconsistente nesse ponto, e seu uso de valores mágicos pode ser criticado.

Por exemplo, "Hello".IndexOf("Z")retorna um valor mágico -1. É talvez torna mais fácil (não é?) Para manipular o resultado:

int position = "Hello".IndexOf("Z");
if (position > 0)
{
    DoSomething(position);
}

em vez de usar uma estrutura personalizada:

SearchOccurrence occurrence = "Hello".IndexOf("Z");
if (occurrence.IsFound)
{
    DoSomething(occurrence.StartOffset);
}

mas não é intuitivo. Por que -1e não -123? Um iniciante também pode pensar erroneamente que 0significa "Não encontrado" também ou apenas digitar errado (position >= 0).

3. Contexto

Se o seu código estiver relacionado a tempos limites nos soquetes de rede, usar algo que foi usado por todos por décadas para manter a consistência não é uma má idéia . Especialmente, 0pois um tempo limite é muito claro: é um valor que não pode ser zero. Usar uma classe personalizada nesse caso pode tornar as coisas mais difíceis de entender:

class Timeout
{
    // A value indicating whether there is a timeout.
    public bool IsTimeoutEnabled { get; set; }

    // The duration of the timeout, in milliseconds.
    public int Duration { get; set; }
}
  • Posso definir Durationcomo 0 se IsTimeoutEnabledfor verdadeiro?
  • Se IsTimeoutEnabledfor falso, o que acontece se eu definir Durationcomo 100?

Isso pode levar a vários erros. Imagine o seguinte pedaço de código:

this.currentOperation.Timeout = new Timeout
{
    // Set the timeout to 200 ms.; we don't want this operation to be longer than that.
    Duration = 200,
};

this.currentOperation.Run();

A operação é executada por dez segundos. Você consegue ver o que há de errado com esse código, sem ler a documentação da Timeoutclasse?

Conclusão

  • nullexpressa bem a ideia de que o valor não está aqui. Não é fornecido. Não disponível. Não é um número, nem uma string zero / vazia ou qualquer outra coisa. Não o utilize para valores máximos ou mínimos.

  • int.MaxValueestá fortemente relacionado à própria linguagem. Não use int.MaxValuepara um limite máximo de velocidade da Vehicleclasse ou uma velocidade máxima aceitável para uma aeronave etc.

  • Evite valores mágicos como -1no seu código. Eles são enganosos e levam a erros no código.

  • Crie sua própria classe, que seria mais direta, com os valores mínimo / máximo especificados. Por exemplo, VehicleSpeedpode ter VehicleSpeed.MaxValue.

  • Não siga nenhuma diretriz anterior e não use valores mágicos se for uma convenção geral por décadas em um campo muito específico, usado pela maioria das pessoas que escreve código nesse campo.

  • Não se esqueça de misturar abordagens. Por exemplo:

    class DnsQuery
    {
        public const int NoTimeout = 0;
    
        public int Timeout { get; set; }
    }
    
    this.query.Timeout = 0; // For people who are familiar with timeouts set to zero.
    // or
    this.query.Timeout = DnsQuery.NoTimeout; // For other people.
    

¹ Você pode criar seu próprio tipo, que inclui o infinito. Aqui, estou falando intapenas do tipo nativo .


1
"usar algo que foi usado por todos por décadas para manter a consistência não é uma má idéia" / "Não siga nenhuma orientação anterior e não use valores mágicos se for uma convenção geral por décadas em um campo muito específico, usado por a maioria das pessoas escrevendo código neste campo ". - Há um erro de digitação em algum lugar, eu acho?
deworde

1
@deworde Eu acredito que MainMa está se referindo às diretrizes que ele mesmo deu acima dessa.
30712 Joshua Drake

1
Eu discordo do exemplo indexOf, como -1 está fora da string, o que Z certamente está.
30712 Joshua Drake

5
"JavaScript, por exemplo, tem um infinito. C # não." - hein?
BlueRaja - Danny Pflughoeft 30/03

+1 especialmente para "Crie sua própria turma", que é o que eu sugeriria. Sempre que um nu intnão estiver expressando o suficiente sobre o tipo para restringir o problema, considere uma nova estrutura com mais informações (instâncias constantes da estrutura que representam valores mágicos, por exemplo, ou uma enumeração para indicar). Ou considere a programação de contratos ou outras soluções, mas acho que uma estrutura personalizada é mais direta.
CodexArcanum

12

Nulo não é melhor que um número mágico.

O importante é NOMEAR os valores que têm efeitos mágicos, se você precisar deles, e garantir que as definições desses nomes estejam em algum lugar que seja visto por qualquer pessoa que colidir com o valor mágico e o wtf.

if (timeout == 4298435) ... // bad.
if (timeout == null) ... // bad.
if (timeout == NEVER_TIME_OUT) ... // yay! puppies and unicorns!

2
Ok, talvez dependa mais do idioma, mas em C #, você provavelmente faria: if (timeout.HasValue) em vez de uma comparação direta com null.
Matt H

2
Nulo não é pior que o número mágico. Com números mágicos, você nunca sabe qual é o número mágico ... pode ser 0, -1 ou qualquer outra coisa. null é apenas nulo.
marco-fiset

9
Nulo significa ausência de valor. Este é o conceito que muitos números mágicos pretendem expressar. Ser capaz de usar nulo com um tipo anulável é uma solução MUITO melhor do que escolher um valor arbitrário no intervalo de valores possíveis para um tipo de dados.
17 de 26

2
Usar "nulo" como seu valor mágico, se o seu tipo tiver "nulo", tudo bem. O importante é NOMEAR, porque com certeza, como atirar no próximo cara a seguir, não saberá o que você quis dizer. Nulo pode significar "infinito", "ainda não especificado", "bug no código que criou a estrutura de dados" ou várias outras coisas. Somente um nome informa o próximo codificador de que você queria que esse valor estivesse lá e que comportamento você queria que ele desencadeasse.
Mjfgates

1
@CodeInChaos: Eu sei que você pode fazer as duas coisas, mas eu prefiro HasValue. Na verdade, eu não sou um grande fã de null em geral, mas os tipos anuláveis ​​usando HasValue parecem um pouco mais próximos de um tipo Option / Maybe para mim, do qual sou fã.
Matt H

10

MAGIC_NUMBERabsolutamente sempre deve ser evitado o código sempre que possível. nullé uma expressão de intenção muito mais clara.


6

Em C #, muitas classes CLR têm um Emptymembro estático :

  • System.String.Empty
  • System.EventArgs.Empty
  • System.Guid.Empty
  • System.Drawing.Rectangle.Empty
  • System.Windows.Size.Empty

Isso evita que você precise se lembrar de usar um valor mágico ou nulo para construir um objeto vazio.

Mas e se você estiver lidando com um tipo de valor simples como um int? Nesse caso, considere se você está sendo vítima da Obsessão Primitiva . É bem possível que sua propriedade numérica aparentemente simples se beneficie de sua própria classe ou estrutura, o que permitiria especificar o Emptymembro e também adicionar outro comportamento específico a esse tipo de valor.


3

Nesse caso, o valor nulo é uma ótima maneira de indicar que não há um máximo. Geralmente, quando o caso especial significa que o valor em questão não se aplica, que você simplesmente não deseja o recurso que ele configura, null é uma boa indicação disso.

Um problema ao usar null para representar casos especiais é que existe apenas um valor nulo e pode haver vários casos especiais. Nesse caso, eu passaria uma enumeração como um parâmetro adicional, que pode indicar um caso especial, ou para usar o valor int normalmente. (Isso é essencialmente o que o Nullable <> faz por você, embora ele use um booleano em vez de uma enumeração e combine os parâmetros em uma única estrutura.)


3

Nesse caso, acho que um tipo anulável faz todo sentido.

Nulo significa ausência de valor. Esse é um conceito distintamente diferente de um número com valor 0.

Se você quiser dizer "Se eu não fornecer um valor, use o máximo", passar nulo é a maneira correta exata de expressar isso.


1

Nulo: valor de erro comum, não especificado, nulo ou ausência de valor.

Zero: um valor real, mas não necessariamente lógico ou intuitivo (neste contexto). Também é um valor comum na inicialização.

No contexto do seu problema, a timeoutInMillisecondspropriedade é opcional e não há menção de que a sobrecarga dessa abordagem a desqualificaria como uma opção.

Conclusão: Existem exceções e as soluções variam de acordo com o idioma e o domínio; neste caso, eu escolheria nulo. Onde (acredito) algumas pessoas entendem isso errado é quando não separam bem os dados da interface. Eles esperam que qualquer cliente leia a documentação (ou implementação) para determinar como esses valores especiais devem ser usados ​​/ manipulados - os casos especiais vazam para o programa do cliente e isso pode não ser claro. Ao adicionar uma boa camada de abstração, o uso pode ser muito mais claro.


0

Nulo é pior do que usar MagicNumber. Nulo representa a idéia expressa melhor, mas não é consistente entre as plataformas na maneira como se comporta, usar MagicNumbersempre funciona da mesma maneira, o que é benéfico.

dependendo do ambiente / idioma usado null poderia

  • seja simplesmente 0
  • pode não ser um valor legal
  • pode produzir resultados inesperados devido à lógica de três vias

MagicNumber sempre se comporta da mesma maneira.


0

Se você esquecer de verificar o número mágico (acontecerá corretamente), um número mágico continuará por um tempo com dados sem sentido. Muito melhor ter um nulo que cause uma exceção o mais rápido possível.


-1

Nulo não é a única alternativa a um número mágico.

public static int NO_TIMEOUT = 0;  // javaish

Nulo é mau. No exemplo acima, você pode se safar disso, pois o código obviamente seria capaz de lidar com um nulo. Mas, em geral, o que acontece quando você começa a passar nulos é que mais cedo ou mais tarde você recebe uma exceção de ponteiro nulo. Pode não acontecer quando você escreve o código pela primeira vez, mas o código é mantido por muito mais tempo do que apenas a primeira versão. É frequentemente mantido por pessoas que não sabem tanto sobre o sistema quanto os desenvolvedores originais.

Scala (por exemplo) tem uma boa alternativa na classe Option. A classe Option possui um dos dois valores, alguns - que agrupam o valor que você realmente deseja e nenhum - que não têm valor.

Isso torna óbvio para qualquer desenvolvedor que pode não haver um valor e você tem um código melhor para isso. Bem, deve tornar óbvio de qualquer maneira.

E nem todos os números mágicos são um problema. Dependendo do contexto 0, 1, 1024, etc., tudo pode ser óbvio. 347? Sim, esse você deve evitar. :-)


4
-1: justifique "nulo é mau".
deworde

4
Definir um alias para um número não muda o fato de que ainda é um número mágico.
17 de 26

Bem, talvez você tenha uma definição diferente de número mágico do que eu. Por favor, veja en.wikipedia.org/wiki/…
Jon Strayer

1
Eu concordo com Jon Strayer aqui. Nulo é um exemplo de ADT em um idioma que na verdade não suporta ADTs. O OP provavelmente pode se safar aqui, mas, em geral, considero que qualquer linguagem que tenha nulo por ter falhado é um pouco de programadores.
perfil completo de Jeremy Wall
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.