Astuces pour bien programmer

Les variables

Créez des variables

ASTUCE : Si le résultat d’une expression est important, créez une variable pour le stocker.

ASTUCE : Si une expression se repète dans le code, créez une variable pour stocker cette information.

Voici un code qui vérifie si la position du héros est à l’extérieur de la zone d’affichage :

if (  x + cos(theta) * y - 400 > 217) ...
if (  x + cos(theta) * y - 400 < 0  ) ...

Il est très difficile de déterminer la signification du test effectué. Par contre, en créant :

  • Une variable HeroPos stockant la position du héros donnée par l’expression : x + cos(theta) * y - 400

  • Une variable LARG_AFF correspondant à la largeur de la zone d’affichage

On obtient une version du code plus facile à lire. Les commentaires sont données à titre indicatif, ils pourraient être omis.

HeroPos.x   = x + cos(theta) * y - 400;  // coordonnée du héros à l’écran
if ( HeroPos.x > LARG_AFF ) ..           // teste si le héros est visible à l'écran
if ( HeroPos.x < 0        ) ..

Evitez les variables redondantes

Pour gérer une liste de momies, un programmeur crée deux variables :

int   nbMomies = 0;      // nombre de momies
bool  EstVide  = true;   // aucune momie dans la liste

Où est le problème de conception ?

Techniquement, ce programme peut fonctionner avec ce choix de conception. Cependant, la variable EstVide est en redondance avec la variable nbMomies. Ainsi, chaque fois que le nombre d’éléments change, la variable nbMomies est modifiée. Mais, il faut aussi penser à mettre à jour la variable EstVide et le risque d’oublier cette variable secondaire existe. Ce choix de conception est dangereux, mieux vaut choisir une unique variable pour stocker la quantité d’éléments présents et recalculer à la volée le test permettant de déterminer si aucune momie existe en créant une fonction dédiée.

Préférez les enum aux numériques

Vous avez sûrement déjà rencontré du code ressemblant à l’exemple suivant :

int Obj = Tab[k][j];
if ( Obj == 1 )
   ..
else if ( (Obj == 0) || (Obj == 4))
   ...
else if (Obj == 7)
   ...

Il est utile pour améliorer la lisibilité d’utiliser une énumération pour remplacer les littéraux présents dans les tests :

enum class Obj { Tresor, Porte, Grille, Fleche };

Obj Objet = Tab[k][j];

if ( Objet == Obj::Tresor )
   ..
else if ( (Objet == Obj::Porte) || (Objet == Obj::Grille))
   ...
else if (Objet == Objet::Fleche)

La lecture du code s’en trouve facilitée : ce programme peut être repris et modifié plus tard sans aucun souci par n’importe quel membre de l’équipe.

Organisez vos données

Le scénario qui fait perdre du temps

Nous allons présenter cette problématique où un programmeur, nommé Tom, crée des variables sans les structurer.

  • Jour 1 : Pour modéliser le héros du jeu, Tom crée la variable HerosPV initialisée à la valeur 200.

  • Jour 4 : Un autre jour, Tom programme la gestion des combats entre le héros et les monstres. Si un monstre réussit à toucher le joueur, celui-ci perd des points de vie. Tom, au moment où il cherche à coder le retrait des points de vie, n’arrive plus à retrouver le nom de la variable en question. Il cherche dans le code rapidement mais ne la trouve pas. Tom en déduit alors qu’il ne l’a jamais créée et il met alors en place une nouvelle variable VieDuHeros initialisée à la valeur 200. Il utilise cette variable pour programmer la gestion des combats. Au final, le programme fonctionne correctement. Cependant, la variable HerosPV est une variable zombie, elle existe dans le programme sans être utilisée.

  • Jour 6 : Lors d’une prochaine séance, Tom cherche à implémenter une potion de soin. Par manque de chance, en recherchant dans le programme il retrouve la variable initiale HerosPV. Il utilise donc cette variable pour gérer les effets de la potion de soin. Tom écrit un code correct, sans erreur de logique. Seulement, lors des tests, il se rend compte que la potion de soin ne fait pas augmenter les points de vie du joueur..

  • Jour 7 : Tom déprime, il a vérifié son code, tout semble correct. Il arrête de coder et va jouer à LOL.

  • Jour 8 : Lors de la phase de Debug, Tom vérifie que la variable HerosPV varie comme prévu lorsque le héros utilise sa potion de vie. Il faudra beaucoup de temps à Tom pour recoller les morceaux du puzzle et trouver l’origine du quiproquo.

