poka-yoke

Par github · awesome-copilot

Concevez du code à l'épreuve des erreurs afin que toute mauvaise utilisation soit inexprimable, plutôt que de vous contenter de la déconseiller. À utiliser lors de la conception d'une interface, d'un schéma ou d'une machine à états quand l'objectif est de rendre les erreurs difficiles à commettre (« rendre les états invalides inexprimables », « que les appelants ne puissent pas se tirer une balle dans le pied », « API type-safe », « pit of success ») ; lors de l'audit de code existant pour détecter les pièges (« qu'est-ce qui pourrait nous mordre ici », « qu'est-ce qui est facile à mal utiliser », « appliquer le poka-yoke à ce repo », « passer ce diff en revue pour repérer les façons de se tromper ») ; ou lorsqu'un bug a récidivé et que le correctif doit fermer la classe plutôt que le cas particulier (« s'assurer que ça n'arrive plus jamais », « c'est la troisième fois »). Particulièrement pertinent pour la monnaie, l'authentification, les permissions, la suppression, les migrations et les pipelines où les échecs sont silencieux. Classe chaque constat selon ce qui se passe quand l'erreur survient et comment le dispositif la détecte — c'est ce qui empêche l'approche de se réduire à une simple revue de code générique.

npx skills add https://github.com/github/awesome-copilot --skill poka-yoke

Poka-Yoke : Rendre l'erreur inexprimable

Les gens feront toujours des erreurs. Ce n'est pas le problème qui mérite d'être résolu. Le problème est de laisser une erreur devenir un défaut.

Shigeo Shingo, ingénieur industriel japonais, a résolu cela sur une chaîne d'assemblage de commutateurs en 1961. Les ouvriers oubliaient constamment un ressort. La solution n'a pas été un rappel : le travail a été scindé pour que l'ouvrier place d'abord les deux ressorts dans un récipient, puis les installe à partir du récipient. Un ressort restant était l'erreur qui s'annonçait d'elle-même, avant que l'unité puisse avancer.

Le récipient est un dispositif. « Veuillez vous souvenir du ressort » ne l'est pas.

La ligne qui fait la majorité du travail

Un commentaire, une docstring, une page wiki, une liste de vérification d'examen ou une ligne dans un fichier d'instructions disant « ne fais pas X » n'est pas un poka-yoke. C'est de la formation, et la formation se dégrade. Un dispositif ne se dégrade pas. Si votre solution repose sur quelqu'un qui doit se souvenir de quelque chose, continuez.

Cela s'applique aussi à vos propres instructions. Une règle écrite dans un fichier de configuration entre en concurrence pour l'attention avec chaque autre règle et en perd un peu plus à chaque augmentation du fichier. Un contrôle qui fait échouer la construction ne l'est pas.

Ce que cela change dans la sortie

Étant donné une conception, les modèles énumèrent facilement ce qu'il faut corriger. Ils déclarent rarement ce que la correction rend impossible, et c'est la différence entre un conseil auquel vous adhérez et une contrainte sur laquelle vous pouvez compter. Cette habitude est l'essentiel de ce que cette compétence est destinée à couvrir.

L'autre moitié consiste à refuser d'accepter un non-dispositif comme solution. « Ajouter une validation », « soyez prudent avec cette fonction », « documentez l'invariant » sont tous l'échelon zéro. Chacun cache un vrai dispositif derrière lui, et nommer ce dispositif est le travail.

Axe 1 : ce qui se passe quand l'erreur se produit

Classez chaque constat sur cette échelle, et dites à quel échelon le code actuel se situe et à quel échelon votre solution arrive.

Échelon Nom Signification
1 Contrôle La mauvaise action ne peut pas être effectuée. Erreur de type, contrainte de base de données, permission manquante.
2 Avertissement C'est possible, mais cela s'annonce au moment où cela se produit. Un linter, une assertion à l'exécution, une confirmation qu'on ne peut pas ignorer.
3 Détection Cela se produit, et vous l'découvrez après. Tests, journalisation, surveillance, examen du code.
0 échelon zéro Dire aux gens de faire attention. Docs, commentaires, « veuillez vous souvenir de ».

La détection n'est pas un échec ; parfois c'est tout ce qui est disponible. Mais un plan qui s'arrête à la détection devrait le dire, plutôt que de le présenter comme une prévention.

