Cent commentaires vallent mieux que pas du tout

Sans commentaires

Voici un petit programme qui fonctionne. C'est presque une copie du premier programme que j'ai écrit en 1976 sur une calculatrice (sauf que ce n'était pas du C, mais de la notation polonaise inversée):

long f(1L);

byte z, n=1;

void setup() {
  Serial.begin(115200);
  for (; n<16; n++){
delay(1000);
    Serial.print(f*=n);
    for (byte b=0; b<z; b++) Serial.print("0");
    Serial.print("\n");
    while (f%10==0) {
      f/=10;
z++;
}}}

void loop()
{
}
Et parfois, je lis avec de tels programme la petite phrase "Ça ne marche pas, aidez moi!". La bonne réponse à donner est que l'on dépasse la capacité des entiers et que l'on doit donc s'arrêter à 15 et pas à 16. Vous n'avez rien compris? C'est normal, ce programme a été écrit par moi, pour moi, et pour ce jour. En changeant peu de choses, on peut le rendre compréhensible par le plus grand nombre ou au contraire totalement illisible. Ne pas oublier non plus que ce que le programme que l'on maîtrise aujourd'hui deviendra moins lisible dans quelques temps pour son auteur quand sera oublié les astuces ou les principes utilisées.

Ce programme ainsi écrit est peu lisible, mais on peut faire pire. Voici le même programme qui fonctionne:

long fonction(0x01);

byte BUILT_IN, _15UL=1;

void setup() {
  Serial.begin(115200);
  for (; _15UL<15; _15UL++){ delay(1000);
  Serial.print(fonction*=_15UL);
  for (byte Long=0; Long<BUILT_IN; Long++) Serial.print("0");
  Serial.print("\n");
  while (fonction%10==0) { fonction/=10;
  BUILT_IN++;

}}}void loop(){{}}
ou encore
long fonction(0x01);byte BUILT_IN,_15UL=1;void setup(){Serial.begin
(115200);for(;_15UL<15;_15UL++){delay(1000);Serial.print(fonction*=
_15UL);for(byte Long=0;Long<BUILT_IN;Long++)Serial.print("0");Serial.
print("\n");while(fonction%10==0){fonction/=10;BUILT_IN++;}}}void
loop(){{}}

 

Présentation

Présenter correctement un programme permet de le lire aisément, permet aux autres de le lire aussi, et permet de s'y retrouver plus tard. Tous les sauts de lignes supplémentaires, les commentaires, les noms longs (qui sont finalement aussi des commentaires) ne prennent pas de place du tout dans le programme final et ne pénalise pas de ce fait la réalisation. Cela aide à la maintenance c'est tout. C'est comme pour une maison, elle ne sera ni plus solide ni plus étanche si on a les plans de passage des gaines d'électricité, d'eau.... mais que c'est ce que c'est pratique pour une modification ultérieure!

C'est pour cela que bien présenter son code ne peut avoir que des avantages. Le seul désagrément est qu'il est un peu plus long à écrire, mais le fait de se forcer à être clair permet de faire moins d'erreurs.

Ne pas oublier non plus que si le programme ne fonctionne pas et que l'on demande de l'aide, il vaut mieux que les personnes sollicitées puissent facilement comprendre ce dont il s'agit, plutôt que de rejeter la demande en pensant "je n'y comprend rien, je ne vais pas perdre mon temps pour une telle pagaille".

 

Les accolades, l'indentation

Je parle ici des accolades définissant les blocs, pas celles des définitions de tableaux, la déclaration

int carres[] = {0, 1, 4, 9, 16, 25, 36, 49, 64, 81};
me convient tout à fait.

Il y a deux styles classiques pour les accolades:
- on ouvre l'accolade sur la ligne qui la demande
- on ouvre l'accolade sur une nouvelle ligne
Dans tous les cas après une accolade ouvrante, tout ce qui suit est décalé vers la droite (2 espaces avec l'IDE d'Arduino), et tout ce qui suit une accolade fermante ainsi que cette accolade est décalé vers la gauche. Cela donne pour loop():

loop() {                                  loop ()
  <instructions>                          {
}                           OU              <instructions>
                                          }
Par défaut L'IDE d'Arduino utilise la première façon (quand on ouvre un nouveau programme). Et la plupart des programmes que je vois utilisent cette façon. Mais je recommande fortement la deuxième méthode car en plus de mieux faire apparaître le début d'un bloc (suite d'instructions qui sont exécutées ensembles), cela permet d'aligner les accolades verticalement par paire. Cela facilite la recherche d'erreurs de blocs.

