Une fonction utilitaire renvoie désormais des secondes au lieu de millisecondes. Ses arguments et son type de retour paraissent identiques. Un appelant cesse de demander une actualisation, un autre trie toujours correctement, et une colonne CSV devient discrètement mille fois trop petite. Une liste de références seule ne dit pas lesquels de ces effets se sont produits.
Supposons qu’une petite application liste des sessions, décide quand les actualiser et exporte leur ancienneté. C’est un exemple pédagogique construit ; les résultats ci-dessous viennent de son JavaScript exécuté avec des entrées fixes et des assertions inchangées. Téléchargez l’exemple complet pour les reproduire.
Préciser le changement avant de lister les appelants
Le téléchargement conserve les deux versions d’une fonction dans elapsed.mjs pour exécuter les mêmes consommateurs avec chacune :
export function elapsedBefore(nowMs, startedMs) {
return nowMs - startedMs;
}
export function elapsedAfter(nowMs, startedMs) {
return (nowMs - startedMs) / 1000;
}
Les deux entrées restent des horodatages en millisecondes. Le retour proposé est en secondes, fractions comprises : 2_500 millisecondes deviennent 2.5, pas 2. Pour nowMs = 60_000 et startedMs = 15_000, le résultat passe de 45_000 à 45.
La question devient précise : quels consommateurs exigent l’ancienne unité, et lesquels n’exigent qu’une propriété préservée par la conversion ? Une vérification de type voyant un nombre dans les deux versions n’y répond pas.
Si les valeurs d’entrée sont peu familières, commencez par suivre une valeur depuis sa source dans le code. Changer l’unité de sortie et celle d’entrée sont deux modifications distinctes ; cet exemple ne change que la sortie.
Construire une liste de candidats avec les références
Cherchez elapsed dans votre éditeur, ou lancez :
rg -n 'elapsed' --glob '*.mjs' .
Ouvrez consumers.mjs. Deux appelants méritent votre attention :
export function needsRefresh(elapsed, nowMs, lastSyncMs) {
return elapsed(nowMs, lastSyncMs) >= 30_000;
}
export function oldestFirst(elapsed, nowMs, sessions) {
return [...sessions]
.sort((a, b) => elapsed(nowMs, b.startedMs) - elapsed(nowMs, a.startedMs))
.map((session) => session.id);
}
needsRefresh compare la valeur à un seuil fixe. oldestFirst compare deux valeurs produites par la même fonction. Marquez les deux comme candidats, puis notez leur promesse à l’appelant : actualiser dès trente secondes ; renvoyer les identifiants de session du plus ancien au plus récent sans modifier le tableau d’entrée.
Les références d’un serveur de langage installé et configuré facilitent la navigation dans le projet disponible. Ce sont des pistes. Alias, fonctions injectées, enregistrement et configuration d’exécution demandent encore une inspection ; la vue des références n’inventorie pas tous les consommateurs qu’un déploiement pourrait sélectionner.
Suivre la fonction à travers le registre
app.mjs transmet la fonction à autre chose que les deux consommateurs directs. Il choisit une fabrique via la configuration, puis lui fournit la fonction sous un autre nom :
const factory = registry.get(config.exporter);
if (!factory) throw new Error(`Unknown exporter: ${config.exporter}`);
const exporter = factory({ measureAge: elapsed });
Suivez register(registry) plus haut dans le même fichier. Dans les modes before et after, il appelle registerAgeColumn, implémenté dans age-column.mjs :
export function registerAgeColumn(registry) {
registry.set('age-column', ({ measureAge }) => ({
kind: 'age-column',
rows(nowMs, sessions) {
return [
'id,age_seconds',
...sessions.map(
(session) =>
`${session.id},${measureAge(nowMs, session.startedMs) / 1000}`,
),
].join('\n');
},
}));
}
L’en-tête CSV promet des secondes. Avec l’ancienne fonction, la division par 1000 les fournit. Avec la nouvelle, la division a lieu deux fois.
Chercher seulement elapsed ne révèle pas l’appel measureAge(...) dans ce fichier. Étendez la recherche à l’alias et à la clé d’enregistrement après les avoir découverts :
rg -n 'measureAge|age-column|exporter' --glob '*.mjs' .
L’application de test sélectionne explicitement { exporter: 'age-column' } et vérifie selectedExporter. Cela relie l’entrée du registre au chemin réellement exécuté. Trouver une fabrique enregistrée sans vérifier la sélection laisserait son usage incertain. Sur un serveur, le travail peut commencer par la route et la configuration qui choisissent le chemin d’une requête.
Exécuter les mêmes attentes sur les deux versions
Décompressez l’archive et entrez dans code-change-impact-analysis. Aucune dépendance tierce n’est nécessaire. L’exécution consignée utilisait Node.js 22.22.1 ; utilisez Node.js 22 ou ultérieur et lancez ces commandes séparément dans votre terminal :
node run.mjs before
node run.mjs after
node run.mjs migrated
after doit se terminer avec le code 1. Ne reliez pas les commandes avec &&, qui arrêterait avant la version migrée. Les trois modes chargent les mêmes assertions de tests.mjs.
| Version | Réussis | Échoués | Code de sortie |
|---|---|---|---|
before : fonction en millisecondes, consommateurs originaux |
7 | 0 | 0 |
after : fonction en secondes, consommateurs originaux |
4 | 3 | 1, échec attendu |
migrated : fonction en secondes, consommateurs dépendant de l’unité adaptés |
7 | 0 | 0 |
Les trois échecs de after concernent les décisions d’actualisation à 30_000 et 31_000 millisecondes, ainsi que le CSV. La vérification juste sous le seuil passe toujours. Les deux tests de tri passent, comme le rejet d’un nom d’exportateur inconnu.
Pour inspecter les valeurs avec les assertions, lancez :
node demo.mjs
Pour nowMs = 60_000, la session alpha commence à 15_000 et beta à 57_500. Les observations affichées comprennent :
| Observation | before |
after |
migrated |
|---|---|---|---|
| Actualiser à trente secondes | true |
false |
true |
| Identifiants du plus ancien au plus récent | alpha, beta |
alpha, beta |
alpha, beta |
Ancienneté CSV de alpha |
45 |
0.045 |
45 |
Ancienneté CSV de beta |
2.5 |
0.0025 |
2.5 |
demo.mjs affiche des observations ; tests.mjs décide si elles satisfont le comportement attendu. Par défaut, node --test run.mjs n’exécute que before : un résultat vert ne validerait donc pas la modification proposée.
Distinguer un consommateur cassé d’un consommateur vérifié
Le résultat est plus utile qu’un nombre de références :
| Consommateur ou zone | Constat | Éléments à l’appui |
|---|---|---|
needsRefresh |
Impact confirmé | Les tests inchangés du seuil de trente secondes échouent avec la fonction en secondes. |
oldestFirst |
Régression exclue pour les entrées testées | Anciennetés variées, secondes fractionnaires, égalités, écarts d’une milliseconde, listes vides et à un élément gardent l’ordre attendu ; l’entrée reste inchangée. |
Exportateur age-column sélectionné |
Impact confirmé pour cette configuration | Le test choisit cette fabrique et compare la chaîne CSV complète. Les deux valeurs d’ancienneté sont mille fois trop petites après le changement. |
| Autres configurations de déploiement ou consommateurs hors téléchargement | Inconnu | Ils n’ont été ni fournis ni exécutés. |
Le résultat du tri s’explique : il compare la différence entre deux anciennetés. Diviser les deux par le même facteur positif préserve le signe pour ces entrées. Le tri exige un ordre relatif, tandis que le seuil et l’exportateur exigent une unité précise.
Ce raisonnement aide à choisir les tests, sans permettre d’affirmer que toute entrée numérique a été vérifiée. Les fixtures utilisent de petits horodatages entiers finis en millisecondes. Valeurs flottantes extrêmes, entrées invalides, planification asynchrone des actualisations et vrai lecteur CSV aval sont hors périmètre. Le téléchargement contient un exportateur et ne prétend rien sur un inventaire de plugins de production.
Migrer les consommateurs dépendant de l’unité
Conservez elapsedAfter. Dans consumers.mjs, la fonction d’actualisation migrée compare des secondes à des secondes :
export function needsRefreshSeconds(elapsed, nowMs, lastSyncMs) {
return elapsed(nowMs, lastSyncMs) >= 30;
}
Dans age-column.mjs, registerAgeColumnSeconds émet directement measureAge(...), supprimant la seconde division. app.mjs choisit ces deux implémentations migrées en mode migrated. oldestFirst reste inchangé.
Les tests exigent toujours les mêmes résultats visibles : actualisation à trente secondes, mêmes identifiants triés et secondes dans le CSV. Les sept réussites finales montrent leur rétablissement pour cette fixture. Si votre correctif modifie aussi les attentes, examinez les assertions avec l’implémentation avant de considérer le résultat vert comme équivalent.
Utiliser l’IA pour suggérer le prochain fichier à inspecter
Pendant la lecture, une question IA facultative peut resserrer la recherche :
Suis la fonction transmise sous le nom
elapsed, y compris les alias et fabriques enregistrées. Pour chaque consommateur candidat, montre le code qui suppose une unité ou utilise seulement l’ordre. Identifie la sélection d’exécution et l’entrée de test permettant de vérifier la conséquence. Sépare les appelants non testés des résultats exécutés.
Vérifiez les chemins proposés dans le source et exécutez les commandes vous-même.