Programação de Código
Comunique-se com o usuário em português. Os termos abaixo são apresentados primeiro em inglês e depois em português na primeira ocorrência; a partir daí, use apenas os termos em português conforme a tabela.
Projetar um deep module: colocar muito comportamento por trás de uma interface pequena, colocado em uma articulação limpa (seam), e testável através dessa interface. Sempre que estiver projetando ou refatorando código, use esta linguagem e estes princípios. O objetivo é: dar alavancagem (leverage) a quem chama, dar localidade (locality) a quem mantém, e tornar tudo facilmente testável.
Glossário
Use os termos a seguir com precisão: não substitua por "componente", "serviço", "API" ou "fronteira". Uma linguagem consistente é o ponto central.
Módulo (Module): Qualquer coisa que tenha uma interface e uma implementação. Deliberadamente sem escala fixa: pode ser uma função, classe, pacote ou uma fatia que atravessa várias camadas. Evite: unidade, componente, serviço.
Interface (Interface): Tudo o que quem chama precisa saber para usar este módulo corretamente: assinatura de tipos, invariantes, restrições de ordem de chamada, padrões de erro, configuração necessária, características de performance. Evite: API, assinatura (muito estreito, refere-se apenas à superfície de tipos).
Implementação (Implementation): O interior do módulo, ou seja, seu corpo de código. Diferente de Adaptador (Adapter): uma coisa pode ser um adaptador pequeno com muita implementação (ex.: um repositório SQLite) ou um adaptador grande com pouca implementação (ex.: um objeto falso em memória). Use "adaptador" ao discutir articulações; caso contrário, use "implementação".
Profundidade (Depth): A alavancagem na interface. Quanto comportamento o chamador (ou teste) consegue usar para cada unidade de interface que aprende. Um módulo com muito comportamento escondido atrás de uma interface pequena é profundo; um módulo cuja interface é quase tão complexa quanto sua implementação é raso.
Articulação (Seam) (Michael Feathers): Um lugar onde você pode mudar o comportamento sem modificar aquele ponto; ou seja, o local da interface do módulo. Onde colocar a articulação é uma decisão de projeto em si, separada do que é colocado atrás dela. Evite: fronteira (confunde com bounded context do DDD).
Adaptador (Adapter): Uma coisa concreta que satisfaz uma interface em uma determinada articulação. Descreve o papel (qual posição ela preenche), não o conteúdo (o que há dentro).
Alavancagem (Leverage): O benefício que o chamador obtém da profundidade. Para cada unidade de interface aprendida, consegue mais capacidade. O valor de uma implementação é recuperado em N pontos de chamada e M testes.
Localidade (Locality): O benefício que o mantenedor obtém da profundidade. Modificações, bugs, conhecimento e validação concentram-se em um só lugar, em vez de espalhados entre os chamadores. Corrige-se uma vez, surte efeito em toda parte.
Profundo vs. Raso
Módulo profundo (Deep module) = interface pequena + implementação vasta:
┌─────────────────────┐
│ Interface pequena │ ← poucos métodos, parâmetros simples
├─────────────────────┤
│ │
│ Implementação │ ← lógica complexa escondida
│ profunda │
│ │
└─────────────────────┘
Módulo raso = interface grande + implementação magra (evitar):
┌─────────────────────────────────┐
│ Interface grande │ ← muitos métodos, parâmetros complexos
├─────────────────────────────────┤
│ Implementação fina │ ← apenas repassa as coisas
└─────────────────────────────────┘
Ao projetar uma interface, pergunte a si mesmo:
- Consigo reduzir o número de métodos?
- Consigo simplificar parâmetros?
- Consigo esconder mais complexidade dentro?
Princípios
- Profundidade é uma propriedade da interface, não da implementação. Um módulo profundo pode internamente ser composto de peças pequenas, simuláveis (mockáveis) e substituíveis; apenas essas peças não pertencem à interface. Um módulo pode ter articulações internas (implementação privada, para uso próprio em testes) e articulações externas (na interface).
- Teste de exclusão. Imagine excluir este módulo. Se a complexidade desaparecer, ele é apenas um repassador. Se a complexidade reaparecer em N pontos de chamada, está cumprindo seu papel.
- Interface é a superfície de teste. Chamadores e testes atravessam a mesma articulação. Se você precisar contornar a interface para testar, muito provavelmente o formato do módulo está errado.
- Um adaptador representa uma articulação hipotética; dois adaptadores confirmam uma articulação real. Não introduza uma articulação a menos que realmente haja algo que varie em ambos os lados da articulação.
Projetando para Testabilidade
Uma boa interface torna os testes naturais:
-
Receba dependências, não as crie você mesmo.
# Testável def fetch_alternates(mpn: str, client: PartSearchClient) -> list[Part]: ... # Difícil de testar def fetch_alternates(mpn: str) -> list[Part]: client = DistributorClient(api_key=os.environ["DIST_API_KEY"]) -
Retorne resultados, não produza efeitos colaterais.
# Testável def landed_cost(bom: Bom, freight: Freight) -> Money: ... # Difícil de testar def apply_landed_cost(bom: Bom) -> None: bom.total += freight_charge -
Superfície pequena. Quanto menos métodos, menos testes necessários; quanto menos parâmetros, mais simples a preparação do teste.
Relações
- Um módulo tem exatamente uma interface (a superfície que ele apresenta a chamadores e testes).
- Profundidade é uma propriedade do módulo, medida pela sua interface.
- Articulação é o local onde a interface do módulo está.
- Adaptador está em uma articulação e satisfaz a interface.
- Profundidade gera alavancagem para chamadores e localidade para mantenedores.
Estruturas Rejeitadas
- Definir profundidade como a razão entre linhas de implementação e linhas de interface (Ousterhout): Isso recompensa implementações infladas. Usamos "profundidade é alavancagem".
- Igualar "interface" a
Protocol/classe abstrata do Python ou métodos públicos de uma classe: Muito estreito: aqui a interface inclui tudo o que o chamador precisa saber. - "Fronteira": Confunde com bounded context do DDD. Use articulação (seam) ou interface.
Leitura Adicional
- Aprofundando um grupo de módulos sob dependências existentes, veja DEEPENING.md: classificação de dependências, disciplina de articulação e estratégia de teste "substituir em vez de adicionar".
- Explorando diferentes projetos de interface, veja DESIGN-IT-TWICE.md: despachar múltiplos subagentes em paralelo para projetar interfaces de maneiras radicalmente diferentes e depois compará-las quanto a profundidade, localidade e posição da articulação.