declaración if - evaluación de cortocircuito frente a legibilidad

90

A veces, una ifdeclaración puede ser bastante complicada o larga, por lo que en aras de la legibilidad es mejor extraer las llamadas complicadas antes del if.

por ejemplo, esto:

if (SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall())
{
    // do stuff
}

dentro de esto

bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();

if (b1 || b2)
{
    //do stuff
}

(proporcionado ejemplo no es que mal, es sólo para la ilustración ... imaginar otras llamadas con múltiples argumentos, etc.)

Pero con esta extracción perdí la evaluación de cortocircuito (SCE).

  1. ¿De verdad pierdo SCE cada vez? ¿Existe algún escenario en el que el compilador pueda "optimizarlo" y seguir proporcionando SCE?
  2. ¿Hay formas de mantener la legibilidad mejorada del segundo fragmento sin perder SCE?
relaxxx
fuente
20
La práctica muestra que la mayoría de las respuestas sobre desempeño que verá aquí o en otros lugares son en la mayoría de los casos incorrectas (4 incorrectas 1 correcta). Mi consejo es siempre hacer un perfil y comprobarlo usted mismo, evitará una "optimización prematura" y aprenderá cosas nuevas.
Marek R
25
@MarekR no se trata solo de rendimiento, se trata de posibles efectos secundarios en OtherCunctionCall ...
relaxxx
3
@David cuando hace referencia a otros sitios, a menudo es útil señalar que la publicación cruzada está mal vista
gnat
7
Si la legibilidad es su principal preocupación, no llame a funciones con efectos secundarios dentro de un if condicional
Morgen
3
Posibles votantes cercanos: lea la pregunta nuevamente. La parte (1) no se basa en opiniones, mientras que la parte (2) puede fácilmente dejar de estar basada en opiniones a través de una edición que elimine la referencia a cualquier supuesta "mejor práctica", como estoy a punto de hacer.
duplode

Respuestas:

119

Una solución natural se vería así:

bool b1 = SomeCondition();
bool b2 = b1 || SomeOtherCondition();
bool b3 = b2 || SomeThirdCondition();
// any other condition
bool bn = bn_1 || SomeFinalCondition();

if (bn)
{
  // do stuff
}

Esto tiene las ventajas de ser fácil de entender, aplicable a todos los casos y tener un comportamiento de cortocircuito.


Esta fue mi solución inicial: un buen patrón en llamadas a métodos y cuerpos de bucle for es el siguiente:

if (!SomeComplicatedFunctionCall())
   return; // or continue

if (!SomeOtherComplicatedFunctionCall())
   return; // or continue

// do stuff

Se obtienen los mismos beneficios de rendimiento de la evaluación de cortocircuitos, pero el código parece más legible.

Horia Coman
fuente
4
@relaxxx: Ya entiendo, pero "más cosas que hacer después del if" también es una señal de que su función o método es demasiado grande y debe dividirse en otros más pequeños. No siempre es la mejor manera, ¡pero muy a menudo lo es!
nperson325681
2
esto viola el principio de la lista blanca
JoulinRouge
13
@JoulinRouge: Interesante, nunca había oído hablar de este principio. Yo mismo prefiero este enfoque de "cortocircuito" por los beneficios de la legibilidad: reduce las sangrías y elimina la posibilidad de que ocurra algo después del bloque sangrado.
Matthieu M.
2
¿Es más legible? Nombra b2correctamente y obtendrás someConditionAndSomeotherConditionIsTrue, no muy significativo. Además, tengo que mantener un montón de variables en mi pila mental durante este ejercicio (y tbh hasta que deje de trabajar en este ámbito). Yo iría con SJuan76la solución número 2 o simplemente pondría todo en una función.
Nathan Cooper
2
No he leído todos los comentarios, pero después de una búsqueda rápida no encontré una gran ventaja del primer fragmento de código, a saber, la depuración. Colocar cosas directamente en la instrucción if en lugar de asignarlas a una variable de antemano y luego usar la variable en su lugar hace que la depuración sea más difícil de lo necesario. El uso de variables también permite agrupar valores semánticamente, lo que aumenta la legibilidad.
rbaleksandar
31

