Prompts pour expliquer du code : du regex au stack trace

Seize questions prêtes pour l'outil, quand il faut comprendre un code écrit par quelqu'un d'autre. Chacune contient déjà un extrait, un motif, une commande ou une trace d'erreur et dit pour qui l'explication est écrite : remplacez le code par le vôtre et appliquez.

Comprendre le code des autres

Un projet hérité se lit lentement : comprendre une fonction sans le moindre commentaire, lire du code dans un langage qu'on ne connaît pas, se le faire expliquer ligne par ligne et savoir pourquoi il a été écrit ainsi.

Une fonction sans commentaires

Traduit du code hérité en une phrase sur ce qu’il calcule réellement.

Prompt

def recalc(items, rate): total = 0 for it in items: if it.status != "cancelled": total += it.price * it.qty if total > 10000: total = total * (1 - rate) return round(total, 2)# héritée du développeur précédent, pas un seul commentaire — explique ce que cette fonction calcule et dans quelles conditions

Résultat

La fonction calcule le montant d’une commande, remise comprise.1. Elle multiplie le prix par la quantité sur chaque ligne et saute les lignes annulées : une ligne dont status vaut "cancelled" n’entre jamais dans le total.2. Dès que le total dépasse 10000, rate lui est appliqué : 0.1 signifie dix pour cent de remise.3. Le résultat est arrondi à deux décimales, donc au centime.Le détail que seul le code révèle : la remise porte sur la totalité du montant et non sur la part qui dépasse le seuil. Une commande de 10001 revient donc moins cher, après remise, qu’une commande de 9999.

Du code dans un langage inconnu

Explique une construction inconnue à partir de celle que vous utilisez déjà dans votre langage.

Prompt

import functoolsdef retry(times): def decorator(fn): @functools.wraps(fn) def wrapper(*args, **kwargs): for attempt in range(times): try: return fn(*args, **kwargs) except TimeoutError: continue raise RuntimeError("all attempts failed") return wrapper return decorator# je fais du PHP et je vois un décorateur pour la première fois — explique-le avec des notions que je connais

Résultat

C’est un décorateur paramétré. Ce qui s’en rapproche le plus en PHP est une fonction qui renvoie une fermeture enveloppant une autre fonction.Cela se lit de l’intérieur vers l’extérieur :- retry(3) renvoie decorator ;- decorator reçoit votre fonction et renvoie wrapper ;- la ligne @retry(3) au-dessus d’une fonction signifie exactement fn = retry(3)(fn) — le nom désigne désormais wrapper.wrapper appelle l’original jusqu’à trois fois et avale TimeoutError sans rien dire, puis lève RuntimeError une fois les tentatives épuisées. functools.wraps sert à garder le nom et la docstring de l’original : sans lui, le débogueur et toutes les traces n’afficheraient plus que wrapper.

Lecture ligne par ligne

Parcourt l’extrait ligne après ligne, pour quand ce n’est pas l’ensemble qui bloque mais un endroit précis.

Prompt

def top_customers(orders, n): totals = dict() for order in orders: totals[order.customer_id] = totals.get(order.customer_id, 0) + order.total ranked = sorted(totals.items(), key=lambda pair: -pair[1]) return ranked[:n]# reprends ligne par ligne, surtout celle avec sorted et le moins devant pair

Résultat

1. totals = dict() — un dictionnaire vide où s’accumulent les montants par client.2. La boucle parcourt les commandes. totals.get(id, 0) renvoie le cumul, ou zéro si le client apparaît pour la première fois : c’est pour cela qu’aucun test particulier sur la première commande n’est nécessaire.3. sorted sur items() trie des paires client-montant. Le moins devant pair[1] inverse l’ordre : décroissant au lieu de croissant, donc les plus gros clients se retrouvent en tête.4. ranked[:n] ne garde que les n premières paires.Le choix de renvoyer les paires plutôt que les seuls identifiants est volontaire : l’appelant a besoin du montant pour l’afficher et n’aura pas à le recalculer.

Pourquoi c’est écrit ainsi

