Conception de bibliothèque

Concevoir une bibliothèque — un paquet publié ou un module interne dès qu'il a un second consommateur. L'implémentation reste modifiable pour toujours, tandis que l'API publique, l'ensemble des dépendances, les caractéristiques de performance et tout comportement observable se figent dès la première utilisation externe (loi de Hyrum). Cinq devoirs envers l'appelant : ne pas prendre le contrôle, n'ajouter aucun coût caché (abstraction à coût zéro comme barre), ne pas conserver d'état caché, ne pas cacher les échecs, ne pas bloquer la fuite. À utiliser lors de la conception ou de la révision d'une API publique, lors de l'extraction de code partagé dans un module, lorsqu'on demande de « rendre cela réutilisable », lorsqu'un changement compatible de signature modifie un comportement observable, et lorsqu'une application est structurée autour des types et du cycle de vie d'un framework.

Qu'est-ce que Conception de bibliothèque ?

Ce skill définit le contrat fondamental d'une bibliothèque (paquet ou module interne) en génie logiciel, en se basant sur la loi de Hyrum : tout comportement observable par un appelant devient une contrainte dès qu'un second consommateur existe. Il établit cinq devoirs stricts envers l'appelant : ne pas prendre le contrôle (séparer mécanisme et politique, ne pas lire l'environnement ou les arguments), n'ajouter aucun coût caché (abstraction à coût zéro dans les deux sens de Stroustrup, pas de threads ou connexions à l'import), ne pas conserver d'état caché (singletons, caches statiques interdits), ne pas cacher les échecs (modélisés dans les types, pas d'exceptions ni de valeurs booléennes vagues), et ne pas bloquer la fuite (deux niveaux d'API, observabilité injectée). Le skill s'applique à tout langage ou plateforme (Claude Code, Cursor, Codex) et est particulièrement pertinent lors de la conception d'API publiques, de l'extraction de code partagé, ou de la refonte d'une application autour d'un framework. Il relie également ces principes à l'architecture propre et au versionnement sémantique, en précisant le moment exact où un module « 0.y.z » doit passer à la version 1.0.0.

Compatible avec~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/ShortArrow/skills/tree/HEAD/skills/library-design

Installed? Explore more Développement et programmation skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Le Contrat de la Bibliothèque

Une application est jugée par ses utilisateurs ; une bibliothèque est jugée par les programmeurs, à un moment précis — lorsqu'ils veulent quelque chose que l'auteur n'avait pas imaginé. Une bonne bibliothèque n'est pas celle qui est pratique lorsqu'elle est utilisée comme prévu. C'est celle qui n'a pas besoin d'être jetée lorsqu'elle est utilisée autrement.

Chaque règle ci-dessous découle d'une asymétrie. À l'intérieur de la bibliothèque, vous pouvez tout changer, pour toujours. À l'extérieur, tout ce qu'un appelant peut observer est figé la première fois que quelqu'un en dépend — et avec suffisamment d'appelants, quelqu'un dépend de tout.

Ce qui se fige, et quand

La loi de Hyrum est la condition de fonctionnement, pas une curiosité : avec suffisamment d'utilisateurs, tout comportement observable devient porteur de charge — formulation d'exception, ordre d'énumération, sortie de log, timing, même un comportement que vous avez documenté comme indéfini. « Ce n'était jamais dans le contrat » ne répare pas l'appelant qui a livré contre lui.

Ainsi, la ligne de gel se situe au niveau de l'observabilité, pas du mot-clé public, et trois conséquences en découlent :

  • La surface commence petite. Un symbole, une fois public, est une promesse que vous pouvez élargir mais jamais retirer discrètement. « Public pour l'instant » est une dette contractée sans bénéfice ; exposez à la demande.
  • Les formats survivent au code. Un format de fichier, un protocole réseau, un schéma persistant survit à chaque réécriture de la bibliothèque qui l'entoure. Sérialiser la structure interne telle quelle soude la couche figée à la couche modifiable.
  • Un module interne franchit cette ligne à son second consommateur, pas à l'empaquetage. Dès que deux appelants existent, son API a la même économie de gel qu'un paquet publié — seule la zone d'impact est plus petite.

Le Versionnement Sémantique est l'orthographe publique de la même ligne, et commence par refuser de fonctionner sans une : « Un logiciel utilisant le Versionnement Sémantique DOIT déclarer une API publique » (SemVer 2.0.0, clause 1). Une fois déclarée, les incréments sont décidés plutôt que ressentis — une modification rétrocompatible de cette API DOIT augmenter MAJEUR (clause 8), une nouvelle fonctionnalité rétrocompatible DOIT augmenter MINEUR (clause 7), une correction rétrocompatible DOIT augmenter CORRECTIF (clause 6). La loi de Hyrum est ce qui rend la déclaration difficile : la spécification fige ce que vous avez déclaré, et les appelants figent ce qu'ils ont observé.

La spécification a également un nom pour le module qui n'a pas encore franchi la ligne. « La version majeure zéro (0.y.z) est destinée au développement initial. TOUT PEUT changer à tout moment » (clause 4). C'est l'étiquette honnête pour un module n'ayant qu'un consommateur, et le jour où il en gagne un second est le jour où la version 1.0.0 est due, pas le jour où il est empaqueté.

Ne prenez aucun contrôle

Séparez le mécanisme de la politique. Effectuer une requête HTTP est un mécanisme ; la relancer trois fois avec un backoff exponentiel est une politique, et une politique intégrée au cœur rétrécit la bibliothèque aux appelants qui la partagent. Fournissez des valeurs par défaut pratiques pour la journalisation, le threading, les relances, les délais d'attente, la mise en cache, la sérialisation — et rendez chacune d'elles remplaçable.

Le rôle de l'application est unique, et ce n'est pas le vôtre. Quitter le processus, écrire sur stdout, intercepter SIGINT, installer des gestionnaires d'exceptions de haut niveau, redimensionner le pool de threads : exactement un composant par processus peut faire ces choses, et une bibliothèque qui le fait combat le composant qui en a le droit. Lire les entrées du processus appartient au même rôle : les variables d'environnement, les fichiers de configuration et les arguments de ligne de commande sont lus à la racine de l'application, qui remet chaque valeur à la bibliothèque sous forme de paramètre ou d'objet d'options. L'appelant choisit alors d'où vient une valeur, et un test la transmet au lieu de configurer la machine.

La magie — auto-détection, auto-conversion, auto-reconnexion, auto-cache — n'est admissible que tant que l'appelant peut reconstruire pourquoi elle a fait ce qu'elle a fait. La commodité que l'utilisateur ne peut pas déboguer est un contrôle pris, pas un travail économisé.

N'ajoutez aucun coût caché

Le moment où les choses se produisent appartient à l'appelant. Un import qui démarre un thread, un constructeur qui ouvre une connexion, un initialiseur statique qui lit des variables d'environnement — chacun déplace un travail réel vers un moment que l'appelant n'a jamais choisi et ne peut pas déplacer.

Une dépendance fait partie de votre API. Son exécution, sa cadence de mise à jour, sa stabilité ABI, sa licence, ses propres effets secondaires d'initialisation sont tous transférés à chaque appelant ; vous choisissez en leur nom.

Les caractéristiques de performance sont un contrat. Transformer une recherche O(1) en O(n) — ou doubler les allocations et les copies — est un changement cassant portant une signature compatible, et les appelants le découvrent en production.

Là où les entrées arrivent plus vite qu'elles ne sortent — flux, événements, logs, files d'attente — un tampon interne illimité est une fuite mémoire différée. La contre-pression fait partie de la conception, pas un détail opérationnel.

La barre pour l'abstraction elle-même est le coût zéro, dans les deux clauses de Stroustrup. Ce que l'appelant n'utilise pas, il ne paie pas : l'étage de commodité ne doit pas taxer l'étage primitif avec ses allocations, son dispatch ou sa configuration. Et ce qu'il utilise, il ne pourrait pas raisonnablement l'écrire mieux à la main : une abstraction battue par du code fait main est contournée, et une bibliothèque contournée en est une jetée portant un numéro de version.

Ne conservez aucun état caché

Singletons, caches statiques, boucles d'événements globales, configuration à l'échelle du processus : chacun fonctionne tant que la vôtre est la seule bibliothèque dans le processus, et entre en collision dès qu'une seconde bibliothèque, un hôte de plugin, ou une suite de tests parallèle la partage. L'état vit dans une instance que l'appelant crée, possède, et peut créer deux fois.

Ne cachez aucun échec

La forme de l'API déclare ce qui peut arriver et qui est responsable. Erreurs, annulation, délai d'attente, succès partiel, absence — modélisés dans les types, pas acheminés via « cela n'arrive normalement jamais » et une exception. La même forme porte les réponses de propriété qu'un commentaire ne peut pas imposer : qui ferme le flux, si le tampon peut être réutilisé, sur quel thread le rappel arrive.

Conservez la cause. Effondrer un échec en false ou null supprime exactement l'information dont la couche au-dessus avait besoin pour décider ; le journaliser vous-même et continuer perd la même information et s'approprie la politique de journalisation de l'appelant.

La qualité se distingue sur le chemin anormal. Sur le chemin heureux, les bibliothèques sont interchangeables ; les délais d'attente, la reconnexion, la déconnexion en cours de flux, les lectures partielles, les données corrompues et les relances sont là où les différences vivent, donc concevez d'abord pour leur composition.

L'annulation se rétrofit mal — un jeton d'annulation traverse chaque signature qu'il touche. Les opérations d'E/S et de longue durée l'acceptent dès la première version.

Ne bloquez aucune échappatoire

Construisez deux étages. Une API de haut niveau qui sert le cas courant en une ligne, reposant sur des primitives publiques — ReadAsync() sur ReadAsync(Memory<byte>) — afin que l'appelant dont l'exigence tombe en dehors de l'étage supérieur descende au lieu de partir. Une bibliothèque avec seulement l'étage supérieur est jetée à la première exigence qu'elle n'avait pas anticipée.

L'observabilité est injectée, pas possédée : les journaliseurs, les métriques, les traces se branchent sur la pile de l'appelant. Un format de diagnostic que seule votre bibliothèque lit est une télémétrie prise en otage.

Un impôt, deux devises

Cette compétence et l'Architecture Propre sont le même refus en deux devises. L'abstraction à coût zéro refuse l'impôt sur les performances d'exécution ; la Règle de Dépendance refuse l'impôt sur la capacité de changement. Dans les deux cas, une abstraction est jugée par ce qu'elle fait sacrifier au côté dépendant.

Devoir iciSon nom architectural
Mécanisme séparé de la politiqueCas d'utilisation tenu à l'écart du framework et du pilote
Valeurs par défaut remplaçablesInversion de dépendance — le côté stable choisit l'implémentation
Primitives sous l'étage de commoditéDétails extérieurs interchangeables
Pas d'état globalDépendances explicites, donc une frontière peut être tracée
Une dépendance fait partie de votre APILa Règle de Dépendance

La forme saine pour l'appelant place votre bibliothèque en bas : un détail remplaçable sous leur politique et leur domaine. La forme inversée — l'application vivant à l'intérieur d'un framework, traînant son cycle de vie, ses types, son injection de dépendances, son modèle d'erreur, son modèle asynchrone — est la bibliothèque agissant comme l'application, écrite à l'échelle architecturale.

La forme générale : une bonne architecture minimise non pas le nombre de dépendances mais la liberté que chaque dépendance retire.

La phrase

Une abstraction dans une bibliothèque existe pour élargir les choix de l'appelant. Au moment où elle prend performance, contrôle ou information en échange, elle a cessé d'abstraire et a commencé à décider.

Quand ne pas appliquer

Un module avec un consommateur, un dépôt, une équipe n'a pas franchi la ligne de gel — il est en 0.y.z selon les termes de SemVer, qu'il porte ou non un numéro — et construire chaque jointure de politique remplaçable pour lui est une spéculation avec une facture de maintenance. Gardez les disciplines qui ne coûtent rien dès le premier jour — pas d'état global, échecs qui conservent leur cause, pas de travail avant que l'appelant ne le demande — et ajoutez les jointures lorsque le second consommateur arrive, ce qui est le moment où elles cessent d'être spéculatives.

Sources

  • Versionnement Sémantique 2.0.0, semver.org : clause 1 (l'API publique DOIT être déclarée), clauses 6–8 (incréments CORRECTIF, MINEUR, MAJEUR), clause 4 (0.y.z), et l'entrée FAQ sur la dépréciation (la documenter, puis publier une version mineure avec la dépréciation en place). Vérification du libellé par rapport à la page.
  • La loi de Hyrum est une observation, pas une norme, et est citée comme telle.

Individual skills in this repo

This repo contains 2 individual skills — each has its own dedicated page.

ShortArrow/interface-design

Deciding how a screen looks and behaves, triggered by the moments that hand the decision to the user unworked: about to ask which layout, which colour, which placement, how to draw a diagram, how wide a pane, how to split a view; about to call a visual choice a matter of taste; about to treat "it looks fine" as having checked it; about to ship a control sized by eye; about to read an automated accessibility pass, or a Lighthouse score of 100, as a clean bill of health. Most of these have an answer in a published standard, a measurable threshold, or one screenshot — WCAG 2.2 fixes contrast, target size, reflow and focus; a design system fixes spacing and components; axe-core finds about 57% of WCAG issues, and Lighthouse's accessibility score is that same engine weighted, so both leave the rest to a person. What survives is a genuine question, asked with the evidence attached. Use before asking any question about appearance, layout or interaction, and before calling

ShortArrow/test-design

Deriving test cases, triggered by the moments that produce weak ones: about to write a test list that is the happy path restated, about to derive tests by reading the implementation they check, about to test the bound the specification names and not the other one, about to enumerate the roles the specification lists and skip the caller it forgot, about to vary one condition at a time from the happy path, about to draw a state or decision table without tying each row to a test, about to write an expected result with "or" in it, about to skip the error paths as unlikely, about to leave a path unimplemented and untested, or about to grade a suite as adequate by reading it. A test derived from the implementation passes by construction; coverage counts classes, not tests. Use when writing a test plan or list, when a use case is the specification in hand, when adding tests to existing code, when reviewing a suite for gaps, and when a suite is all green and proves little.

Skills associés