Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

Chapitre 227 minIntermédiaire

Reprendre un code existant : lire, comprendre, rétro-concevoir

Par Josselyn C., prof de BTS SIO. Mis à jour le .

À la fin de ce chapitre, tu sauras

  • explorer un projet inconnu dans le bon ordre, sans tout lire ;
  • suivre le parcours d'une fonctionnalité de la route à la base ;
  • interroger l'historique Git pour comprendre pourquoi le code est ainsi ;
  • reconstituer un diagramme de classes et un schéma de base à partir du code.

4 exercices corrigés1 schéma

Reprendre un code existant, c'est comprendre assez une application que tu n'as pas écrite pour la modifier sans la casser : à quoi elle sert, comment elle se lance, comment ses fichiers s'enchaînent et pourquoi elle est écrite ainsi. La rétro-conception (reverse engineering) va un cran plus loin : elle reconstitue, à partir du code et de la base, les modèles qui manquent, comme un diagramme de classes ou un schéma de base de données. Ce chapitre donne une méthode d'exploration, les commandes Git qui racontent l'histoire du code et les techniques de rétro-conception.

Avant de lire : trois réflexes

Tu arrives sur Sorbier Véhicules. L'alternant qui l'a écrite est parti, le README tient en trois lignes, et ton premier ticket t'attend. Avant d'ouvrir un fichier, adopte trois réflexes.

Ne pas réécrire. Devant un code inconnu, la tentation est de tout refaire « proprement ». C'est presque toujours une erreur : le code existant contient des années de corrections de cas particuliers que personne n'a documentés. Tu les perdrais, et tu réintroduirais leurs bugs. On améliore un code existant par petites touches, protégées par des tests (chapitre 8).

Supposer qu'il y a une raison. Une ligne qui te paraît inutile ou absurde a souvent une raison que tu ne connais pas encore : un client particulier, un bug de navigateur, une contrainte de la base. Avant de la supprimer, cherche pourquoi elle est là. L'historique Git te le dira souvent, on y vient plus bas.

Prendre des notes. Ouvre un fichier NOTES.md (ou une page de wiki) et écris au fil de l'eau ce que tu découvres : comment lancer l'application, où est quoi, ce qui t'a surpris, les questions à poser. Ces notes deviendront la documentation qui manquait (chapitre 10).

L'ordre d'exploration

Ne plonge pas au hasard dans cinquante fichiers. Explore dans cet ordre : chaque étape prépare la suivante.

ÉtapeCe que tu regardesCe que tu apprends
1Le README.mdÀ quoi sert l'application, comment l'installer et la lancer
2package.json ou composer.jsonLe langage et ses versions, les bibliothèques, les commandes du projet
3L'arborescenceL'organisation : MVC, couches, dossiers de tests
4La configuration (.env.example, docker-compose.yml, config/)Les services nécessaires : base, ports, variables d'environnement
5Le point d'entrée (server.js, public/index.php)Comment tout se branche au démarrage
6Un seul parcours, de la route jusqu'à la baseComment une fonctionnalité traverse les couches
7Les testsLe comportement attendu, écrit noir sur blanc
8L'historique GitPourquoi le code est comme il est

Lire les fichiers de dépendances

Le fichier de dépendances est la carte d'identité technique du projet. Voici celui de l'API de Sorbier Frais :

json
{
  "name": "sorbier-frais-api",
  "version": "1.4.2",
  "private": true,
  "scripts": {
    "dev": "node --watch server.js",
    "start": "node server.js",
    "test": "jest"
  },
  "dependencies": {
    "express": "^5.1.0",
    "jsonwebtoken": "^8.5.1",
    "mysql2": "^3.11.0"
  },
  "devDependencies": {
    "jest": "^30.0.0",
    "supertest": "^7.0.0"
  },
  "engines": { "node": ">=22" }
}

En vingt secondes, tu sais que l'application est en version 1.4.2, qu'elle demande Node.js 22 ou plus, qu'elle utilise Express 5, des jetons JWT et MySQL, et que ses tests tournent avec Jest et Supertest. Surtout, les scripts te donnent les vraies commandes : npm run dev lance le serveur en rechargement automatique, npm test lance les tests. Quand le README est faux ou absent, ce sont eux qui disent la vérité.