Explique non pas ce que fait le code, mais pourquoi son auteur a choisi cette forme.

Prompt

_cache = None_lock = threading.Lock()def get_config(): global _cache if _cache is None: with _lock: if _cache is None: _cache = load_config_from_disk() return _cache# pourquoi le test sur None est écrit deux fois, avant le verrou et à l’intérieur

Résultat

C’est le motif double-checked locking, et chacun des deux tests répond à un besoin différent.Le premier est le chemin rapide. Une fois la configuration chargée, tous les appels suivants voient la valeur prête et repartent aussitôt sans toucher au verrou. Prendre un verrou coûte du temps à chaque appel, alors qu’il n’est réellement utile qu’une seule fois dans la vie du processus.Le second concerne la course. Pendant que le premier fil tient le verrou et lit le fichier, un deuxième a déjà franchi le test extérieur et attend derrière. Sans le test intérieur, il rechargerait la configuration et écraserait le résultat du premier.La ligne global fait que l’affectation modifie la variable du module au lieu d’en créer une locale.

Erreurs et traces d’exécution

Le texte rouge se lit avant de réparer quoi que ce soit : comprendre un message d'erreur, suivre la trace d'une application qui plante, une installation de dépendance qui échoue et une ligne du journal de production.

L’application plante

Lit la trace : où ça s’est arrêté, quelle ligne est fautive et ce que veut dire l’erreur elle-même.

Prompt

Traceback (most recent call last): File "app/orders.py", line 42, in create_order total = calc_total(items) File "app/pricing.py", line 17, in calc_total return sum(item.price * item.qty for item in items)AttributeError: 'dict' object has no attribute 'price'que s’est-il passé et dans quelle ligne chercher la cause

Résultat

AttributeError veut dire que l’objet n’a pas d’attribut nommé price.Une trace se lit de haut en bas pour le chemin d’appel et de bas en haut pour la cause :- create_order, dans orders.py ligne 42, a appelé calc_total ;- calc_total, dans pricing.py ligne 17, a demandé item.price et a échoué.C’est la ligne 17 qui casse, mais le problème n’est pas là. Le message indique que items contient des dictionnaires et non des objets, or l’accès par point ne fonctionne pas sur un dictionnaire. Autrement dit, l’appelant a transmis les données telles quelles depuis le JSON, sans construire de modèle. Corrigez là où items est assemblé, ou lisez le champ avec item["price"].

L’installation échoue

Traduit le journal d’installation depuis la langue du gestionnaire de paquets vers la nôtre.

Prompt

ERROR: Could not find a version that satisfies the requirement pandas==2.9.1 (from versions: 2.0.0, 2.1.4, 2.2.2)ERROR: No matching distribution found for pandas==2.9.1impossible d’installer les dépendances, que signifie ce message

Résultat

Cette version de la bibliothèque n’existe pas.pip est allé dans l’index des paquets, a trouvé pandas et a listé les versions disponibles : la plus récente est la 2.2.2. Vous demandez la 2.9.1, que personne ne publie, d’où le « no matching distribution ».En pratique, c’est presque toujours l’une de ces trois choses : une faute de frappe dans le numéro, un requirements.txt repris d’un projet plus récent, ou une version publiée uniquement pour une autre version de Python. La seconde ligne n’est pas un problème distinct, c’est la conclusion de la première.

Une ligne du journal de production

Explique ce que le serveur signale vraiment quand le journal affiche un délai dépassé et trois chiffres.

Prompt

2026-09-12T03:14:07Z ERROR db pool: timeout acquiring connection after 30s, active=20 idle=0 waiting=1372026-09-12T03:14:07Z ERROR api POST /orders 503 in 30012ms request_id=8c1f2aqu’est-ce que le serveur essaie de me dire avec ces deux lignes

Résultat

