¿Los tipos anulables son preferibles a los números mágicos?

22

Últimamente he tenido un pequeño debate con un compañero de trabajo. Estamos utilizando específicamente C #, pero esto podría aplicarse a cualquier lenguaje con tipos anulables. Digamos, por ejemplo, que tiene un valor que representa un máximo. Sin embargo, este valor máximo es opcional. Sostengo que un número anulable sería preferible. Mi compañero de trabajo favorece el uso de cero, citando precedentes. Por supuesto, cosas como los sockets de red a menudo han usado cero para representar un tiempo de espera ilimitado. Si escribiera código que tratara con sockets hoy, personalmente usaría un valor anulable, ya que siento que representaría mejor el hecho de que NO hay tiempo de espera.

¿Qué representación es mejor? Ambos requieren una comprobación de condición para el valor que significa "ninguno", pero creo que un tipo anulable transmite la intención un poco mejor.

Matt H
fuente
66
Si se usa un número, póngalo en una constante, no directamente en el código.
Renato Dinhani
@ RenatoDinhaniConceição que no puede ser una regla general. De lo contrario, terminas softcoding todo.
Simon Bergot

Respuestas:

24

Considerar:

  • Idioma,

  • Marco de referencia,

  • Contexto.

1. Idioma

Usar ∞ puede ser una solución para un máximo.

  • JavaScript, por ejemplo, tiene un infinito. C # no.

  • Ada, por ejemplo, tiene rangos. C # no lo hace.

En C #, la hay int.MaxValue, pero no puede usarla en su caso. int.MaxValuees el número entero máximo, 2,147,483,647. Si en su código, tiene un valor máximo de algo, como una presión máxima aceptada antes de que algo explote, usar 2,147,483,647 no tiene sentido.

2. Marco

.NET Framework es bastante inconsistente en este punto, y su uso de valores mágicos puede ser criticado.

Por ejemplo, "Hello".IndexOf("Z")devuelve un valor mágico -1. Tal vez hace que sea más fácil (¿verdad?) Manipular el resultado:

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

en lugar de usar una estructura personalizada:

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

pero no es intuitivo en absoluto. ¿Por qué -1y no -123? Un principiante también puede pensar erróneamente que eso 0significa "No encontrado" o simplemente escribir mal (position >= 0).

3. Contexto

Si su código está relacionado con los tiempos de espera en los sockets de red, no es una mala idea usar algo que fue utilizado por todos durante décadas para ser coherente . Especialmente, 0para un tiempo de espera es muy claro: es un valor que no puede ser cero. Usar una clase personalizada en este caso puede hacer que las cosas sean más difíciles 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; }
}
  • ¿Puedo establecer Durationa 0 si IsTimeoutEnabledes cierto?
  • Si IsTimeoutEnabledes falso, ¿qué sucede si configuro Durationa 100?

Esto puede conducir a múltiples errores. Imagine la siguiente pieza 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();

La operación se ejecuta durante diez segundos. ¿Puedes ver lo que está mal con este código, sin leer la documentación de la Timeoutclase?

Conclusión

  • nullexpresa bien la idea de que el valor no está aquí. No está provisto. No disponible. No es un número, ni una cadena cero / vacía, ni nada. No lo use para valores máximos o mínimos.

  • int.MaxValueestá fuertemente relacionado con el lenguaje en sí. No lo use int.MaxValuepara un límite de velocidad máxima de Vehicleclase o una velocidad máxima aceptable para una aeronave, etc.

  • Evita los valores mágicos como -1en tu código. Son engañosos y conducen a errores en el código.

  • Cree su propia clase que sería más sencilla, con los valores mínimos / máximos especificados. Por ejemplo VehicleSpeedpuede tener VehicleSpeed.MaxValue.

  • No siga ninguna directriz anterior y use valores mágicos si es una convención general durante décadas en un campo muy específico, utilizado por la mayoría de las personas que escriben código en este campo.

  • No olvides mezclar enfoques. Por ejemplo:

    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.
    

¹ Puede crear su propio tipo que incluye infinito. Aquí, solo estoy hablando del inttipo nativo .

