📖 Écrire un tutoriel pour débutants

Pourquoi on renomme le code, on le commente et on fige ses valeurs - et comment savoir si on l'a bien fait

⏱️ D'abord, une épreuve

Voici trois fonctions tirées de vrais projets de flocking publiés sur GitHub. Le nom de chaque fonction a été masqué, tout le reste est authentique - c'est exactement le genre de code que vous allez ouvrir pendant le Tuto-Flock.

Sans exécuter le code, dites laquelle des trois forces chaque fonction calcule. Vous avez une trentaine de secondes par fonction.

1 Quelle force cette fonction calcule-t-elle ?
boid.js
fn(boids) {
  let d = 25;
  let steer = createVector(0, 0);
  let count = 0;
  for (let other of boids) {
    let dd = p5.Vector.dist(this.pos, other.pos);
    if ((dd > 0) && (dd < d)) {
      let diff = p5.Vector.sub(this.pos, other.pos);
      diff.normalize();
      diff.div(dd);
      steer.add(diff);
      count++;
    }
  }
  if (count > 0) { steer.div(count); }
  return steer;
}

La même fonction, écrite comme un tutoriel :

boid.js (version lisible)
calculerSeparation(agents) {
  // on ne regarde que les agents a moins de 25 pixels
  let distanceDeConfort = 25;
  let forceDeFuite = createVector(0, 0);
  let nombreDeVoisinsTropProches = 0;

  for (let agent of agents) {
    let distance = p5.Vector.dist(this.position, agent.position);
    if (distance > 0 && distance < distanceDeConfort) {
      // vecteur qui pointe de l'autre agent VERS moi : je m'eloigne
      let directionDeFuite = p5.Vector.sub(this.position, agent.position);
      directionDeFuite.normalize();
      // plus l'agent est proche, plus je fuis fort : on divise par la distance
      directionDeFuite.div(distance);
      forceDeFuite.add(directionDeFuite);
      nombreDeVoisinsTropProches++;
    }
  }

  // la force finale est la MOYENNE des fuites
  if (nombreDeVoisinsTropProches > 0) {
    forceDeFuite.div(nombreDeVoisinsTropProches);
  }
  return forceDeFuite;
}
2 Et celle-ci ?
boid.js
fn(boids) {
  let d = 50;
  let sum = createVector(0, 0);
  let count = 0;
  for (let other of boids) {
    let dd = p5.Vector.dist(this.pos, other.pos);
    if ((dd > 0) && (dd < d)) {
      sum.add(other.vel);
      count++;
    }
  }
  if (count > 0) {
    sum.div(count);
    sum.setMag(this.ms);
    return p5.Vector.sub(sum, this.vel);
  }
  return createVector(0, 0);
}

La même fonction, écrite comme un tutoriel :

boid.js (version lisible)
calculerAlignement(agents) {
  // je regarde les agents a moins de 50 pixels : mes voisins
  let rayonDePerception = 50;
  let sommeDesVitesses = createVector(0, 0);
  let nombreDeVoisins = 0;

  for (let agent of agents) {
    let distance = p5.Vector.dist(this.position, agent.position);
    if (distance > 0 && distance < rayonDePerception) {
      // on additionne leur VITESSE, pas leur position
      sommeDesVitesses.add(agent.vitesse);
      nombreDeVoisins++;
    }
  }

  if (nombreDeVoisins > 0) {
    // la direction moyenne du groupe
    sommeDesVitesses.div(nombreDeVoisins);
    sommeDesVitesses.setMag(this.vitesseMaximale);
    // la force = l'ecart entre la direction du groupe et la mienne
    return p5.Vector.sub(sommeDesVitesses, this.vitesse);
  }
  return createVector(0, 0);
}
3 Et la dernière ?
boid.js
fn(boids) {
  let d = 50;
  let sum = createVector(0, 0);
  let count = 0;
  for (let other of boids) {
    let dd = p5.Vector.dist(this.pos, other.pos);
    if ((dd > 0) && (dd < d)) {
      sum.add(other.pos);
      count++;
    }
  }
  if (count > 0) {
    sum.div(count);
    return p5.Vector.sub(sum, this.pos);
  }
  return createVector(0, 0);
}

La même fonction, écrite comme un tutoriel :

boid.js (version lisible)
calculerCohesion(agents) {
  let rayonDePerception = 50;
  let sommeDesPositions = createVector(0, 0);
  let nombreDeVoisins = 0;

  for (let agent of agents) {
    let distance = p5.Vector.dist(this.position, agent.position);
    if (distance > 0 && distance < rayonDePerception) {
      // on additionne leur POSITION, pas leur vitesse
      sommeDesPositions.add(agent.position);
      nombreDeVoisins++;
    }
  }

  if (nombreDeVoisins > 0) {
    // la moyenne des positions = le centre de gravite du groupe
    sommeDesPositions.div(nombreDeVoisins);
    // la force pointe de moi VERS ce centre de gravite
    return p5.Vector.sub(sommeDesPositions, this.position);
  }
  return createVector(0, 0);
}
Répondez aux trois épreuves pour voir le verdict.

🔍 Ce que l'épreuve vient de montrer

Les fonctions 2 et 3 sont presque identiques. Une seule ligne les sépare :

sum.add(other.vel);   // -> alignement : la direction moyenne
sum.add(other.pos);   // -> cohesion   : le centre de gravite