Bien faire les décalages (on parlera de bien indenter le code) est important. L'IDE d'Arduino possède même des raccourcis pour le faire. Cnt-T permet de réindenter tout le code en réalignant le début de la ligne (cela fait aussi autre chose dont je parlerai juste après).

Voici le premier programme respectant ces règles:

long f(1L);

byte z, n=1;

void setup() 
{
  Serial.begin(115200);
  for (; n<16; n++)
  {
    delay(1000);
    Serial.print(f*=n);
    for (byte b=0; b<z; b++) Serial.print("0");
    Serial.print("\n");
    while (f%10==0)
    {
      f/=10;
      z++;
    }
  }
}

void loop()
{
}
On voit ainsi mieux que le setup contient une boucle for qui elle même contient une boucle while.

Vous pouvez aussi remarquer que l'IDE d'Arduino, et d'autres éditeurs de code, informent de l'accolade ou de la parenthèse correspondante quand le curseur est à côté d'une accolade ou d'une parenthèse.

 

Un peu d'espace

La deuxième chose que fait le cnt-T est d'aérer un peu le code en séparant avec un espace les variables des opérateurs. Il vaut mieux écrire ainsi surtout si on utilise des comparaisons ">" et les objets dynamiques utilisant "->". Une phrase

if (point->x>ref->x)...
est nettement moins lisible que
if (point->x  >  ref->x)...
déjà que bien écrit, ce n'est pas terrible!

Voici le premier programme passé avec la moulinette cnt-T:

long f(1L);

byte z, n = 1;

void setup()
{
  Serial.begin(115200);
  for (; n < 16; n++)
  {
    delay(1000);
    Serial.print(f *= n);
    for (byte b = 0; b < z; b++) Serial.print("0");
    Serial.print("\n");
    while (f % 10 == 0)
    {
      f /= 10;
      z++;
    }
  }
}

void loop()
{
}

 

Éviter le mélange de style

Vous choisissez un style d'écriture, autant que possible gardez toujours le même. Dans mon programme exemple, j'ai deux initialisations, l'une pour f, l'autre pour n qui ont deux styles différents:

f(1L)
n = 1
Il vaut mieux choisir un seul style, le deuxième est plus clair pour un débutant, le premier devient indispensable avec les objets. Si je veux rendre mon programme le plus lisible possible, il faut que j"écrive le début ainsi:
long f = 1L;

byte z, n = 1;
...

Ne pas mélanger les styles comprend aussi les conventions des accolades, les façons d'écrire les variables...

 

Les noms des variables, des constantes, des fonctions...