Arseni Mourzenko
fuente
1
"Usar algo que fue usado por todos por décadas para ser consistente no es una mala idea" / "No sigas ninguna directriz previa y usa valores mágicos si es una convención general durante décadas en un campo muy específico, usado por la mayoría de la gente escribe código en este campo ". - Creo que hay un error tipográfico en alguna parte.
deworde
1
@deworde Creo que MainMa se refiere a las pautas que él mismo dio por encima de esa.
Joshua Drake
1
No estoy de acuerdo con el ejemplo indexOf, ya que -1 está fuera de la cadena, que Z sin duda es.
Joshua Drake
55
"JavaScript, por ejemplo, tiene un infinito. C # no". - ¿eh?
BlueRaja - Danny Pflughoeft
+1 especialmente para "Crea tu propia clase", que es lo que habría sugerido. Cada vez que un bare intno exprese lo suficiente sobre el tipo para restringir el problema, considere una nueva estructura con más información (instancias constantes de la estructura que representan valores mágicos, por ejemplo, o una enumeración para indicar). O considere la programación por contrato u otras soluciones, pero creo que una estructura personalizada es más sencilla.
CodexArcanum
12

Nulo no es mejor que un número mágico.

Lo importante es NOMBRAR los valores que tienen efectos mágicos, si tiene que tener esos valores, y asegurarse de que las definiciones de esos nombres estén en algún lugar que pueda ver cualquiera que se encuentre con el valor mágico y wtf.

if (timeout == 4298435) ... // bad.
if (timeout == null) ... // bad.
if (timeout == NEVER_TIME_OUT) ... // yay! puppies and unicorns!
mjfgates
fuente
2
Ok, tal vez depende más del lenguaje, pero en C #, probablemente harías: if (timeout.HasValue) en lugar de una comparación directa con null.
Matt H
2
Nulo no es peor que el número mágico. Con los números mágicos, nunca se sabe cuál es el número mágico ... podría ser 0, -1 u otra cosa. nulo es simplemente nulo.
marco-fiset
99
Nulo significa ausencia de valor. Este es el concepto que muchos números mágicos tienen la intención de expresar. Ser capaz de usar nulo con un tipo anulable es una solución MUCHO mejor que elegir un valor arbitrario del rango de valores posibles para un tipo de datos.
17 de 26
2
Usar "nulo" como su valor mágico, si su tipo tiene "nulo", está bien. Lo importante es NOMBRARLO, porque seguro que, como disparar al próximo tipo que viene, no sabrá a qué se refería. Nulo puede significar "infinito", "aún no especificado", "error en el código que creó la estructura de datos" o cualquier otra cantidad de cosas. Solo un nombre le permite al próximo codificador saber que usted quiso decir que ese valor estaba allí y qué comportamiento quería que activara.
mjfgates
1
@CodeInChaos: Sé que puedes hacer ambas cosas, pero prefiero HasValue. En realidad, no soy un gran admirador de nulo en general, pero los tipos anulables que usan HasValue se sienten un poco más cerca de un tipo Opción / Quizás, del que soy fanático.
Matt H
10

MAGIC_NUMBEREl código siempre debe evitarse siempre que sea posible. nulles una expresión de intención mucho más clara.

DeadMG
fuente
6

En C #, muchas clases de CLR tienen un Emptymiembro estático :

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

Esto evita que tenga que recordar si usar un valor mágico o usar nulo para construir un objeto vacío.

Pero, ¿qué pasa si se trata de un tipo de valor simple como un int? En ese caso, considera si estás siendo víctima de la obsesión primitiva . Es muy posible que su propiedad numérica aparentemente simple se beneficie de su propia clase o estructura, lo que le permitiría especificar el Emptymiembro y también agregar otro comportamiento específico a ese tipo de valor.

Kyralessa
fuente
3

En este caso, el valor nulo es una excelente manera de indicar que no hay un máximo. En general, cuando el caso especial significa que el valor en cuestión no se aplica, que simplemente no desea la característica que configura, nulo es una buena indicación de esto.