Les connexions à la base sont épuisées.Première ligne : le pool a attendu une connexion libre pendant 30 secondes puis a abandonné. active=20 signifie que les vingt sont prises, idle=0 qu’aucune n’est libre, waiting=137 que cent trente-sept requêtes font la queue derrière.La seconde ligne en est la conséquence : POST /orders est resté suspendu ces mêmes 30 secondes et a renvoyé 503.Lu ensemble : les requêtes arrivent plus vite que la base ne relâche ses connexions. Le pool n’est pas cassé, il signale honnêtement qu’il a atteint son plafond. La suite se cherche du côté des requêtes longues qui monopolisent une connexion, et de la taille du pool par rapport au nombre de processus.

Expressions régulières et commandes

Les lignes qui marchent tant que personne n'y touche : décoder une expression régulière de validation, celle qui traîne dans un fichier de configuration et savoir ce que fait vraiment une commande copiée d'une procédure.

Ce que vérifie l’expression régulière

Découpe le motif en morceaux et dit quelles adresses passent et lesquelles sont rejetées à tort.

Prompt

^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z][A-Za-z]+$c’est dans la validation de notre formulaire — que vérifie-t-il exactement et que laisse-t-il passer de trop

Résultat

Le motif vérifie la forme d’une adresse, pas son existence.- ^ et $ exigent que la chaîne entière corresponde, et non un morceau à l’intérieur.- La partie avant l’arobase : lettres, chiffres, point, tiret bas, pourcent, plus et tiret, au moins un caractère.- @ — exactement une arobase.- Le domaine : lettres, chiffres, points et tirets.- À la fin, un point et une extension d’au moins deux lettres.Ce qui passe à tort : deux points de suite, un point en début de nom, un domaine qui n’existe pas. Ce qui est rejeté sans raison : les adresses internationalisées et les extensions en alphabet non latin. Pour un formulaire cela suffit en général, puisque l’existence est de toute façon confirmée par le courriel de vérification.

Une expression tirée d’une configuration

Montre quelles parties de la ligne de journal atterrissent dans quel groupe, et dans quel ordre.

Prompt

^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+) - - \[([^\]]+)\] "([A-Z]+) ([^ ]+) [^"]+" ([0-9]+) ([0-9]+)$cela découpe une ligne d’access.log — qu’est-ce qui atterrit dans chaque groupe

Résultat

L’expression découpe une ligne du journal du serveur web en six groupes.1. L’adresse IP : quatre nombres séparés par des points. Le point échappé désigne un vrai point et non « n’importe quel caractère ».2. L’horodatage entre crochets : tout caractère y est admis sauf le crochet fermant, et c’est précisément ce qui empêche le groupe d’avaler la fin de la ligne.3. La méthode : des lettres majuscules.4. Le chemin : tout jusqu’à la première espace.5. Le code de réponse.6. La taille de la réponse en octets.L’ordre des groupes est l’ordre des champs dans le résultat : le premier est l’adresse, le cinquième le statut. Une ligne dont le chemin contient une espace ne correspond pas dans son ensemble et sera simplement ignorée.

Une commande copiée d’une procédure

Démonte la chaîne du terminal avant que vous n’appuyiez sur Entrée sur un serveur de production.

Prompt

tar -czf - ./data | ssh backup@nas "cat > /backups/data-$(date +%F).tar.gz"# trouvée dans notre procédure de sauvegarde — explique ce qui va se passer avant que je la lance

Résultat

La commande sauvegarde le dossier data sur une autre machine sans rien écrire sur celle-ci.- tar -czf - fabrique l’archive et la compresse, mais l’écrit sur la sortie standard au lieu d’un fichier : c’est le sens du tiret isolé.- La barre verticale transmet ce flux à la commande suivante.- ssh se connecte à la machine nas avec l’utilisateur backup et y lance cat, qui dépose le flux dans un fichier.- $(date +%F) insère la date du jour dans le nom : on obtient data-2026-09-13.tar.gz.L’archive ne se pose nulle part en entier : elle traverse le réseau au fur et à mesure, aucun espace disque n’est donc requis sur la machine source. Un seul point tranchant : si un fichier porte déjà ce nom, il est écrasé sans un mot.

SQL et requêtes en base

