¿Cuál es el beneficio de terminar si ... más si se construye con una cláusula else?


136

Nuestra organización tiene una regla de codificación requerida (sin ninguna explicación) que:

if ... else if construcciones deben terminarse con una cláusula else

Ejemplo 1:

if ( x < 0 )
{
   x = 0;
} /* else not needed */

Ejemplo 2

if ( x < 0 )
{
    x = 0;
}
else if ( y < 0 )
{
    x = 3;
}
else    /* this else clause is required, even if the */
{       /* programmer expects this will never be reached */
        /* no change in value of x */
}

¿Qué caso límite está diseñado para manejar esto?

Lo que también me preocupa de la razón es que el Ejemplo 1 no necesita un elsepero el Ejemplo 2 sí. Si la razón es la reutilización y la extensibilidad, creo que elsedebería usarse en ambos casos.


30
Tal vez pregunte a su empresa por la razón y el beneficio. A primera vista, obliga al programador a pensarlo y agregar un comentario "no se requiere ninguna acción". El mismo razonamiento detrás (y al menos tan controvertido) de las excepciones comprobadas de Java.
Thilo

32
El ejemplo 2 es en realidad un buen ejemplo de dónde assert(false, "should never go here")podría tener sentido
Thilo

2
Nuestra organización tiene reglas similares pero no tan granulares. El propósito en nuestro caso es doble. Primero, consistencia del código. En segundo lugar, sin cadenas sueltas / legibilidad. Exigir lo demás, incluso cuando no sea necesario, agrega claridad al código incluso cuando no está documentado. Requerir otra cosa es algo que he hecho en el pasado, incluso cuando no es necesario, solo para asegurarme de tener en cuenta todos los resultados posibles en la aplicación.
LuvnJesus

44
Siempre podrías vencer si con if (x < 0) { x = 0; } else { if (y < 0) { x = 3; }}. O simplemente podría seguir tales reglas, muchas de las cuales son tontas, simplemente porque se le exige.
Jim Balter

3
@Thilo, llego un poco tarde, pero aún así nadie se ha dado cuenta del error: no hay indicios de que lo demás nunca debería suceder, solo que no debería tener efectos secundarios (lo que parece normal cuando se realizan < 0verificaciones), por lo que esa afirmación continúa. para bloquear el programa en lo que probablemente sea el caso más común donde los valores están en los límites esperados.
Loduwijk

Respuestas:


148

Como se menciona en otra respuesta, esto es de las pautas de codificación MISRA-C. El propósito es la programación defensiva, un concepto que a menudo se usa en la programación de misión crítica.

Es decir, todos if - else ifdeben terminar con un else, y todos switchdeben terminar con un default.

Hay dos razones para esto:

  • Código autodocumentado. Si se escribe un elsesino dejarlo vacío que significa: "Definitivamente he considerado el escenario cuando ni iftampoco else ifson ciertas".

    No escribir una elseno quiere decir: "Yo tampoco considera el escenario donde ni iftampoco else ifson verdaderas, o he olvidado por completo a considerarlo y hay potencialmente un error de grasa aquí en mi código".

  • Detener el código fuera de control. En el software de misión crítica, debe escribir programas robustos que tengan en cuenta incluso lo poco probable. Entonces puedes ver código como

    if (mybool == TRUE) 
    {
    } 
    else if (mybool == FALSE) 
    {
    }
    else
    {
      // handle error
    }
    

    Este código será completamente ajeno a los programadores de PC y los científicos informáticos, pero tiene mucho sentido en el software de misión crítica, ya que detecta el caso en que el "mybool" se ha corrompido, por cualquier razón.

    Históricamente, temerías la corrupción de la memoria RAM debido a EMI / ruido. Esto no es un gran problema hoy. Es mucho más probable que la corrupción de la memoria ocurra debido a errores en otras partes del código: punteros a ubicaciones incorrectas, errores fuera de los límites de la matriz, desbordamiento de pila, código desbocado, etc.

    Entonces, la mayoría de las veces, un código como este regresa para abofetearse cuando ha escrito errores durante la etapa de implementación. Lo que significa que también podría usarse como una técnica de depuración: el programa que está escribiendo le dice cuándo ha escrito errores.