Côté PHP, Sorbier Véhicules a son composer.json :

json
{
  "name": "sorbier/vehicules",
  "type": "project",
  "require": {
    "php": ">=8.4",
    "twig/twig": "^3.0",
    "monolog/monolog": "^3.0"
  },
  "require-dev": {
    "phpunit/phpunit": "^13.0"
  },
  "autoload": {
    "psr-4": { "Sorbier\\Vehicules\\": "src/" }
  },
  "scripts": {
    "test": "phpunit tests"
  }
}

PHP 8.4 au minimum, Twig pour les gabarits HTML, Monolog pour les journaux, PHPUnit pour les tests. La section autoload t'apprend que la classe Sorbier\Vehicules\Service\Disponibilite se trouve dans src/Service/Disponibilite.php : c'est la convention PSR-4, qui fait correspondre l'espace de noms au chemin du fichier.

Lancer l'application et ses tests en premier

Avant de chercher un bug ou d'ajouter quoi que ce soit, fais tourner le projet tel qu'il est. C'est ton point de référence.

bash
# API Node.js
cp .env.example .env          # puis renseigne les variables
docker compose up -d          # la base MySQL du projet, si elle est fournie
npm ci                        # installe exactement les versions du package-lock.json
npm test

# Application PHP
composer install              # installe exactement les versions du composer.lock
composer test

npm ci et composer install installent les versions figées dans le fichier de verrouillage (package-lock.json, composer.lock), celles avec lesquelles le projet a été testé. npm install ou composer update pourraient installer des versions plus récentes et changer le comportement avant même que tu aies touché au code.

Si des tests échouent avant ton intervention, note-le : ils ne sont pas de ton fait, et tu dois le savoir pour ne pas les confondre avec une régression que tu aurais introduite. Si l'application ne démarre pas parce que le README est périmé, reconstitue les étapes à partir des scripts et du docker-compose.yml, puis corrige le README : c'est déjà de la maintenance.

Suivre le parcours d'une fonctionnalité

Prenons une fonctionnalité de Sorbier Frais : afficher le résumé d'une note (mois, nombre de lignes, total). La requête GET /api/notes/42/resume traverse quatre couches.

GET /api/notes/42/resumeRouteContrôleurServiceDépôtBase MySQLroutes/notes.routes.jscontrollers/notes.controller.jsservices/notes.jsrepositories/notes.repository.jsnote_frais, ligne_fraisassocie l'URL à une fonctionapplique l'authentificationlit la requête, appelle le servicerenvoie la réponse JSONrègles métier et calculsles bugs de calcul sont souvent icirequêtes SQL préparéesles bugs de données sont souvent icitables, clés, contraintesles données remontent jusqu'à la réponse
Le parcours d'une requête dans l'API de Sorbier Frais

La route associe une URL et une méthode HTTP à une fonction. Elle dit aussi quels middlewares s'exécutent avant.

js
// routes/notes.routes.js
const express = require('express');
const { requireAuth } = require('../middlewares/auth');
const notesController = require('../controllers/notes.controller');

const router = express.Router();
router.get('/:id/resume', requireAuth, notesController.getResume);

module.exports = router;

Le contrôleur traduit le HTTP en appel métier, et le résultat en réponse. Dans une application bien découpée, il est mince : quelques lignes.

js
// controllers/notes.controller.js
const notesRepo = require('../repositories/notes.repository');
const { resumeNote } = require('../services/notes');

async function getResume(req, res) {
  const note = await notesRepo.trouverParId(req.params.id);
  if (note === null) return res.status(404).json({ erreur: 'Note introuvable' });
  res.json(resumeNote(note));
}

module.exports = { getResume };

Le service porte la logique : calculs, règles de gestion. C'est là que vivent la plupart des bugs de calcul.

js
// services/notes.js
function totalNote(lignes) {
  return lignes.reduce((total, ligne) => total + ligne.montant, 0);
}

