ConstructiCat Logo
CodeBust.
Browse section ▾

Code limité au chemin heureux.

Les assistants IA tendent à générer du code qui ne gère que le cas réussi et bien formé — en sautant la validation des entrées, la gestion des erreurs, les vérifications de null/vide et les cas limites — si bien que le code marche dans la démo et casse en production.

##Signs and Symptoms

Un relecteur repère un code limité au chemin heureux lorsque chaque ligne suppose que la précédente a réussi : les appels réseau ne sont pas vérifiés pour les statuts non-2xx, JSON.parse/await res.json() n'est jamais encapsulé, les champs nullables sont déréférencés directement, les tableaux sont indexés sans vérifier leur longueur, et l'entrée externe arrive directement dans la logique sans validation. Il y a généralement exactement un seul chemin de retour et aucun throw, aucune clause de garde et aucun catch (ou un catch vide ou réduit à console.log).

// Généré par IA : seul le scénario de succès existe
async function getUserCity(userId) {
  const res = await fetch(`/api/users/${userId}`);
  const user = await res.json();
  return user.address.city.toUpperCase();
}

Modes de défaillance silencieusement absents : res.ok n'est jamais vérifié (un 404/500 renvoie un corps d'erreur que .json() peut rejeter), user pourrait être {}, address pourrait être null, city pourrait être undefinedCannot read properties of undefined. La fonction « marche » contre un stub heureux et lève une exception contre la réalité.

Signes révélateurs dans un diff :

  • Une nouvelle fonction async sans try/catch et sans .catch() sur la promesse.
  • Des chaînes de propriétés directes (a.b.c.d) sur des données qui ont franchi une frontière de confiance/IO.
  • Des arr[0] non vérifiés, des find(...)!, des casts as ou des assertions non-null (!) tenant lieu de véritable traitement.
  • Un // TODO: handle errors ou un catch (e) {} nu laissé en guise de placeholder.
  • La description de la PR dit « gère X » mais seule la branche où X réussit est implémentée.

##Reasons for the Problem

