Instruction if - évaluation de court-circuit vs lisibilité

90

Parfois, une ifinstruction peut être assez compliquée ou longue, donc pour des raisons de lisibilité, il est préférable d'extraire les appels compliqués avant le if.

par exemple ceci:

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

dans ce

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

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

(l'exemple fourni n'est pas si mal, c'est juste à titre d'illustration ... imaginez d'autres appels avec plusieurs arguments, etc.)

Mais avec cette extraction, j'ai perdu l'évaluation des courts-circuits (SCE).

  1. Est-ce que je perds vraiment SCE à chaque fois? Existe-t-il un scénario dans lequel le compilateur est autorisé à «l'optimiser» tout en fournissant SCE?
  2. Existe-t-il des moyens de conserver la lisibilité améliorée du deuxième extrait de code sans perdre SCE?
relaxxx
la source
20
La pratique montre que la plupart des réponses sur les performances que vous verrez ici ou ailleurs sont dans la plupart des cas fausses (4 fausses 1 correcte). Mon conseil est de toujours faire un profilage et de le vérifier vous-même, vous éviterez une "optimisation prématurée" et apprendrez de nouvelles choses.
Marek R
25
@MarekR n'est pas seulement une question de performances, il s'agit d'effets secondaires possibles dans OtherCunctionCall ...
relaxxx
3
@David lorsque vous faites référence à d'autres sites, il est souvent utile de souligner que la publication croisée est mal vue
gnat
7
Si la lisibilité est votre principale préoccupation, n'appelez pas les fonctions avec des effets secondaires à l'intérieur d'un si conditionnel
Morgen
3
Électeurs proches potentiels: relisez la question. La partie (1) n'est pas basée sur une opinion, tandis que la partie (2) peut facilement cesser d'être basée sur une opinion grâce à une modification qui supprime la référence à toute "meilleure pratique" supposée, comme je suis sur le point de le faire.
duplode

Réponses:

119

Une solution naturelle ressemblerait à ceci:

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

if (bn)
{
  // do stuff
}

Cela présente les avantages d'être facile à comprendre, d'être applicable à tous les cas et d'avoir un comportement de court-circuit.


C'était ma solution initiale: un bon modèle dans les appels de méthode et les corps de boucle for est le suivant:

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

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

// do stuff

On obtient les mêmes avantages de performances que l'évaluation des courts-circuits, mais le code semble plus lisible.

Horia Coman
la source
4
@relaxxx: Je comprends, mais "plus de choses à faire après le if" est aussi un signe que votre fonction ou méthode est trop grande et devrait être divisée en plus petites. Ce n'est pas toujours la meilleure façon mais très souvent!
nperson325681
2
cela viole le principe de la liste blanche
JoulinRouge
13
@JoulinRouge: Intéressant, je n'avais jamais entendu parler de ce principe. Je préfère moi-même cette approche "court-circuit" pour les avantages sur la lisibilité: elle réduit les indentations et élimine la possibilité que quelque chose se produise après le bloc en retrait.
Matthieu M.
2
Est-ce plus lisible? Nommez b2correctement et vous obtiendrez someConditionAndSomeotherConditionIsTrue, pas super significatif. De plus, je dois garder un tas de variables sur ma pile mentale pendant cet exercice (et jusqu'à ce que j'arrête de travailler dans ce cadre). J'irais avec SJuan76la solution numéro 2 ou tout simplement mettre le tout dans une fonction.
Nathan Cooper
2
Je n'ai pas lu tous les commentaires mais après une recherche rapide, je n'ai pas trouvé un gros avantage du premier extrait de code à savoir le débogage. Placer des éléments directement dans l'instruction if au lieu de l'affecter à une variable au préalable, puis utiliser la variable à la place rend le débogage plus difficile qu'il ne devrait l'être. L'utilisation de variables permet également de regrouper les valeurs sémantiquement, ce qui augmente la lisibilité.
rbaleksandar
31

J'ai tendance à décomposer les conditions sur plusieurs lignes, c'est-à-dire:

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

Même lorsque vous traitez avec plusieurs opérateurs (&&), il vous suffit d'avancer l'indentation avec chaque paire de parenthèses. SCE intervient toujours - pas besoin d'utiliser des variables. Écrire du code de cette façon me le rendait beaucoup plus lisible depuis des années déjà. Exemple plus complexe:

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

Si vous avez de longues chaînes de conditions et que conserver une partie du court-circuit, vous pouvez utiliser des variables temporaires pour combiner plusieurs conditions. En prenant votre exemple, il serait possible de faire par exemple

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

Si vous avez un compilateur compatible C ++ 11, vous pouvez utiliser des expressions lambda pour combiner des expressions en fonctions, comme ci-dessus:

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

if (e() && some_other_expression) { ... }
Un mec programmeur
la source
21

1) Oui, vous n'avez plus SCE. Sinon, tu aurais ça

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

fonctionne dans un sens ou dans l'autre selon qu'il y a une ifdéclaration plus tard. Beaucoup trop complexe.

2) Ceci est basé sur une opinion, mais pour des expressions raisonnablement complexes, vous pouvez faire:

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

Si cela est trop complexe, la solution évidente est de créer une fonction qui évalue l'expression et l'appelle.