Un SELECT qui remplit un demi-écran se démonte morceau par morceau : comprendre une requête SQL de rapport, démêler une fonction de fenêtrage et voir sur quoi la requête bute quand elle ralentit en production.

Une requête de rapport

Traduit un long SELECT en une phrase sur les lignes qui figureront dans le rapport.

Prompt

SELECT c.name, date_trunc('month', o.created_at) AS month, count(*) AS orders, sum(o.total) AS revenueFROM orders oJOIN customers c ON c.id = o.customer_idWHERE o.created_at >= now() - interval '1 year'GROUP BY 1, 2HAVING sum(o.total) > 1000ORDER BY revenue DESC-- explique ce que renvoie cette requête et pourquoi une partie des clients n’apparaît jamais dans le rapport

Résultat

La requête calcule le chiffre d’affaires par client, ventilé par mois, sur la dernière année.- JOIN relie la commande au client. C’est une jointure ordinaire et non LEFT, donc les clients sans la moindre commande n’apparaissent pas du tout.- WHERE écarte tout ce qui a plus d’un an.- date_trunc ramène la date au premier du mois : c’est ainsi que les commandes individuelles se regroupent par mois.- GROUP BY 1, 2 regroupe sur les deux premières expressions de la liste : le nom du client et le mois.- HAVING écarte les groupes en dessous de 1000. Contrairement à WHERE, ce filtre s’applique une fois la somme calculée.Une ligne du résultat, c’est un client sur un mois, et non un client dans son ensemble.

Fonction de fenêtrage

Explique ce que calcule OVER et en quoi une fenêtre diffère d’un regroupement ordinaire.

Prompt

SELECT customer_id, created_at, total, sum(total) OVER (PARTITION BY customer_id ORDER BY created_at) AS running_total, row_number() OVER (PARTITION BY customer_id ORDER BY total DESC) AS rnFROM orders-- explique ce que calculent ces deux OVER et pourquoi les lignes ne sont pas fusionnées

Résultat

Une fonction de fenêtrage calcule sur un groupe sans fusionner les lignes : il en sort autant qu’il en est entré, chacune gagnant simplement deux colonnes.- PARTITION BY customer_id découpe la table en piles par client. Une pile, c’est la fenêtre.- Dans la première colonne, la somme est cumulée : ORDER BY created_at fixe l’ordre à l’intérieur de la fenêtre et, par défaut, le cadre va du début de la fenêtre à la ligne courante. Pour la troisième commande d’un client, c’est la somme des trois premières.- Le second OVER trie autrement, par montant décroissant, et row_number attribue les rangs : le 1 revient à la plus grosse commande de ce client.C’est exactement ce qui sépare une fenêtre d’un GROUP BY : le regroupement aurait laissé une ligne par client et perdu les commandes une à une.

Ce sur quoi la requête bute

Explique la requête et signale au passage ce qu’elle va rencontrer avec de vraies données.

Prompt

SELECT *FROM orders oLEFT JOIN order_items i ON i.order_id = o.idWHERE DATE(o.created_at) = '2026-09-01' AND o.status != 'cancelled'ORDER BY o.created_at DESC-- instantanée sur la base de test, elle reste bloquée en production — explique la requête et ce sur quoi elle bute ici

Résultat

La requête récupère les commandes du premier septembre avec toutes leurs lignes. LEFT JOIN signifie qu’une commande sans ligne sort quand même, avec les colonnes d’order_items vides.Ce sur quoi elle bute avec de vraies données :- DATE(o.created_at) est une fonction posée sur la colonne, et l’index sur created_at en devient inutilisable : la base doit calculer une date pour chaque ligne de la table. Invisible sur cent lignes de test, c’est un parcours complet sur des millions.- SELECT * sur une jointure un-vers-plusieurs renvoie chaque commande autant de fois qu’elle a de lignes. Il sort nettement plus de lignes que de commandes, et l’application doit les regrouper elle-même.- Les deux tables ont des colonnes de même nom (id, created_at) ; dans le résultat elles se mélangent et le nom ne dit plus de quelle table vient le champ.

Expliquer à un débutant et en entretien

