FlowingDev

Le Diff, expliqué : l'ingrédient secret de Git et de toute revue de code

Découvrez comment les algorithmes de diff trouvent les ajouts, suppressions et changements précis entre deux textes, le moteur au cœur du versioning et de la collaboration sur le code.

Essayer l'outil: Comparateur de différences

En une phrase

Un « diff » est un résumé calculé des différences précises entre deux fichiers ou blocs de texte, qui montre exactement ce qui a été ajouté, supprimé ou modifié pour passer de l'« avant » à l'« après ».

Le problème que ça résout

Imaginez le monde de l'informatique au début des années 70. Le stockage est d'une cherté hallucinante, et se connecter à un ordinateur distant se fait via un modem plus lent qu'un escargot endormi. Vous êtes développeur aux Bell Labs, et vous devez mettre à jour un fichier de code source sur un serveur à l'autre bout du campus. Le fichier fait quelques milliers de lignes, mais vous n'en avez changé que trois.

Allez-vous renvoyer le fichier entier via cette connexion d'une lenteur infinie ? Pas question. C'est une perte de temps et de ressources. Ce que vous voulez vraiment, c'est envoyer uniquement les modifications.

C'est exactement le problème qui a conduit Douglas McIlroy à créer la commande diff originale pour le système d'exploitation Unix en 1974. Son but était de créer un outil capable de trouver de manière programmatique l'ensemble minimal de changements ligne par ligne nécessaires pour transformer un fichier en un autre. Le résultat de cette commande diff, un fichier « patch », était minuscule et pouvait être envoyé rapidement. Le destinataire pouvait alors utiliser un programme compagnon, patch, pour appliquer ces changements à sa propre copie du fichier original, la mettant ainsi à jour.

Cette idée simple et puissante — isoler le changement lui-même en tant que donnée — était révolutionnaire. C'est la brique fondamentale de tous les systèmes de gestion de version modernes comme Git, Subversion et Mercurial. C'est le moteur derrière les revues de code, les outils de collaboration sur des documents (comme le mode « Suggestion » de Google Docs), et les systèmes de gestion de configuration. Ça résout le problème fondamental du suivi et de la communication de l'évolution de n'importe quel texte numérique.

Comment ça marche sous le capot

À première vue, faire un diff semble simple : il suffit de scanner deux textes et de repérer ce qui est différent. Mais le faire efficacement et produire l'ensemble de différences le plus petit et le plus lisible est un problème classique en informatique. Le secret, ce n'est pas de chercher ce qui est différent, mais ce qui est identique.

La Plus Longue Sous-séquence Commune (LCS)

La plupart des algorithmes de diff, y compris le célèbre algorithme de Hunt–McIlwain qui animait le diff original, sont basés sur la résolution du problème de la « Plus Longue Sous-séquence Commune » (LCS ou PLSC en français).

Une sous-séquence est une suite d'éléments qui apparaissent dans le même ordre que dans la séquence originale, mais pas forcément les uns à la suite des autres. La LCS est la plus longue de ces sous-séquences que deux séquences ont en commun.

Prenons un exemple simple, sans code.

  • Original : Le rapide renard roux
  • Nouveau : Le lent chat roux

L'algorithme, travaillant ligne par ligne (ou ici, mot par mot), trouve que la Plus Longue Sous-séquence Commune est : Le roux.

Une fois la LCS trouvée, la logique est simple :

  • Tout élément de l'Original qui n'est pas dans la LCS a forcément été supprimé. (rapide, renard)
  • Tout élément du Nouveau texte qui n'est pas dans la LCS a forcément été ajouté. (lent, chat)

En trouvant le plus long socle de contenu commun, l'algorithme peut clairement et de manière concise identifier les îlots de changement autour de lui. Cette méthode produit un ensemble minimal de différences, ce qui est exactement ce que nous voulons pour un diff propre et compréhensible.

De la LCS à un diff lisible

Trouver les changements ne représente que la moitié du travail. L'autre moitié consiste à les présenter dans un format standardisé et lisible. Vous avez probablement déjà vu ça si vous avez jeté un œil à une pull request sur GitHub. Le format le plus courant est le « format de diff unifié ».

Appliquons-le à un exemple légèrement différent :

  • Fichier A (ancien) :
    An apple a day.
    Keeps the doctor away.
    Or so they say.
    
  • Fichier B (nouveau) :
    An apple a day,
    Keeps the doctor away.
    For what it's worth.
    

Un outil de diff générerait quelque chose comme ça :

