¿Es esta una situación correcta para usar una constante?

42

Así que mi profesor me devolvió comentarios sobre un proyecto en el que he estado trabajando. Atracó algunas marcas para este código:

if (comboVendor.SelectedIndex == 0) {
  createVendor cv = new createVendor();
  cv.ShowDialog();
  loadVendors();
}

Esto se encuentra en un controlador combinado de "índice cambiado". Se utiliza cuando el usuario desea crear un nuevo proveedor, mi opción principal (índice 0, que nunca cambia) abre el cuadro de diálogo "Crear un nuevo proveedor". Entonces el contenido de mi cuadro combinado termina luciendo así:

Create New Vendor...
Existing Vendor
Existing Vendor 2
Existing Vendor 3

Su problema es con el código de la primera línea:

if (comboVendor.SelectedIndex == 0)

Afirma que el 0 debería ser una constante, y en realidad me atracó marcas debido a eso. Afirma que no debería usar literales en mi código en absoluto.

La cuestión es que no entiendo por qué querría hacer que ese código en esa situación sea una constante. Ese índice nunca cambiará, ni es algo que deba modificar. Parece un desperdicio de memoria mantener un solo 0 en la memoria que se usa para una situación muy específica y que nunca cambia.

Rojo 5
fuente
32
Él está siendo dogmático. Los números mágicos en general son algo bueno para evitar. Creo que -1, 0 y 1 pueden considerarse excepciones a esa regla. Además, una constante como esta no ocuparía más espacio que el literal 0.
Dave Mooney
23
@DaveMooney: ¿Estás seguro de que no estás teniendo una reacción instintiva aquí? Es cierto que cosas como " -1in str.indexOf(substr) != -1" strcontiene substrestán perfectamente justificadas. Pero aquí, el significado del 0 no es obvio (¿cuál es la relación con la creación de un nuevo proveedor?) Ni es realmente constante (¿y si cambia la forma de crear un nuevo proveedor?).
62
tienes que aprender las reglas antes de poder romper las reglas
Ryathal
12
Este método me falló inesperadamente. La lista fue ordenada alfabéticamente. Solía - - Crear nueva - - por lo que los guiones serían una especie primero y no modificable índice 0 al método CreateNew. Luego, alguien agregó un artículo que comienza con una comilla simple 'Mi artículo' que se ordena antes de los guiones. Mi codificación rígida provocó que el programa se bloqueara la próxima vez que se cargó la lista. Tuve que modificar manualmente el archivo de datos de la lista para recuperar los datos del cliente.
Hand-E-Food
14
Puedes usarlo int.Zeropara hacerlo feliz :)
Paul Stovell

Respuestas:

90

La forma correcta de hacer esto en C # es no confiar en el orden de los ComboItems .

public partial class MyForm : Form
{
    private readonly object VENDOR_NEW = new object();

    public MyForm()
    {
        InitializeComponents();
        comboVendor.Items.Insert(0, VENDOR_NEW);
    }

    private void comboVendor_Format(object sender, ListControlConvertEventArgs e)
    {
        e.Value = (e.ListItem == VENDOR_NEW ? "Create New Vendor" : e.ListItem);
    }

    private void comboVendor_SelectedIndexChanged(object sender, EventArgs e)
    {
        if(comboVendor.SelectedItem == VENDOR_NEW)
        {
            //Special logic for selecting "create new vendor"
        }
        else
        {
            //Usual logic
        }
    }
}
BlueRaja - Danny Pflughoeft
fuente
22
Bueno, si esto es C #, no deberías usar ALL_CAPS para constantes. Las constantes deben ser PascalCased - stackoverflow.com/questions/242534/…
Groky
44
@Groky: ¿Por qué importa esto? ¿A quién le importa cómo nombra sus constantes? Esto es 100% correcto si usa ALL_CAPS de manera consistente para constantes.
marco-fiset
44
@marcof: Bueno, si las constantes son parte de una interfaz pública, entonces debería seguir las pautas de nomenclatura de MS. Si no, entonces al menos debería estar aprendiendo las mejores prácticas temprano.
Groky
2
Esto sigue siendo incorrecto. Cambia el problema de tener un literal entero (cero) a un literal de cadena (Crear nuevo proveedor). En el mejor de los casos, el problema ahora es "qué pasa si la etiqueta cambia" en lugar de "qué pasa si cambia el índice".
Freiheit
77
@Freiheit: Incorrecto. La constante de cadena aquí es solo para visualización; no afecta la lógica del programa en absoluto. A diferencia del uso de valores / cadenas mágicas para almacenar el estado del programa, cambiar esta cadena (o agregar / eliminar elementos de la lista) nunca podría romper nada.
BlueRaja - Danny Pflughoeft
83

