Nommer n'est pas trouver
Vous connaissez maintenant sept motifs MOAD. Connaître les noms importe : cela vous permet de reconnaître un motif quand vous le voyez. Mais la reconnaissance dans une leçon contrôlée diffère de la détection dans un codebase que vous n'avez jamais ouvert.
Un codebase ne marque pas ses défauts. Un MOAD sédimentaire ne vient pas avec un commentaire qui dit // O(N²) — fix this. Un thundering herd ne s'annonce pas comme une ruée de cache miss. Vous les trouvez en lisant le code avec une question spécifique à l'esprit : quelle structure de données contient ces valeurs, et quelles opérations s'exécutent contre elle dans une boucle ?
La détection est une compétence distincte de la reconnaissance. La reconnaissance dit : oui, ce motif est MOAD-0001. La détection dit : laissez-moi trouver tous les endroits dans ce codebase où ce motif pourrait exister, que je puisse voir le code complet ou seulement un nom de symbole.
Premier Scan
Un premier passage utilise grep. Chaque MOAD a un substrat : une structure de données ou une API dont la présence, près de certaines opérations, est un signal qui vaut la peine d'être examiné.
MOAD-0001 (Sédimentaire) : List.contains dans une boucle
# Signal: membership test on a list variable inside a loop
grep -rn '.contains(' src/ | grep -v HashSet | grep -v TreeSet
grep -rn 'visited =' src/ | grep -v set | grep -v Set
MOAD-0002 (Intertangle): shared mutable flag across phases
# Signal: static mutable field written by one subsystem, read by another
grep -rn 'static ' src/ | grep -v final | grep -v class | grep -v void
MOAD-0003 (Leaked Context): ThreadLocal in a pooled executor
# Signal: ThreadLocal.set() without guaranteed ThreadLocal.remove()
grep -rn 'ThreadLocal' src/
grep -rn 'ThreadLocal.set' src/ -l
MOAD-0004 (Logged Secret): HTTP headers in log output
# Signal: log call with headers variable near auth endpoints
grep -rn 'log.*header' src/
grep -rn 'Authorization' src/ --include='*.log'
MOAD-0005 (Thundering Herd): cache miss with no synchronization
# Signal: cache.get() + null check + cache.put() without lock
grep -rn 'cache.get' src/ -A4 | grep 'cache.put'
Ces motifs produisent des candidats, pas des défauts confirmés. Chaque candidat a besoin de triage : lire le code environnant, vérifier le type de structure de données, confirmer que l'opération s'exécute à grande échelle.
Lire le Code pour la Complexité
Grep trouve des candidats. Lire confirme. Quand vous ouvrez un fichier candidat, vous lisez avec une question : le coût de cette opération grandit-il avec la taille d'entrée ?
Pour MOAD-0001, le protocole de confirmation :
1. Trouvez la boucle externe. Qu'est-ce qui limite son nombre d'itérations ?
2. Trouvez l'opération interne (.contains, .indexOf, 'in'). Quelle structure de données s'exécute-t-elle contre ?
3. Cette structure de données grandit-elle avec la même entrée qui pilote la boucle externe ?
4. Si oui : le coût est O(N²) où N = taille d'entrée. Défaut confirmé.
5. Si non : la structure interne est bornée (config, enum, petite constante). Faux positif.
Un parcours de graphe visitant N nœuds, vérifier une liste visited à chaque étape : la boucle et la structure de données interne grandissent toutes deux avec N. Confirmé.
Un gestionnaire de demande vérifier une liste blanche de 5 adresses IP administrateur : la liste blanche ne grandit jamais avec le volume de demande. Faux positif.
Le même protocole s'applique à chaque MOAD : identifier le pilote externe, identifier la structure interne, demander si les deux mettent à l'échelle ensemble.
Score de Surge : Prioriser vos découvertes
Pas tous les défauts confirmés justifient un correctif immédiat. Un MOAD dans une bibliothèque avec 10 000 dépendants en aval a un score de surge plus élevé que le même MOAD dans un outil interne privé.
Score de surge = speedup × in-degree. Speedup : à quelle vitesse le correctif s'exécute-t-il à l'échelle de production typique ? In-degree : combien de packages ou de services en aval hériter du correctif automatiquement quand l'amont le fusionne ?
Un MOAD-0001 confirmé dans le solveur de dépendances d'Apache Maven, s'exécutant sur des graphes de 50 000 nœuds, avec 1 000+ plugins Maven en aval qui héritent automatiquement des changements : le score de surge est très élevé. Ce correctif appartient à l'avant de votre queue.
Un MOAD-0001 confirmé dans un outil CLI pour un seul utilisateur sans dépendants : score de surge proche de zéro. Vaut la peine d'être corrigé, mais pas urgent.
Nœuds workaholics vs gluttons. Un nœud avec betweenness élevée & speedup élevée est un workaholic : il gère un flux critique & vidangera les queues en aval quand il est débloqué. Ne le corrigez que après avoir confirmé la capacité en aval. Un nœud avec out-degree élevée & speedup faible est un glutton : il consomme tout ce qui lui est alimenté et ne ressent aucune douleur. Corriger un workaholic sans étager la capacité en aval crée MOAD-0005 (thundering herd) à l'échelle infrastructure.
Scan à Merge : Un Pipeline MOAD
Un défaut confirmé avec un score de surge élevé se déplace à travers un pipeline. Chaque étape produit un artefact. Aucune étape n'est optionnelle.
scan → candidate list (grep output, static analysis results)
ticket → defect description (MOAD number, location, complexity analysis)
patch → code change (data structure swap, primitive adoption)
test → unit test (O(1) proof: time the fix at N=100 and N=10,000)
UNDF → public disclosure post (undefect.com, public domain)
disclose → CVE or CWE reference if security-relevant
PR → upstream pull request with patch + test + UNDF link
merge → maintainer acceptance; fix propagates via version bump
Chaque artefact alimente l'étape suivante. Un correctif sans test ne peut pas être vérifié. Un test sans divulgation ne peut pas se propager à d'autres instances du même motif. Une divulgation sans PR en amont isole le correctif dans une branche.
Un post MOAD (UNDF) est l'étape que la plupart des ingénieurs omettent. Ils corrigent le défaut, envoient un PR, et se considèrent comme faits. Mais un correctif sans un post nommé signifie que chaque futur ingénieur qui rencontre le même motif doit redécouvrir à la fois le problème et le correctif indépendamment. Un post MOAD ferme la boucle de connaissance : il nomme le motif, montre la méthode de détection, & établit un lien vers le correctif. Les futurs chercheurs trouvent le correctif en cherchant le nom du motif.
Patcher la planète à l'échelle. Un seul correctif MOAD-0001 dans une bibliothèque largement utilisée se propage à chaque projet qui l'importe. Un post MOAD assure que les ingénieurs dans les projets qui ne mettront jamais à jour cette bibliothèque apprennent toujours le correctif. Les deux chemins s'exécutent en parallèle.
Écrire un Ticket de Défaut
Un bon ticket de défaut répond à cinq questions :
1. Où : fichier exact, classe, fonction, et plage de lignes
2. Quoi : le type de structure de données et l'opération contre elle
3. Pourquoi : l'analyse de complexité (O(N²) ou pire, avec N défini)
4. Impact : quelles entrées déclenchent le comportement du pire cas, et à quelle échelle
5. Correctif : la structure de données ou primitive à substituer
Un ticket qui répond à tous les cinq est autosuffisant : un mainteneur qui n'a jamais lu votre analyse peut reproduire votre découverte et vérifier votre correctif. Les tickets qui sautent (3) ou (4) exigent que le mainteneur répète votre analyse de complexité avant de pouvoir fusionner. Cette friction réduit la probabilité de fusion.
La crédibilité se combine. Un premier PR qui inclut un ticket clair, un correctif bien ciblé, & un test de référence est fusionné. Un deuxième PR du même auteur est examiné avec moins de friction. Un troisième PR est examiné par le mainteneur qui a fusionné les deux premiers. La réputation en open source est un ledger d'artefacts : chaque correctif accepté gagne la confiance pour le suivant.
Lire un Candidat Réel
Voici un vrai candidat MOAD-0001 en Python. Lisez-le et complétez le protocole de triage.
class DependencyResolver:
def resolve(self, package, resolved=None, seen=None):
if resolved is None:
resolved = []
if seen is None:
seen = []
if package in seen:
return
seen.append(package)
for dep in self.registry.get_dependencies(package):
self.resolve(dep, resolved, seen)
resolved.append(package)
return resolved
Questions de triage :
1. Quelle structure de données est `seen`?
2. Quelle opération s'exécute contre elle à la ligne 6 ?
3. `seen` grandit-il avec la taille d'entrée ?
4. La boucle qui pilote les appels récursifs grandit-elle aussi avec la taille d'entrée ?
5. Est-ce un MOAD-0001 confirmé ou un faux positif ?
Votre Correctif
Un défaut confirmé avec un score de surge élevé a besoin d'un correctif complet : le correctif de code, un test qui prouve l'amélioration, & un aperçu du post MOAD.
Le test doit être un test de performance, pas un test de justesse. Un test de justesse passe avant et après le correctif — c'est le point ; le résultat ne change pas. Un test de performance à deux tailles d'entrée prouve l'amélioration :
import time
def build_graph(n):
# n packages, each depending on the previous one
return {f'pkg{i}': [f'pkg{i-1}'] if i > 0 else [] for i in range(n)}
for n in [100, 1000, 5000]:
registry = build_graph(n)
resolver = DependencyResolver(registry)
start = time.perf_counter()
resolver.resolve(f'pkg{n-1}')
elapsed = time.perf_counter() - start
print(f'n={n}: {elapsed:.4f}s')
Avant le correctif, le temps écoulé grandit de façon quadratique avec n. Après le correctif, il grandit de manière linéaire. Imprimez les deux et incluez les nombres dans la description de votre PR.
Un aperçu du post MOAD couvre : le nom du motif, le substrat (solveur de dépendances Python), la méthode de détection (grep pour in seen où seen commence comme []), le correctif, & un lien vers votre PR. Le post va à undefect.com en domaine public. Les futurs ingénieurs cherchant 'Python list membership in loop slow' le trouveront.