Que de temps perdu… Comment éviter ce problème ?

  • Il ne s’agit pas ici d’un souci de logique : tout le code est écrit correctement.

  • La lisibilité est de qualité car les noms des variables sont explicites.

  • Pas d’erreur de syntaxe car le programme ne s’est pas arrêté avec un message d’erreur.

On parle de bug certes car le programme ne se comporte pas comme il devrait. Mais, il s’agit ici plutôt d’un problème de conception au niveau de l’organisation du code. En effet, en créant des variables un peu partout dans le code, Tom a fini par se mélanger les pinceaux.

Regrouper

Pour structurer les variables, il est important, de localiser les variables de votre jeu au même endroit. Cela restreint la zone de recherche à quelques lignes. Il est aussi adroit de regrouper les variables par thème : toutes les variables qui concernent le héros, puis celles des méchants, et ensuite viennent celles du décor par exemple. Idéalement les structures sont particulièrement indiquée pour rassembler toutes ces informations :

struct Pos { int x; int y; };
struct _Heros
{
   Pos P;
   int  HPointDeVie;
};

struct VarPartie
{
   // héros
   _Heros Hero;

   // Méchants
   Pos PosMomie; // position momie

   // objets
   Pos Posclef;  // position de la clef
};

Les fonctions

Identifier une fonction

Prenons un cas pratique. Supposons que nous devons modéliser un aventurier dans un labyrinthe. Celui-ci se déplace et nous devons déterminer s’il passe à proximité de certains pièges. Pour cela, nous notons les coordonnées des pièges P1x, P1y, P2x, P2y, P3x et P3y et les coordonnées du héros Hx et Hy. Le code qui détermine si le héros est proche d’un piège est le suivant :

if ( pow(P1x-Hx,2) + pow(P1y-Hy,2) < 20*20 )   return true;
if ( pow(P2x-Hx,2) + pow(P2y-Hy,2) < 20*20 )   return true;
if ( pow(P3x-Hx,2) + pow(P3y-Hy,2) < 20*20 )   return true;

return false

ASTUCE : Si des lignes de code se répètent, c’est un signe indiquant qu’une fonction doit être créée.

Note

Il peut vous sembler étrange d’écrire une fonction pour remplacer « juste » trois lignes de code. Cependant, cela ne doit pas vous arrêter.

Pour créer la fonction, il suffit donc de passer en arguments les coordonnées du piège et du héros :

bool ProcheHeros(int x, int y, int Hx, int Hy) :
   return pow(x-Hx,2) + pow(y-Hy,2) < 20*20;

Améliorer une fonction

Dans le paragraphe précédent, nous avons construit une fonction qui teste la proximité entre un élément du jeu et notre personnage. Cependant, cette fonction est trop spécialisée. En effet, on teste la proximité entre un piège et le héros. Mais en fait, cette fonction peut tout aussi bien être utilisée pour tester la proximité entre deux éléments. Nous renommons la fonction pour qu’elle soit réutilisable pour d’autres traitements :

bool Proximité(int x1, int y1, int x2, int y2) :
   return pow(x1-x2,2) + pow(y1-y2,2) < 20*20;

On peut encore améliorer notre résultat. En effet, il reste un littéral dans cette fonction permettant de régler le seuil de proximité. Là encore, cela dépend de la taille des objets à l’écran. Il faut rendre cette fonction la plus adaptable possible. Ainsi, nous allons passer le seuil en paramètre :

bool Proximité(int x1, int y1, int x2, int y2, int distmin) :
   return pow(x1-x2,2) + pow(y1-y2,2) < distmin*distmin;

La fonction peut être maintenant réutilisée pour diverses configurations : le héros et les momies, le héros et les pièges, les flèches et les monstres… Cette fonction n’utilise pas de variables globales. Le traitement qu’elle effectue est donc entièrement indépendant du reste du programme. Ainsi, cette fonction pourrait être importée dans un autre programme et être réutilisée telle quelle.

Restructurer son code

Il est normal d’écrire plusieurs lignes de code et ensuite de chercher à les structurer en fonctions. Cependant, le mauvais réflexe consiste à sélectionner, un peu au hasard, des portions de code pour créer vos fonctions. Certes on arrive à un résultat, mais souvent pas plus satisfaisant. Alors on essaye autre chose, mais ce n’est guère mieux. Pour structurer plusieurs lignes de code à posteriori, vous devez identifier les rôles des fonctions et seulement ensuite répartir les lignes de code suivant le rôle de chaque fonction.

CONSEIL : Lorsque vous restructurez votre code en fonctions ; il faut d’abord identifier chaque fonction ainsi que leur rôle et seulement ensuite répartir les lignes de code et non l’inverse.