El orden en el cuadro combinado podría cambiar. ¿Qué sucede si agrega otra opción como "Crear proveedor especial ..." antes de "Crear nuevo vendedor ..."

La ventaja de usar una constante es que si hay muchos métodos que dependen del orden del cuadro combinado, solo tiene que cambiar la constante y no todos los métodos si esto cambia.

Usar una constante también es más legible que un literal.

if (comboVendor.SelectedIndex == NewVendorIndex)

La mayoría de los idiomas compilados sustituirán la constante en el momento de la compilación, por lo que no hay penalización de rendimiento.

Michael Krussel
fuente
55
Usaría un código descriptivo en el atributo de valor, en lugar de confiar en un índice que puede cambiar (mover la opción de creación al final de la lista, romperá absolutamente el método constante). Entonces solo busca ese código.
CaffGeek
1
@Chad Estoy de acuerdo en que identificar los controles por orden en una interfaz gráfica de usuario es muy frágil. Si el lenguaje admite agregar un valor al elemento gui que se puede buscar, lo usaría.
Michael Krussel
3
@solution porque esa es la convención.
Ikke
3
@ Ikke Eso definitivamente no es la convención. stackoverflow.com/questions/242534/…
SolutionYogi
1
Si estamos hablando de C #, NewVendorIndex ES la convención. Es consistente con el resto del estilo .NET.
MaR
36

Esta situación que describe es una decisión judicial, personalmente no usaría una si solo se usa una vez y ya es legible.

Sin embargo, la verdadera respuesta es que él eligió esto para darte una lección.

No olvide que es profesor, su trabajo es enseñarle codificación y mejores prácticas.

Yo diría que está haciendo un muy buen trabajo en realidad.

Seguro que puede parecer un poco absoluto, pero estoy seguro de que lo pensarás de nuevo antes de usar números mágicos.

También se metió en tu piel lo suficiente como para que te unieras a una comunidad en línea sobre programadores solo para descubrir qué se considera una mejor práctica en esta situación.

Felicitaciones a su profesor.

Thanos Papathanasiou
fuente
+1 para indicar que, en este caso, los medios justifican el resultado final
oliver-clare
@ Thanos- " No olvides que es profesor, su trabajo es enseñarte la codificación y las mejores prácticas " . Este nunca fue el caso de mi profesor. Diría que su trabajo es enseñarle lo que el departamento considera importante una vez que se crea el curso.
Ramhound
13

[...] mi opción principal (índice 0, que nunca cambia) abre el cuadro de diálogo "Crear un nuevo proveedor".

El hecho de que tuvieras que explicar eso prueba por qué deberías usar una constante. Si introdujo una constante como NEW_VENDOR_DIALOG, su código se explicaría más por sí mismo. Además, los compiladores optimizan las constantes, por lo que no habrá un cambio en el rendimiento.

Escribir programas para programadores, no compiladores. A menos que esté tratando específicamente de micro-optimizar, lo que no parece ser.

kba
fuente
2
-1 por mencionar incluso el rendimiento. Incluso si no está optimizado, una computadora puede realizar miles de millones de operaciones como esta antes de que el usuario se dé cuenta.
Boris Yankov
3
@Boris OP parecía preocupado por el desperdicio de recursos, por eso lo mencioné. No veo cómo mi respuesta sería menos correcta debido a eso.
kba
12

Afirma que el 0 debería ser una constante, y en realidad me atracó marcas debido a eso.

Estoy de acuerdo El uso de cero aquí es "mágico". Imagine que está leyendo este código por primera vez. No sabes por qué cero es especial, y el literal no te dice nada acerca de por qué cero es especial. Si en cambio dijisteif(comboVendor.SelectedIndex == CreateNewVendorIndex) , se vuelve extremadamente claro para el lector primerizo lo que significa el código.

Afirma que no debería usar literales en mi código en absoluto.

Esa es una posición extrema; Una posición realista sería decir que el uso de literales es una bandera roja que indica que el código podría no ser tan claro como podría ser. A veces es apropiado.

La cuestión es que no entiendo por qué querría hacer que ese código en esa situación sea una constante. Ese índice nunca cambiará