Tiendo a dividir las condiciones en varias líneas, es decir:

if( SomeComplicatedFunctionCall()
 || OtherComplicatedFunctionCall()
  ) {

Incluso cuando se trata de varios operadores (&&), solo necesita hacer una sangría avanzada con cada par de paréntesis. SCE todavía entra en acción, no es necesario utilizar variables. Escribir código de esta manera me lo hizo mucho más legible desde hace años. Ejemplo más complejo:

if( one()
 ||( two()> 1337
  &&( three()== 'foo'
   || four()
    )
   )
 || five()!= 3.1415
  ) {
AmigoJack
fuente
28

Si tiene largas cadenas de condiciones y qué mantener algunos de los cortocircuitos, entonces podría usar variables temporales para combinar múltiples condiciones. Tomando su ejemplo, sería posible hacer, por ejemplo,

bool b = SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
if (b && some_other_expression) { ... }

Si tiene un compilador compatible con C ++ 11, puede usar expresiones lambda para combinar expresiones en funciones, similar a lo anterior:

auto e = []()
{
    return SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
};

if (e() && some_other_expression) { ... }
Algún tipo programador
fuente
21

1) Sí, ya no tiene SCE. De lo contrario, tendrías eso

bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();

funciona de una forma u otra dependiendo si hay una ifdeclaración más adelante. Demasiado complejo.

2) Esto se basa en opiniones, pero para expresiones razonablemente complejas puede hacer:

if (SomeComplicatedFunctionCall()
    || OtherComplicatedFunctionCall()) {

Si es demasiado complejo, la solución obvia es crear una función que evalúe la expresión y la llame.

SJuan76
fuente
21

También puedes usar:

bool b = someComplicatedStuff();
b = b || otherComplicatedStuff(); // it has to be: b = b || ...;  b |= ...; is bitwise OR and SCE is not working then 

y SCE funcionará.

Pero no es mucho más legible que, por ejemplo:

if (
    someComplicatedStuff()
    ||
    otherComplicatedStuff()
   )
KIIV
fuente
3
No estoy interesado en combinar booleanos con un operador bit a bit, eso no me parece bien escrito. Generalmente uso lo que parece más legible a menos que esté trabajando a un nivel muy bajo y los ciclos del procesador cuenten.
Hormiga
3
Lo he usado específicamente b = b || otherComplicatedStuff();y @SargeBorsch hace una edición para eliminar SCE. Gracias por notar ese cambio @Ant.
KIIV
14

1) ¿Realmente pierdo SCE cada vez? ¿Se permite al compilador algún escenario "optimizarlo" y seguir proporcionando SCE?

No creo que esta optimización esté permitida; especialmente OtherComplicatedFunctionCall()puede tener algunos efectos secundarios.

2) ¿Cuál es la mejor práctica en tal situación? ¿Es la única posibilidad (cuando quiero que SCE) tenga todo lo que necesito directamente dentro y "simplemente formatee para que sea lo más legible posible"?

Prefiero refactorizarlo en una función o una variable con un nombre descriptivo; que preservará tanto la evaluación de cortocircuitos como la legibilidad:

bool getSomeResult() {
    return SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
}

...

if (getSomeResult())
{
    //do stuff
}

Y a medida que implementamos en getSomeResult()función de SomeComplicatedFunctionCall()y OtherComplicatedFunctionCall(), podríamos descomponerlos de forma recursiva si aún son complicados.

Songyuanyao
fuente
2
Me gusta esto porque puede obtener algo de legibilidad al darle a la función contenedora un nombre descriptivo (aunque probablemente no getSomeResult), muchas otras respuestas realmente no agregan nada de valor
aw04
9

1) ¿Realmente pierdo SCE cada vez? ¿Se permite al compilador algún escenario "optimizarlo" y seguir proporcionando SCE?

