2009-05-25 7 views
7

J'espère juste que ce qui suit ne semble pas vous aimez jabber redondant :)
Quoi qu'il en soit, il est que:question de style à propos de morceau existant de code (C/C++)

for (p = fmt; *p; p++) { 
    if (*p != '%') { 
     putchar(*p); 
     continue; 
    } 
    switch (*++p) { 
     /* Some cases here */ 
     ... 
    } 
} 

Et je me demandais pourquoi l'auteur (Kernighan/Ritchie) a utilisé le continue dans l'instruction if.
Je pensais que c'était pour la simple raison qu'il pensait que ce serait plus élégant que d'indenter l'ensemble switch sous une déclaration else, qu'en pensez-vous?

Répondre

15

Probablement. Le cerveau humain a un espace de pile limité, ce qui rend difficile la gestion de structures profondément imbriquées. Tout ce qui aplatit l'information que nous sommes censés analyser en facilite la compréhension.

De même, je préfère normalement ceci:

bool foo(int arg) 
{ 
    if(!arg) { 
     /* arg can't be 0 */ 
     return false; 
    } 

    /* Do some work */ 
    return true; 
} 

à ceci:

bool foo(int arg) 
{ 
    if(!arg) { 
     /* arg can't be 0 */ 
     return false; 
    } else { 
     /* Do some work */ 
     return true; 
    } 
} 

Ou pire, à ceci:

bool foo(int arg) 
{ 
    if(arg) { 
     /* Do some work */ 
     return true; 
    } else { 
     /* arg can't be 0 */ 
     return false; 
    } 
} 

Dans le dernier exemple, la partie qui fait le travail pourrait être assez long. Au moment où le lecteur arrive à la clause else, il peut ne pas se souvenir comment il est arrivé là. Mettre les conditions de renflouement au plus près du début permet de s'assurer que les personnes qui essaient d'appeler vos fonctions auront une bonne idée des entrées attendues par la fonction. De plus, comme d'autres l'ont souligné, la continuation indique clairement qu'il n'est pas nécessaire de lire davantage le code dans la boucle pour déterminer si un traitement supplémentaire est effectué après ce point pour ce cas, ce qui facilite le suivi du code. . Encore une fois, moins vous forcez le lecteur à garder une trace, mieux c'est.

1

Il y a toujours plusieurs façons d'écrire du code comme celui-ci -

Mettre l'interrupteur entier à l'intérieur d'une instruction else serait tout à fait valable. Je suppose que la raison pour laquelle ils l'ont fait de cette façon ~ peut être juste la façon dont ils pensaient à l'époque:

"si la valeur à p n'est pas égale à '%', mettre alors continuer."

Si vous avez basculé sous un autre, il n'est peut-être pas aussi évident pour l'enregistreur que vous passiez à l'itération suivante dans ce cas particulier.

Ceci est un choix de style complètement personnel, cependant. Je ne m'inquiéterais pas trop - il suffit de l'écrire d'une manière qui a le plus de sens pour vous et votre équipe.

1

Je suis d'accord. Mais vous ne pouvez pas le considérer comme une «simple raison», c'est en fait une bonne raison, car cela réduit la complexité globale du code. Le rendre plus court et plus facile à lire et à comprendre.

2

Il est tellement plus facile de lire quand il est mis comme ça. Est-ce que nous avons fini ici avec cette itération à travers la boucle?

Oui?Alors laissez-nous continuer avec l'itération suivante.

+1

ce peut soutenir - cela dépend si cet idiome est commun dans votre code. Si ce n'est pas le cas, il présente l'inconvénient d'être un moyen inhabituel de contrôler une boucle, et un problème potentiel de maintenance/source de bogue. –

+0

oui, c'est tout à fait correct. Les gens qui ne sont pas habitués à cet idiome peuvent être confus lorsqu'ils sont confrontés à son maintien. C'est juste ce que je suis habitué et ce que l'heuristique m'a appris est meilleur. C'est plus facile pour moi de me perdre dans des blocs imbriqués si-alors-alors pour remarquer une pause; ou continuer; –

1

Si vous utilisez un else alors tout à l'intérieur des else a besoin d'être en retrait:

if() 
{ 
    doA(); 
} 
else 
{ 
    doB(); 
    if() 
    { 
    doC(); 
    } 
    else 
    { 
    doD() 
    } 
} 

Si vous utilisez continue vous n'avez pas besoin de tiret:

if() 
{ 
    doA() 
    continue; 
} 
doB(); 
if() 
{ 
    doC(); 
    continue; 
} 
doD(); 

aussi, continue moyens que je peux arrêter de penser à ce cas: par exemple, si je vois else alors peut-être il y aura plus de traitement de l'affaire '%' plus tard dans la boucle, soit à la fin de la else déclaration; alors qu'en voyant continue je sais instantanément que le traitement du cas '%' dans la boucle est complètement terminé.

1

La raison la plus probable est que le switch qui suit est plutôt long - cela ressemble à l'analyse de format printf.

2

