CommunityCodierung & Entwicklunggithub.com

ShortArrow/Bibliotheksentwurf

Entwerfen einer Bibliothek – eines veröffentlichten Pakets oder eines In-App-Moduls ab dem Moment, in dem es einen zweiten Verbraucher gibt. Die Implementierung bleibt für immer bearbeitbar, während die öffentliche API, die Abhängigkeiten, die Leistungsmerkmale und jedes beobachtbare Verhalten ab der ersten externen Nutzung einfrieren (Hyrums Gesetz). Fünf Pflichten gegenüber dem Aufrufer: Keine Kontrolle übernehmen, keine versteckten Kosten hinzufügen (Zero-Cost-Abstraktion als Maßstab), keinen versteckten Zustand halten, keinen Fehler verbergen, keinen Ausweg blockieren. Anwenden beim Entwerfen oder Überprüfen einer öffentlichen API, beim Extrahieren von gemeinsamem Code in ein Modul, wenn man aufgefordert wird, „das wiederverwendbar zu machen“, wenn eine signaturkompatible Änderung ein beobachtbares Verhalten verändert und wenn eine Anwendung um die Typen und den Lebenszyklus eines Frameworks herum geformt wird.

Was ist Bibliotheksentwurf?

Diese Skill-Anleitung definiert den Vertrag einer Bibliothek: Sobald ein Modul einen zweiten Konsumenten hat, frieren alle beobachtbaren Verhaltensweisen ein (Hyrums Gesetz). Die fünf Kernpflichten gegenüber dem Aufrufer lauten: Keine Kontrolle übernehmen (Trennung von Mechanismus und Richtlinie), keine versteckten Kosten hinzufügen (Zero-Cost-Abstraktion), keinen versteckten Zustand halten (globale Variablen vermeiden), keinen Fehler verbergen (Fehler durch Typen modellieren) und keinen Ausweg blockieren (zweistöckige API mit öffentlichen Primitiven). Die Anleitung betont Semantic Versioning und die Notwendigkeit, Abhängigkeiten als Teil der API zu betrachten. Sie unterstützt jede Programmiersprache und jedes Framework und gilt für veröffentlichte Pakete sowie interne Module ab dem zweiten Konsumenten. Ausnahmen werden für Module mit nur einem Konsumenten oder Team gemacht.

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

Installed? Explore more Codierung & Entwicklung skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

Der Bibliotheksvertrag

Eine Anwendung wird von ihren Benutzern beurteilt; eine Bibliothek von Programmierern, und zwar in dem bestimmten Moment – wenn sie etwas wollen, das der Autor nicht vorhergesehen hat. Eine gute Bibliothek ist nicht die, die bequem ist, wenn sie wie beabsichtigt verwendet wird. Es ist die, die nicht weggeworfen werden muss, wenn sie anders verwendet wird.

Jede der folgenden Regeln folgt aus einer Asymmetrie. Innerhalb der Bibliothek darfst du alles ändern, für immer. Außerhalb ist alles, was ein Aufrufer beobachten kann, eingefroren, sobald jemand zum ersten Mal davon abhängt – und bei genügend Aufrufern hängt jemand von allem ab.

Was einfriert und wann

Hyrums Gesetz ist die Betriebsbedingung, keine Kuriosität: Bei genügend Nutzern wird jedes beobachtbare Verhalten tragend – Ausnahmeformulierungen, Aufzählungsreihenfolge, Protokollausgabe, Zeitverhalten, sogar Verhalten, das Sie als undefiniert dokumentiert haben. „Das war nie im Vertrag“ repariert den Aufrufer nicht, der dagegen ausgeliefert hat.

Die Einfrierlinie liegt also bei der Beobachtbarkeit, nicht beim public-Schlüsselwort, und drei Konsequenzen ergeben sich:

  • Die Oberfläche beginnt klein. Ein Symbol, einmal öffentlich, ist ein Versprechen, das man erweitern, aber nie leise zurückziehen kann. „Öffentlich für jetzt“ ist Schulden ohne Nutzen; nur bei Bedarf freigeben.
  • Formate überleben Code. Ein Dateiformat, ein Netzwerkprotokoll, ein persistiertes Schema überlebt jede Neuschreibung der Bibliothek darum herum. Die interne Struktur so zu serialisieren, schweißt die eingefrorene Schicht an die bearbeitbare.
  • Ein In-App-Modul überschreitet diese Grenze bei seinem zweiten Konsumenten, nicht beim Verpacken. Sobald zwei Aufrufer existieren, hat seine API die gleiche Einfrierökonomie wie ein veröffentlichtes Paket – nur der Sprengradius ist kleiner.

Semantische Versionierung ist die öffentliche Schreibweise derselben Grenze, und sie beginnt damit, sich zu weigern, ohne eine zu arbeiten: „Software, die semantische Versionierung verwendet, MUSS eine öffentliche API deklarieren“ (SemVer 2.0.0, Klausel 1). Sobald deklariert, werden die Erhöhungen entschieden und nicht gefühlt – eine abwärtsinkompatible Änderung an dieser API MUSS MAJOR erhöhen (Klausel 8), neue abwärtskompatible Funktionalität MUSS MINOR erhöhen (Klausel 7), eine abwärtskompatible Fehlerbehebung MUSS PATCH erhöhen (Klausel 6). Hyrums Gesetz ist das, was die Deklaration schwierig macht: Die Spezifikation friert ein, was du deklariert hast, und die Aufrufer frieren ein, was sie beobachtet haben.