Que nunca cambiará es una excelente razón para hacerlo constante . Es por eso que las constantes se llaman constantes; porque nunca cambian

ni es algo que deba modificar.

De Verdad? No puedes ver ninguno situación en la que alguien quiera cambiar el orden de las cosas en un cuadro combinado?

El hecho de que pueda ver una razón por la cual esto podría cambiar en el futuro es una buena razón para no hacerlo constante. Más bien debería ser un campo entero estático de solo lectura no constante. Una constante debe ser una cantidad que se garantice que permanezca igual durante todo el tiempo . Pi y el número atómico de oro son buenas constantes. Los números de versión no son; Cambian cada versión. El precio del oro es obviamente una constante terrible; Cambia cada segundo. Solo haga cosas constantes que nunca, nunca cambien .

Parece un desperdicio de memoria mantener un solo 0 en la memoria que se usa para una situación muy específica y que nunca cambia.

Ahora llegamos al quid de la cuestión.

Esta es quizás la línea más importante en su pregunta porque indica que tiene una comprensión profundamente defectuosa de (1) memoria y (2) optimización. Estás en la escuela para aprender, y ahora sería un buen momento para obtener una comprensión correcta de los fundamentos. ¿Puede explicar en detalle por qué cree que "es un desperdicio de memoria mantener un solo cero en la memoria"? En primer lugar, ¿por qué cree que es importante optimizar el uso de cuatro bytes de memoria en un proceso con al menos dos mil millones de bytes de almacenamiento direccionable por el usuario? Segundo, precisamente , ¿qué recurso imaginas que se consume aquí? ? ¿Qué quieres decir con "memoria" consumida?

Estoy interesado en las respuestas a estas preguntas primero porque son una oportunidad para que aprenda cómo su comprensión de la optimización y la gestión de la memoria es incorrecta, y segundo porque siempre quiero saber por qué los principiantes creen cosas extrañas, para poder diseñar mejores herramientas para llevarlos a tener creencias correctas.

Eric Lippert
fuente
6

El tiene razón. Tienes razón. Te equivocas.

Tiene razón, conceptualmente, que se deben evitar los números mágicos. Las constantes hacen que el código sea más legible al agregar contexto a lo que significa el número. En el futuro, cuando alguien lea su código, sabrá por qué se utilizó un número en particular. Y si necesita cambiar un valor en algún punto de la línea, es mucho mejor cambiarlo en un lugar en lugar de intentar buscar en todas partes donde se usa un número en particular.

Dicho esto, tienes razón. En este caso específico, realmente no creo que se justifique una constante. Está buscando el primer elemento de la lista, que siempre es cero. Nunca será 23. O -pi. Usted está buscando específicamente cero. Realmente no creo que necesites abarrotar el código haciéndolo constante.

Sin embargo, está equivocado al suponer que una constante se lleva a cabo como una variable, 'usar memoria'. Existe una constante para el humano y el compilador. Le dice al compilador que ponga ese valor en ese lugar durante la compilación, donde de lo contrario habría puesto un número literal. E incluso si llevara la constante en la memoria, para todas las aplicaciones excepto las más exigentes, la pérdida de eficiencia ni siquiera sería medible. Preocuparse por el uso de la memoria de un solo entero definitivamente cae en 'optimización prematura'.

Gran maestro B
fuente
1
Casi hubiera votado esta respuesta, si no fuera por el tercer párrafo, alegando que usar el literal 0 es lo suficientemente claro y no se justifica una constante. Una solución mucho mejor hubiera sido no confiar en la indexación del elemento del cuadro combinado, sino en el valor del elemento seleccionado. Las constantes mágicas son constantes mágicas incluso si son 0 o 3.14 o cualquier otra cosa, asígneles un nombre apropiado ya que esto hace que el código sea más legible.
Roland Tepp
Puede ser mejor usar el valor en la lista, pero de eso no se trataba su pregunta. Su pregunta era si ese era un uso apropiado para una constante. Y si uno se compara con 0 en el contexto de la interfaz con la GUI (por cualquier razón, tal vez alguien está buscando el primer elemento independientemente del valor), creo que sería innecesario usar una constante en ese lugar.
GrandmasterB
No estaría de acuerdo ... Con solo mirar el código, nunca es inmediatamente obvio cuál es el significado de 0; realmente podría ser que siempre está buscando el primer elemento de la lista y eso es todo, pero luego leería mucho más limpio, si la comparación fuera 'comboVendor.SelectedIndex == FirstIndex' en su lugar?
Roland Tepp
2

