Fallo de la declaración de cambio ... ¿debería permitirse? [cerrado]

120

Desde que tengo uso de razón, he evitado utilizar fallos en las sentencias switch. En realidad, no puedo recordar que alguna vez haya entrado en mi conciencia como una forma posible de hacer las cosas, ya que se me inculcó desde el principio que no era más que un error en la declaración de cambio. Sin embargo, hoy encontré un código que lo usa por diseño, lo que me hizo preguntarme de inmediato qué piensan todos en la comunidad sobre la falla de la declaración de cambio.

¿Es algo que un lenguaje de programación no debería permitir explícitamente (como lo hace C #, aunque proporciona una solución) o es una característica de cualquier lenguaje que sea lo suficientemente potente como para dejarlo en manos del programador?

Editar: No fui lo suficientemente específico para lo que quise decir con fallos. Yo uso mucho este tipo:

    switch(m_loadAnimSubCt){
        case 0:
        case 1:
            // Do something
            break;
        case 2:
        case 3:
        case 4:
            // Do something
            break;
   }

Sin embargo, me preocupa algo como esto.

   switch(m_loadAnimSubCt){
        case 0:
        case 1:
            // Do something, but fall through to the other cases
            // after doing it.

        case 2:
        case 3:
        case 4:
            // Do something else.
            break;
   }

De esta manera, siempre que el caso sea 0, 1, hará todo lo que esté en la instrucción de cambio. He visto esto por diseño y simplemente no sé si estoy de acuerdo en que las declaraciones de cambio deben usarse de esta manera. Creo que el primer ejemplo de código es muy útil y seguro. El segundo parece un poco peligroso.

Fostah
fuente
51
No estoy de acuerdo con lo no constructivo. 27 votos a favor sobre la pregunta dicen que hay otros que también se sienten así.
Wes Modes
2
"no constructivo" es una vieja y algo vaga razón cercana. Hoy en día, estas preguntas pueden y deben ser marcadas para ser cerradas como puramente basadas en opiniones. SO es para preguntas de respuesta sobre código o diseño, no 'cuál es la opinión de "la comunidad" [lo que sea que sea] sobre mi opinión sobre la Característica X'. Programmers.SE encajaría mejor, pero incluso entonces, el valor de una pregunta tan amplia y basada en opiniones sigue siendo muy dudoso.
underscore_d
1
Hay casos de uso muy claros en los que se usa fallthrough, por lo que no estoy seguro de por qué se basaría en opiniones. Estoy de acuerdo en que esta pregunta no es un poco clara, pero no debería haberse cerrado por no ser constructivo. Tal vez simplemente editado para ser un caso más claro de: "¿Cuándo se usa la falla?" Que tiene una respuesta clara. Los buenos textos en C ++ repasan esto, es confuso para los nuevos codificadores (o incluso para los codificadores más experimentados de otros lenguajes), por lo que parece una buena pregunta para SO. De hecho, parece una pregunta de SO prototípicamente buena. Incluso si la forma en que se preguntó no fue perfecta.
Eric

Respuestas:

89

Puede depender de lo que considere fallido. Estoy de acuerdo con este tipo de cosas:

switch (value)
{
  case 0:
    result = ZERO_DIGIT;
    break;

  case 1:
  case 3:
  case 5:
  case 7:
  case 9:
     result = ODD_DIGIT;
     break;

  case 2:
  case 4:
  case 6:
  case 8:
     result = EVEN_DIGIT;
     break;
}

Pero si tiene una etiqueta de caso seguida de un código que pasa a otra etiqueta de caso, casi siempre lo consideraría malo. Quizás mover el código común a una función y llamar desde ambos lugares sería una mejor idea.

Y tenga en cuenta que utilizo la definición de "malvado" de las preguntas frecuentes de C ++

Fred Larson
fuente
Estoy de acuerdo con usted: no creo que su ejemplo sea fallido, y si hay un caso en el que quiero ejecutar varios bloques de código mediante fallthrough, probablemente haya una mejor manera de hacerlo.
Dave DuPlantis
10
+1 simplemente para esa definición de "maldad". Lo tomaré prestado, gracias ;-)
Joachim Sauer
Entiendo tu punto y tienes razón, pero tu ejemplo es realmente malo. Nadie debería probar dígitos de prueba como este. Scnr, tu respuesta es correcta de todos modos.
Tim Büthe
Por ejemplo, eso es exactamente lo que C # no permite. Probablemente por una razón :-)
Joey
Lamentablemente la definición del mal parece tener sin conexión se ha ido
HopefullyHelpful
57

