web-dev-qa-db-fra.com

Les affectations dans la partie conditionnelle des conditions sont-elles une mauvaise pratique?

Supposons que je veuille écrire une fonction qui concatène deux chaînes en C. La façon dont je l'écrirais est la suivante:

void concat(char s[], char t[]){
    int i = 0;
    int j = 0;

    while (s[i] != '\0'){
        i++;
    }

    while (t[j] != '\0'){
        s[i] = t[j];
        i++;
        j++;
    }

    s[i] = '\0';

}

Cependant, K&R dans leur livre l'a implémenté différemment, notamment en incluant autant que possible la partie condition de la boucle while:

void concat(char s[], char t[]){
    int i, j;
    i = j = 0;
    while (s[i] != '\0') i++;

    while ((s[i++]=t[j++]) != '\0');

}

Quelle voie est préférée? Est-il encouragé ou découragé d'écrire du code comme le font K&R? Je crois que ma version serait plus facile à lire par d'autres personnes.

35
Richard Smith

Préférez toujours la clarté à l'intelligence. Auparavant, le meilleur programmeur était celui dont personne ne pouvait comprendre le code. "Je ne peux pas comprendre son code, il doit être un génie", ont-ils dit. De nos jours, le meilleur programmeur est celui dont tout le monde peut comprendre le code. Le temps de l'ordinateur est désormais moins cher que celui du programmeur.

Tout imbécile peut écrire du code qu'un ordinateur peut comprendre. Les bons programmeurs écrivent du code que les humains peuvent comprendre. (M. Fowler)

Donc, sans aucun doute, je choisirais l'option A. Et c'est ma réponse définitive.

79
Tulains Córdova

La règle d'or, comme dans la réponse de Tulains Córdova, est de s'assurer d'écrire du code intelligible. Mais je ne suis pas d'accord avec la conclusion. Cette règle d'or signifie écrire du code que le programmeur typique qui finira par maintenir votre code peut comprendre. Et vous êtes le meilleur juge pour savoir qui est le programmeur type qui finira par maintenir votre code.

Pour les programmeurs qui n'ont pas commencé par C, la première version est probablement plus facile à comprendre, pour des raisons que vous connaissez déjà.

Pour ceux qui ont grandi avec ce style de C, la deuxième version pourrait bien être plus facile à comprendre: pour eux, il est tout aussi compréhensible de ce que fait le code, pour eux, cela laisse moins de questions pourquoi il est écrit tel quel, et pour eux , moins d'espace vertical signifie que plus de contexte peut être affiché à l'écran.

Vous devrez vous fier à votre bon sens. À quel public souhaitez-vous rendre votre code plus facile à comprendre? Ce code est-il écrit pour une entreprise? Ensuite, le public cible est probablement les autres programmeurs de cette entreprise. Est-ce un projet de passe-temps personnel sur lequel personne ne travaillera que vous-même? Ensuite, vous êtes votre propre public cible. Est-ce ce code que vous souhaitez partager avec d'autres? Ensuite, ces autres sont votre public cible. Choisissez la version qui correspond à ce public. Malheureusement, il n'y a pas de moyen unique d'encourager.

32
hvd

EDIT: La ligne s[i] = '\0'; a été ajouté à la première version, ce qui le corrige comme décrit dans la variante 1 ci-dessous, donc cela ne s'applique plus à la version actuelle du code de la question.

La deuxième version a l'avantage distinctif d'être correct, tandis que la première ne l'est pas - elle ne termine pas correctement la chaîne cible.