Die Spezifikation hat auch einen Namen für das Modul, das die Grenze noch nicht überschritten hat. „Hauptversion null (0.y.z) ist für die anfängliche Entwicklung. ALLES kann sich jederzeit ändern“ (Klausel 4). Das ist das ehrliche Etikett für ein Modul mit einem Konsumenten, und der Tag, an dem es einen zweiten bekommt, ist der Tag, an dem 1.0.0 fällig ist, nicht der Tag, an dem es verpackt wird.

Keine Kontrolle übernehmen

Trenne Mechanismus von Richtlinie. Eine HTTP-Anfrage durchzuführen ist Mechanismus; sie dreimal mit exponentiellem Backoff zu wiederholen ist Richtlinie, und Richtlinie, die in den Kern eingebrannt ist, schränkt die Bibliothek auf Aufrufer ein, die sie teilen. Liefere bequeme Standardwerte für Protokollierung, Threading, Wiederholungen, Timeouts, Caching, Serialisierung – und mache jeden davon ersetzbar.

Die Anwendungsrolle ist einzigartig, und es ist nicht deine. Den Prozess zu beenden, auf stdout zu schreiben, SIGINT abzufangen, top-level Ausnahmehandler zu installieren, den Thread-Pool zu skalieren: genau eine Komponente pro Prozess darf dies tun, und eine Bibliothek, die es tut, bekämpft die dazu berechtigte Komponente. Das Lesen der Prozesseingaben gehört zu derselben Rolle: Umgebungsvariablen, Konfigurationsdateien und Befehlszeilenargumente werden an der Anwendungswurzel gelesen, die der Bibliothek jeden Wert als Parameter oder Optionsobjekt übergibt. Der Aufrufer wählt dann, woher ein Wert kommt, und ein Test übergibt ihn, anstatt die Maschine einzurichten.

Magie – automatische Erkennung, automatische Konvertierung, automatische Wiederverbindung, Auto-Cache – ist nur zulässig, solange der Aufrufer rekonstruieren kann, warum es getan hat, was es getan hat. Bequemlichkeit, die der Benutzer nicht debuggen kann, ist übernommene Kontrolle, keine gesparte Arbeit.

Keine versteckten Kosten hinzufügen

Wann Dinge passieren, gehört dem Aufrufer. Ein Import, der einen Thread startet, ein Konstruktor, der eine Verbindung öffnet, ein statischer Initialisierer, der Umgebungsvariablen liest – jeder verschiebt echte Arbeit zu einem Zeitpunkt, den der Aufrufer nie gewählt hat und nicht zurückverschieben kann.

Eine Abhängigkeit ist Teil deiner API. Ihre Laufzeit, ihr Update-Rhythmus, ihre ABI-Stabilität, ihre Lizenz, ihre eigenen Initialisierungsnebenwirkungen übertragen sich auf jeden Aufrufer; du wählst in ihrem Namen.

Leistungsmerkmale sind Vertrag. Eine O(1)-Suche in O(n) zu verwandeln – oder Allokationen und Kopien zu verdoppeln – ist eine bahnbrechende Änderung, die eine kompatible Signatur trägt, und Aufrufer entdecken sie in der Produktion.

Wo Eingaben schneller ankommen, als sie abgehen – Streams, Ereignisse, Protokolle, Warteschlangen – ist ein unbegrenzter interner Puffer ein verzögerter Speicherverlust. Gegendruck ist Teil des Designs, kein operationelles Detail.

Der Maßstab für die Abstraktion selbst ist Nullkosten, in beiden Klauseln von Stroustrup. Was der Aufrufer nicht nutzt, bezahlt er nicht: die Komfortebene darf die primitive Ebene nicht mit ihren Allokationen, ihrem Dispatch oder ihrer Einrichtung besteuern. Und was sie nutzen, könnten sie vernünftigerweise nicht besser von Hand schreiben: eine Abstraktion, die von handgeschriebenem Code geschlagen wird, wird umgangen, und eine umgangene Bibliothek ist eine weggeworfene mit einer Versionsnummer.

Keinen versteckten Zustand halten

Singletons, statische Caches, globale Ereignisschleifen, prozessweite Konfiguration: jede funktioniert, solange deine die einzige Bibliothek im Prozess ist, und kollidiert, sobald eine zweite Bibliothek, ein Plugin-Host, oder eine parallele Testsuite sie teilt. Zustand lebt in einer Instanz, die der Aufrufer erstellt, besitzt, und zweimal erstellen kann.

Keinen Fehler verbergen