Pourquoi les modèles le produisent

  • La probabilité du token suivant favorise le flux canonique. La suite la plus probable après const user = await res.json() est return user.something — pas une vérification de statut. La gestion d'erreur est du code répétitif à forte variance qui diffère d'une base de code à l'autre, elle est donc statistiquement « surprenante » et se fait abandonner.
  • Les données d'entraînement sont biaisées vers le chemin heureux. Les tutoriels, extraits de README, billets de blog et réponses acceptées sur Stack Overflow retirent la validation et la gestion d'erreur par souci de concision (« gestion d'erreur omise pour plus de clarté »). Le modèle a appris à partir de textes qui ont délibérément supprimé le code même que vous voulez.
  • Complaisance / récompense pour l'apparence propre. Les assistants ajustés par RLHF sont récompensés pour des réponses concises et directement pertinentes. Le code défensif ressemble à du bruit ; le modèle optimise donc pour l'extrait soigné qui semble répondre au prompt.
  • Aucun contexte du dépôt. Le modèle ignore que vous avez un type AppError, un wrapper Result<T>, un schéma zod ou une convention de journalisation, il ne peut donc pas les réutiliser — et opte pour « rien » plutôt que de deviner vos conventions.
  • Tendances comportementales documentées. L'analyse par OX Security de plus de 300 dépôts cite l'Évitement des refactorings et la Fixation sur le manuel parmi ses principaux anti-patterns d'IA — le modèle produit du code fonctionnel pour le prompt immédiat et ne le durcit jamais. arXiv 2509.20491 catalogue les odeurs propres à l'IA autour des échecs silencieux ; arXiv 2510.03029 constate des odeurs d'implémentation élevées comme le Bloc catch vide dans la sortie des LLM.

Pourquoi c'est nuisible

  • Correction : plante sur null, sur les collections vides, les timeouts et les réponses non 200 — précisément les entrées qui n'apparaissent pas dans un test manuel rapide.
  • Sécurité : le chemin heureux fait implicitement confiance à ses entrées. La validation omise aux frontières est la porte d'entrée des bugs d'injection, de traversée de répertoire et de pollution de prototype. Le rapport d'OX présente cela comme du code « non sécurisé par bêtise », livré vite sans discernement.
  • Charge de revue et débogage : dans l'enquête Stack Overflow Developer Survey 2025, 66 % des développeurs citent « les solutions IA presque justes, mais pas tout à fait » comme leur principale frustration et 45 % disent que déboguer du code généré par IA prend plus de temps. Le code du chemin heureux est l'archétype du « presque juste » — il se lit bien et échoue à l'exécution.
  • Accumulation de dette technique : comme le modèle ne refactorise ni ne réutilise les utilitaires d'erreur existants, chaque fonction limitée au chemin heureux est un nouveau bloc à usage unique. Les données 2025 de GitClear montrent le schéma plus large — les lignes copiées-collées sont passées de 8,3 % (2021) à 12,3 % (2024), les blocs dupliqués de 5 lignes et plus ont été multipliés par ~8 en 2024, tandis que le refactoring (lignes déplacées) chutait d'environ 25 % à moins de 10 %. La gestion d'erreur manquante est greffée plus tard, dupliquée à chaque site d'appel, jamais centralisée.

##Treatment

Tactiques de revue et de prompting

  • Faites énumérer les modes de défaillance d'abord par le modèle. Prompt : "Avant d'écrire du code, liste les modes de défaillance de cette fonction (mauvaise entrée, erreur réseau, statut non 200, réponse vide/malformée, champs manquants, concurrence). Puis implémente le traitement de chacun." Forcer l'étape d'énumération contre la tendance du token suivant à les ignorer.
  • Pointez les conventions à réutiliser. "Utilise notre type AppError/Result existant de lib/errors.ts et le logger de lib/log.ts ; valide la réponse avec le schéma zod de schemas/user.ts." Cela convertit « aucun traitement » en réutilisation plutôt qu'en un bloc sur mesure (évite le Code dupliqué).
  • Exigez l'exécution des contrôles. "Lance eslint et tsc --noEmit et corrige chaque avertissement, y compris @typescript-eslint/no-floating-promises." Le contrôle de types avec strictNullChecks transforme les déréférencements null silencieux en erreurs de compilation que le modèle doit traiter.
  • Réclamez les tests négatifs. Demandez des tests unitaires couvrant les entrées vides/null/erronées, pas seulement le cas heureux — l'absence de ces tests est en soi l'odeur.

Le refactoring

Ajoutez la validation aux frontières et des clauses de garde, et centralisez le traitement. Manœuvres nommées : Introduire une clause de garde (throw précoce sur un état invalide), Introduire une assertion / validation aux frontières (parser-plutôt-que-valider aux limites IO) et Introduire un cas particulier / objet Null (?? "UNKNOWN" au lieu de planter).

// après : les modes de défaillance sont de première classe
async function getUserCity(userId: string): Promise<string> {
  if (!userId) throw new InvalidArgumentError("userId required");   // clause de garde

  const res = await fetch(`/api/users/${encodeURIComponent(userId)}`);
  if (!res.ok) throw new ApiError(`user fetch failed: ${res.status}`);

  const user = UserSchema.parse(await res.json());                  // validation à la frontière
  return user.address?.city?.toUpperCase() ?? "UNKNOWN";           // cas particulier pour le trou
}

Si le même schéma try/valider/journaliser commence à se répéter d'un site d'appel à l'autre, appliquez Extraire une fonction dans un helper partagé fetchJson<T>(url, schema) afin que la gestion d'erreur vive à un seul endroit plutôt que d'être copiée-collée (le piège de duplication de GitClear).

##Detected by

  • typescript-eslint @typescript-eslint/no-floating-promisesSignale les promesses dont le chemin de rejet n'est jamais géré (pas d'await/catch) — un symptôme courant du chemin heureux pour les appels asynchrones.
  • ESLint no-emptySignale les blocs vides, y compris les blocs catch vides — le 'traitement d'erreur' factice que le modèle laisse derrière lui.
  • SonarSource (JS/TS) javascript:S2486Les exceptions ne doivent pas être ignorées — signale les erreurs capturées mais avalées, la cousine dégénérée de l'absence totale de traitement.