Es una espada de doble filo. A veces es muy útil, pero a menudo peligroso.

¿Cuándo es bueno? Cuando desee que 10 casos se procesen todos de la misma manera ...

switch (c) {
  case 1:
  case 2:
            ... Do some of the work ...
            /* FALLTHROUGH */
  case 17:
            ... Do something ...
            break;
  case 5:
  case 43:
            ... Do something else ...
            break;
}

La única regla que me gusta es que si alguna vez haces algo elegante en el que excluyes el descanso, necesitas un comentario claro / * FALLTHROUGH * / para indicar que esa era tu intención.

John M
fuente
3
+1! Estoy de acuerdo en que ocasionalmente es la respuesta correcta, y DOBLEMENTE estoy de acuerdo en que cuando sea por diseño, debería comentarse. (En una revisión de código, HAGO que el comentario del otro desarrollador sea un error de diseño no vacío. No es que suceda; somos una tienda de C # donde eso no es compatible :)
John Rudy
4
Yo mismo uso un comentario / * FALL THROUGH * / en mi código cuando sea apropiado. =]
extraño
2
Si usa PC-Lint, debe insertar / * - fallthrough * /, de lo contrario se queja.
Steve Melnikoff
1
Aún así, si quisiera dejar en claro que el comportamiento es intencional, también puede if(condition > 1 && condition <10) {condition = 1}; switch(...guardar sus casos (para una apariencia clara) y todos pueden ver que realmente desea que esos casos desencadenen la misma acción.
Mark
1
Esta sigue siendo una codificación terrible. El primer caso debería llamar a una función, el segundo caso debería llamar a la misma función y luego llamar a una segunda función.
BlueRaja - Danny Pflughoeft
21

¿Has oído hablar del dispositivo de Duff ? Este es un gran ejemplo del uso de interruptores fallidos.

Es una función que se puede utilizar y se puede abusar de ella, como casi todas las funciones del idioma.

tzot
fuente
Aprenda algo nuevo todos los días, ¡gracias! Sin embargo, lo siento por los pobres desarrolladores que pasan por tales contorsiones.
Justin R.
Vaya, eso es algo para envolver tu cerebro.
Fostah
6
Tenga en cuenta el comentario citado por Duff en ese artículo: "Este código forma algún tipo de argumento en ese debate, pero no estoy seguro de si es a favor o en contra".
John Carter
El dispositivo de Duff es claramente malvado
Marjeta
21

La caída es realmente útil, dependiendo de lo que esté haciendo. Considere esta forma ordenada y comprensible de organizar las opciones:

switch ($someoption) {
  case 'a':
  case 'b':
  case 'c':
    // Do something
    break;

  case 'd':
  case 'e':
    // Do something else
    break;
}

Imagínese hacer esto con if / else. Sería un desastre.

Lucas Omán
fuente
1
Encuentro que este tipo de fallos es muy útil
Mitchel Sellers
si 'hacer algo' es más que un poco que el interruptor, será el desastre que si / si no. Con if / else está claro en todas partes qué variable se prueba. Incluso este pequeño ejemplo no se vería tan mal con if / else en mis ojos
grenix
10

Puede ser muy útil algunas veces, pero en general, el comportamiento deseado no es una falla. Se debe permitir la caída, pero no implícita.

Un ejemplo, para actualizar versiones antiguas de algunos datos:

switch (version) {
    case 1:
        // Update some stuff
    case 2:
        // Update more stuff
    case 3:
        // Update even more stuff
    case 4:
        // And so on
}
Peter Mortensen
fuente
6

Me encantaría una sintaxis diferente para los fallbacks en los conmutadores, algo como, errr ..

switch(myParam)
{
  case 0 or 1 or 2:
    // Do something;
    break;
  case 3 or 4:
    // Do something else;
    break;
}

Nota: Esto ya sería posible con enumeraciones, si declara todos los casos en su enumeración usando banderas, ¿verdad? Tampoco suena tan mal; los casos podrían (¿deberían?) muy bien ser ya parte de su enumeración.

¿Quizás este sería un buen caso (sin juego de palabras) para una interfaz fluida usando métodos de extensión? Algo como, errr ...

int value = 10;
value.Switch()
  .Case(() => { /* Do something; */ }, new {0, 1, 2})
  .Case(() => { /* Do something else */ } new {3, 4})
  .Default(() => { /* Do the default case; */ });

Aunque probablemente sea incluso menos legible: P

Erik van Brakel
fuente
1
Me gusta mucho la primera sugerencia. Eso se lee muy bien y ahorra algunas líneas de código. El segundo tardó un minuto en asimilar mi cerebro, pero tiene sentido, pero parece preparado para ser un desastre.
Fostah
1
Sí, eso es lo que pensé también, fue solo un pedo mental aleatorio que solté, en un esfuerzo por encontrar una forma diferente de cambiar usando construcciones de lenguaje existentes.
Erik van Brakel
¡Repasé esta pregunta y realmente me gusta esto! Tenía una idea sobre cómo mejorar la legibilidad de esto, pero SO no me permite publicar comentarios de varias líneas :( Alguien debería implementar esto como POC, parece divertido de implementar y usar (:
Victor Elias
5

Como con cualquier cosa: si se usa con cuidado, puede ser una herramienta elegante.

Sin embargo, creo que los inconvenientes más que justifican no usarlo y finalmente no permitirlo más (C #). Entre los problemas están:

  • es fácil "olvidar" un descanso
  • no siempre es obvio para los mantenedores de código que una interrupción omitida fue intencional

Buen uso de un interruptor / caso fallido:

switch (x)
{
case 1:
case 2:
case 3:
 Do something
 break;
}

Baaaaad uso de un interruptor / caso fallido :

switch (x)
{
case 1:
    Some code
case 2:
    Some more code
case 3:
    Even more code
    break;
}

Esto se puede reescribir usando construcciones if / else sin ninguna pérdida en mi opinión.

Mi última palabra: manténgase alejado de las etiquetas de caso fallidas como en el mal ejemplo, a menos que esté manteniendo un código heredado donde este estilo se usa y se comprende bien.

Steffenj
fuente
3
Puede volver a escribir la mayoría de las construcciones del lenguaje usando if () y goto ... eso no justifica el abandono de una construcción explícita.
Shog9
2
Lo sé, pero las construcciones switch / case que utilizan explícitamente el "caso 1: algo de código caso 2: algo más de código caso 3: ruptura de código final;" puede y debe reescribirse usando if / else if (mi opinión).
steffenj
1
Tenga en cuenta que el cambio puede ser muchas veces más rápido que una lista de if-elses. Switch utiliza una tabla de saltos, o cuando eso no es posible, sus saltos condicionales se estructurarán en un árbol de decisión en lugar de una lista lineal de saltos condicionales. Las declaraciones If-else anidadas bien estructuradas serán, en el mejor de los casos, igualmente rápidas.
Jochem Kuijpers
4

Es poderoso y peligroso. El mayor problema con la caída es que no es explícito. Por ejemplo, si se encuentra con un código editado con frecuencia que tiene un interruptor con fallos, ¿cómo sabe que es intencional y no un error?

Donde sea que lo use, me aseguro de que esté correctamente comentado:

switch($var) {
    case 'first':
        // Fall-through
    case 'second':
        i++;
        break;
 }
Dan Hulton
fuente
2
La intención es obvia si no hay un código para el caso "primero". Si codifica allí y también desea ejecutar el código para el caso 'segundo', entonces el comentario es importante. Pero evitaría ese escenario de todos modos.
Joel Coehoorn
1
@Joel - Estoy completamente de acuerdo. En mi tienda, hemos tenido personas que han incluido comentarios negativos en tales casos, y creo que reduce la legibilidad. Sin embargo, si hay código allí, ingrese el comentario final.
Fred Larson
¿Qué queremos decir con "no explícito"? Por eso nos rompemos; Otros idiomas hacen esto. Es solo un problema para las personas que no han aprendido sus idiomas en orden. C # lo exige para el caso predeterminado ffs, sin motivo para imho.
mckenzm
3

El uso de fallos como en su primer ejemplo está claramente bien, y no lo consideraría un fallo real.

El segundo ejemplo es peligroso y (si no se comenta extensamente) no es obvio. Enseño a mis alumnos a no usar tales construcciones a menos que consideren que vale la pena dedicarle un bloque de comentarios, que describe que esto es una falla intencional y por qué esta solución es mejor que las alternativas. Esto desalienta el uso descuidado, pero aún lo permite en los casos en que se usa con ventaja.

Esto es más o menos equivalente a lo que hicimos en proyectos espaciales cuando alguien quería violar el estándar de codificación: tenía que solicitar la dispensa (y me llamaron para asesorar sobre el fallo).

Wouter van Ooijen
fuente
2

No me gusta que mis switchdeclaraciones fracasen, es demasiado propenso a errores y difícil de leer. La única excepción es cuando varias casedeclaraciones hacen exactamente lo mismo.

Si hay algún código común que varias ramas de una declaración de cambio quieran usar, lo extraigo en una función común separada que se puede llamar en cualquier rama.

Matt Dillard
fuente
1

En algunos casos, el uso de fallos es un acto de pereza por parte del programador; podrían usar una serie de || declaraciones, por ejemplo, pero en su lugar utilizan una serie de casos de cambio 'catch-all'.

Dicho esto, los he encontrado especialmente útiles cuando sé que eventualmente voy a necesitar las opciones de todos modos (por ejemplo, en una respuesta de menú), pero aún no he implementado todas las opciones. Del mismo modo, si está fallando tanto para 'a' como para 'A', me parece mucho más limpio usar el interruptor faltante que una declaración if compuesta.

Probablemente sea una cuestión de estilo y de cómo piensan los programadores, pero generalmente no me gusta eliminar componentes de un lenguaje en nombre de la 'seguridad', razón por la cual tiendo más hacia C y sus variantes / descendientes que, digamos, Java. Me gusta ser capaz de jugar con punteros y cosas por el estilo, incluso cuando no tengo "motivos" para hacerlo.

madriguera
fuente
Estrictamente hablando. Las declaraciones de cambio saltan a través de un valor establecido a un montón de ubicaciones de código específicas. Si bien es probable que el compilador capte tales cosas, existe un valor de velocidad y corrección para una declaración de cambio sobre una serie de declaraciones o. Si p es 0 op es 1 op es 2. De hecho, tuve que evaluar si p era 0 y p era 1, en lugar de saltar a la ubicación p = 2 en el código. Que resultó ser idéntico a p0 y p1. Pero nunca tuve que revisarlos.
Tatarizar
1

Fall-through debe usarse solo cuando se usa como una tabla de salto en un bloque de código. Si hay alguna parte del código con una ruptura incondicional antes de más casos, todos los grupos de casos deben terminar de esa manera.

Cualquier otra cosa es "malvada".

BCS
fuente