Hubiera reemplazado el 0con una constante para aclarar el significado, como NewVendorIndex. Nunca se sabe si su pedido cambiará.

Bernardo
fuente
1

Esa es la preferencia total de tu profesor. Por lo general, solo usa una constante si el literal se usará varias veces, desea que sea obvio para el lector cuál es el propósito de la línea, o su literal posiblemente cambiará en el futuro, y solo desea cambiar en un solo lugar Sin embargo, para este semestre, el profesor es el jefe, por lo que lo haría a partir de ahora en esa clase.

¿Buena formación para el mundo corporativo? Muy posiblemente.

Jonathan Henson
fuente
2
No estoy de acuerdo con la parte "solo usar una constante si el literal se usará varias veces". Con 0 (y tal vez -1, 1) a menudo es claro, en la mayoría de los casos es bueno darle un nombre a algo que es mucho más claro mientras lee el código.
johannes
1

Para ser honesto, aunque no creo que su código sea la mejor práctica, su sugerencia es francamente un poco grotesca.

Una práctica más común para un cuadro combinado .NET es dar al elemento "Seleccionar .." un valor vacío, mientras que los elementos reales tienen valores significativos, y luego hacen:

if (string.IsNullOrEmpty(comboVendor.SelectedValue))

más bien que

if (comboVendor.SelectedIndex == 0)
Carson63000
fuente
3
En su ejemplo, nulo es simplemente otro literal. El corazón de esta lección es que se deben evitar los literales.
flojo el
@overslacked: no hay un literal nulo en mi ejemplo.
Carson63000
Incluso si hubiera un literal nulo, nulo es un literal aceptable para usar, ni siquiera creo que haya una manera de verificar si una referencia a un objeto es nula sin usar verificar si es igual a nulo.
Ramhound
Carson63000 - Me refería a su uso de IsNullOrEmpty. Estás cambiando un "valor mágico" por otro. @Ramhound: nulo es un literal aceptable en algunos casos, no hay duda, pero no creo que este sea un buen ejemplo (usando nulo o en blanco como valor mágico).
flojo
-1 para "un poco grotesco". Si bien tiene razón en que usar el valor sería más convencional y más fácil de entender, si usa el
índice
1

No está equivocado por enfatizar el valor del uso de constantes y usted no está equivocado al usar literales. A menos que haya enfatizado que este es el estilo de codificación esperado, no debe perder marcas por usar literales ya que no son dañinos. He visto literales usados ​​por todas partes muchas veces en el código comercial.

Sin embargo, su punto es bueno. Esta puede ser su forma de informarle sobre los beneficios de las constantes:

1-Protegen su código hasta cierto punto de alteraciones accidentales

2-Como dice @DeadMG en su respuesta, si se usa el mismo valor literal en muchos lugares, puede aparecer con un valor diferente por error, por lo que las constantes preservan la consistencia.

Las 3 constantes conservan el tipo, por lo que no tiene que usar algo como 0F para significar cero.

