¿Cuándo usar otra cosa en condiciones? [duplicar]

13

¿Cuándo usar otra cosa en condiciones?

1)

a)

long int multiplyNumbers(int n)
{
    if (n >= 1) {
        return n*multiplyNumbers(n-1);
    } else {
        return 1;
    }
}

o

b)

long int multiplyNumbers(int n)
{
    if (n >= 1) {
        return n*multiplyNumbers(n-1);
    } 

    return 1;
}

2)

a)

int max(int num1, int num2) {
   int result;

   if (num1 > num2) {
      result = num1;
   } else {
      result = num2;
   }

   return result; 
}

o

b)

int max(int num1, int num2) {

   if (num1 > num2) {
      return num1;
   } else {
      return num2;
   }
}

o

c)

int max(int num1, int num2) {
   if (num1 > num2) {
      return num1;
   } 

   return num2;
}

¿Hay una regla sobre cuándo usar otra cosa?

¿Las instrucciones if con else llevan más memoria? Por un lado, son más legibles, pero por otro lado, demasiada anidación es una mala lectura.

Si lanzo excepciones, entonces es mejor no usar otra cosa, ¿pero si estas son operaciones condicionales ordinarias como en mis ejemplos?

    
pregunta zohub 19.01.2018 - 19:56

6 respuestas

42

Puedo decirte cuál yo usaría y por qué razones, sin embargo, creo que habrá tantas opiniones como miembros de la comunidad aquí ...

1) Ninguno. Yo escribiría:

long int multiplyNumbers(int n)
{
    if (n < 1) {
        return 1;
    }
    return n*multiplyNumbers(n-1);
}

Esto se debe a que me gusta la idea de "salida anticipada" en los casos especiales, y esto me recuerda ese escenario. La regla general es: si necesita manejar un caso especial o un caso general, primero maneje el caso especial. Es de suponer que el caso especial (por ejemplo, un error o una condición de límite) se manejará rápidamente y el resto del código aún no está demasiado lejos de la condición 'if', por lo que no puede omitir algunas líneas de código para examinarlo. También facilita la lectura del manejo del caso general, ya que solo tiene que desplazarse hacia abajo en el cuerpo de la función.

En otras palabras, en lugar de:

void foo(int bar)
{
    if (!special_case_1) {
        if (!special_case_2) {
            return handle_general_case();
        }
        else {
            return handle_special_case_2();
        }
    }
    else {
        return handle_special_case_1();
    }
}

Estoy abogando por escribir esto:

int foo(int bar)
{
    if (special_case_1) {
        return handle_special_case_1();
    }
    if (special_case_2) {
        return handle_special_case_2();
    }
    return handle_general_case();
}

Se dará cuenta de que, si es posible, prefiero no introducir una variable adicional 'resultado' aquí, principalmente porque entonces no tengo que preocuparme de si se inicializa o no. (Si lo introduce, entonces lo inicializa en la parte superior, solo para sobrescribirlo en cada rama, o no lo hace, y luego puede olvidarse de inicializarlo en alguna rama. Además, incluso si lo inicializa en cada rama, su analizador de código estático puede no ver esto y puede quejarse sin razón.)

2) Yo usaría (b) (con la declaración de 'resultado' tirada). Esto se debe a que, en este caso, no puedo decir que num1 < num2 o num1 > = num2 es un caso especial. Son iguales a mis ojos y por lo tanto (b) se prefiere a (c). (Ya expliqué por qué, en este caso, no introduciría una variable adicional, por lo que (a) en mis ojos es inferior a (b).)

    
respondido por el user285148 19.01.2018 - 20:33
15

No hay reglas estrictas y rápidas, pero aquí hay algunas pautas básicas que le pido a mi equipo que siga:

  • Evita los ifs profundamente anidados
  • Organice los bloques de código para que estén cerca de la condición que los afecta
  • Para la mayoría de las comprobaciones de validación, las comprobaciones nulas o la lógica de programación típica, puede evitar las declaraciones else usando el patrón de guarda.
  • Para la lógica de negocios, donde no solo está verificando las condiciones de borde, sino que está eligiendo qué ruta seguir, es más importante representar la lógica en el código de una manera que se asigna fácilmente a la especificación. Entonces, si la especificación contiene otras declaraciones (o flechas, en un diagrama de flujo), está bien tenerlas en el código.

Ejemplo simple. En lugar de

if (condition1)
{
    if (condition2)
    {
        DoSomething();
        return result;
    }
    else
    {
        return errorCode;
    }
}
else
{
    return errorCode;
}

