Écrire du code de qualité

De Ensiwiki
Aller à : navigation, rechercher

Écrire du code correct est une tâche difficile. Mais la correction (i.e. absence de bug, respect du cahier des charges) est loin d'être le seul critère de qualité du code source d'un programme.

Cette page recense quelques erreurs fréquentes trouvées dans les TP et projets d'étudiants. Elle est loin d'être exhaustive. Elle a été conçue pour être utilisée par les correcteurs de TP, pour renvoyer les étudiants sur des explications détaillées dans les corrigés (par exemple, pour pouvoir écrire « problème d'indentation, cf http://ensiwiki.ensimag.fr/index.php/Ecrire_du_code_de_qualite#tabs »). Bien sûr, mieux vaut prévenir que guérir, donc le mieux pour un étudiant est encore d'avoir lu cette page avant de rendre son TP !

Indentation avec des mélanges de tabs et d'espaces

Indenter avec des tabulations ou avec des espaces, les deux ont des avantages, c'est une question de gout. Mais mélanger les deux est rarement une bonne idée. En particulier, mélanger les espaces et les tabulation, en modifiant les réglages de son éditeur de texte, c'est à peu près à coup sur une erreur. Voici un petit exemple pour illustrer le problème. Un étudiant a configuré son éditeur pour afficher des tabulations de 3 caractères, et a écrit ceci (les . représentent des espaces, et les <----> représentent les tabulations) :

int f() {
......int x;
<-><->char y;
....<>return x + y;
}

Sur son éditeur, le code s'affiche correctement. Mais la configuration normale est d'avoir 1 tabulation = 8 caractères (essayez de dire aux commandes cat et less d'afficher autre chose qu'un alignement à 8 caractères pour une tabulation ...). Si on ouvre ce même fichier avec un éditeur configuré avec des tabulations de 8 (ou par exemple, avec la commande less dans un terminal), on verra ceci :

int f() {
......int x;
<------><------>char y;
....<-->return x + y;
}

Solutions :

  1. Ne pas jouer avec sa largeur de tabulation, et s'assurer qu'elle est à 8 caractères
  2. Indenter uniquement avec des tabulations, ou uniquement avec des espaces, mais ne pas mélanger

Commentaires redondants ou non-pertinents

Les commentaires dans le code, c'est bien. Mais trop de commentaire tue le commentaire.

Un exemple (tiré d'un TP de SystemC/TLM) :

// notification de l'evenement associé a l'interruption
it_event.notify();

Qu'apporte le commentaire ici ? Pas grand chose, on savait déjà que c'était une notification, et le nom de la variable it_event nous avait déjà dit qu'il s'agissait d'un événement associé a l'interruption.

Un autre :

// acquitement de l'interruption sur le LCDC
initiator.write(LCDC_BASE_ADDR+8, START_ACK);

Ici, le commentaire est pertinent, parce qu'on n'aurait pas eu de moyen simple de voir d'après le code qu'il s'agissait d'un acquittement d'interruption. Mais si on ré-écrit le code avec une constante nommée, c'est déjà plus clair :

// acquitement de l'interruption sur le LCDC
initiator.write(LCDC_BASE_ADDR+LCDC_INT_REG, START_ACK);

Mieux, si on fait une petite méthode ack_lcdc_interrupt(), le nom de la fonction rendra le code explicite, sans commentaire.

La règle d'or avec les commentaires : un bon commentaire répond à la question pourquoi ?, et pas à aux questions quoi ? ou comment ?. Si un relecteur se pose la question que fait ce bout de code ? ou comment ce bout de code fait-il ce qu'il fait ?, en général c'est que le code n'est pas clair, et la solution n'est pas d'ajouter des commentaires.

Solutions :

  1. Quand on pense à ajouter un commentaire, se demander si le code n'est pas déjà assez clair sans
  2. Si le code n'est pas assez clair, clarifier le code plutôt que de le commenter
  3. Utiliser des variables et des fonctions avec un nom explicite

Lignes trop longues

Une règle communément admise en programmation est d'éviter les lignes longues. En général, on considère qu'une ligne est trop longue à partir du moment où elle dépasse 80 caractères. Du bon code doit être du code simple, et l'œil humain aura du mal à lire les lignes longues (c'est la même raison qui fait que les journaux découpent le texte en colonnes).

Exemple 1 (tiré d'un TP de SystemC/TLM)

            initiator.write(ADR_LCD + 8 * i,(d & 0xF0000000)|(d & 0x0F000000) >> 4|(d & 0x00F00000) >> 8|(d & 0x000F0000) >> 12);

Le code est dur à lire, et si la fenêtre de mon éditeur n'est pas assez grande, la ligne sera coupée en deux à un endroit arbitraire. À titre de comparaison, voici ce qu'on peut écrire pour le même code :

            initiator.write(ADR_LCD + 8 * i, 
                            (d & 0xF0000000)      |
                            (d & 0x0F000000) >> 4 |
                            (d & 0x00F00000) >> 8 |
                            (d & 0x000F0000) >> 12);

En bonus, dans la deuxième version, l'alignement fait ressortir la régularité du code, si on s'est trompé d'un chiffre dans l'écriture d'une valeur hexa, on s'en rendra compte immédiatement.

Exemple 2 (tiré d'un projet GL)

	procedure Regle_Inst(A: Arbre; ...) is
	begin
        	-- ...
		case Acces_Noeud_Inst(A) is
                	-- ...
			when Noeud_Inf | Noeud_Sup | Noeud_Inf_Egal | Noeud_Sup_Egal =>
				if ... then
                                	-- ...
				else
                                	-- ...
					case Acces_Nature(Typ1) is
						when Type_Int =>
							if Acces_Nature(Typ2) = Type_Float then
								Changer_Fils_1(A,Creation_Conv_Flottant(Acces_Fils_1(A),
																						Acces_Num_Ligne(Acces_Fils_1(A))));
							end if;
                                	-- ...
					end case;
				end if;
		end case;
	end Regle_Inst;

(dans le code dont est tiré cet exemple, la procédure fait 412 lignes, et le premier niveau de case a 27 cas différents ...)

En dehors du fait que les auteurs avaient probablement une largeur de tab non-standard, qui les a empêché de se rendre compte qu'une de leurs lignes commençait en colonne 176 (vous ne la voyez probablement pas, elle est à droite de l'écran !), on peut remarquer un grand nombre de niveaux d'imbrication : un case dans un if dans un case dans une procédure dans un paquetage ...

Une meilleure solution aurait été de créer une procédure (disons Regle_Expressions) contenant le code qui se trouve derrière le when Noeud_Inf | ... | ... =>. Le code se serait alors écrit :

	procedure Regle_Inst(A: Arbre; ...) is
	begin
        	-- ...
		case Acces_Noeud_Inst(A) is
                	-- ...
			when Noeud_Inf | Noeud_Sup | Noeud_Inf_Egal | Noeud_Sup_Egal =>
				Regle_Expressions(A);
		end case;
	end Regle_Inst;

Créer une procédure aurait plusieurs avantages :

  • La procédure est un morceau de code bien identifié. Quand on écrit cette procédure, on peut se concentrer dessus sans être perturbé par le code qui se trouve autour. On peut relire, tester, documenter la procédure indépendamment des endroits où elle est appelée.
  • La procédure a un nom. Si on a choisi ce nom judicieusement, un relecteur doit comprendre immédiatement de quoi il s'agit, sans avoir besoin de lire une éventuelle documentation.
  • La procédure est réutilisable. Dans l'exemple ci-dessous, c'était sans surprise : le morceau de code incriminé est copié-collé au caractère près dans une autre procédure, et ce genre de copié-collé rends le code inmaintenable.

Solutions :

  1. Couper manuellement les lignes aux endroits appropriés.
  2. Si une ligne est longue à cause d'une imbrication profonde (indentation due à un if dans un while dans un switch dans ...), alors il faut penser à mieux découper son code en fonctions.