Je pense qu'il aurait des raisons assez pour indenter le code sous le commutateur, et indentant la viande entière de la fonction est tout à fait inutile de l'espace horizontal. Au moment où le code a été écrit, j'imagine que 80 largeurs de caractères étaient toujours populaires.

Je ne pense pas que ce soit difficile à comprendre, mais je pense qu'il est assez agréable de mentionner ce que vous ne faites pas immédiatement, puis GTFO. Parce qu'avec la suite, il est clair que le code est fait pour cette itération de boucle.

9

Si un autre aurait été utilisé, vous devez également vérifier s'il n'y a pas de code après l'autre.

Je pense que c'est une bonne habitude générale de quitter un contexte le plus tôt possible car cela conduit à un code beaucoup plus clair.


Par exemple:

if(arg1 == NULL) 
    return; 

if(arg2 == NULL) 
    return; 

//Do some stuff 

contre

if(arg1 != null) 
{ 
    if(arg2 != null) 
    { 
    //Do some stuff 
    } 
} 
+1

+1 Effacer le code. En plus de cela, lorsque le code est modifié par la suite, vous n'avez pas besoin de penser à tous les cas d'erreurs car ils ont été traités en haut, vous savez que vous n'avez qu'à traiter les cas valides. Je pense qu'il est très élégant d'écrire du code de cette façon - j'avais de l'expérience avec la version 'autre' aussi bien que c'était un standard dans mon autre entreprise - p. un seul truc de retour ... – stefanB

1

Il pourrait y avoir plus d'une raison de continuer/briser une boucle. Donc, il regarderait prochaine:

loop 
{ 
    if (cond1) 
    { 
     if (cond2) 
     { 
     if (cond2) 
     { 
      more conditions... 
     } 
     } 
    } 
    else 
    { 
     the loop action 
    } 
} 

à mon humble avis, il est pas si élégante et lisible que la boucle dans votre exemple, par exemple:

loop 
{ 
    if (cond1) 
     continue; 
    if (cond2) 
     continue; 
    if (cond2) 
     continue; 
    if(more conditions...) 
     continue; 

    the loop action 
} 

Et vous ne même pas besoin de comprendre toutes les structures de tous " si "s" (cela pourrait être beaucoup plus complexe) de comprendre la logique de la boucle.

P.S. juste pour le cas: je ne pense pas que les auteurs ont pensé à écrire cette boucle, ils l'ont juste écrit :)

-2

Eh bien, j'ai écrit des programmes en C pendant environ 11 ans et j'ai dû lire 5 fois votre morceau de code pour le comprendre! Kernighan et Ritchie étaient actifs dans les années soixante. À ce moment-là, être capable de comprendre un morceau de code n'était pas pertinent. Etre capable d'écrire du code qui correspond à 16 Ko était.

Donc, je ne suis pas surpris.C est un langage terrible quand vos teatchers sont K & R. Il suffit de regarder realloc: qui connaitrait quelque chose comme ça aujourd'hui? Dans les années 60, il faisait fureur, mais il est maintenant effroyable, au moins: o)

+2

K & R est sorti en 1978, pas les années 1960, et est généralement considéré comme un très bon morceau d'écriture, concis et bien écrit. La clarté du code était très pertinente: lors de la Conférence de l'ingénierie logicielle de l'OTAN en 1968, dix ans plus tôt, les participants étaient d'accord pour dire que le logiciel devenait si complexe qu'il y avait une «crise du logiciel». L'exemple de code en cours de discussion utilise des idiomes C (et C++) très courants. –

+0

Eh bien, regardez RealLoc. Un vrai morceau de merde selon les normes d'aujourd'hui. Si vous aimez K et R, cool, vous avez juste 2 décennies de retard! Tout le meilleur! – SRO

0

Je m'en tiens aux enseignements de Dijkstra: goto est néfaste. Et continuer/rompre sont les petits frères de goto. Si le problème est que vous indentez trop le code, la solution ne met pas un continu dans la boucle, mais réduit la complexité en séparant le code dans différentes fonctions ou en réfléchissant à une meilleure façon de l'organiser.

Par exemple, extrait @Kamarey serait encore plus clair comme ceci:

loop 
{ 
    if (!(cond1 || 
     cond2 || 
     cond2 || 
     ...)) 
    { 
     the loop actions; 
    } 
} 

ou par exemple @Ori Pessah pourrait être exprimé comme:

bool foo(int arg) 
{ 
    if(arg) { 
     /*do some work*/ 
    } 

    return arg != 0; 
} 

En bref, je peux habituellement pas trouvé une assez bonne raison d'utiliser des constructions de programmation non structurés (peut-être pour certains codes de nettoyage dans un ocasions très limité ...)

+0

Je respecte ce que vous dites. Je sais que Dijkstra a exprimé son objection à l'utilisation de GOTO mais a-t-il dit quelque chose au sujet de continuer/pause? Je trouve que ces instructions simplifient le code. –

+0

Si je comprends bien, E.D. objecté à GOTOs arbitraires. continuer/casser sont assez limités dans ce que peuvent être leurs cibles; D'autre part, GOTO peut potentiellement aller à n'importe quel endroit. – mlp