function resumeNote(note) {
  return {
    mois: note.mois,
    nbLignes: note.lignes.length,
    total: totalNote(note.lignes),
  };
}

module.exports = { totalNote, resumeNote };

Le dépôt (repository) parle à la base, avec des requêtes préparées.

js
// repositories/notes.repository.js
const pool = require('../db');

async function trouverParId(id) {
  const [notes] = await pool.execute(
    'SELECT id_note, mois, etat FROM note_frais WHERE id_note = ?',
    [id],
  );
  if (notes.length === 0) return null;
  const [lignes] = await pool.execute(
    'SELECT categorie, libelle, montant FROM ligne_frais WHERE id_note = ?',
    [id],
  );
  return { ...notes[0], lignes };
}

module.exports = { trouverParId };

En quatre fichiers, tu connais l'architecture de toute l'API : les autres fonctionnalités suivent le même chemin. Tu as aussi repéré, sans le chercher, un endroit fragile : totalNote additionne ligne.montant sans vérifier son type. Garde-le dans tes notes ; on y revient au chapitre suivant.

Côté PHP, Sorbier Véhicules suit le même principe avec un contrôleur frontal : toutes les requêtes passent par public/index.php, qui choisit le contrôleur selon l'URL.

php
<?php
// public/index.php (extrait)
declare(strict_types=1);

require __DIR__ . '/../vendor/autoload.php';

use Sorbier\Vehicules\Controleur\ReservationControleur;

$action = $_GET['action'] ?? 'planning';
$controleur = new ReservationControleur(/* dépendances : PDO, Twig, journal */);

match ($action) {
    'planning' => $controleur->planning(),
    'reserver' => $controleur->reserver($_POST),
    'annuler'  => $controleur->annuler((int) ($_POST['id'] ?? 0)),
    default    => http_response_code(404),
};

Le match est la table des routes. Pour comprendre « réserver », tu suis ReservationControleur::reserver(), puis le service Disponibilite, puis le dépôt et sa requête SQL. Le cours Architecture MVC détaille cette organisation en couches.

Tu n'as pas à deviner où une fonction est définie ni qui l'appelle : l'éditeur le sait. Sur Windows et Linux, Ctrl ; sur macOS, Cmd (sauf mention contraire).

RaccourciEffetÀ quoi ça sert en maintenance
F12Aller à la définitionDescendre d'une couche : du contrôleur au service
Maj+F12Afficher les référencesSavoir qui appelle une fonction avant de la modifier
Ctrl+POuvrir un fichier par son nomAller vite sans fouiller l'arborescence
Ctrl+Maj+FChercher dans tous les fichiers« Où est calculé le total ? », « qui lit montant ? »
Ctrl+Maj+OLister les symboles du fichierVoir les fonctions d'un long fichier
Alt+← (Ctrl+- sur macOS)Revenir à l'endroit précédentRemonter après une série de F12

Maj+F12 répond à la question la plus importante avant toute modification : si je touche à cette fonction, qui risque de casser ? Sur Sorbier Frais, totalNote est appelée par le résumé, par l'export CSV et par le tableau de bord du comptable. Une correction devra être testée sur les trois.

Interroger l'historique Git

Le code dit comment l'application fonctionne. L'historique dit pourquoi elle est ainsi. Quatre commandes suffisent pour l'interroger.

bash
git log --oneline -- services/notes.js        # les commits qui ont touché ce fichier
git blame -L 1,15 services/notes.js           # le dernier commit de chaque ligne
git log -S "ligne.montant" --oneline          # les commits qui ont ajouté ou retiré ce texte
git show 3f9c2ab                              # le détail d'un commit : message et modifications

git blame affiche, pour chaque ligne, le commit, l'auteur et la date de sa dernière modification :

text
3f9c2ab1 (Inès Martin 2026-10-30 10:14:02 +0100  2) function totalNote(lignes) {
3f9c2ab1 (Inès Martin 2026-10-30 10:14:02 +0100  3)   return lignes.reduce((total, ligne) => total + ligne.montant, 0);
3f9c2ab1 (Inès Martin 2026-10-30 10:14:02 +0100  4) }