--- a/file_a.txt
+++ b/file_b.txt
@@ -1,3 +1,3 @@
-An apple a day.
+An apple a day,
 Keeps the doctor away.
-Or so they say.
+For what it's worth.

Décortiquons ça :

  • --- a/file_a.txt : Le fichier « de départ ». Le - indique la source des suppressions.
  • +++ b/file_b.txt : Le fichier « d'arrivée ». Le + indique la source des ajouts.
  • @@ -1,3 +1,3 @@ : C'est le « hunk header » (en-tête de bloc). C'est un peu cryptique, mais ça donne le contexte. -1,3 signifie « ce bloc commence à la ligne 1 et fait 3 lignes de long dans le fichier original ». +1,3 signifie « ce bloc commence à la ligne 1 et fait 3 lignes de long dans le nouveau fichier ».
  • Les lignes commençant par un espace ( ) sont des lignes de contexte. Elles sont identiques dans les deux fichiers et sont affichées pour vous aider à comprendre où le changement a eu lieu.
  • Les lignes commençant par - sont des suppressions. Elles n'existent que dans le texte « avant ».
  • Les lignes commençant par + sont des ajouts. Elles n'existent que dans le texte « après ».

L'outil montre le changement de An apple a day. à An apple a day, non pas comme une modification d'une seule ligne, mais comme une suppression de l'ancienne ligne et un ajout de la nouvelle. Cette approche ligne par ligne est une caractéristique fondamentale de la plupart des outils de diff traditionnels.

Au-delà du texte brut : les diffs sémantiques

Un diff standard basé sur les lignes est génial pour de la prose ou du code, mais il ne tient plus la route avec des données structurées comme le JSON, le XML ou le YAML.

Considérez ce JSON :

// Original
{
  "name": "Alex",
  "role": "Developer"
}

Et celui-ci :

// New
{
  "role": "Developer",
  "name": "Alex"
}

Un diff textuel verrait ça comme un effacement complet suivi d'une réécriture :

-  "name": "Alex",
-  "role": "Developer"
+  "role": "Developer",
+  "name": "Alex"

C'est techniquement correct mais sémantiquement inutile. L'ordre des clés dans un objet JSON n'a généralement pas d'importance. Un outil de diff sémantique est plus malin. Il analyse d'abord le texte pour le transformer en structure de données, puis compare les structures. Il identifierait correctement que ces deux objets JSON sont identiques, ce qui ne donnerait aucune différence. C'est crucial pour comparer des fichiers de configuration, des réponses d'API, ou toute autre donnée structurée où ce qui compte, c'est le sens, pas juste le formatage du texte.

Histoires vécues

Le bug d'un seul caractère qui a planté le paiement

Un développeur junior intégrait un nouveau prestataire de paiement. Il a copié la requête d'API d'exemple de la documentation, a entré ses clés d'API et l'a lancée. Échec. Il a réessayé. Échec. Il a passé des heures à fixer son code et la documentation, convaincu qu'ils étaient identiques. Frustré, il a collé l'exemple « fonctionnel » de la doc d'un côté d'un outil de diff et son propre code de l'autre.

Au début, tout semblait identique. Mais il a alors remarqué une subtile surbrillance à la fin de la ligne de sa clé d'API. Un unique espace invisible à la fin. Le copier-coller depuis la page web l'avait inclus, et son code l'envoyait consciencieusement, invalidant la clé. L'outil de diff, qui voyait l'espace comme un caractère parmi d'autres, était la seule « paire d'yeux » capable de le voir.

Leçon : Un diff est votre microscope ultime. Il n'a aucune supposition et vous montrera exactement ce qui est là, y compris les caractères invisibles qui peuvent mettre un système à genoux.

La débâcle de la dérive de configuration

Un site web à fort trafic a commencé à subir des erreurs bizarres et intermittentes. L'ingénieure d'astreinte, Maya, était perplexe. Le dernier déploiement datait d'une semaine et avait été stable. Rien dans les logs n'indiquait une cause claire. Son flair de dev lui disait que quelque chose sur le serveur avait été modifié manuellement.

Elle a récupéré le fichier de configuration Nginx officiel depuis leur dépôt Git, puis s'est connectée en SSH sur le serveur de production et a copié la configuration réellement en cours d'exécution. Elle a collé les deux dans un outil de diff. Bingo. Trois lignes étaient différentes. Quelqu'un avait ajouté une règle de redirection « temporaire » directement sur le serveur pour corriger un problème mineur la semaine dernière et l'avait complètement oublié. Ce « correctif » entrait maintenant en conflit avec de nouveaux schémas de trafic. Maya a retiré les lignes parasites et les erreurs ont disparu. L'équipe a immédiatement mis en place une politique pour auditer quotidiennement les configurations des serveurs par rapport à Git.