Au lieu de choisir des noms courts, on peut prendre pour les variables des noms longs, voir des noms composés de plusieurs mots. Quelques exemples:
Pas terriblePlus clair
r3 = 1,7racineCubique = 1,7
for (byte i = 0; ...for (byte colonne = 0; ...

Par paresse, vous pouvez utiliser des abréviations, mais remplacez-les par des noms corrects avant de montrer votre code à quelqu'un d'autre. On peut garder des abréviation usuelles, nbBoucles est aussi bien que nombreDeBoucles, x y est peut être plus parlant que abscisse et ordonnée.

Il semblerait qu'il y ait un consensus pour l'écriture des noms en plus de ce qui est imposé par le C/C++:
- une constante s'écrit tout en majuscules, du coup le caractère "_" permet de séparer les mots
          const float RACINE_CUBIQUE_DE_3 = 1.732;
- une variable ou une fonction s'écrivent en minuscules et les mots sont séparés par une majuscule
          int tempsEteint = 1000;
          void rectanglePlein(int x, int y, int largeur, int longueur)
- une classe commence par une majuscule
          Stepper monMoteur(...)

Si les noms des variables est bien choisi, le programme devient beaucoup plus compréhensible. Voici mon programme de tout à l'heure:

long factorielle = 1L;

byte nbZeros, nombre = 1;

void setup()
{
  Serial.begin(115200);
  for (; nombre < 16; nombre++)
  {
    delay(1000);
    Serial.print(factorielle *= nombre);
    for (byte zeros = 0; zeros < nbZeros; zeros++) Serial.print("0");
    Serial.print("\n");
    while (factorielle % 10 == 0)
    {
      factorielle /= 10;
      nbZeros++;
    }
  }
}

void loop()
{
}
Sans avoir a analyser ce programme, on peut voir qu'il s'agit d'un affichage de factorielle 1 à 15, et que les zéros sont mis de côté et affichés à part. Mais cela ne nous renseigne pas pourquoi le calcul est ainsi fait.

Notez que l'on peut redéfinir (localement) des noms déjà utilisés. Dans le deuxième programme de cette page, j'ai utilisé le nom de BUILT_IN pour compter le nombre de zéros, ce nom est le numéro de la broche de la led de la carte (13 pour une Uno). J'ai le droite de le faire, mais cela va pas mal embrouiller les choses. De même il y a une variable 8 bits que j'ai nommé Long...

 

Éviter les codes trop compactés

Ce code a été écrit pour moi. très bien, mais si je veux le partager, il faut éviter que la personne se pose trop de questions. J'ai mis la ligne

Serial.print(factorielle *= nombre);
. Elle veut dire "multiplie factorielle par nombre et met le tout dans factorielle" (c'est le *=) "puis appelle print avec le résultat de cette opération. C'est correct, cela fonctionne, mais c'est moins lisible que si je l'avais fait en deux fois (il est possible que le compilateur optimise et que cela revienne au même):
factorielle *= nombre;
Serial.print(factorielle);
Ici l'écriture en deux lignes est plus aisée à lire.

Il y a aussi l'initialisation de la première boucle for. La variable nombre est déclarée globale et lors de sa déclaration elle est initialisée. Ce n'est donc pas la peine de l'initialiser une seconde fois au début de la boucle. Mais ce n'est pas bien commun et cela n'aide pas à la lecture. C'est plus clair si elle est initialisé dans la boucle comme la variable zeros.

 

Les commentaires

Hé oui, cela existe aussi. Que l'on en mette ou pas la taille du code final est le même. C'est comme les noms des variables qui sont quasiment des commentaires. Tout cela n'est pas retenu dans le code final. Autant pas s'en priver. Si le choix du nom d'une variable ou d'une fonction est assez simple à choisir, mettre un commentaire pertinent n'est pas toujours aisé. Je rappelle qu'il y a deux types de commentaires:
- le commentaire de fin de ligne. Il commence par // et va jusqu'à la fin de la ligne
- le commentaire commençant par /* et finissant par */

Le premier type est plus usuel, le deuxième est utilisé si on a un texte long, si on a besoin de mettre un commentaire au milieu d'une ligne.

Le gros problème du commentaire est de savoir ce que l'on va mettre, et de ne pas faire de pléonasme. Par exemple

Serial.begin(115200); // Initialise la liaison série
est un commentaire pour les premiers jours d'un débutant. Pour la plupart c'est un pléonasme et l'instruction est forcément plus claire que le commentaire. Je peux mettre un commentaire, mais pour savoir lequel, il faut se poser la question "je vois bien que j'initialise la liaison série, mais pourquoi j'ai besoin de le faire ou pourquoi à cette vitesse...". Dans le cadre de mon petit programme, je pense que le commentaire sur cette ligne est inutile, mais si je devais en mettre un, je mettrais sans doute:
Serial.begin(115200); // Pour l'affichage des factorielles
Inutile de préciser "sur la console" c'est plus ou moins indiqué par le mot Serial.

Revoici le petit programme "correctement" écrit:

/* Ce petit programme va afficher sur la console les factorielles des
nombres de 1 à 15 
Il est écrit par Olivier le 7 Janvier de l'an 2021 */

long factorielle = 1L; // !0 n'existant pas, on commence à 1, contient le résultat
byte nbZeros; // Mettre les zéros de côté permet de calculer jusqu'à !15

void setup()
{
  Serial.begin(115200);
  for (byte nombre = 1; nombre <= 15; nombre++) // Affiche !1 à !15
  {
    delay(1000); // Attente entre deux affichage de factorielle
    factorielle *= nombre; // Calcul de la nouvelle valeur à partir de l'ancienne
    Serial.print(factorielle);
    for (byte zeros = 0; zeros < nbZeros; zeros++) Serial.print("0"); // Affichage des 0 finaux
    Serial.print("\n");
    while (factorielle % 10 == 0) // Si le résultat est divisible par 10
    {
      // Enlever les zéros à droite permet un calcul plus grand, on est limité dans la taille du long
      factorielle /= 10; 
      nbZeros++; // Comptage du nombre de zéros pour l'affichage
    }
  }
}

void loop()
{
}

 

Commentaires inhabituels

J'ai vu la ligne de code (bibliothèque accelStepper)

setMaxSpeed(100.0);
sans que l'auteur ne sache si la vitesse était en tr/ms ou en rd/s... Ce serait bien de pouvoir spécifier l'unité! Alors il faudrait écrire
setMaxSpeed(100.0); // 100 pas/s
Cela fait redondance pour le nombre 100, et c'est un pléonasme car c'est la traduction exacte en français de l'instruction. Et en plus il faudrait éventuellement un deuxième commentaire pour dire pourquoi on appelle la fonction. Cela pourrait donc donner:
setMaxSpeed(100.0); // 100 pas/s; Vitesse d'approche
Je cherchais pendant un temps comment l'écrire, et voici la première possibilité:
setMaxSpeed(100.0 /* pas/s */); // Vitesse d'approche
Pour peu que l'on passe une formule mathématique contenant des / ou des *, on ne s'en sort plus:
setMaxSpeed(50.0 * 2 /* pas/s */); // Vitesse d'approche double de la vitesse de coupe
J'ai eu l'idée d'utiliser une variable commentaire. C'est un mot à moi pour désigner une variable qui ne sert que de commentaire, que l'on peut affecter, mais dont on n'utilise pas la valeur ensuite. En principe elle est ignorée par le compilateur:
float vitesse_en_pas_par_seconde; // variable commentaire
...
setMaxSpeed(vitesse_en_pas_par_seconde = 50.0); // Vitesse de coupe
...
setMaxSpeed(vitesse_en_pas_par_seconde = 100.0); // Vitesse d'approche
C'est plus lisible, mais sait-on jamais ce qu'en fait le compilateur. Il y a quand même une affectation, ce n'est pas bien compréhensible par un novice... Au passage, j'ai pris une nouvelle convention: les les phrases en minuscules qui contiennent au moins un "_" mais pas aux extrémités sont des commentaires (les "_" aux extrémités sont pour les variables internes au bibliothèques). QuickStep V2.0 utilise la formulation suivante:
#define pas_par_seconde; // define commentaire
...
setMaxSpeed(50.0 pas_par_seconde); // Vitesse de coupe
...
setMaxSpeed(100.0 pas_par_seconde); // Vitesse d'approche
Le #define permet de remplacer pas_par_seconde par... rien du tout! C'est clair, plus court, on a une unité, le préprocesseur vire le commentaire avant que le compilateur ne le voit. C'est la méthode que je préfère. Le #define ferait partie de la bibliothèque, les setMaxSpeed() seraient dans les exemples et dans les programmes des utilisateurs. Bien entendu, on pourra toujours écrire
setMaxSpeed(100.0 rd_par_s);
ce qui est un commentaire faux. Ce n'est qu'un commentaire, on peut y mettre ce que l'on veut, comme pour les autres commentaires!

Du coup, on peut déclarer un define commentaire

#define milli_secondes
pour pouvoir écrire tranquillement
delay(100 milli_secondes);
D'ailleurs est-ce vraiment un commentaire? J'ai appris à l'école que l'on devait toujours mettre une unité! Maintenant c'est fait

Pour le clin d'oeil à QuickStep, vous risquez un jour de trouver

quickStepDeplacement1(3200 micro_pas, SENS_POSITIF, 6250 coups_d_horloges_par_pas, 1024 pas_pour_accelerer, 512 pas_pour_decelerer);
plutôt que:
// Faire tourner le moteur en 16 micro-pas d'un tour, dans le sens direct, à 0,1tr/s 
// avec une accélération de 50 micro-pas/s2 et une décélération deux fois plus importante
quickStepDeplacement1(3200, 1, 6250, 1024, 512);

 


dansetrad.fr Contactez-moi