Die API-Form erklärt, was passieren kann und wer verantwortlich ist. Fehler, Abbruch, Timeout, Teilerfolg, Abwesenheit – in den Typen modelliert, nicht über „das passiert normalerweise nie“ und eine Ausnahme geleitet. Dieselbe Form trägt die Besitzantworten, die ein Kommentar nicht erzwingen kann: wer schließt den Stream, ob der Puffer wiederverwendet werden darf, auf welchem Thread der Callback ankommt.

Behalte die Ursache. Das Zusammenfallen eines Fehlers in false oder null entfernt genau die Informationen, die die darüberliegende Schicht benötigte, um zu entscheiden; selbst zu protokollieren und fortzufahren, verliert dieselben Informationen und nimmt die Protokollierungsrichtlinie des Aufrufers mit.

Qualität trennt sich auf dem abnormalen Pfad. Auf dem Happy Path sind Bibliotheken austauschbar; Timeout, Wiederverbindung, Unterbrechung während des Streams, teilweises Lesen, beschädigte Daten und Wiederholung sind die Unterschiede, also entwerfe zuerst für ihre Komposition.

Abbruch lässt sich schlecht nachrüsten – ein Abbruchtoken fädelt sich durch jede Signatur, die es berührt. I/O und langlaufende Operationen akzeptieren es ab der ersten Version.

Keinen Ausweg blockieren

Baue zwei Stockwerke. Eine High-Level-API, die den gemeinsamen Fall in einer Zeile bedient, basierend auf öffentlichen Primitiven – ReadAsync() über ReadAsync(Memory<byte>) – damit der Aufrufer, dessen Anforderung außerhalb des oberen Stockwerks liegt, hinabsteigt, anstatt zu gehen. Eine Bibliothek mit nur dem oberen Stockwerk wird bei der ersten Anforderung weggeworfen, die sie nicht vorhergesehen hat.

Beobachtbarkeit wird injiziert, nicht besessen: Logger, Metriken, Traces stecken in den Stack des Aufrufers. Ein Diagnoseformat, das nur deine Bibliothek liest, ist als Geisel genommene Telemetrie.

Eine Steuer, zwei Währungen

Diese Skill und Clean Architecture sind dieselbe Ablehnung in zwei Währungen. Zero-Cost-Abstraktion verweigert die Steuer auf die Laufzeitleistung; die Dependency Rule verweigert die Steuer auf die Änderbarkeit. In beiden wird eine Abstraktion danach beurteilt, was sie die abhängige Seite aufgeben lässt.

Pflicht hierIhr architektonischer Name
Mechanismus getrennt von RichtlinieAnwendungsfall getrennt von Framework und Treiber
Standardwerte ersetzbarDependency Inversion – die stabile Seite wählt die Implementierung
Primitiven unter der KomfortebeneÄußere Details austauschbar
Kein globaler ZustandAbhängigkeiten explizit, sodass eine Grenze gezogen werden kann
Eine Abhängigkeit ist Teil deiner APIDie Dependency Rule

Die gesunde Form des Aufrufers platziert deine Bibliothek unten: ein ersetzbares Detail unter ihrer Richtlinie und Domäne. Die umgekehrte Form – die Anwendung lebt innerhalb eines Frameworks, schleppt seinen Lebenszyklus, seine Typen, sein DI, sein Fehlermodell, sein asynchrones Modell – ist die Bibliothek, die als Anwendung fungiert, geschrieben im Architekturmaßstab.

Die allgemeine Form: Gute Architektur minimiert nicht die Anzahl der Abhängigkeiten, sondern die Freiheit, die jede Abhängigkeit wegnimmt.

Der Satz

Eine Abstraktion in einer Bibliothek existiert, um die Wahlmöglichkeiten des Aufrufers zu erweitern. In dem Moment, in dem sie Leistung, Kontrolle oder Informationen im Austausch nimmt, hat sie aufgehört zu abstrahieren und begonnen zu entscheiden.

Wann nicht anwenden

Ein Modul mit einem Konsumenten, einem Repository, einem Team hat die Einfrierlinie nicht überschritten – es ist bei 0.y.z in SemVer-Begriffen, ob es nun eine Nummer trägt oder nicht – und für dieses Modul jeden ersetzbaren Richtlinien-Seam zu bauen, ist Spekulation mit einem Wartungsaufwand. Behalte die Disziplinen, die von Tag eins an nichts kosten – kein globaler Zustand, Fehler, die ihre Ursache behalten, keine Arbeit, bevor der Aufrufer fragt – und füge die Seams hinzu, wenn der zweite Konsument eintrifft, was der Moment ist, in dem sie aufhören, spekulativ zu sein.

Quellen

  • Semantic Versioning 2.0.0, semver.org: Klausel 1 (öffentliche API MUSS deklariert werden), Klauseln 6–8 (PATCH, MINOR, MAJOR-Erhöhungen), Klausel 4 (0.y.z), und der FAQ-Eintrag zur Veraltung (dokumentieren, dann ein Minor-Release mit der Veraltung herausgeben). Wortlaut gegen die Seite geprüft.
  • Hyrums Gesetz ist eine Beobachtung, kein Standard, und wird als solche zitiert.

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.

Verwandte Skills