340 votes

Quel est le but d'utiliser des accolades (c.-à-d. {}) pour une instruction if ou une boucle sur une seule ligne?

Je suis en train de lire des notes de cours de mon professeur de C++ et il a écrit ce qui suit:

  1. Utilisez l'indentation // OK
  2. Ne vous fiez jamais à la précédence des opérateurs - Utilisez toujours des parenthèses // OK
  3. Utilisez toujours un bloc { } - même pour une seule ligne // pas OK, pourquoi ???
  4. Objet const à gauche de la comparaison // OK
  5. Utilisez unsigned pour les variables qui sont >= 0 // astuce sympa
  6. Définir un pointeur sur NULL après la suppression - Protection contre la double suppression // pas mal

La 3ème technique n'est pas claire pour moi: que gagnerais-je en plaçant une ligne dans un { ... }?

Par exemple, prenons ce code bizarre:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
    if (i % 2 == 0)
    {
        j++;
    }
}

et remplacez-le par:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
    if (i % 2 == 0)
        j++;

Quel est l'avantage d'utiliser la 1ère version?

265 votes

Lisibilité et maintenabilité. Il n'est pas immédiatement évident à quel bloc de déclaration appartient 'j++', et qu'ajouter du code après ne sera pas associé à l'instruction if.

30 votes

J'ai toujours été conseillé d'utiliser les accolades {} pour ces lignes pour quelques raisons. Cela rend le code plus clair à lire. De plus, quelqu'un d'autre dans six mois pourrait avoir besoin de modifier votre code, donc la clarté est importante et avec les accolades, une erreur est moins susceptible de se produire. Il n'y a rien de techniquement plus correct à ce sujet, c'est plus une question de bonne pratique. Gardez à l'esprit qu'un projet peut contenir des milliers et des milliers de lignes de code pour un nouveau venu à parcourir!

4 votes

Je le fais parce que le code change, et ça m'agace quand je dois ajouter des accolades pour ajouter une ligne de code. "Toujours" est un mot fort, cependant.

523voto

Luchian Grigore Points 136646

Essayons également de modifier i lorsque nous incrémentons j :

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
    if (i % 2 == 0)
        j++;
        i++;

Oh non ! Venant de Python, cela semble correct, mais en réalité ce ne l'est pas, car c'est équivalent à :

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
    if (i % 2 == 0)
        j++;
i++;

Bien sûr, c'est une erreur stupide, mais même un programmeur expérimenté pourrait la faire.

Une autre très bonne raison est soulignée dans la réponse de ta.speot.is.

Une troisième que je peux penser est les if imbriqués :

if (cond1)
   if (cond2) 
      doSomething();

Maintenant, supposez que vous voulez désormais exécuter doSomethingElse() lorsque cond1 n'est pas remplie (nouvelle fonctionnalité). Donc :

if (cond1)
   if (cond2) 
      doSomething();
else
   doSomethingElse();

ce qui est clairement incorrect, puisque le else est associé au if interne.


Édition : Comme cela attire l'attention, je vais clarifier mon point de vue. La question à laquelle je répondais est :

Quel est l'avantage d'utiliser la 1ère version ?

Ce que j'ai décrit. Il y a certains avantages. Mais, à mon avis, les règles "toujours" ne s'appliquent pas toujours. Donc je ne soutiens pas entièrement

Utilisez toujours un bloc { } - même pour une seule ligne // pas OK, pourquoi ???

Je ne dis pas d'utiliser toujours un bloc {}. S'il s'agit d'une condition et d'un comportement assez simples, ne le faites pas. Si vous pensez que quelqu'un pourrait venir plus tard et modifier votre code pour ajouter une fonctionnalité, alors oui.

0 votes

C'est bien, mais seulement deux points. 1) Encore pire est d'avoir j++;i++; sur la même ligne en pensant que c'est correct, ce n'est pas le cas. 2) Un bon IDE devrait vous indiquer que i est non défini car il est hors de portée.

5 votes

@Science_Fiction: Vrai, mais si vous ajoutez i++ avant j++, alors les deux variables seront toujours dans la portée lorsqu'elles sont utilisées.

26 votes