EDITAR

En cuanto a por qué elseno es necesario después de cada uno de ellos if:

Una if-elseo if-else if-elsecubre completamente todos los valores posibles que puede tener una variable. Pero una ifdeclaración simple no está necesariamente allí para cubrir todos los valores posibles, tiene un uso mucho más amplio. En la mayoría de los casos, solo desea verificar una determinada condición y, si no se cumple, no haga nada. Entonces simplemente no tiene sentido escribir programación defensiva para cubrir el elsecaso.

Además, desordenaría el código por completo si escribiera un vacío elsedespués de cada uno if.

MISRA-C: 2012 15.7 no da razones por las cuales elseno es necesario, solo dice:

Nota: elseno se requiere una declaración final para una if declaración simple .


66
Si la memoria está dañada, espero que destruya mucho más que mybool, incluido quizás el código de verificación en sí. ¿Por qué no agrega otro bloque que verifique que está if/else if/elsecompilado a lo que espera? ¿Y luego uno más para verificar el verificador anterior también?
Oleg V. Volkov

49
Suspiro. Miren chicos, si no tienen absolutamente ninguna otra experiencia más allá de la programación de escritorio, no hay necesidad de hacer comentarios sabelotodo sobre algo de lo que obviamente no tienen experiencia. Esto siempre sucede cuando se discute la programación defensiva y se detiene un programador de PC. Agregué el comentario "Este código será completamente ajeno a los programadores de PC" por una razón. No programa software de seguridad crítica para que se ejecute en computadoras de escritorio basadas en RAM . Período. El código mismo residirá en una ROM flash con sumas de verificación ECC y / o CRC.
Lundin

66
@Deduplicator De hecho (a menos que mybooltenga un tipo no booleano, como fue el caso antes de que C obtuviera el suyo bool; entonces el compilador no haría la suposición sin un análisis estático adicional). Y sobre el tema de 'Si escribe otra cosa pero la deja vacía, significa: "Definitivamente he considerado el escenario cuando ni si ni si es cierto". Mi primera reacción es asumir que el programador olvidó poner código. el bloque else, de lo contrario, ¿por qué tener un bloque else vacío sentado allí? Un // unusedcomentario sería apropiado, no solo un bloque vacío.
JAB