Quand l'explication compte plus que le code : expliquer du code à un débutant qui écrit son premier programme, reprendre un exercice posé en entretien d'embauche et raconter un script à un responsable non technique.

Le premier programme

Une explication pour la deuxième semaine de code : ce que fait chaque ligne et pourquoi elle est écrite ainsi.

Prompt

nums = [4, 8, 15, 16, 23, 42]total = 0for n in nums: if n % 2 == 0: total += nprint(total / len(nums))# je commence tout juste Python — explique pas à pas ce qui se passe ici

Résultat

Le programme additionne les nombres pairs de la liste et divise le résultat par le nombre de TOUS les éléments.1. nums est une liste de six nombres.2. total = 0 est la boîte dans laquelle on va accumuler. Le zéro sert de point de départ.3. for n in nums est la boucle : n prend tour à tour chaque nombre de la liste.4. n % 2 == 0 signifie que le reste de la division par deux vaut zéro, donc que le nombre est pair.5. total += n ajoute ce qu’on vient de trouver au cumul.6. La division par len(nums) est une division par la longueur de la liste, soit six.Cela ressemble à une erreur d’intention : la liste contient quatre nombres pairs dont la somme fait 70, et l’on divise par six. Pour la moyenne des nombres pairs, il faut diviser par le nombre de pairs.

Un exercice d’entretien

L’algorithme décortiqué de façon à pouvoir l’expliquer à voix haute devant un tableau.

Prompt

def search(nums, target): lo, hi = 0, len(nums) - 1 while hi >= lo: mid = (lo + hi) // 2 if nums[mid] == target: return mid if nums[mid] > target: hi = mid - 1 else: lo = mid + 1 return -1# question posée en entretien — explique l’idée de l’algorithme et pourquoi il est rapide

Résultat

C’est une recherche dichotomique dans un tableau trié. L’idée tient en une phrase : à chaque étape, la moitié de ce qui reste est écartée.- lo et hi sont les bornes de la portion où la réponse peut encore se trouver.- mid est le milieu. La double barre oblique est une division entière, on obtient donc un indice et non une fraction.- Si le milieu est la valeur cherchée, c’est fini.- Si le milieu est plus grand, la réponse est à gauche et hi se déplace.- Sinon elle est à droite et lo se déplace.- La boucle s’arrête quand les bornes se croisent : la valeur est absente, -1 est renvoyé.Le coût est logarithmique : un million d’éléments, c’est une vingtaine d’étapes au lieu d’un million. La condition indispensable est que le tableau soit déjà trié, sinon l’algorithme renvoie une mauvaise réponse sans prévenir.La relance classique en entretien : dans les langages à entiers de taille fixe, lo + hi peut déborder, d’où l’écriture lo + (hi - lo) // 2.

L’expliquer à son responsable

Le script raconté sans le moindre terme technique, pour parler à quelqu’un qui ne lit pas de code.

Prompt

import csv, smtplibrows = list(csv.DictReader(open("orders.csv")))late = [r for r in rows if r["shipped_at"] == ""]body = "Commandes non expédiées : " + str(len(late))smtplib.SMTP("mail.local").sendmail("bot@shop.local", "ops@shop.local", body)# je dois expliquer à un responsable non technique ce que fait ce script

Résultat

Le script vérifie quelles commandes ne sont pas encore expédiées et en signale le nombre par courriel.Étape par étape, sans jargon :1. Il ouvre le tableau des commandes, un fichier ordinaire qu’Excel ouvrirait aussi.2. Il retient les lignes dont la date d’expédition est vide : commande passée, mais pas partie.3. Il compte combien de lignes de ce type il y a.4. Il envoie un courriel à l’adresse du service, avec ce seul nombre dans le texte.Ce qu’un responsable doit savoir : le script ne modifie rien et ne gêne personne, il se contente de lire et de signaler, on peut donc le lancer toutes les heures. Son unique faiblesse : il lit un fichier, et montre donc la situation à l’heure du dernier export, pas celle de l’instant présent.

Prompts pour expliquer du code : 16 analyses | iBro