Axe 2 : comment le dispositif remarque

Les trois lentilles d'inspection de Shingo. C'est une liste de contrôle pour identifier les risques, pas de la décoration :

  • Contact — la mauvaise chose peut-elle physiquement s'adapter ? Deux paramètres adjacents du même type peuvent être échangés silencieusement. Une string qui devrait être l'une de quatre valeurs. L'argent en tant que float.
  • Valeur fixe — l'ensemble est-il complet ? Un commutateur sans vérification d'exhaustivité. Une config où une clé manquante signifie silencieusement « off ». Une énumération gérée dans trois de cinq endroits.
  • Étape de mouvement — l'ordre est-il correct, et chaque étape s'est-elle déroulée ? Une écriture en deux phases sans transaction. Une nouvelle tentative sans clé d'idempotence. Une ressource acquise sur un chemin et libérée sur un autre.

Inspecter à la source

L'endroit le moins cher pour détecter une erreur est celui où elle est faite, pas où elle se manifeste. Une validation qui s'exécute trois couches sous l'entrée a déjà laissé la mauvaise valeur se propager, et la pile d'appels pointera vers le mauvais module. Poussez le contrôle à la limite que la valeur franchit.

Concevoir quelque chose de nouveau

La prévention des erreurs est la moins chère avant que le code n'ait d'appelants. Une fois qu'il en a, chaque dispositif est une migration ; avant, un dispositif est gratuit.

Travaillez à partir du site d'appel. Une signature qui se lit bien isolément se lit souvent terriblement là où elle est utilisée :

# l'erreur est exprimable : rien n'empêche de rembourser une commande qui n'a jamais été payée
def refund(order: dict) -> Refund:
    return payments.refund(order["payment_id"])

# l'erreur n'est plus exprimable
def refund(order: PaidOrder) -> Refund: ...

Les mouvements, à peu près dans l'ordre de leur fréquence d'application :

Rendre les états invalides non représentables. Un ensemble de champs optionnels où seules certaines combinaisons sont légales devient une union discriminée où les combinaisons illégales ne peuvent pas être construites.

Parser, ne pas valider. Convertir une entrée non structurée en un type qui porte la preuve à la limite, une seule fois, plutôt que de revérifier la même chaîne en neuf endroits.

Distinguer les concepts qui partagent une primitive. transfer(from: str, to: str) accepte ses arguments transposés. Des types distincts pour les deux concepts, ou des paramètres keyword-only, rendent la transposition une erreur de compilation.

Encoder l'ordre. Quand les appels doivent se produire en séquence, laissez chaque étape retourner le type que l'étape suivante nécessite, ainsi l'ordre incorrect n'est pas accepté à la compilation.

Rendre le chemin destructif plus étroit que le chemin sûr. Un argument obligatoire et non-defaulting pour la portée d'une suppression. Un défaut qui signifie « rien » plutôt que « tout ».

Terminez en nommant ce que la conception rend maintenant impossible, et, tout aussi important, ce que vous avez délibérément laissé possible et pourquoi. Une conception dont les limites ne sont pas énoncées sera considérée comme fiable au-delà de ces limites.

Auditer le code qui existe déjà

Vous ne recherchez pas des bugs. Un bug est une erreur qui a déjà eu lieu. Vous recherchez des erreurs qui sont disponibles : des endroits où faire la mauvaise chose est facile, silencieux, et semble correct.

Exécutez d'abord le scanner fourni pour les formes détectables textuellement, puis lisez pour celles qu'aucun scanner ne peut voir :

python3 scripts/detect_hazards.py --paths .          # arborescence entière
python3 scripts/detect_hazards.py --staged           # pré-commit
python3 scripts/detect_hazards.py --diff --json      # CI, sort avec code non-zéro en cas de constat
python3 scripts/detect_hazards.py --severity high

Aucune dépendance, il s'exécute donc en CI et dans un hook pre-commit sans étape d'installation. Il signale ce qu'il a analysé : une analyse de zéro fichier sort avec code non-zéro plutôt que de signaler un certificat de propreté, car une approbation obtenue par erreur de frappe est pire qu'aucune vérification.

Classez les constats par rayon de blast multiplié par la facilité de l'erreur. Une valeur non vérifiée atteignant une écriture, une suppression, un paiement ou une décision d'authentification surclasse une qui ne peut produire qu'un crash propre. Pour chaque constat, déclarez : où il est, quelle est l'erreur, quelle est la conséquence, quel dispositif existe aujourd'hui, quel dispositif le fermerait, et quel échelon cela atteint.

