web-dev-qa-db-fra.com

Pourquoi nous utilisons if, else if au lieu de multiple if block si le corps est une instruction de retour

J'ai toujours l'habitude d'utiliser l'instruction if, else-if au lieu de plusieurs instructions if.

Exemple:

int val = -1;
if (a == b1) {
   return c1;
} else if (a == b2) {
   return c2;
} ...
...
} else {
   return c11;
}

Comment se compare-t-il à l'exemple 2:

if (a == b1) {
   return c1;
}
if (a == b2) {
   return c2;
}
....

if (a == b11) {
   return c11;
}

Je sais que les fonctionnalités sont les mêmes. Mais est-il préférable de faire si sinon, si non? Cela a été soulevé par un de mes amis lorsque j'ai souligné qu'il pouvait structurer la base de code différemment pour la rendre plus propre. C'est déjà une habitude pour moi depuis longtemps mais je n'ai jamais demandé pourquoi.

34
Lily

Les instructions if-elseif-else Cessent de faire des comparaisons dès qu'elles en trouvent une qui est vraie. if-if-if Fait chaque comparaison. Le premier est plus efficace.

Edit: Il a été souligné dans les commentaires que vous faites un return dans chaque bloc if. Dans ces cas, ou dans les cas où le contrôle quittera la méthode (exceptions), il n'y a pas de différence entre faire plusieurs instructions if et faire des instructions if-elseif-else.

Cependant, il est préférable d'utiliser de toute façon if-elseif-else. Supposons que vous modifiez votre code de manière à ne pas faire de return dans chaque bloc de if. Ensuite, pour rester efficace, vous devez également passer à un idiome if-elseif-else. Le faire être if-elseif-else Dès le début vous évite des modifications à l'avenir, et est plus clair pour les personnes qui lisent votre code (soyez témoin de la mauvaise interprétation que je viens de vous donner en faisant un survol de votre code!).

59
CanSpice

Qu'en est-il du cas où b1 == b2? (Et si a == b1 et a == b2?)

Lorsque cela se produit, de manière générale, les deux morceaux de code suivants peuvent avoir un comportement différent:

if (a == b1) {
   /* do stuff here, and break out of the test */
} 
else if (a == b2) {
   /* this block is never reached */
} 

et:

if (a == b1) {
   /* do stuff here */
}
if (a == b2) {
   /* do this stuff, as well */
}

Si vous souhaitez délimiter clairement les fonctionnalités des différents cas, utilisez if-else ou switch-case pour faire n test.

Si vous souhaitez des fonctionnalités différentes pour plusieurs cas, utilisez plusieurs blocs if comme tests séparés.

Il ne s'agit pas de "meilleures pratiques" mais de définir si vous avez un ou plusieurs tests.

10
Alex Reynolds

Ils ne sont PAS fonctionnellement équivalents.

La seule façon dont il serait fonctionnellement équivalent est que vous fassiez une instruction "if" pour chaque valeur possible d'un (c'est-à-dire: chaque valeur éventuellement int, telle que définie dans limits.h en C; en utilisant INT_MIN et INT_MAX, ou l'équivalent en Java ).

L'instruction else vous permet de couvrir toutes les valeurs restantes possibles sans avoir à écrire des millions d'instructions "si".

En outre, il est préférable d'utiliser le codage si ... sinon si ... sinon, tout comme dans une instruction switch/case, votre compilateur vous lancera un avertissement si vous ne fournissez pas de déclaration de cas "par défaut". Cela vous empêche d'ignorer les valeurs non valides dans votre programme. par exemple:

double square_root(double x) {
    if(x > 0.0f) {
        return sqrt(x);
    } else if(x == 0.0f) {
        return x;
    } else {
        printf("INVALID VALUE: x must be greater than zero");
        return 0.0f;
    }
}

Voulez-vous taper des millions d'instructions if pour chaque valeur possible de x dans ce cas? J'en doute :)

À votre santé!