Un git show 3f9c2ab affiche ensuite le message du commit, par exemple refactor(notes): calcule le total côté API (#229). Tu sais maintenant que le total était calculé ailleurs avant le 30 octobre, que le changement répondait au ticket 229 et à qui poser la question. Le nom « blame » (« blâmer ») est trompeur : la commande sert à trouver le contexte d'une ligne, pas un coupable.

git log -S (la « pioche ») retrouve le commit qui a fait apparaître ou disparaître un morceau de code précis, même s'il a changé de fichier depuis. C'est l'outil pour répondre à « depuis quand ce code est-il là ? ».

La rétro-conception : reconstituer les modèles

La rétro-conception consiste à remonter du code et des données vers les modèles qui les décrivent : schéma de base, diagramme de classes, diagramme de séquence. Elle sert quand ces modèles n'existent pas, sont perdus ou ne correspondent plus au code. En maintenance, c'est fréquent.

De la base au schéma relationnel

La base est souvent la partie la plus stable et la plus parlante d'une application. Commence par elle.

sql
SHOW TABLES;
SHOW CREATE TABLE reservation;

SHOW CREATE TABLE donne le script complet d'une table, avec ses clés et ses contraintes. Pour lister d'un coup toutes les clés étrangères, interroge le dictionnaire de données de MySQL :

sql
SELECT TABLE_NAME, COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME
FROM information_schema.KEY_COLUMN_USAGE
WHERE TABLE_SCHEMA = 'sorbier_vehicules'
  AND REFERENCED_TABLE_NAME IS NOT NULL
ORDER BY TABLE_NAME;
TABLE_NAMECOLUMN_NAMEREFERENCED_TABLE_NAMEREFERENCED_COLUMN_NAME
reservationid_utilisateurutilisateurid_utilisateur
reservationid_vehiculevehiculeid_vehicule
utilisateurid_serviceserviceid_service

Tu en déduis le schéma relationnel, au format des sujets (clé primaire en gras, clés étrangères précédées de #) :

service (id_service, libelle)

utilisateur (id_utilisateur, nom, prenom, email, #id_service)

vehicule (id_vehicule, immatriculation, modele, places, km_compteur)

reservation (id_reservation, debut, fin, motif, km_depart, km_retour, statut, #id_vehicule, #id_utilisateur)

Et de là, le MCD : une réservation concerne un véhicule et est faite par un utilisateur, qui appartient à un service. Les outils comme DBeaver ou MySQL Workbench dessinent aussi le diagramme de la base automatiquement (fonction de rétro-ingénierie ou onglet « Diagramme ER »). Le dessin est pratique, mais lis les contraintes toi-même : un outil ne te dira pas qu'une colonne statut en VARCHAR n'accepte en pratique que trois valeurs, ni qu'une clé étrangère manque. Le cours Merise rappelle le passage du schéma relationnel au MCD.

Du code au diagramme de classes

Pour une application objet, ouvre les classes du modèle et note, pour chacune, ses attributs, ses méthodes publiques et ses liens avec les autres.

php
<?php
// src/Modele/Reservation.php
declare(strict_types=1);

namespace Sorbier\Vehicules\Modele;

use DateTimeImmutable;

final class Reservation
{
    public function __construct(
        private ?int $id,
        private Vehicule $vehicule,
        private Utilisateur $demandeur,
        private DateTimeImmutable $debut,
        private DateTimeImmutable $fin,
        private string $statut = 'confirmee',
    ) {}

    public function annuler(): void
    {
        $this->statut = 'annulee';
    }

    public function dureeEnHeures(): float
    {
        return ($this->fin->getTimestamp() - $this->debut->getTimestamp()) / 3600;
    }
}

Le constructeur te donne les attributs, et leurs types te donnent les associations : une Reservation connaît un Vehicule et un Utilisateur. Dans le diagramme de classes, cela donne deux associations orientées, de multiplicité 1 côté véhicule et côté utilisateur. Les méthodes publiques annuler() et dureeEnHeures() deviennent les opérations de la classe. Les outils de génération (extensions de VS Code, PhpStorm, Visual Studio pour le C#) produisent ce diagramme, mais le faire à la main sur trois ou quatre classes centrales t'oblige à les comprendre. Le chapitre Du diagramme de classes au code C#, et rétro-conception du cours UML donne les notations et la démarche.

Du parcours au diagramme de séquence

Le parcours que tu as suivi plus haut (route, contrôleur, service, dépôt, base) se dessine directement en diagramme de séquence : une ligne de vie par objet, une flèche par appel, dans l'ordre. C'est le modèle le plus utile pour expliquer à quelqu'un, ou à une commission, où se place ta modification.

Quand le code résiste

Certains morceaux restent obscurs même après lecture : une fonction de 200 lignes, des noms d'une lettre, une logique imbriquée sur cinq niveaux. Quelques techniques aident.

  • L'exécuter plutôt que la lire. Pose un point d'arrêt à l'entrée et avance pas à pas en regardant les variables (chapitre 4). Voir les valeurs réelles vaut mieux que de les imaginer.
  • Écrire des tests de caractérisation. Appelle la fonction avec des entrées variées et fige ses résultats actuels dans des tests, justes ou faux. Tu documentes ainsi son comportement, et tu disposes d'un filet pour la modifier (chapitre 8).
  • Dessiner. Un organigramme ou une table de décision sur papier débrouille souvent une cascade de if.
  • Lire les tests existants. Un test bien nommé (refuse une réservation qui chevauche une autre) explique une règle mieux qu'un commentaire.
  • Demander. À l'auteur s'il est joignable, à un collègue, à l'utilisateur qui connaît le métier. Dix minutes de question économisent souvent une journée de lecture.

Un assistant d'intelligence artificielle peut aussi résumer ce que fait une fonction ou proposer des hypothèses. Traite ses réponses comme celles d'un collègue qui n'a pas lu le reste du projet : utiles pour démarrer, à vérifier en exécutant le code. Et ne lui colle jamais un code qui contient des secrets ou des données personnelles.

Exercices

Exercice 1 sur 4 Lire un package.json Réussi À revoir

Voici le package.json d'une application que tu reprends :

json
{
  "name": "gestion-stages",
  "version": "2.3.0",
  "scripts": {
    "start": "node src/index.js",
    "dev": "node --watch src/index.js",
    "test": "jest --coverage",
    "lint": "eslint src"
  },
  "dependencies": { "express": "^5.1.0", "mysql2": "^3.11.0", "bcrypt": "^6.0.0" },
  "devDependencies": { "jest": "^30.0.0", "eslint": "^10.0.0" }
}
  1. Quel est le point d'entrée de l'application ?
  2. Quelle commande lance les tests, et que produit-elle en plus des résultats ?
  3. Quel indice te dit que l'application stocke des mots de passe ?
  4. Quelle commande utiliser pour installer les dépendances sans risquer de changer leurs versions ?
  5. La version 2.3.0 est-elle une information sur Express, sur Node.js ou sur l'application ?
Voir la réponseMasquer la réponse de l'exercice 1
  1. src/index.js, lancé par npm start (ou npm run dev en rechargement automatique).
  2. npm test, qui exécute jest --coverage : en plus des résultats, Jest produit un rapport de couverture de code (la part des lignes exécutées par les tests).
  3. La dépendance bcrypt, une bibliothèque de hachage de mots de passe.
  4. npm ci, qui installe exactement les versions du package-lock.json. Encore faut-il que ce fichier soit dans le dépôt.
  5. Sur l'application elle-même : c'est son numéro de version, au format SemVer.

Tu avais trouvé ?

Exercice 2 sur 4 Interpréter un git blame Réussi À revoir

Dans Sorbier Véhicules, la fonction qui vérifie la disponibilité d'un véhicule contient une ligne surprenante :

text
a71e09d3 (Hugo Bernard 2025-11-04 16:22:41 +0100 31)     if ($vehicule->immatriculation() === 'GA-482-LK') { return false; }
  1. Que fait cette ligne ?
  2. Que fais-tu avant de la supprimer ?
  3. Quelle commande affiche le message et le contenu du commit qui l'a introduite ?
Voir la réponseMasquer la réponse de l'exercice 2
  1. Elle déclare un véhicule précis toujours indisponible, quelle que soit la date.
  2. Tu cherches pourquoi : git show a71e09d3 pour lire le message et le ticket associé, puis tu demandes au responsable du parc. Le véhicule est peut-être immobilisé pour une longue réparation, ou réservé à la direction. Si la règle est toujours valable, elle ne doit pas rester codée en dur : elle doit devenir une donnée (une colonne disponible ou une table d'immobilisations), ce qui est une évolution à proposer. Si elle ne l'est plus, tu la supprimes dans un commit à part, avec un message qui explique pourquoi.
  3. git show a71e09d3.

Tu avais trouvé ?

Exercice 3 sur 4 Rétro-concevoir un schéma Réussi À revoir

Voici deux tables extraites d'une base sans documentation :

sql
CREATE TABLE stage (
  id INT AUTO_INCREMENT PRIMARY KEY,
  etudiant_id INT NOT NULL,
  entreprise_id INT NOT NULL,
  tuteur_id INT NULL,
  debut DATE NOT NULL,
  fin DATE NOT NULL,
  FOREIGN KEY (etudiant_id) REFERENCES etudiant (id),
  FOREIGN KEY (entreprise_id) REFERENCES entreprise (id)
);

CREATE TABLE etudiant (
  id INT AUTO_INCREMENT PRIMARY KEY,
  nom VARCHAR(50) NOT NULL,
  classe VARCHAR(10) NOT NULL
);
  1. Écris le schéma relationnel de stage au format des sujets.
  2. Que signifie tuteur_id INT NULL ?
  3. Quelle anomalie remarques-tu sur tuteur_id, et comment vérifier vers quelle table il pointe ?
Voir la réponseMasquer la réponse de l'exercice 3
  1. stage (id, debut, fin, #etudiant_id, #entreprise_id, tuteur_id), avec etudiant_id en référence à id de etudiant et entreprise_id en référence à id de entreprise.
  2. Un stage peut ne pas avoir de tuteur (encore) : la colonne accepte NULL, la relation est facultative.
  3. tuteur_id porte le nom d'une clé étrangère mais n'en est pas une : aucune contrainte FOREIGN KEY ne la déclare, donc le SGBD accepte n'importe quel nombre. Pour savoir vers quelle table elle pointe, cherche les requêtes du code qui l'utilisent dans une jointure (git grep -n "tuteur_id"), puis vérifie les données (SELECT DISTINCT tuteur_id … comparé aux identifiants de la table candidate). Une fois la relation confirmée, ajouter la contrainte est une bonne maintenance préventive, après avoir corrigé les valeurs orphelines.

Tu avais trouvé ?

Exercice 4 sur 4 Dans quel ordre ? Réussi À revoir

Tu reprends une application PHP inconnue et tu as une matinée pour corriger un premier bug. Remets ces actions dans l'ordre : lancer les tests ; lire le README ; corriger le bug ; suivre le parcours de la page concernée ; lire composer.json ; reproduire le bug ; installer les dépendances avec composer install.

Voir la réponseMasquer la réponse de l'exercice 4

Lire le README, lire composer.json, installer les dépendances avec composer install, lancer les tests (pour connaître l'état de départ), suivre le parcours de la page concernée, reproduire le bug, corriger le bug.

On peut reproduire le bug avant de suivre le parcours si l'on sait déjà le déclencher depuis l'interface : les deux ordres se défendent. Ce qui ne se défend pas, c'est de corriger avant d'avoir reproduit.

Tu avais trouvé ?

Chapitre terminé !

Fais les 4 exercices avant de passer à la suite : c'est en t'entraînant que tout se fixe.

chapitres
–
de série
–
du cours
–
Chapitre suivant : Reproduire un bug et lire une stack traceRevoir le sommaire du cours