Trois lettres. Avec les noms d'origine, rien ne vous prévient : sum et d et count ne veulent rien dire de particulier, et il faut lire les dix lignes en entier pour trancher. Avec sommeDesVitesses contre sommeDesPositions, la réponse est dans le nom.

Voilà pourquoi on renomme. Pas pour faire joli, ni pour faire plaisir à la prof. Un nom bien choisi remplace un paragraphe d'explication. C'est la forme de documentation la moins chère à écrire et la seule qui ne devient jamais périmée, parce qu'elle voyage avec le code.

🎯 Votre vraie mission : écrire pour quelqu'un

Quand vous déconstruisez le projet de flocking, ne vous dites pas « je renomme parce que c'est demandé ». Dites-vous plutôt ceci :

« Je transforme ce code en tutoriel pour un débutant. Mon lecteur est un étudiant de première session qui sait programmer un peu, mais qui n'a jamais entendu parler de flocking, de boid, ni de vecteur. Il ouvre mon fichier tout seul, sans moi à côté. Il doit comprendre. »

Ce lecteur imaginaire change tout, parce qu'il pose sans arrêt quatre questions, et que votre code doit y répondre sans que vous soyez là :

La question du débutantCe qui y répond dans votre code
« Ça fait quoi, cette fonction ? »Le nom de la fonction : un verbe à l'infinitif, calculerSeparation()
« C'est quoi, cette variable ? »Le nom de la variable : rayonDePerception, pas d
« Pourquoi on divise par la distance ? »Un commentaire qui explique l'intention, pas la syntaxe
« Ça change quoi si je touche à ça ? »Les valeurs en dur qu'il peut modifier pour voir l'effet
Le test qui ne trompe pas : si vous avez besoin d'expliquer une ligne à l'oral pour qu'on la comprenne, c'est que le code n'est pas encore fini. Votre voix ne sera pas dans le fichier remis.

🧗 Les trois niveaux du tutoriel

Les trois consignes du Tuto-Flock ne sont pas trois corvées séparées : ce sont trois niveaux qui répondent à trois questions différentes.

1Renommer : dire quoi

Les noms racontent ce que fait le code. Un nom de fonction commence par un verbe, un nom de variable dit ce que la variable contient, et rien n'est abrégé.

d, dd, sum, count, steer, other, ms
     ↓
distanceDeConfort, distance, sommeDesPositions,
nombreDeVoisins, forceDeFuite, agent, vitesseMaximale

2Commenter : dire pourquoi

Le commentaire n'explique jamais la syntaxe - le lecteur sait lire du JavaScript. Il explique l'intention et les maths.

// MAUVAIS : on divise le vecteur par la distance
directionDeFuite.div(distance);

// BON : plus l'agent est proche, plus je fuis fort
directionDeFuite.div(distance);

Le deuxième commentaire apprend quelque chose. Le premier répète la ligne à voix haute.

3Figer les valeurs : montrer l'effet

Remplacer les paramètres par des valeurs codées en dur n'est pas une régression : c'est ce qui rend le code manipulable. Le débutant voit 25 écrit dans la fonction, il le change pour 100, et il découvre par lui-même ce que fait la séparation. C'est ce qu'on appelle un tutoriel.

En vrai projet, on ferait l'inverse (on sort les valeurs dans un fichier de configuration). Ici, l'objectif n'est pas de livrer un produit : c'est de rendre l'algorithme compréhensible et bidouillable. Sachez faire les deux, et sachez pourquoi vous choisissez l'un ou l'autre.

📝 Le gabarit du tutoriel (à coller dans votre README)

Votre README n'est pas une liste de liens : c'est la page d'accueil de votre tutoriel. Voici une structure qui répond aux questions du débutant dans l'ordre où il se les pose.

## Ma déconstruction d'un projet de flocking

### D'où vient le code
- Projet d'origine : [nom](lien GitHub)
- Langage et technologie : ...
- Comment le faire tourner : ...

### Ce que fait ce programme, en trois phrases
(Expliquez à quelqu'un qui n'a jamais vu de flocking. Pas de jargon
sans définition : si vous écrivez "boid", dites ce que c'est.)

### Les trois forces, et où elles se trouvent dans le code

| Force | Fonction renommée | Fichier et ligne | Ce qu'elle fait |
|---|---|---|---|
| Alignement | calculerAlignement() | ... | ... |
| Cohésion | calculerCohesion() | ... | ... |
| Séparation | calculerSeparation() | ... | ... |

### Les maths, expliquées
- Pourquoi on additionne les vitesses pour l'alignement : ...
- Pourquoi on additionne les positions pour la cohésion : ...
- Pourquoi on divise par la distance dans la séparation : ...

### Les valeurs que vous pouvez changer pour jouer
| Valeur | Où | Effet si on l'augmente | Effet si on la diminue |
|---|---|---|---|
| rayonDePerception = 50 | ... | ... | ... |
| distanceDeConfort = 25 | ... | ... | ... |

### Ce que j'ai compris et ce qui me reste flou
(Cette section compte : dire ce qu'on ne comprend pas encore
est un signe de compréhension, pas d'échec.)

✅ Avant de remettre : la relecture du débutant

Relisez votre code en vous mettant dans la tête de votre lecteur. Cochez seulement ce qui est vrai.

Le test final : faites lire une de vos fonctions renommées à quelqu'un de votre classe qui a déconstruit un autre projet. S'il comprend ce qu'elle calcule sans que vous disiez un mot, votre tutoriel fonctionne.
← Retour à l'énoncé des laboratoires Gradients