55
@JAB Sí, el elsebloque debe contener algún tipo de comentario si no hay código. La práctica común de vacío elsees un solo punto y coma además de un comentario: else { ; // doesn't matter }. Como no hay ninguna razón por la cual alguien simplemente escribiría un punto y coma con sangría en una línea propia. Práctica similar se utiliza a veces en bucles vacías: while(something) { ; // do nothing }. (código con saltos de línea, obviamente. Los comentarios SO no los permiten)
Lundin

55
Me gustaría señalar que este código de muestra con dos IFs y otros pueden ir a else incluso en el software de escritorio habitual en entornos de subprocesos múltiples, por lo que el valor de mybool podría cambiarse en el medio
Iłya Bursov

61

Su empresa siguió la guía de codificación MISRA. Hay algunas versiones de estas pautas que contienen esta regla, pero de MISRA-C: 2004 :

Regla 14.10 (requerido): Todos si… si no si las construcciones se terminarán con una cláusula else.

Esta regla se aplica siempre que una declaración if es seguida por una o más declaraciones if; al final ifse le seguirá una else declaración. En el caso de una ifdeclaración simple , la else declaración no necesita ser incluida. El requisito para una else declaración final es la programación defensiva. La elsedeclaración tomará las medidas apropiadas o contendrá un comentario adecuado sobre por qué no se toman medidas. Esto es consistente con el requisito de tener una defaultcláusula final en una switchdeclaración. Por ejemplo, este código es una declaración if simple:

if ( x < 0 )
{
 log_error(3);
 x = 0;
} /* else not needed */

mientras que el código siguiente muestra una if, else ifconstructo

if ( x < 0 )
{
 log_error(3);
 x = 0;
}
else if ( y < 0 )
{
 x = 3;
}
else /* this else clause is required, even if the */
{ /* programmer expects this will never be reached */
 /* no change in value of x */
}

En MISRA-C: 2012 , que reemplaza la versión 2004 y es la recomendación actual para nuevos proyectos, existe la misma regla pero está numerada 15.7 .

Ejemplo 1: en un solo programador if, el programador puede necesitar verificar n número de condiciones y realizar una sola operación.

if(condition_1 || condition_2 || ... condition_n)
{
   //operation_1
}

En un uso regular, no es necesario realizar una operación todo el tiempo cuando ifse usa.

Ejemplo 2: Aquí el programador verifica n número de condiciones y realiza múltiples operaciones. En el uso regular if..else ifes como switchsi tuviera que realizar una operación como predeterminada. Por lo tanto, el uso elsees necesario según el estándar misra

if(condition_1 || condition_2 || ... condition_n)
{
   //operation_1
}
else if(condition_1 || condition_2 || ... condition_n)
{
  //operation_2
}
....
else
{
   //default cause
}

Las versiones actuales y pasadas de estas publicaciones están disponibles para su compra a través de la tienda web MISRA ( vía ).


1
Gracias, su respuesta es todo el contenido de la regla Misra, pero espero una respuesta para mi confusión en la edición de parte de la pregunta.
Trevor

2
Buena respuesta. Por curiosidad, ¿esas guías dicen algo sobre qué hacer si espera que la elsecláusula sea inalcanzable? (Deje la condición final en su lugar, ¿arroja un error, tal vez?)
jpmc26

17
Voy a rechazar el voto por plagio y enlaces que violen los derechos de autor. Editaré la publicación para que quede más claro cuáles son tus palabras y cuáles son las palabras de MISRA. Inicialmente, esta respuesta no era más que copiar / pegar sin procesar.
Lundin

8
@TrieuTheVan: Las preguntas no deben ser objetivos móviles . Asegúrese de que su pregunta esté completa antes de publicarla.
TJ Crowder

1
@ jpmc26 No, pero el objetivo de MISRA no es darle un código estricto, es darle un código seguro. Cualquier compilador en estos días optimizará el código inalcanzable de todos modos, por lo que no hay inconveniente.
Graham

19

Esto es equivalente a requerir un caso predeterminado en cada switch.

Este extra disminuirá la cobertura del código de su programa.


En mi experiencia con portar el kernel de Linux o el código de Android a una plataforma diferente, muchas veces hacemos algo mal y en logcat vemos algunos errores como

if ( x < 0 )
{
    x = 0;
}
else if ( y < 0 )
{
    x = 3;
}
else    /* this else clause is required, even if the */
{       /* programmer expects this will never be reached */
        /* no change in value of x */
        printk(" \n [function or module name]: this should never happen \n");

        /* It is always good to mention function/module name with the 
           logs. If you end up with "this should never happen" message
           and the same message is used in many places in the software
           it will be hard to track/debug.
        */
}

2
Aquí es donde __FILE__y __LINE__macros son una herramienta útil para la toma de la ubicación de origen fácil de encontrar si el mensaje es cada vez impreso.
Peter Cordes

9

Solo una breve explicación, ya que hice esto hace unos 5 años.

No hay (con la mayoría de los idiomas) ningún requisito sintáctico para incluir una elsedeclaración "nula" (e innecesaria {..}), y en "pequeños programas simples" no hay necesidad. Pero los programadores reales no escriben "pequeños programas simples" y, lo que es más importante, no escriben programas que se usarán una vez y luego se descartarán.

Cuando uno escribe un if / else:

if(something)
  doSomething;
else
  doSomethingElse;

todo parece simple y apenas se ve el punto de agregar {..}.

Pero algún día, dentro de unos meses, algún otro programador (¡nunca cometerá tal error!) Necesitará "mejorar" el programa y agregará una declaración.

if(something)
  doSomething;
else
  doSomethingIForgot;
  doSomethingElse;

De repente se doSomethingElseolvida que se supone que debe estar en la elsepierna.

Entonces eres un buen programador y siempre lo usas {..}. Pero tu escribes:

if(something) {
  if(anotherThing) {
    doSomething;
  }
}

Todo está bien hasta que ese nuevo niño haga una modificación a medianoche:

if(something) {
  if(!notMyThing) {
  if(anotherThing) {
    doSomething;
  }
  else {
    dontDoAnything;  // Because it's not my thing.
  }}
}

Sí, está formateado incorrectamente, pero también lo es la mitad del código en el proyecto, y el "formateador automático" se ve afectado por todas las #ifdefdeclaraciones. Y, por supuesto, el código real es mucho más complicado que este ejemplo de juguete.

Desafortunadamente (o no), he estado fuera de este tipo de cosas durante algunos años, así que no tengo un nuevo ejemplo "real" en mente: lo anterior es (obviamente) artificial y un poco tonto.


7

Esto se hace para que el código sea más legible, para referencias posteriores y para dejar en claro, a un revisor posterior, que los casos restantes manejados por el último else, son casos de no hacer nada , para que no se pasen por alto a primera vista.

Esta es una buena práctica de programación, que hace que el código sea reutilizable y ampliable .


6

Me gustaría agregar, y en parte contradecir, las respuestas anteriores. Si bien es común usar if-else if de una manera similar a un interruptor que debería cubrir el rango completo de valores pensables para una expresión, de ninguna manera se garantiza que cualquier rango de condiciones posibles esté completamente cubierto. Lo mismo puede decirse sobre la construcción del interruptor en sí, de ahí el requisito de usar una cláusula predeterminada, que capture todos los valores restantes y, si no se requiere de otra manera, puede usarse como una protección de aserción.

La pregunta en sí presenta un buen contraejemplo: la segunda condición no se relaciona en absoluto con x (que es la razón por la que a menudo prefiero la variante basada en if más flexible que la variante basada en switch). Del ejemplo, es obvio que si se cumple la condición A, x debe establecerse en un cierto valor. Si no se cumple A, entonces se prueba la condición B. Si se cumple, entonces x debería recibir otro valor. Si no se cumplen ni A ni B, entonces x debería permanecer sin cambios.

Aquí podemos ver que se debe usar una rama vacía para comentar sobre la intención del programador para el lector.

Por otro lado, no puedo ver por qué debe haber una cláusula else, especialmente para la última declaración if. En C, no existe tal cosa como 'más si'. Solo hay si y más. En cambio, de acuerdo con MISRA, la construcción debería tener una sangría formal de esta manera (y debería haber puesto las llaves de apertura en sus propias líneas, pero eso no me gusta):

if (A) {
    // do something
}
else {
    if (B) {
        // do something else (no pun intended)
    }
    else {
        // don't do anything here
    }
}

Cuando MISRA pide colocar llaves alrededor de cada rama, se contradice al mencionar "si ... si no construye".

Cualquiera puede imaginar la fealdad de los árboles profundamente anidados si no, ver aquí en una nota al margen . Ahora imagine que esta construcción se puede extender arbitrariamente a cualquier lugar. Luego, pedir una cláusula else al final, pero no en ningún otro lado, se vuelve absurdo.

if (A) {
    if (B) {
        // do something
    }
    // you could to something here
}
else {
    // or here
    if (B) { // or C?
        // do something else (no pun intended)
    }
    else {
        // don't do anything here, if you don't want to
    }
    // what if I wanted to do something here? I need brackets for that.
}

Por lo tanto, estoy seguro de que las personas que desarrollaron las pautas de MISRA tenían en mente la intención de cambiar si no es así.

Al final, se trata de que definan con precisión qué se entiende con un "if ... else if construct"


5

La razón básica es probablemente la cobertura del código y la otra implícita: ¿cómo se comportará el código si la condición no es verdadera? Para una prueba genuina, necesita alguna forma de ver que ha probado con la condición falsa. Si cada caso de prueba que tiene pasa por la cláusula if, su código podría tener problemas en el mundo real debido a una condición que no probó.

Sin embargo, algunas condiciones pueden ser como el Ejemplo 1, como en una declaración de impuestos: "Si el resultado es menor que 0, ingrese 0." Aún necesita hacerse una prueba donde la condición es falsa.


5

Lógicamente, cualquier prueba implica dos ramas. ¿Qué haces si es verdad y qué haces si es falso?

Para aquellos casos en que cualquiera de las ramas no tiene funcionalidad, es razonable agregar un comentario sobre por qué no necesita tener funcionalidad.

Esto puede ser beneficioso para el próximo programador de mantenimiento. No deberían tener que buscar demasiado para decidir si el código es correcto. Puedes prehunt el elefante .

Personalmente, me ayuda, ya que me obliga a mirar el caso más y evaluarlo. Puede ser una condición imposible, en cuyo caso puedo lanzar una excepción ya que se viola el contrato. Puede ser benigno, en cuyo caso un comentario puede ser suficiente.

Su experiencia puede ser diferente.


4

La mayoría de las veces cuando solo tiene una sola ifdeclaración, es probable que sea una de las razones, tales como:

  • Verificaciones de guardia de función
  • Opción de inicialización
  • Rama de procesamiento opcional

Ejemplo

void print (char * text)
{
    if (text == null) return; // guard check

    printf(text);
}

Pero cuando lo haga if .. else if, probablemente sea una de las razones, tales como:

  • Caja de cambio dinámico
  • Horquilla de procesamiento
  • Manejo de un parámetro de procesamiento

Y en caso de que if .. else ifcubra todas las posibilidades, en ese caso su último if (...)no es necesario, simplemente puede eliminarlo, porque en ese punto los únicos valores posibles son los cubiertos por esa condición.

Ejemplo

int absolute_value (int n)
{
    if (n == 0)
    {
        return 0;
    }
    else if (n > 0)
    {
        return n;
    }
    else /* if (n < 0) */ // redundant check
    {
        return (n * (-1));
    }
}

Y en la mayoría de estos motivos, es posible que algo no encaje en ninguna de las categorías en su if .. else if, por lo tanto, la necesidad de manejarlos en una elsecláusula final , el manejo se puede hacer a través de un procedimiento de nivel comercial, notificación de usuario, mecanismo de error interno, ..etc.

Ejemplo

#DEFINE SQRT_TWO   1.41421356237309504880
#DEFINE SQRT_THREE 1.73205080756887729352
#DEFINE SQRT_FIVE  2.23606797749978969641

double square_root (int n)
{
         if (n  > 5)   return sqrt((double)n);
    else if (n == 5)   return SQRT_FIVE;
    else if (n == 4)   return 2.0;
    else if (n == 3)   return SQRT_THREE;
    else if (n == 2)   return SQRT_TWO;
    else if (n == 1)   return 1.0;
    else if (n == 0)   return 0.0;
    else               return sqrt(-1); // error handling
}

Esta elsecláusula final es bastante similar a algunas otras cosas en idiomas como Javay C++, como:

  • default caso en una declaración de cambio
  • catch(...)que viene después de todos los catchbloques específicos
  • finally en una cláusula try-catch

2

Nuestro software no era de misión crítica, pero también decidimos usar esta regla debido a la programación defensiva. Agregamos una excepción de lanzamiento al código teóricamente inalcanzable (switch + if-else). Y nos salvó muchas veces ya que el software falló rápidamente, por ejemplo, cuando se agregó un nuevo tipo y se nos olvidó cambiar uno o dos en caso contrario o cambiar. Como beneficio adicional, fue muy fácil encontrar el problema.


2

Bueno, mi ejemplo implica un comportamiento indefinido, pero a veces algunas personas intentan ser elegantes y fallan mucho, eche un vistazo:

int a = 0;
bool b = true;
uint8_t* bPtr = (uint8_t*)&b;
*bPtr = 0xCC;
if(b == true)
{
    a += 3;
}
else if(b == false)
{
    a += 5;
}
else
{
    exit(3);
}

Probablemente nunca esperarías tener lo boolque no es trueni false, sin embargo, puede suceder. Personalmente, creo que este es un problema causado por una persona que decide hacer algo elegante, pero una elsedeclaración adicional puede evitar más problemas.


1

Actualmente estoy trabajando con PHP. Crear un formulario de registro y un formulario de inicio de sesión. Solo estoy usando if y else. No más si o algo que sea innecesario.

Si el usuario hace clic en el botón Enviar -> pasa a la siguiente declaración if ... si el nombre de usuario es menor que 'X', entonces alerta. Si tiene éxito, verifique la longitud de la contraseña, etc.

No es necesario un código adicional, como otro, si eso podría descartar la confiabilidad del tiempo de carga del servidor para verificar todo el código adicional.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.