7
Cloud

Avec des instructions return dans chaque branche if.

Dans votre code, vous avez des instructions return dans chacune des conditions if. Lorsque vous avez une situation comme celle-ci, il y a deux façons d'écrire cela. Le premier est de savoir comment vous l'avez écrit dans l'exemple 1:

if (a == b1) {
   return c1;
} else if (a == b2) {
   return c2;
} else {
   return c11;
}

L'autre est le suivant:

if (a == b1) {
   return c1;
}
if (a == b2) {
   return c2;
}
return c11; // no if or else around this return statement

Ces deux façons d'écrire votre code sont identiques.

La façon dont vous avez écrit votre code dans l'exemple 2 ne se compilerait pas en C++ ou Java (et serait un comportement indéfini en C), car le compilateur ne sait pas que vous avez couvert tous les possibles valeurs de a donc il pense qu'il y a un chemin de code à travers la fonction qui peut vous mener à la fin de la fonction sans retourner de valeur de retour.

if (a == b1) {
   return c1;
}
if (a == b2) {
   return c2;
}
...
if (a == b11) {
   return c11;
}
// what if you set a to some value c12?

Sans les instructions return dans chaque branche if.

Sans les instructions return dans chaque branche if, votre code ne serait fonctionnellement identique que si les instructions suivantes sont vraies:

  1. Vous ne modifiez pas la valeur de a dans aucune des branches if.
  2. == est une relation d'équivalence (au sens mathématique) et aucune des b1 à b11 sont dans la même classe d'équivalence.
  3. == n'a pas d'effets secondaires.

Pour clarifier davantage le point # 2 (et aussi le point # 3):

  • == est toujours une relation d'équivalence en C ou Java et n'a jamais d'effets secondaires.
  • Dans les langues qui vous permettent de remplacer le ==, comme C++, Ruby ou Scala, le == L'opérateur peut ne pas être une relation d'équivalence et il peut avoir des effets secondaires. Nous espérons certainement que quiconque supplantera le == L'opérateur était suffisamment sain d'esprit pour écrire une relation d'équivalence qui n'a pas d'effets secondaires, mais il n'y a aucune garantie.
  • En JavaScript et certains autres langages de programmation avec des règles de conversion de type lâche, il existe des cas intégrés dans le langage où == n'est ni transitif, ni symétrique. (En Javascript, === est une relation d'équivalence.)

En termes de performances, l'exemple n ° 1 est garanti de ne pas effectuer de comparaisons après celui qui correspond. Il peut être possible pour le compilateur d'optimiser # 2 pour ignorer les comparaisons supplémentaires, mais c'est peu probable. Dans l'exemple suivant, ce n'est probablement pas le cas, et si les chaînes sont longues, les comparaisons supplémentaires ne sont pas bon marché.

if (strcmp(str, "b1") == 0) {
  ...
}
if (strcmp(str, "b2") == 0) {
  ...
}
if (strcmp(str, "b3") == 0) {
  ...
}
1
Ken Bloom

Cela dépend totalement de la condition que vous testez. Dans votre exemple, cela ne fera finalement aucune différence, mais comme meilleure pratique, si vous souhaitez que l'une des conditions soit finalement exécutée, vous feriez mieux de l'utiliser sinon

if (x > 1) {
    System.out.println("Hello!");
}else if (x < 1) {
    System.out.println("Bye!");
}

Notez également que si la première condition est VRAIE, la seconde ne sera PAS vérifiée du tout, mais si vous utilisez

if (x > 1) {
    System.out.println("Hello!");
}
if (x < 1) {
    System.out.println("Bye!");
}

La deuxième condition sera vérifiée même si la première condition est VRAIE. L'optimiseur pourrait éventuellement résoudre ce problème, mais pour autant que je sache, il se comporte de cette façon. De plus, le premier est celui qui est destiné à être écrit et se comporte comme ceci, donc c'est toujours le meilleur choix pour moi, sauf si la logique l'exige autrement.