references/hazard-catalog.md est la taxonomie des formes avec leurs identifiants et dispositifs. Les patterns spécifiques à chaque langage sont dans references/lang-python.md, references/lang-typescript.md et references/lang-rust-go.md.

Après un incident

Séparez trois choses qui sont confondues, car la correction appartient à la troisième :

  • Défaut — ce que l'utilisateur a constaté.
  • Erreur — l'action spécifique erronée qu'une personne a prise.
  • Risque — la propriété du système qui a rendu cette erreur disponible.

Corriger l'erreur corrige un cas. Corriger le risque corrige la classe. Ensuite balayez : la même forme existe presque certainement ailleurs, et trouver la deuxième et la troisième instance est la différence entre un patch et une leçon.

Attribuez la cause au système plutôt qu'à une personne. Pas principalement pour la gentillesse : « ils ont fait une erreur » est une explication qui semble complète mais ne prédit rien et n'empêche rien, et elle termine l'investigation prématurément.

À quoi ressemble une bonne sortie

  • Ancrée aux lignes. orders.py:142, apply_discount est examinable ; « la logique de réduction » ne l'est pas.
  • Classée, avec le classement visible, donc un lecteur qui s'arrête à mi-chemin a quand même couvert les constats qui importent.
  • Un dispositif nommé par constat, pas « ajouter une validation ».
  • L'échelon énoncé, avant et après.
  • Les limites énoncées. Ce que la correction ne couvre pas est la partie que les lecteurs ont le plus besoin de connaître et ne reçoivent le plus souvent pas.
  • Dimensionnée honnêtement. Trois constats qui importent valent mieux que onze gonflés pour un chiffre rond.

Ce à éviter

Accepter l'échelon zéro comme une solution. Si la proposition est un commentaire, une doc ou une convention, le travail n'est pas terminé.

Confondre les dispositifs que personne ne peut contourner avec les dispositifs que personne ne contourne. Un hook pre-commit peut être contourné avec --no-verify ; il a besoin de CI derrière pour être une vraie barrière. Dites lequel vous proposez.

Suradapter à un incident. Une machinerie qui prévient un échec spécifique doit elle-même être comprise et maintenue. Demandez-vous si la forme est assez courante pour la justifier.

Traiter la surveillance comme une prévention. La détection réduit le coût d'une défaillance ; elle ne réduit pas la probabilité. Les deux méritent d'être présents, et les confondre signifie que la probabilité n'est jamais traitée.

Preuves et leurs limites

Cette méthode a été comparée à 591 exécutions en aveugle à travers six familles de modèles, évaluées par rapport à des assertions écrites avant les exécutions par un évaluateur qui n'a jamais vu quelle configuration a produit une réponse. Le comportement qu'elle change le plus fiablement est d'énoncer ce qu'une conception interdit : 45 % des réponses l'ont fait sans être invités, 80 % avec la méthode appliquée, sur 132 verdicts évalués.

Cette moyenne cache où l'effet réside. Quand on demande carrément de concevoir une interface, les modèles le font déjà 77 % du temps ; les compétences en ajoutent onze points. Les grands gains se situent dans les tâches où personne n'a demandé d'examen de conception — écrire un endpoint passe de 14 % à 79 %, livrer une feature agent de 33 % à 83 %, construire un formulaire de 29 % à 64 %.

Énoncé honnêtement, car les limites importent : chaque exécution était le premier tour d'une session nouvelle, cela mesure donc le plafond plutôt que ce qui survit à une longue session de travail. La comparaison était contre aucune méthodologie, pas contre une autre, elle n'établit donc pas que cette méthode est ce qui a produit le gain. Et la méthode a un coût mesurable : les réponses se sont un peu dégradées pour détecter le défaut spécifique déjà présent sur la page, tout en s'améliorant pour changer la forme qui l'a permis. Si vous voulez que le bug devant vous soit trouvé, utilisez un examinateur. Si vous voulez que cette classe de bug cesse d'être exprimable, utilisez ceci.

Les exécutions brutes, le harnais et les listes de contrôle d'assertion sont à https://github.com/rainmanjam/poka-yoke.

Skills similaires