SJuan76
la source
21

Vous pouvez aussi utiliser:

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

et SCE fonctionnera.

Mais ce n'est pas beaucoup plus lisible que par exemple:

if (
    someComplicatedStuff()
    ||
    otherComplicatedStuff()
   )
KIIV
la source
3
Je n'ai pas envie de combiner des booléens avec un opérateur binaire, cela ne me semble pas bien typé. En général, j'utilise ce qui semble le plus lisible, sauf si je travaille à un niveau très bas et que les cycles du processeur comptent.
Ant
3
J'ai utilisé b = b || otherComplicatedStuff();spécifiquement et @SargeBorsch a fait une modification pour supprimer SCE. Merci de m'avoir signalé ce changement @Ant.
KIIV
14

1) Est-ce que je perds vraiment du SCE à chaque fois? Un scénario est-il autorisé au compilateur pour «l'optimiser» et toujours fournir SCE?

Je ne pense pas qu'une telle optimisation soit autorisée; surtout OtherComplicatedFunctionCall()pourrait avoir des effets secondaires.

2) Quelle est la meilleure pratique dans une telle situation? Est-ce seulement la possibilité (quand je veux SCE) d'avoir tout ce dont j'ai besoin directement à l'intérieur si et "formatez-le simplement pour qu'il soit aussi lisible que possible"?

Je préfère le refactoriser en une fonction ou une variable avec un nom descriptif; ce qui préservera à la fois l'évaluation des courts-circuits et la lisibilité:

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

...

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

Et lorsque nous implémentons en getSomeResult()fonction de SomeComplicatedFunctionCall()et OtherComplicatedFunctionCall(), nous pourrions les décomposer de manière récursive s'ils sont encore compliqués.

songyuanyao
la source
2
j'aime cela parce que vous pouvez gagner en lisibilité en donnant à la fonction wrapper un nom descriptif (bien que probablement pas getSomeResult), trop d'autres réponses n'ajoutent vraiment rien de valeur
aw04
9

1) Est-ce que je perds vraiment du SCE à chaque fois? Un scénario est-il autorisé au compilateur pour «l'optimiser» et toujours fournir SCE?

Non, vous ne le faites pas, mais c'est appliqué différemment:

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

Ici, le compilateur ne s'exécutera même pas OtherComplicatedFunctionCall()s'il SomeComplicatedFunctionCall()retourne true.

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

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

Ici, les deux fonctions vont exécuter parce qu'ils doivent être stockés dans b1et b2. Ff b1 == truealors b2ne sera pas évalué (SCE). Mais OtherComplicatedFunctionCall()a déjà été exécuté.

Si b2n'est utilisé nulle part ailleurs, le compilateur peut être assez intelligent pour intégrer l'appel de fonction à l'intérieur du if si la fonction n'a pas d'effets secondaires observables.

2) Quelle est la meilleure pratique dans une telle situation? Est-ce seulement la possibilité (quand je veux SCE) d'avoir tout ce dont j'ai besoin directement à l'intérieur si et "formatez-le simplement pour qu'il soit aussi lisible que possible"?

Ça dépend. Avez-vous besoin OtherComplicatedFunctionCall() d'exécuter en raison d'effets secondaires ou si la performance de la fonction est minime, vous devez utiliser la deuxième approche pour la lisibilité. Sinon, tenez-vous-en à SCE par la première approche.

Coq coiffé
la source
8

Une autre possibilité qui court-circuite et a les conditions en un seul endroit:

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

Vous pouvez mettre la boucle dans une fonction et laisser la fonction accepter une liste de conditions et afficher une valeur booléenne.

levilime
la source
1
@Erbureth Non, ils ne le sont pas. Les éléments du tableau sont des pointeurs de fonction, ils ne sont exécutés que lorsque les fonctions sont appelées dans la boucle.
Barmar
Merci Barmar, mais j'ai fait un montage, Erbureth avait raison, avant le montage (je pensais que mon montage se propagerait visuellement plus directement).
levilime
4

Très étrange: vous parlez de lisibilité lorsque personne ne mentionne l'utilisation du commentaire dans le code:

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

En plus de cela, je précède toujours mes fonctions avec quelques commentaires, sur la fonction elle-même, sur son entrée et sa sortie, et parfois je mets un exemple, comme vous pouvez le voir ici:

/*---------------------------*/
/*! 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)

Evidemment le formatage à utiliser pour vos commentaires peut dépendre de votre environnement de développement (Visual studio, JavaDoc sous Eclipse, ...)

En ce qui concerne SCE, je suppose que vous entendez par là ce qui suit:

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
la source
-7

La lisibilité est nécessaire si vous travaillez dans une entreprise et votre code sera lu par quelqu'un d'autre. Si vous écrivez un programme pour vous-même, c'est à vous de décider si vous voulez sacrifier les performances au nom d'un code compréhensible.

br0lly
la source
23
Gardant à l'esprit que «vous dans six mois» est définitivement «quelqu'un d'autre», et «vous demain» peut parfois l'être. Je ne sacrifierais jamais la lisibilité pour les performances tant que je n'aurais pas eu de preuves solides qu'il y avait un problème de performances.
Martin Bonner soutient Monica