1
Anddo

Dans la plupart des cas, l'utilisation d'instructions if-elseif-else et switch sur des instructions if-if-if est plus efficace (car elle facilite la création de tables de saut/recherche par le compilateur) et de meilleures pratiques car elle rend votre code plus lisible, De plus, le compilateur s'assure que vous incluez un cas par défaut dans le commutateur. Cette réponse, ainsi que cette tablea comparant les trois déclarations différentes ont été synthétisées en utilisant d'autres messages de réponse sur cette page ainsi que ceux d'un similaire SO question .

1
janeon

J'ai tendance à penser que l'utilisation de else if est plus facile et plus robuste face aux changements de code. Si quelqu'un devait ajuster le flux de contrôle de la fonction et remplacer un retour avec effet secondaire ou un appel de fonction par un try-catch les else-if échouerait dur si toutes les conditions étaient vraiment exclusives. Cela dépend vraiment beaucoup du code exact avec lequel vous travaillez pour porter un jugement général et vous devez considérer les compromis possibles avec brièveté.

1
pmr

Je préfère les structures if/else, car il est beaucoup plus facile d'évaluer tous les états possibles de votre problème dans chaque variation avec des commutateurs. C'est plus robuste que je trouve et plus rapide à déboguer, surtout lorsque vous effectuez plusieurs évaluations booléennes dans un environnement de type faible comme PHP, par exemple pourquoi elseif est mauvais (exagéré pour la démonstration):

if(a && (c == d))
{
} elseif ( b && (!d || a))
{
} elseif ( d == a && ( b^2 > c))
{
} else {
}

Ce problème a au-delà de 4 ^ 2 = 16 états booléens, ce qui est simplement pour démontrer les effets de typage faible qui aggravent les choses. Il n'est pas si difficile d'imaginer une variable à trois états, un problème à trois variables impliqué dans un if ab elseif bc type de chemin.

Laissez l'optimisation au compilateur.

1
Ewald van Geffen

if et else if est différent de deux instructions if consécutives. Dans le premier, lorsque le CPU prend la première branche if la else if ne sera pas vérifié. Dans les deux instructions if consécutives, même si la première if est vérifiée et prise, la if suivante sera également vérifiée et prise si la condition est vraie.

0
Tony The Lion

Je pense que ces extraits de code sont équivalents pour la simple raison que vous avez de nombreuses instructions return. Si vous aviez une seule instruction return, vous utiliseriez des constructions else qui ici ne sont pas nécessaires.

0
Alessandro Santini

Votre comparaison repose sur le fait que le corps des instructions if renvoie le contrôle de la méthode. Sinon, la fonctionnalité serait différente.

Dans ce cas, ils exécutent la même fonctionnalité. Ce dernier est beaucoup plus facile à lire et à comprendre à mon avis et serait mon choix quant à l'utilisation.

0
Justin Niessner

Ils font potentiellement des choses différentes.

Si a est égal à b1 et b2, vous entrez deux blocs if. Dans le premier exemple, vous n'en saisissez qu'un seul. J'imagine que le premier exemple est plus rapide car le compilateur doit probablement vérifier chaque condition séquentiellement car certaines règles de comparaison peuvent s'appliquer à l'objet. Il peut être en mesure de les optimiser ... mais si vous ne voulez en saisir qu'une, la première approche est plus évidente, moins susceptible de conduire à une erreur de développeur ou à un code inefficace, donc je recommanderais certainement cela.

0
Philluminati

La réponse de CanSpice est correcte. Une considération supplémentaire pour les performances consiste à déterminer quel conditionnel se produit le plus souvent. Par exemple, si a == b1 ne se produit que 1% du temps, vous obtenez de meilleures performances en vérifiant d'abord l'autre cas.

La réponse de Gir Loves Tacos est également bonne. La meilleure pratique consiste à vous assurer que tous les cas sont couverts.

0
jhsowter