Parfois, une instruction if
peut être assez longue ou longue, aussi, pour des raisons de lisibilité, il est préférable d’extraire les appels compliqués avant le if
.
par exemple. cette:
if (SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall())
{
// do stuff
}
dans cette
bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();
if (b1 || b2)
{
//do stuff
}
(l'exemple fourni n'est pas cela mauvais, c'est juste pour illustration ... imaginez d'autres appels avec plusieurs arguments, etc.)
Mais avec cette extraction, j'ai perdu l'évaluation du court-circuit (SCE).
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 l'avantage d'être facile à comprendre, 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 boucles for est le suivant:
if (!SomeComplicatedFunctionCall())
return; // or continue
if (!SomeOtherComplicatedFunctionCall())
return; // or continue
// do stuff
L’évaluation des courts-circuits offre les mêmes avantages en termes de performances de Nice, mais le code semble plus lisible.
J'ai tendance à décomposer les conditions sur plusieurs lignes, à savoir:
if( SomeComplicatedFunctionCall()
|| OtherComplicatedFunctionCall()
) {
Même si vous avez plusieurs opérateurs (&&), il vous suffit d’avancer l’indention avec chaque paire de crochets. SCE entre toujours en jeu - inutile d'utiliser des variables. Écrire du code de cette façon le rendait beaucoup plus lisible depuis des années déjà. Exemple plus complexe:
if( one()
||( two()> 1337
&&( three()== 'foo'
|| four()
)
)
|| five()!= 3.1415
) {
Si vous avez de longues chaînes de conditions et que vous devez 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 expressions lambda pour combiner des expressions en fonctions, comme ci-dessus:
auto e = []()
{
return SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
};
if (e() && some_other_expression) { ... }
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 travaillera.
Mais ce n'est pas beaucoup plus lisible que par exemple:
if (
someComplicatedStuff()
||
otherComplicatedStuff()
)
1) Oui, vous n'avez plus de SCE. Sinon, vous auriez ça
bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();
fonctionne dans un sens ou dans l’autre selon qu’il existe une instruction if
ultérieurement. Bien trop complexe.
2) Ceci est basé sur l'opinion, mais pour des expressions raisonnablement complexes, vous pouvez faire:
if (SomeComplicatedFunctionCall()
|| OtherComplicatedFunctionCall()) {
Si cela s'avère trop complexe, la solution évidente consiste à créer une fonction qui évalue l'expression et l'appelle.
1) Est-ce que je perds vraiment SCE à chaque fois? Le compilateur est-il autorisé dans certains scénarios à "l'optimiser" tout en fournissant la SCE?
Je ne pense pas qu'une telle optimisation est autorisée; en particulier OtherComplicatedFunctionCall()
peut avoir des effets secondaires.
2) Quelle est la meilleure pratique dans une telle situation? Est-ce seulement une possibilité (quand je veux SCE) d'avoir tout ce dont j'ai besoin directement à l'intérieur si et "juste le formater pour qu'il soit aussi lisible que possible"?
Je préfère le reformuler dans une fonction ou une variable avec un nom descriptif; qui préservera à la fois l’évaluation des courts-circuits et la lisibilité:
bool getSomeResult() {
return SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
}
...
if (getSomeResult())
{
//do stuff
}
Et à mesure que nous implémentons getSomeResult()
sur la base de SomeComplicatedFunctionCall()
et OtherComplicatedFunctionCall()
, nous pourrions les décomposer de manière récursive si elles sont toujours compliquées.
1) Est-ce que je perds vraiment SCE à chaque fois? Le compilateur est-il autorisé dans certains scénarios à "l'optimiser" tout en fournissant la SCE?
Non vous ne le faites pas, mais c'est appliqué différemment:
if (SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall())
{
// do stuff
}
Ici, le compilateur ne lancera même pas OtherComplicatedFunctionCall()
si SomeComplicatedFunctionCall()
renvoie true.
bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();
if (b1 || b2)
{
//do stuff
}
Ici, les deux fonctions will s'exécutent car elles doivent être stockées dans b1
Et b2
. Ff b1 == true
Alors b2
Ne sera pas évalué (SCE). Mais OtherComplicatedFunctionCall()
a déjà été exécuté.
Si b2
N'est utilisé nulle part ailleurs, le compilateur pourrait être assez intelligent pour insérer l'appel de fonction dans le if si la fonction n'a aucun effet secondaire observable.
2) Quelle est la meilleure pratique dans une telle situation? Est-ce seulement une possibilité (quand je veux SCE) d'avoir tout ce dont j'ai besoin à l'intérieur si et "juste le formater pour qu'il soit aussi lisible que possible"?
Ça dépend. Avez-vous besoinOtherComplicatedFunctionCall()
à exécuter en raison d'effets secondaires ou si l'impact sur les performances de la fonction est minime, vous devez utiliser la deuxième approche pour plus de lisibilité. Sinon, respectez la SCE lors de la première approche.
Une autre possibilité qui court-circuite et a les conditions dans un 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 insérer la boucle dans une fonction et la laisser accepter une liste de conditions et générer une valeur booléenne.
Très étrange: vous parlez de lisibilité quand personne ne mentionne l'utilisation de comment 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 fais toujours précéder mes fonctions de commentaires, sur la fonction elle-même, sur ses entrées et ses sorties, 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)
Bien entendu, la mise en forme à 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à:
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) {
...
}