Contrato de Biblioteca (Library Design)

Diretrizes para projetar bibliotecas ou módulos compartilhados que respeitem a liberdade do chamador, congelem apenas o que é observável e ofereçam abstrações de custo zero.

O que é Contrato de Biblioteca (Library Design)?

Esta skill estabelece o 'Contrato de Biblioteca', um conjunto de princípios para projetar bibliotecas (pacotes publicados ou módulos internos de aplicativos) que priorizam a liberdade e a previsibilidade para os chamadores. Baseia-se na Lei de Hyrum: qualquer comportamento observável torna-se parte do contrato assim que um segundo consumidor depende dele, mesmo que não tenha sido explicitamente documentado. As cinco obrigações para com o chamador são: não assumir controle (separar mecanismo de política), não adicionar custos ocultos (abstração de custo zero), não manter estado oculto, não esconder falhas (modelar erros nos tipos) e não bloquear a fuga (fornecer APIs primitivas abaixo da camada de conveniência). A skill compara esses deveres aos princípios da Arquitetura Limpa, mostrando que ambos rejeitam taxas – uma sobre desempenho em tempo de execução, a outra sobre capacidade de mudança. É aplicável ao projetar ou revisar APIs públicas, extrair código compartilhado em módulos, quando for solicitado a 'tornar isso reutilizável' e quando uma alteração compatível com a assinatura altera um comportamento observável. Uma nota importante: módulos com um único consumidor (0.y.z no SemVer) não precisam de todas as sobreposições de política substituíveis; elas devem ser adicionadas apenas quando o segundo consumidor chegar.

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

Installed? Explore more Programação e Desenvolvimento skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

O Contrato de Biblioteca

Um aplicativo é julgado por seus usuários; uma biblioteca é julgada por programadores, e em um momento específico — quando eles querem algo que o autor não imaginou. Uma boa biblioteca não é aquela que é conveniente quando usada como pretendido. É aquela que não precisa ser descartada quando usada de outra forma.

Cada regra abaixo decorre de uma assimetria. Dentro da biblioteca você pode mudar qualquer coisa, para sempre. Fora dela, tudo o que um chamador pode observar está congelado na primeira vez que alguém depende disso — e com chamadores suficientes, alguém depende de tudo.

O que congela, e quando

A lei de Hyrum é a condição operacional, não uma curiosidade: dados usuários suficientes, todo comportamento observável se torna estrutural — redação de exceção, ordem de enumeração, saída de log, tempo, até mesmo o comportamento que você documentou como indefinido. "Nunca estava no contrato" não desfaz o erro do chamador que lançou uma versão contra ele.

Portanto, a linha de congelamento reside na observabilidade, não na palavra-chave public, e três consequências se seguem:

  • A superfície começa pequena. Um símbolo, uma vez público, é uma promessa que você pode ampliar, mas nunca retirar silenciosamente. "Público por enquanto" é dívida contraída sem benefício; exponha sob demanda.
  • Formatos sobrevivem ao código. Um formato de arquivo, um protocolo de rede, um esquema persistido sobrevive a cada reescrita da biblioteca ao seu redor. Serializar a struct interna como está solda a camada congelada à editável.
  • Um módulo interno do aplicativo cruza esta linha no segundo consumidor, não no empacotamento. No momento em que existem dois chamadores, sua API tem a mesma economia de congelamento que um pacote publicado — apenas o raio da explosão é menor.

O Versionamento Semântico é a ortografia pública da mesma linha, e começa recusando-se a funcionar sem uma: "Software que usa Versionamento Semântico DEVE declarar uma API pública" (SemVer 2.0.0, cláusula 1). Uma vez declarada, os incrementos são decididos em vez de sentidos — uma alteração incompatível com versões anteriores nessa API DEVE aumentar MAJOR (cláusula 8), nova funcionalidade compatível com versões anteriores DEVE aumentar MINOR (cláusula 7), uma correção compatível com versões anteriores DEVE aumentar PATCH (cláusula 6). A lei de Hyrum é o que torna a declaração difícil: a especificação congela o que você declarou, e os chamadores congelam o que observaram.

A especificação também tem um nome para o módulo que ainda não cruzou a linha. "Versão principal zero (0.y.z) é para desenvolvimento inicial. QUALQUER COISA PODE mudar a qualquer momento" (cláusula 4). Esse é o rótulo honesto para um módulo com um consumidor, e o dia em que ganha um segundo é o dia em que 1.0.0 é devido, não o dia em que é empacotado.

Não assuma controle

Separe mecanismo de política. Executar uma requisição HTTP é mecanismo; repeti-la três vezes com backoff exponencial é política, e política embutida no núcleo restringe a biblioteca aos chamadores que a compartilham. Forneça padrões convenientes para logging, threading, repetições, timeouts, cache, serialização — e torne cada um deles substituível.

O papel do aplicativo é singular, e não é seu. Sair do processo, escrever em stdout, capturar SIGINT, instalar manipuladores de exceção de nível superior, redimensionar o pool de threads: exatamente um componente por processo pode fazer isso, e uma biblioteca que o faz luta contra o componente com direito a fazê-lo. Ler as entradas do processo pertence ao mesmo papel: variáveis de ambiente, arquivos de configuração e argumentos de linha de comando são lidos na raiz do aplicativo, que entrega cada valor à biblioteca como um parâmetro ou um objeto de opções. O chamador então escolhe de onde um valor vem, e um teste o passa em vez de configurar a máquina.

Magia — autodetecção, autoconversão, autoconectividade, autolimpeza — é admissível apenas enquanto o chamador puder reconstruir por que fez o que fez. Conveniência que o usuário não pode depurar é controle assumido, não trabalho economizado.