Cela semble très raisonnable, mais néglige le fait que l'éditeur fait l'indentation, pas vous, et il va indenter le i++; d'une manière qui montre immédiatement qu'il ne fait pas partie de la boucle. (Dans le passé, cela aurait pu être un argument raisonnable, et j'ai vu de tels problèmes. Il y a environ 20 ans. Pas depuis.)

332voto

ta.speot.is Points 15157

Il est très facile de modifier accidentellement le flux de contrôle avec des commentaires si vous n'utilisez pas { et }. Par exemple:

if (condition)
  do_something();
else
  do_something_else();

must_always_do_this();

Si vous commentez do_something_else() avec un commentaire sur une seule ligne, vous obtiendrez ceci:

if (condition)
  do_something();
else
  //do_something_else();

must_always_do_this();

Il compile, mais must_always_do_this() n'est pas toujours appelé.

Nous avons eu ce problème dans notre base de code, où quelqu'un était intervenu pour désactiver rapidement une fonctionnalité avant la publication. Heureusement, nous l'avons repéré lors de la revue de code.

3 votes

Ohh mon garçon !! c'est un comportement défini que must_always_do_this(); s'exécutera si vous commentez //do_something_else();

0 votes

Bonne réponse, mais il semble que la première phrase l'ait mal tournée, donc je l'ai corrigée. Je le dis au cas où je me tromperais car il est étrange que personne d'autre n'ait remarqué et corrigé.

1 votes

@Supr, comme il l'a écrit pour la première fois, il dit qu'il est difficile de rompre le flux correct si vous utilisez des accolades, puis donne un exemple de la facilité avec laquelle il est possible de le rompre sans avoir correctement mis en parenthèses le code

60voto

James Kanze Points 96599

Je doute de la compétence du conférencier. En considérant ses points :

  1. OK
  2. Est-ce que quelqu'un écrirait vraiment (ou voudrait lire) (b*b) - ((4*a)*c) ? Certaines priorités sont évidentes (ou devraient l'être), et les parenthèses supplémentaires ajoutent simplement à la confusion. (D'un autre côté, vous _devriez_ utiliser les parenthèses dans les cas moins évidents, même si vous savez qu'elles ne sont pas nécessaires.)
  3. Pas tout à fait. Il existe deux conventions largement répandues pour le formatage des conditionnels et des boucles : if ( cond ) { code; } et : if ( cond ) { code; } Dans le premier cas, je suis d'accord avec lui. L'ouverture de { n'est pas très visible, il est donc préférable de toujours supposer qu'elle est là. En revanche, dans le second cas, je (et la plupart des personnes avec lesquelles j'ai travaillé) n'avons aucun problème à omettre les accolades pour une seule instruction. (À condition, bien sûr, que l'indentation soit systématique et que vous utilisiez ce style de manière cohérente. (Et beaucoup de très bons programmeurs, écrivant un code très lisible, omettent les accolades même lorsqu'ils formatent de la première manière.)
  4. NON. Des choses comme if ( NULL == ptr ) sont assez laides pour entraver la lisibilité. Écrivez les comparaisons de manière intuitive. (Ce qui, dans de nombreux cas, se traduit par la constante à droite.) Son point 4 est un mauvais conseil ; tout ce qui rend le code artificiel le rend moins lisible.
  5. NON. Tout sauf int est réservé à des cas spéciaux. Pour les programmeurs C et C++ expérimentés, l'utilisation de unsigned signale les opérateurs bit à bit. C++ n'a pas de vrai type cardinal (ou tout autre type de sous-intervalle efficace) ; unsigned ne fonctionne pas pour les valeurs numériques, en raison des règles de promotion. Les valeurs numériques sur lesquelles aucune opération arithmétique n'aurait de sens, comme les numéros de série, pourraient éventuellement être unsigned. Je serais en désaccord, cependant, parce que cela envoie le mauvais message : les opérations bit à bit n'ont pas de sens non plus. La règle de base est que les types intégraux sont int, _à moins qu'il y ait une raison significative d'utiliser un autre type.
  6. NON. Faire cela systématiquement est trompeur et ne protège pas réellement contre quoi que ce soit. Dans du code strictement orienté objet, delete this; est souvent le cas le plus fréquent (et vous ne pouvez pas définir this à NULL), et sinon, la plupart des delete se trouvent dans des destructeurs, de sorte que vous ne pouvez de toute façon pas accéder au pointeur ultérieurement. Et le définir à NULL ne fait rien pour les autres pointeurs qui traînent. Définir systématiquement le pointeur à NULL donne un faux sentiment de sécurité, et ne vous apporte pas grand-chose en réalité.

Regardez le code dans l'une des références typiques. Stroustrup enfreint chaque règle que vous avez donnée, à l'exception de la première, par exemple.

Je vous suggère de trouver un autre conférencier. Celui qui sait vraiment de quoi il parle.

14 votes

Le numéro 4 pourrait être laid mais il a une utilité. Il essaie d'éviter le if (ptr = NULL). Je ne pense pas avoir jamais utilisé delete this, est-ce plus courant que ce que j'ai vu? Je ne pense pas qu'il soit si mal de définir un pointeur sur NULL après utilisation mais cela dépend. Peut-être que c'est juste moi mais la plupart de ses directives ne semblent pas si mauvaises.

17 votes

@Firedragon: La plupart des compilateurs vous mettront en garde à propos de if (ptr = NULL) à moins que vous ne l'écriviez comme ceci : if ((ptr = NULL)). Je dois convenir avec James Kanze que la laideur de mettre NULL en premier en fait un NON définitif pour moi.

35 votes

@JamesKanze : Je dois dire que je ne suis pas d'accord avec la plupart de ce que vous avez dit ici - bien que j'apprécie et respecte vos arguments pour y arriver. Pour les programmeurs C et C++ expérimentés, l'utilisation d'opérateurs de bits non signés. - Je ne suis pas du tout d'accord: l'utilisation d'opérateurs de bits signale l'utilisation d'opérateurs de bits. Pour moi, l'utilisation de unsigned indique une aspiration de la part du programmeur selon laquelle la variable devrait représenter uniquement des nombres positifs. Le mélange avec des nombres signés provoquera généralement un avertissement du compilateur, ce qui était probablement ce que le conférencier voulait dire.

49voto

Konrad Rudolph Points 231505

Toutes les autres réponses défendent la règle 3 de votre professeur.

Permettez-moi de dire que je suis d'accord avec vous : la règle est redondante et je ne la conseillerais pas. Il est vrai que cela théoriquement prévient les erreurs si vous ajoutez toujours des accolades. D'un autre côté, je n'ai jamais rencontré ce problème dans la vraie vie : contrairement à ce que les autres réponses laissent entendre, je n'ai jamais oublié d'ajouter les accolades une fois qu'elles sont devenues nécessaires. Si vous utilisez une indentation correcte, il devient immédiatement évident que vous devez ajouter des accolades une fois que plus d'une instruction est indentée.

La réponse de Component 10 met en lumière le seul cas concevable où cela pourrait vraiment conduire à une erreur. Mais d'un autre côté, remplacer du code via une expression régulière exige toujours énormément de soin de toute façon.

Penchons-nous maintenant sur l'autre côté de la médaille : y a-t-il un désavantage à utiliser toujours des accolades ? Les autres réponses ignorent simplement ce point. Mais il y a un inconvénient : cela prend beaucoup d'espace vertical à l'écran, et cela peut rendre votre code illisible car cela signifie que vous devez défiler plus que nécessaire.

Considérez une fonction avec beaucoup de clauses de garde au début (et oui, ce qui suit est un code C++ horrible mais dans d'autres langages, cela serait une situation assez courante) :

void some_method(obj* a, obj* b)
{
    if (a == nullptr)
    {
        throw null_ptr_error("a");
    }
    if (b == nullptr)
    {
        throw null_ptr_error("b");
    }
    if (a == b)
    {
        throw logic_error("Impossible de faire une méthode sur des objets identiques");
    }
    if (not a->precondition_met())
    {
        throw logic_error("Précondition pour a non respectée");
    }

    a->do_something_with(b);
}

Il s'agit d'un code horrible, et je soutiens fermement que ce qui suit est beaucoup plus lisible :

void some_method(obj* a, obj* b)
{
    if (a == nullptr)
        throw null_ptr_error("a");
    if (b == nullptr)
        throw null_ptr_error("b");
    if (a == b)
        throw logic_error("Impossible de faire une méthode sur des objets identiques");
    if (not a->precondition_met())
        throw logic_error("Précondition pour a non respectée");

    a->do_something_with(b);
}

De même, les boucles imbriquées courtes bénéficient de l'omission des accolades :

matrix operator +(matrix const& a, matrix const& b) {
    matrix c(a.w(), a.h());

    for (auto i = 0; i < a.w(); ++i)
        for (auto j = 0; j < a.h(); ++j)
            c(i, j) = a(i, j) + b(i, j);

    return c;
}

Comparez avec :

matrix operator +(matrix const& a, matrix const& b) {
    matrix c(a.w(), a.h());

    for (auto i = 0; i < a.w(); ++i)
    {
        for (auto j = 0; j < a.h(); ++j)
        {
            c(i, j) = a(i, j) + b(i, j);
        }
    }

    return c;
}

Le premier code est concis ; le second code est gonflé.

Et oui, cela peut être atténué dans une certaine mesure en plaçant l'accolade ouvrante sur la ligne précédente. Donc : si vous insistez sur les accolades, au moins mettez l'accolade ouvrante sur la ligne précédente.

En résumé : ne rédigez pas un code inutile qui prend de l'espace à l'écran.


Depuis que j'ai écrit la réponse originale, j'ai principalement accepté le style de code prédominant et j'utilise des accolades sauf si je peux mettre l'ensemble de l'instruction unique sur la ligne précédente. Je maintiens toujours que ne pas utiliser des accolades redondantes est généralement plus lisible, et je n'ai toujours jamais rencontré de bug causé par cela.

0 votes

+1 pour un argument concurrent raisonnable. Cependant, j'ai tendance à ne pas être d'accord. C'est peut-être étrange, mais je trouve ton exemple de matrice beaucoup plus difficile à lire sans les crochets supplémentaires. Je peux voir que la première semble meilleure étant donné que le throw vous expulserait de la fonction, mais pour la plupart des exemples, je voudrais les crochets. J'ai aussi vu où la première ligne d'une fonction est conditionnelle et suivie d'un 'return' et ce n'est pas si mal non plus. Mais l'utilisation de cela par rapport à la possibilité d'erreurs, je pense que c'est tout simplement trop grand.

29 votes

Si vous ne croyez pas à l'écriture de code qui occupe inutilement de l'espace à l'écran, alors vous n'avez pas d'affaires à mettre l'accolade d'ouverture sur sa propre ligne. Je vais probablement devoir me cacher et fuir la sainte vengeance de GNU, mais sérieusement -- soit vous voulez que votre code soit compact verticalement, soit vous ne le voulez pas. Et si c'est le cas, ne faites pas des choses conçues uniquement pour rendre votre code moins compact verticalement. Mais comme vous dites, après avoir corrigé cela, vous voudriez également supprimer les accolades redondantes. Ou peut-être juste écrire if (a == nullptr) { throw null_ptr_error("a"); } sur une seule ligne.

0 votes

Pour ce que ça vaut, dans l'exemple de matrice, personnellement, je laisserais les accolades, avec l'accolade ouvrante sur la même ligne que la for et les accolades fermantes chacune sur leur propre ligne. Mais je n'aurais pas de ligne vide après la boucle, et je pourrais ne pas en avoir une avant non plus, donc j'utiliserais au maximum une ligne de plus que votre style préféré.

41voto

jam Points 2030

La base de code sur laquelle je travaille est parsemée de code écrit par des gens qui ont une aversion pathologique pour les accolades, et pour les personnes qui interviennent plus tard, cela peut vraiment faire une différence en termes de maintenabilité.

L'exemple le plus fréquent de problème que j'ai rencontré est le suivant :

if (déclaration vraiment incroyablement stupide et massivement longue qui dépasse la largeur de l'éditeur) do_foo;
    this_looks_like_a_then_statement_but_isn't;

Donc, lorsque je souhaite ajouter une instruction then, je peux facilement me retrouver avec ceci si je ne fais pas attention :

if (déclaration vraiment incroyablement stupide et massivement longue qui dépasse la largeur de l'éditeur) do_foo;
{
    this_looks_like_a_then-statement_but_isn't;
    i_want_this_to_be_a_then_statement_but_it's_not;
}

Étant donné qu'il faut environ 1 seconde pour ajouter des accolades et que cela peut vous faire économiser au minimum quelques minutes de débogage confus, pourquoi choisiriez-vous jamais l'option de réduction de l'ambiguïté ? Cela me semble être une fausse économie.

18 votes

Le problème dans cet exemple réside-t-il dans l'indentation incorrecte et les lignes trop longues plutôt que dans les accolades ?

8 votes

Oui, mais suivre les consignes de conception/codage qui sont uniquement 'sécurisées' en supposant que les gens suivent également d'autres consignes (comme ne pas avoir des lignes trop longues) semble demander des ennuis. Si les accolades avaient été présentes dès le début, il aurait été impossible de se retrouver avec un bloc if incorrect dans cette situation.

16 votes

Comment l'ajout de crochets (en le rendant if(really long...editor){ do_foo;}) vous aiderait-il à éviter ce cas ? Il semble que le problème resterait le même. Personnellement, je préfère éviter les crochets quand ce n'est pas nécessaire, cependant cela n'a rien à voir avec le temps nécessaire pour les écrire mais avec la lisibilité réduite due aux deux lignes supplémentaires dans le code.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X