4-Para facilitar la lectura, COBOL usa CERO como una palabra reservada para el valor cero (pero también le permite usar el cero literal) - Entonces, dar un nombre a un valor a veces es útil, por ejemplo: (Fuente: ms-Constantes

class CalendarCalc
{
    const int months = 12;
    const int weeks = 52; //This is not the best way to initialize weeks see comment
    const int days = 365;

    const double daysPerWeek = (double) days / (double) weeks;
    const double daysPerMonth = (double) days / (double) months;
}

o como en su caso (como se muestra en la respuesta de @Michael Krussel)

Ninguna posibilidad
fuente
2
¿No será una definición extraña para días por semana? Hay 7 días a la semana, no 7.4287143
Winston Ewert
@ WinstonEwert, gracias por tu comentario. 365/52 = 7.01923076923077 = 7 + (1/52). Ahora, si elimina la parte fraccionaria y calcula 7 * 52, obtiene 364 días, que no es la cantidad correcta de días en un año. Tener una fracción es más preciso que perderlo (ya que puede formatear el resultado para mostrar 7 si solo desea mostrar el número). De todos modos, fue solo un ejemplo de MS sobre constantes, pero su punto es interesante.
NoChance
1
Por supuesto, es solo un ejemplo, pero me opongo a su afirmación de que es más preciso incluir la fracción. Una semana se define como 7 días. Dada su definición, 26 semanas son 182.5 días, lo que simplemente no es exacto. Realmente, el problema es tu int weeks = 52, no hay 52 semanas por año. Hay 52.142857142857146 semanas en un año, y ese es el número en el que debe mantener las fracciones. Por supuesto, lo único que es realmente constante en todo ese conjunto de constantes es la cantidad de meses.
Winston Ewert
0

Solo necesitaría ponerlo en una constante si tiene una derivación compleja, o si se repite con frecuencia. De lo contrario, un literal está bien. Poner todo en una constante es una exageración total.

DeadMG
fuente
Solo si lo considera dos veces a menudo y nunca necesita modificar el código. Es realmente fácil crear errores cambiando uno de los dos casos.
BillThor
0

En realidad, como se mencionó, ¿qué pasa si se trata de cambios de posición? Lo que podría / debería hacer es usar un código, en lugar de confiar en el índice.

Entonces, cuando crea su lista de selección, termina con html como

<select>
    <option value='CREATE'>Create New Vendor...</option>
    <option value='1'>Existing Vendor</option>
    <option value='2'>Existing Vendor 2</option>
    <option value='3'>Existing Vendor 3</option>
</select>

Luego, en lugar de verificar selectedIndex === 0, verifique que el valor sea el CREATECODEque estaría en una constante y se hubiera utilizado tanto para esta prueba como para crear la lista de selección.

CaffGeek
fuente
3
Probablemente sea un buen enfoque, pero dado que está usando C #, html no es el código de ejemplo más esperanzador.
Winston Ewert
0

Me desharía de eso por completo. Simplemente coloque un botón Crear nuevo junto a la lista del cuadro combinado. Haga doble clic en un elemento de la lista para editar, o haga clic en el botón. No tenga la nueva funcionalidad enterrada en el cuadro combinado. Entonces el número mágico se elimina por completo.

En general, cualquier número literal en el código debe definirse como una constante para poner el contexto alrededor del número. ¿Qué significa cero? En este caso 0 = NEW_VENDOR. En otros casos, puede significar algo diferente, por lo que siempre es una buena idea para la legibilidad y la facilidad de mantenimiento poner algo de contexto a su alrededor.

Jon Raynor
fuente
0

Como han dicho otros, debe usar otro método que no sea el número de índice para identificar el elemento del cuadro combinado que corresponde a una acción determinada; o puede encontrar el índice con alguna lógica programática y almacenarlo en una variable.

La razón por la que escribo es para abordar su comentario sobre el "uso de la memoria". En C #, como en la mayoría de los lenguajes, el compilador "dobla" las constantes. Por ejemplo, compile el siguiente programa y examine el IL. Encontrará que todos esos números ni siquiera llegan al IL, y mucho menos a la memoria de la computadora:

public class Program
{
    public static int Main()
    {
        const int a = 1000;
        const int b = a + a;
        const int c = b + 42;
        const int d = 7928345;
        return (a + b + c + d) / (-a - b - c - d);
    }
}

IL resultante:

.method public hidebysig static 
    int32 Main () cil managed 
{
    .maxstack 1
    .locals init (
        [0] int32 CS$1$0000
    )

    IL_0000: nop
    IL_0001: ldc.i4.m1  // the constant value -1 to be returned.
    IL_0002: stloc.0
    IL_0003: br.s IL_0005

    IL_0005: ldloc.0
    IL_0006: ret
}

Entonces, ya sea que use una constante, un literal o un kilobyte de código usando aritmética constante, el valor se trata literalmente en la IL.

Un punto relacionado: el plegado constante se aplica a los literales de cadena. Muchos creen que una llamada como esta causa demasiada concatenación de cadenas innecesaria e ineficiente:

public class Program
{
    public static int Main()
    {
        const string a = "a";
        const string b = a + a;
        const string c = "C";
        const string d = "Dee";
        return (a + b + c + d).Length;
    }
}

Pero mira el IL:

IL_0000: nop
IL_0001: ldstr "aaaCDee"
IL_0006: callvirt instance int32 [mscorlib]System.String::get_Length()
IL_000b: stloc.0
IL_000c: br.s IL_000e
IL_000e: ldloc.0
IL_000f: ret

En pocas palabras: los operadores en expresiones constantes dan como resultado expresiones constantes, y el compilador hace todo el cálculo; no afecta el rendimiento en tiempo de ejecución.

phoog
fuente