relajate
A veces, un if
declaración puede ser bastante complicada o larga, por lo que en aras de la legibilidad es mejor extraer llamadas complicadas antes de la if
.
por ejemplo, esto:
if (SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall())
{
// do stuff
}
dentro de esto
bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();
if (b1 || b2)
{
//do stuff
}
(siempre que el ejemplo no sea que malo, es solo para ilustrar… imagina otras llamadas con múltiples argumentos, etc.)
Pero con esta extracción perdí la evaluación de cortocircuito (SCE).
- ¿Realmente pierdo SCE cada vez? ¿Existe algún escenario en el que el compilador pueda “optimizarlo” y seguir proporcionando SCE?
- ¿Hay formas de mantener la legibilidad mejorada del segundo fragmento sin perder SCE?
Horia Coman
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 los beneficios de ser fácil de entender, ser 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
Uno obtiene los mismos buenos beneficios de rendimiento de la evaluación de cortocircuitos, pero el código parece más legible.
-
@relaxxx: Te entiendo, pero “más cosas que hacer después de la
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!– mike3996
17 de octubre de 2016 a las 9:16
-
esto viola el principio de la lista blanca
– Joulin Rouge
17 de octubre de 2016 a las 9:21
-
@JoulinRouge: Interesante, nunca había oído hablar de este principio. Yo mismo prefiero este enfoque de “cortocircuito” por los beneficios en la legibilidad: reduce las sangrías y elimina la posibilidad de que ocurra algo después del bloque sangrado.
– Matthieu M.
17/10/2016 a las 12:32
-
¿Es más legible? Nombre
b2
correctamente y obtendríassomeConditionAndSomeotherConditionIsTrue
, 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). iría conSJuan76
la solución número 2 de o simplemente poner todo en una función.–Nathan Cooper
17/10/2016 a las 12:44
-
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 declaración if en lugar de asignarlo 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 que debería ser. El uso de variables también permite agrupar valores semánticamente, lo que aumenta la legibilidad.
– rbaleksandar
18/10/2016 a las 18:18
AmigoJack
Tiendo a dividir las condiciones en varias líneas, es decir:
if( SomeComplicatedFunctionCall()
|| OtherComplicatedFunctionCall()
) {
Incluso cuando se trata de múltiples operadores (&&), solo necesita avanzar la sangría con cada par de corchetes. SCE todavía se activa, no es necesario usar variables. Escribir código de esta manera lo hizo mucho más legible para mí durante años. Ejemplo más complejo:
if( one()
||( two()> 1337
&&( three()== 'foo'
|| four()
)
)
|| five()!= 3.1415
) {
Si tiene largas cadenas de condiciones y qué hacer para 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, podría usar expresiones lambda para combinar expresiones en funciones, similar a la anterior:
auto e = []()
{
return SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
};
if (e() && some_other_expression) { ... }
1) Sí, ya no tienes SCE. De lo contrario, tendrías eso
bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();
funciona de una forma u otra dependiendo si hay un if
declaración más tarde. 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 llamarla.
KIIV
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()
)
-
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 en un nivel muy bajo y los ciclos del procesador cuenten.
– Hormiga
18/10/2016 a las 16:21
-
he usado específicamente
b = b || otherComplicatedStuff();
y @SargeBorsch hacen una edición para eliminar SCE. Gracias por notarme sobre ese cambio @Ant.– KIIV
18/10/2016 a las 18:09
1) ¿Realmente pierdo SCE cada vez? ¿Se permite al compilador algún escenario para “optimizarlo” y aún proporcionar SCE?
No creo que tal optimización esté permitida; especialmente OtherComplicatedFunctionCall()
podría tener algunos efectos secundarios.
2) ¿Cuál es la mejor práctica en tal situación? ¿Es la única posibilidad (cuando quiero SCE) tener todo lo que necesito directamente dentro si y “solo formatearlo 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 cortocircuito como la legibilidad:
bool getSomeResult() {
return SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
}
...
if (getSomeResult())
{
//do stuff
}
Y a medida que implementamos getSomeResult()
Residencia en SomeComplicatedFunctionCall()
y OtherComplicatedFunctionCall()
podríamos descomponerlos recursivamente si aún son complicados.
-
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 en un nivel muy bajo y los ciclos del procesador cuenten.
– Hormiga
18/10/2016 a las 16:21
-
he usado específicamente
b = b || otherComplicatedStuff();
y @SargeBorsch hacen una edición para eliminar SCE. Gracias por notarme sobre ese cambio @Ant.– KIIV
18/10/2016 a las 18:09
Gallo con sombrero
1) ¿Realmente pierdo SCE cada vez? ¿Se permite al compilador algún escenario para “optimizarlo” y aún proporcionar SCE?
No, no lo hace, 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 voluntad correr porque tienen que ser almacenados en b1
y b2
. ff b1 == true
después b2
no será evaluado (SCE). Pero OtherComplicatedFunctionCall()
ya se ha ejecutado.
Si b2
no se usa en ninguna otra parte el compilador puede que sea lo suficientemente inteligente como para alinear la llamada de función dentro del if si 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 SCE) tener todo lo que necesito directamente dentro si y “solo formatearlo para que sea lo más legible posible”?
Eso depende. Tú necesitar OtherComplicatedFunctionCall()
para ejecutarse debido a los 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, adhiérase a SCE a través del primer enfoque.
La práctica muestra que la mayoría de las respuestas sobre el rendimiento que verá aquí o en otros lugares son, en la mayoría de los casos, incorrectas (4 incorrectas, 1 correcta). Mi consejo es que hagas siempre un perfilado y lo compruebes tú mismo, evitarás la “optimización prematura” y aprenderás cosas nuevas.
– Marek R.
17 de octubre de 2016 a las 8:27
@MarekR no se trata solo de rendimiento, se trata de posibles efectos secundarios en OtherCunctionCall…
– relaxxxx
17 oct 2016 a las 8:45
@David al referirse a otros sitios, a menudo es útil señalar que la publicación cruzada está mal vista
– mosquito
17 oct 2016 a las 9:35
Si la legibilidad es su principal preocupación, no llame a funciones con efectos secundarios dentro de un condicional condicional.
– Morgen
17 oct 2016 a las 14:38
Posibles votantes cercanos: lea la pregunta nuevamente. La parte (1) es no basado en la opinión, mientras que la parte (2) puede dejar fácilmente de estar basada en la opinión a través de una edición que elimine la referencia a cualquier supuesta “mejor práctica”, como estoy a punto de hacer.
– duplo
17/10/2016 a las 17:55