No, no lo haces, pero se aplica de manera diferente:

if (SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall())
{
    // do stuff
}

Aquí, el compilador ni siquiera se ejecutará OtherComplicatedFunctionCall()si SomeComplicatedFunctionCall()devuelve verdadero.

bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();

if (b1 || b2)
{
    //do stuff
}

Aquí, ambas funciones se ejecutarán porque deben almacenarse en b1y b2. Ff b1 == trueentonces b2no será evaluado (SCE). Pero ya OtherComplicatedFunctionCall()se ha ejecutado.

Si no b2se usa en ningún otro lugar, el compilador podría ser lo suficientemente inteligente como para insertar la llamada a la función dentro de if if la función no tiene efectos secundarios observables.

2) ¿Cuál es la mejor práctica en tal situación? ¿Es la única posibilidad (cuando quiero que SCE) tenga todo lo que necesito directamente dentro y "simplemente formatee para que sea lo más legible posible"?

Eso depende. ¿ Necesita OtherComplicatedFunctionCall() ejecutar debido a efectos secundarios o el impacto en el rendimiento de la función es mínimo? Entonces debe usar el segundo enfoque para mejorar la legibilidad. De lo contrario, apéguese a SCE en el primer enfoque.

Gallo con sombrero
fuente
8

Otra posibilidad de que se produzca un cortocircuito y tenga las condiciones en un solo lugar:

bool (* conditions [])()= {&a, &b, ...}; // list of conditions
bool conditionsHold = true;
for(int i= 0; i < sizeOf(conditions); i ++){
     if (!conditions[i]()){;
         conditionsHold = false;
         break;
     }
}
//conditionsHold is true if all conditions were met, otherwise false

Puede poner el ciclo en una función y dejar que la función acepte una lista de condiciones y genere un valor booleano.

levilime
fuente
1
@Erbureth No, no lo son. Los elementos de la matriz son punteros de función, no se ejecutan hasta que las funciones se llaman en el bucle.
Barmar
Gracias Barmar, pero hice una edición, Erbureth tenía razón, antes de la edición (pensé que mi edición se propagaría visualmente de manera más directa).
levilime
4

Muy extraño: estás hablando de legibilidad cuando nadie menciona el uso de comentarios dentro del código:

if (somecomplicated_function() || // let me explain what this function does
    someother_function())         // this function does something else
...

Además de eso, siempre antecedo mis funciones con algunos comentarios, sobre la función en sí, sobre su entrada y salida, y a veces pongo un ejemplo, como puedes ver aquí:

/*---------------------------*/
/*! interpolates between values
* @param[in] X_axis : contains X-values
* @param[in] Y_axis : contains Y-values
* @param[in] value  : X-value, input to the interpolation process
* @return[out]      : the interpolated value
* @example          : interpolate([2,0],[3,2],2.4) -> 0.8
*/
int interpolate(std::vector<int>& X_axis, std::vector<int>& Y_axis, int value)

Obviamente, el formato a utilizar para sus comentarios puede depender de su entorno de desarrollo (Visual studio, JavaDoc bajo Eclipse, ...)

En lo que respecta a SCE, supongo que con esto se refiere a lo siguiente:

bool b1;
b1 = somecomplicated_function(); // let me explain what this function does
bool b2 = false;
if (!b1) {                       // SCE : if first function call is already true,
                                 // no need to spend resources executing second function.
  b2 = someother_function();     // this function does something else
}

if (b1 || b2) {
...
}
Dominique
fuente
-7

La legibilidad es necesaria si trabaja en una empresa y su código será leído por otra persona. Si escribe un programa para usted mismo, depende de usted si desea sacrificar el rendimiento en aras de un código comprensible.

br0lly
fuente
23
Teniendo en cuenta que "tú dentro de seis meses" definitivamente es "otra persona" y "tú mañana" a veces puede serlo. Nunca sacrificaría la legibilidad por el rendimiento hasta que tuviera alguna evidencia sólida de que había un problema de rendimiento.
Martin Bonner apoya a Monica el