La "cession en condition" permet d'exprimer le concept de "copier chaque caractère avant de vérifier le caractère nul" de manière très concise et d'une manière qui rend l'optimisation pour le compilateur est un peu plus facile, bien que de nombreux ingénieurs logiciels trouvent de nos jours ce style de code moins lisible. Si vous insistez pour utiliser la première version, vous devrez soit

  1. ajoutez la terminaison nulle après la fin de la deuxième boucle (en ajoutant plus de code, mais vous pouvez faire valoir que la compatibilité en vaut la peine) ou
  2. changer le corps de la boucle en "d'abord assigner, puis vérifier ou enregistrer le caractère assigné, puis incrémenter les indices". Vérifier la condition au milieu de la boucle signifie sortir de la boucle (réduire la clarté, désapprouvée par la plupart des puristes). Enregistrer le caractère attribué signifierait introduire une variable temporaire (réduire la clarté et l'efficacité). À mon avis, ces deux éléments annihileraient l'avantage.
14
hjhill

Les réponses de Tulains Córdova et hvd couvrent assez bien les aspects de clarté/lisibilité. Permettez-moi d'ajouter la portée comme une autre raison en faveur des affectations dans des conditions. Une variable déclarée dans la condition n'est disponible que dans la portée de cette instruction. Vous ne pouvez pas utiliser cette variable par la suite par accident. La boucle for fait cela depuis des siècles. Et il est suffisamment important que le prochain C++ 17 introduit une syntaxe similaire pour if et switch :

if (int foo = bar(); foo > 42) {
    do_stuff();
}

foo = 23;   // compiler error: foo is not in scope
5
besc

Non. C'est un style C très standard et normal. Votre exemple est mauvais, car il ne devrait s'agir que d'une boucle for, mais en général il n'y a rien de mal à

if ((a = f()) != NULL)
    ...

par exemple (ou avec while).

3
Miles Rout

Les deux styles sont bien formés, corrects et appropriés. Laquelle est la plus appropriée dépend en grande partie des directives de style de votre entreprise. Les IDE modernes faciliteront l'utilisation des deux styles grâce à l'utilisation de la syntaxe en direct qui met explicitement en évidence les zones qui pourraient autrement devenir une source de confusion.

Par exemple, l'expression suivante est mise en évidence par Netbeans :

if($a = someFunction())

pour "affectation accidentelle".

enter image description here

Pour dire explicitement à Netbeans que "oui, je voulais vraiment faire ça ...", l'expression peut être enveloppée dans un ensemble de parenthèses.

if(($a = someFunction()))

enter image description here

En fin de compte, tout se résume aux directives de style d'entreprise et à la disponibilité d'outils modernes pour faciliter le processus de développement.

2
Luke A. Leber

Au temps de K&R

  • "C" était un code d'assemblage portable
  • Il a été utilisé par les programmeurs qui pensaient dans le code d'assemblage
  • Le compilateur n'a pas fait beaucoup d'optimisation
  • La plupart des ordinateurs avaient des "jeux d'instructions complexes", par exemple while ((s[i++]=t[j++]) != '\0') correspondrait à une instruction sur la plupart des processeurs (je m'attends à la Dec VAC)

Il y a des jours

  • La plupart des gens qui lisent du code C ne sont pas des programmeurs de code d'assemblage
  • Les compilateurs C font beaucoup d'optimisation, donc le code plus simple à lire est susceptible d'être traduit dans le même code machine.

(Une note sur toujours utiliser des accolades - le 1er ensemble de code prend plus de place en raison d'avoir un "inutile" {}, d'après mon expérience, ceux-ci empêchent souvent le code qui a été mal fusionné du compilateur et permettent des erreurs avec des ";" incorrects emplacements à détecter par les outils.)

Cependant, autrefois, la 2e version du code aurait été lue. (Si j'ai bien compris!)

concat(char* s, char *t){      
    while (*s++);
    --s;
    while (*s++=*t++);
}
2
Ian

Même pouvoir le faire est une très mauvaise idée. Il est familièrement connu comme "Le dernier bug du monde", comme ceci:

if (alert = CODE_RED)
{
   launch_nukes();
}

Bien que vous ne risquiez pas de faire une erreur tout à fait aussi grave, il est très facile de visser accidentellement et de provoquer une erreur difficile à trouver dans votre base de code. La plupart des compilateurs modernes insèrent un avertissement pour les affectations dans un conditionnel. Ils sont là pour une raison, et vous feriez bien de les écouter et d'éviter simplement cette construction.

2
Mason Wheeler