Es más fácil de leer:

if (!condition1) return errorCode;
if (!condition2) return errorCode;
DoSomething();
return result;
  1. Hemos reducido el nivel de anidamiento. Las personas que leen el código no tienen que combinar las condiciones en su cabeza para descubrir la lógica.

  2. La acción asociada con cada expresión condicional está justo al lado de la instrucción if que determina si se ejecutará. Las personas que lean el código no tendrán que seguir los corchetes para averiguar qué código se ve afectado por qué condición, y no tendrán que desplazarse hacia arriba y hacia abajo para comprender el código.

  3. Este enfoque sigue el patrón de guarda que reduce complejidad cíclica , que ha demostrado reducir las tasas de defectos.

Ver también:

Cómo evitar si las cadenas

Patrón de código para tener la menor complixidad

Código de flecha de aplanamiento

    
respondido por el John Wu 19.01.2018 - 21:43
10

No hay una regla universal, y encontrará personas razonables que no están de acuerdo sobre si tener una devolución múltiple es una buena idea.

En los casos en que puede hacer que el código sea tan simple con una sola devolución, esto es preferible. Por ejemplo:

int max(int num1, int num2) {
   return (num1 > num2) ? num1 : num2;
}

En los casos en los que puede simplificar el código utilizando múltiples devoluciones, creo que es aceptable si sigue el lenguaje de "salida anticipada":

long int multiplyNumbers(int n)
{
    if (n < 1) {
        return 1;
    } 
    return n*multiplyNumbers(n-1);
}

Es decir, tiene algunos casos especiales que se pueden manejar en la parte superior de la función, omitiendo la lógica más compleja. Pero aparte de esto, recomendaría no tener múltiples devoluciones o devoluciones dentro de bloques anidados, ya que hace que la lógica sea más difícil de seguir en funciones más complejas.

    
respondido por el JacquesB 19.01.2018 - 20:27
4

Respecto al uso de la memoria:

  • Esto no es algo de lo que debas preocuparte.
  • No, en general, realmente no debería haber una diferencia.
  • El compilador cambiará las cosas como mejor le parezca. La legibilidad es realmente el único problema aquí.

No creo que haya una respuesta definitiva absoluta. Sin embargo, hay algunos aspectos que quizás desee considerar.

Ejemplo 1

Preferiría regresar antes, si la condición representa algún tipo de caso especial.

unsigned factorial(unsigned n)
{
    if (n <= 1) {
        return 1;  // this is a special case
    }

    return n * factorial(n-1);  // this is the "normal" case
}

Si tiene dos casos y no puede decidir qué caso es especial, entonces un enfoque de if/else podría tener más sentido.

Ejemplo 2

Habrá excepciones, pero evitaría declarar una variable que no se use hasta más adelante. Así que elegiría 2a) sobre 2b). 2c) tiene sentido si num1 > num2 es algo especial, como se describe anteriormente.

    
respondido por el doubleYou 19.01.2018 - 20:29
1

No hay una regla fija. Con experiencia, descubrirás varias reglas que aplicarás en varios casos. En tu ejemplo, tienes dos ramas que son simétricas y simples, por lo que solo usas if (...) {return x; } else {return y; }

Aspectos a tener en cuenta: a veces tiene una función bastante grande con algunos casos especiales fáciles de eliminar primero. si (condición simple) devuelve x; si (otra condición simple) devuelve y; seguido de un montón de código.

Otra cosa a considerar: para la depuración, es posible que desee establecer un punto de interrupción cuando la función regrese, por lo que solo desea una declaración de retorno. (Si tiene un depurador que le permite interrumpirse cuando la función regresa, incluso con varias declaraciones de devolución, tiene suerte).

Y no desea secuencias anidadas si / else, con el resto de la primera si está a media milla de distancia, por lo que organiza el código en consecuencia.

    
respondido por el gnasher729 19.01.2018 - 22:04
1

Como ya han mencionado otros: en general, es preferible mantener el camino feliz con el mínimo sangrado posible, y regresar temprano cuando sea necesario.

En una nota al margen, en estos dos casos simples, recomendaría usar una expresión condicional, ya que este es el caso de uso ideal para ello:

1) return (n < 1) ? 1 : n*multiplyNumbers(n-1);

2) return (num2 < num1) ? num1 : num2;

Ciertamente evitaría el método 2.a. Es largo, y la variable temporal es un desorden adicional sin beneficio de legibilidad (por ejemplo, no tiene un nombre informativo).

    
respondido por el Alexander 20.01.2018 - 23:02

Lea otras preguntas en las etiquetas