Un problema con el uso de nulo para representar casos especiales es que solo hay un valor nulo y puede haber múltiples casos especiales. En este caso, pasaría una enumeración como parámetro adicional, que puede indicar un caso especial, o usar el valor int normalmente. (Esto es esencialmente lo que Nullable <> hace por usted, aunque utiliza un booleano en lugar de una enumeración y combina los parámetros en una sola estructura).

JGWeissman
fuente
3

En este caso, creo que un tipo anulable tiene mucho sentido.

Nulo significa ausencia de valor. Este es un concepto claramente diferente a un número que tiene un valor de 0.

Si desea decir "Si no le doy un valor, use el máximo", entonces pasar nulo es la forma exacta correcta de expresar eso.

17 de 26
fuente
1

Nulo: valor de error común, no especificado, nulo o ausencia de valor.

Cero: un valor real, pero no necesariamente lógico o intuitivo (en este contexto). También un valor común en la inicialización.

En el contexto de su problema, la timeoutInMillisecondspropiedad es opcional y no se menciona que la sobrecarga de este enfoque lo descalifique como una opción.

Conclusión: hay excepciones y las soluciones varían según el idioma y el dominio; en este caso, elegiría Null. Donde (creo) que algunas personas se equivocan es cuando no separan bien los datos de la interfaz. Solo esperan que cualquier cliente lea la documentación (o implementación) para determinar cómo se utilizarán / manejarán estos valores especiales: los casos especiales se filtran en el programa del cliente y puede ser bastante poco claro. Al agregar una buena capa de abstracción, el uso puede ser mucho más claro.

justin
fuente
0

Nulo es peor de usar que MagicNumber. Null representa la idea expresada mejor, pero no es coherente en todas las plataformas en cómo se comporta, el uso MagicNumbersiempre funciona igual, lo cual es beneficioso.

dependiendo del entorno / lenguaje utilizado, nulo podría

  • simplemente sea 0
  • puede no ser un valor legal
  • puede producir resultados inesperados debido a la lógica de tres vías

MagicNumber siempre se comporta igual.

Ryathal
fuente
0

Si olvida verificar el número mágico (va a suceder correctamente), entonces un número mágico continuará por un tiempo con datos sin sentido. Mucho mejor tener un valor nulo que cause una excepción lo antes posible.

Tom Hawtin - tackline
fuente
-1

Nulo no es la única alternativa a un número mágico.

public static int NO_TIMEOUT = 0;  // javaish

Nulo es malo. En el ejemplo anterior, es posible que pueda salirse con la suya ya que el código obviamente podría manejar un valor nulo. Pero, en general, lo que sucede cuando comienzas a pasar nulos es que tarde o temprano obtienes una excepción de puntero nulo. Puede que no suceda cuando escribe el código por primera vez, pero el código se mantiene durante mucho más tiempo que solo la primera versión. A menudo es mantenido por personas que no saben tanto sobre el sistema como los desarrolladores originales.

Scala (por ejemplo) tiene una buena alternativa en la clase Opción. La clase Opción tiene uno de dos valores, Algunos, que envuelve el valor que realmente desea y Ninguno, que no tiene ningún valor.

Eso hace obvio para cualquier desarrollador que puede que no haya un valor y que tenga un mejor código para eso. Bueno, debería hacerlo obvio de todos modos.

Y no todos los números mágicos son un problema. Dependiendo del contexto 0, 1, 1024, etc., todo puede ser obvio. 347? Sí, ese que debes evitar. :-)

Jon Strayer
fuente
44
-1: Justifica "nulo es malo".
deworde
44
Definir un alias para un número no cambia el hecho de que sigue siendo un número mágico.
17 de 26
Bueno, quizás tengas una definición diferente de número mágico que yo. Ver en.wikipedia.org/wiki/…
Jon Strayer
1
Estoy de acuerdo con Jon Strayer aquí. Null es un ejemplo de ADT en un lenguaje que en realidad no es compatible con ADT. El OP probablemente puede salirse con la suya aquí, pero en general considero que cualquier lenguaje que no haya fallado es ligeramente programador.
Jeremy Wall