Leçon : Votre système de gestion de version est votre source de vérité. Faire un diff entre la réalité et cette source de vérité est le meilleur moyen de détecter la « dérive de configuration » et de trouver des changements non autorisés ou oubliés.

La revue de code type « Ça m'a l'air bon »

Un développeur senior, Ben, a reçu une pull request d'une nouvelle recrue. Le titre était « Mises à jour ». Le diff était un océan de rouge et de vert sur 20 fichiers et plus de 3 000 lignes. Il contenait une nouvelle fonctionnalité, un correctif pour un bug sans rapport, un reformatage massif du code pour passer des tabulations aux espaces, et une mise à niveau de librairie. C'était impossible à relire. Le correctif était-il bon ? La nouvelle fonctionnalité introduisait-elle une faille de sécurité ? Tout était caché dans une avalanche de changements d'espacement.

Ben a rejeté la PR avec une note bienveillante : « Bienvenue ! Le diff d'une PR raconte une histoire. Celui-ci essaie de raconter quatre histoires différentes en même temps. Peux-tu s'il te plaît diviser ça en quatre PR distinctes ? » La nouvelle recrue s'est exécutée. La PR de reformatage a été instantanément approuvée. Le correctif de bug était facile à vérifier. La mise à jour de la librairie était simple. Et la nouvelle fonctionnalité a enfin pu être examinée pour ce qu'elle était.

Leçon : La valeur d'un diff est inversement proportionnelle à sa taille et sa complexité. Des diffs petits et ciblés représentant un unique changement logique sont faciles à relire, à comprendre et à déboguer plus tard.

Erreurs et pièges courants

  • Ignorer les espaces (whitespace). Un changement de tabulations en espaces ou l'ajout d'un retour à la ligne final peut apparaître comme un changement massif à l'échelle d'un fichier pour un outil de diff. Bien que ce soit parfois intentionnel, ça crée souvent du bruit qui masque les vrais changements importants. Configurez vos outils pour ignorer ou mettre en évidence les changements d'espacement de manière appropriée.
  • Le diff « sémantiquement aveugle ». Comme mentionné précédemment, utiliser un diff textuel simple sur des données structurées comme du JSON ou du XML peut être extrêmement trompeur. Réorganiser des attributs ou des clés peut ressembler à un changement énorme alors que, fonctionnellement, rien n'a changé. Utilisez toujours un outil de diff sémantique pour ces formats.
  • Oublier le contexte. Un diff vous montre ce qui a changé, mais il ne vous dit jamais pourquoi. C'est le rôle du message de commit ou de la description de la pull request. Un diff sans contexte, c'est comme une réponse sans question ; il est difficile de juger si c'est juste ou non.
  • Créer des diffs « Frankenstein ». Regrouper des changements sans rapport dans un seul commit (un correctif, une fonctionnalité et une correction de coquille) rend le diff cauchemardesque à lire. Cela rend impossible l'annulation de l'un de ces changements plus tard sans affecter les autres. Chaque commit doit être un changement unique, logique et atomique.

Pourquoi c'est un incontournable

Comprendre le diffing n'est pas une option pour un développeur moderne ; c'est aussi fondamental que de savoir utiliser un clavier. Vous rencontrerez des diffs plusieurs fois par jour, tous les jours :

  • Quand vous lancez git status ou git diff pour voir votre propre travail non commité.
  • Quand vous créez une pull request pour que vos collègues la relisent.
  • Quand vous relisez la pull request de quelqu'un d'autre.
  • Quand vous utilisez git blame pour savoir qui a écrit une ligne de code spécifique et pourquoi.
  • Quand vous déboguez un problème en comparant une configuration qui fonctionne avec une qui est cassée.

Même pour les non-développeurs, le concept est puissant. C'est la fonction « Suivi des modifications » de votre document Word. C'est l'historique des versions de votre article Wikipédia. C'est la capacité de voir comment un contrat a évolué entre deux versions. Comprendre le diffing, c'est comprendre comment nous gérons et communiquons le changement dans le monde numérique. C'est l'historique auditable et vérifiable du progrès.

Pour aller plus loin

Théorie bouclée. Place à la pratique — 100 % dans ton navigateur.

Essayer l'outil: Comparateur de différences