Não adicione custos ocultos

Quando as coisas acontecem pertence ao chamador. Um import que inicia uma thread, um construtor que abre uma conexão, um inicializador estático que lê variáveis de ambiente — cada um move trabalho real para um momento que o chamador nunca escolheu e não pode mover de volta.

Uma dependência é parte da sua API. Seu tempo de execução, sua cadência de atualização, sua estabilidade ABI, sua licença, seus próprios efeitos colaterais de inicialização são transferidos para cada chamador; você está escolhendo em nome deles.

As características de desempenho são contrato. Transformar uma busca O(1) em O(n) — ou duplicar alocações e cópias — é uma mudança significativa usando uma assinatura compatível, e os chamadores a descobrem em produção.

Onde a entrada chega mais rápido do que sai — streams, eventos, logs, filas — um buffer interno ilimitado é um vazamento de memória adiado. Contrapressão faz parte do design, não um detalhe operacional.

O padrão para a própria abstração é custo zero, em ambas as cláusulas de Stroustrup. O que o chamador não usa, não paga: a camada de conveniência não deve taxar a primitiva com suas alocações, seu dispatch ou sua configuração. E o que eles usam, não poderiam razoavelmente escrever melhor manualmente: uma abstração superada por código feito à mão é contornada, e uma biblioteca contornada é descartada com um número de versão.

Não mantenha estado oculto

Singletons, caches estáticos, loops de eventos globais, configuração em todo o processo: cada um funciona enquanto a sua é a única biblioteca no processo, e colide quando uma segunda biblioteca, um host de plugin, ou um conjunto de testes paralelo a compartilha. O estado vive em uma instância que o chamador cria, possui, e pode criar duas vezes.

Não esconda falhas

A forma da API declara o que pode acontecer e quem é responsável. Erros, cancelamento, timeout, sucesso parcial, ausência — modelados nos tipos, não roteados através de "isso nunca acontece normalmente" e uma exceção. A mesma forma carrega as respostas de propriedade que um comentário não pode impor: quem fecha o stream, se o buffer pode ser reutilizado, em qual thread o callback chega.

Mantenha a causa. Colapsar falha em false ou null remove exatamente a informação que a camada acima precisava para decidir; logá-la você mesmo e continuar perde a mesma informação e assume a política de logging do chamador.

A qualidade se separa no caminho anormal. No caminho feliz, as bibliotecas são intercambiáveis; timeout, reconexão, desconexão no meio do stream, leituras parciais, dados corrompidos e repetição são onde as diferenças vivem, então projete para sua composição primeiro.

O cancelamento se adapta mal — um token de cancelamento percorre cada assinatura que toca. Operações de I/O e de longa duração o aceitam desde a primeira versão.

Não bloqueie a fuga

Construa dois andares. Uma API de alto nível que atende ao caso comum em uma linha, apoiada em primitivas públicas — ReadAsync() sobre ReadAsync(Memory<byte>) — para que o chamador cujo requisito cai fora do andar superior desça em vez de sair. Uma biblioteca com apenas o andar superior é descartada no primeiro requisito que não previu.

A observabilidade é injetada, não possuída: loggers, métricas, traces se conectam à pilha do chamador. Um formato de diagnóstico que apenas sua biblioteca lê é telemetria feita refém.

Um imposto, duas moedas

Esta habilidade e a Arquitetura Limpa são a mesma recusa em duas moedas. A abstração de custo zero recusa o imposto sobre o desempenho em tempo de execução; a Regra da Dependência recusa o imposto sobre a capacidade de mudança. Em ambas, uma abstração é julgada pelo que faz o lado dependente abrir mão.

Dever aquiSeu nome arquitetural
Mecanismo separado da políticaCaso de uso mantido separado do framework e driver
Padrões substituíveisInversão de dependência — o lado estável escolhe a implementação
Primitivas sob o andar de conveniênciaDetalhes externos trocáveis
Sem estado globalDependências explícitas, para que um limite possa ser traçado
Uma dependência é parte da sua APIA Regra da Dependência

A forma saudável do chamador coloca sua biblioteca na base: um detalhe substituível abaixo de sua política e domínio. A forma invertida — o aplicativo vivendo dentro de um framework, arrastando seu ciclo de vida, seus tipos, sua DI, seu modelo de erro, seu modelo assíncrono — é a biblioteca agindo como o aplicativo, escrita em escala arquitetural.

A forma geral: uma boa arquitetura minimiza não o número de dependências, mas a liberdade que cada dependência tira.

A sentença

Uma abstração em uma biblioteca existe para ampliar as escolhas do chamador. No momento em que ela tira desempenho, controle ou informação em troca, ela parou de abstrair e começou a decidir.

Quando não aplicar

Um módulo com um consumidor, um repositório, uma equipe não cruzou a linha de congelamento — está em 0.y.z nos termos do SemVer, quer carregue ou não um número — e construir todas as costuras de política substituível para ele é especulação com uma conta de manutenção. Mantenha as disciplinas que não custam nada desde o primeiro dia — sem estado global, falhas que mantêm sua causa, nenhum trabalho antes de o chamador pedir — e adicione as costuras quando o segundo consumidor chegar, que é o momento em que elas deixam de ser especulativas.

Fontes

  • Versionamento Semântico 2.0.0, semver.org: cláusula 1 (API pública DEVE ser declarada), cláusulas 6–8 (incrementos PATCH, MINOR, MAJOR), cláusula 4 (0.y.z), e a entrada de FAQ sobre deprecação (documente-a, depois emita um lançamento menor com a deprecação em vigor). Redação verificada em relação à página.
  • A lei de Hyrum é uma observação, não um padrão, e é citada como tal.

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.

Habilidades Relacionadas