Aula 05 · 60–75 minutos

Refatore sem inverter o risco

Quinta aula: refatore uma dependência concreta em Go.

Resultado e mapa da aula

Você sairá com um roteiro que reduz risco de refatoração: primeiro preserva a evidência, depois muda uma dependência observável, e só então considera melhorias adjacentes. O compilador e os testes são sensores a cada passo, não uma validação tardia.

  1. Identifique o import concreto no consumidor.
  2. Proteja o comportamento com teste.
  3. Extraia somente a necessidade real.
  4. Componha o adaptador existente e revise o diff.

Antes da mudança, identifique o acoplamento

Comece pela regra, não pelo repositório. Pergunte: qual ação ela realmente usa? Se o serviço armazena *postgres.OrderRepository, ele conhece o nome e a forma de um detalhe que deveria ficar na borda.

Passos da refatoração
dependência concreta → necessidade real → interface mínima → composição → teste

O risco surge ao tentar "abstrair tudo" de uma vez. Uma interface grande ou criada no pacote de infraestrutura apenas move nomes; ela não coloca a política no controle.

Sequência segura de refatoração

  1. Escreva um teste do comportamento que precisa permanecer.
  2. Liste os métodos do repositório realmente chamados pelo serviço.
  3. Declare no consumidor uma interface com só esses métodos.
  4. Troque o campo e o construtor para o contrato.
  5. Monte a implementação existente em main e execute os testes.

A cada passo, o programa ainda deve compilar. Isso reduz a área de diagnóstico quando algo muda.

A mudança mínima

// antes: checkout conhece um detalhe
type Service struct { orders *postgres.OrderRepository }

// depois: checkout possui a necessidade
type OrderSaver interface { Save(context.Context, Order) error }
type Service struct { orders OrderSaver }

Não reescreva o adaptador se ele já tem Save. A implementação implícita de Go faz o trabalho; o ponto importante é remover a importação de infraestrutura do pacote de negócio.

Use os controles do diagrama para comparar as composições e seguir as relações.

Recupere da memória

De onde deve partir a interface durante a refatoração?

Fonte primária recomendada

Go Code Review Comments — Interfaces. Releia a recomendação de não definir interfaces antes de seu uso real.

Próxima: escolher com critério.

Mude uma seta mantendo o comportamento

Problema em trabalho real: Uma refatoração ampla — criar uma interface global e reescrever todos os repositórios — aumenta a área de mudança e torna difícil atribuir uma regressão. O objetivo é retirar uma dependência concreta de um consumidor específico, não “abstrair o sistema”.

Fluxo e momento de execução: Primeiro, um teste protege o comportamento. Depois, você mede os métodos realmente chamados, declara o contrato no consumidor, altera campo e construtor, conecta o adaptador existente em main e executa compilação/testes a cada passo.

Abordagem insuficiente: Não extraia uma interface Repository com todos os métodos do banco nem mova o contrato para postgres. Isso preserva a infraestrutura como dona da API e cria consumidores acidentalmente acoplados.

Decisão recomendada: Faça a mudança mínima: OrderSaver contém Save, Service o recebe e o repositório atual continua sendo usado se já satisfaz o método. Interfaces diferentes e pequenas podem coexistir para consumidores com necessidades distintas.

Como verificar na prática: Use go test ./... depois de cada passo e revise os imports do pacote checkout: postgres deve desaparecer. Adicione um teste de compilação implícito ao construir o serviço com o adaptador real; a integração continua sendo validada em sua própria suíte.

Limite de segurança: Não aproveite a refatoração para alterar autorização, schema ou tratamento de erro sem testes específicos: isso mistura riscos. Preserve contexto, validação e observabilidade enquanto muda somente a direção da dependência.

Fonte primária: Go Code Review Comments — Interfaces.

Guia de solução do exercício

Fluxo de execução: Proteja o comportamento com teste, descubra os métodos usados, extraia o contrato no consumidor, altere o construtor e componha a implementação existente.

Abordagem inadequada: Trocar todos os repositórios por uma interface enorme em uma alteração única aumenta o risco e apaga a causa de uma falha.

type Repository interface { Save(Order) error; Find(string) (Order, error); List() []Order; Delete(string) error }

Por que falha e como corrigir: A interface representa a capacidade do banco, não a necessidade do serviço. A revisão fica grande e o compilador aponta muitos erros sem explicar a intenção arquitetural.

Abordagem correta: Faça uma mudança mínima guiada por uso e preserve o adaptador atual.

// checkout
type OrderSaver interface { Save(Order) error }
func NewService(orders OrderSaver) *Service { return &Service{orders: orders} }

Raciocínio, trade-offs e limites: Compile e execute os testes a cada passo. Não reescreva PostgresOrderRepository se ele já possui Save. Extraia outro contrato apenas quando outro consumidor tiver outro conjunto de necessidades; interfaces diferentes podem coexistir.

Abra o diagrama relacionado e compare o fluxo.

Pergunta de mercado e entrevista

1. Como refatorar sem aumentar o risco?

Como responder: Use teste de proteção, descubra métodos usados, crie contrato mínimo no consumidor, altere o construtor, componha em main e teste em cada passo.

2. Onde a interface deve nascer numa refatoração?

Como responder: No consumidor que sente o acoplamento. Criá-la no banco costuma exportar detalhes e transforma a capacidade da infraestrutura em uma API geral.

3. Você reescreveria o repositório existente?

Como responder: Não se ele já satisfaz o contrato. A mudança menor preserva comportamento e reduz risco. Reescreva apenas quando o próprio adaptador tiver uma necessidade independente.

Pratique mais 9 questões de recuperação

Responda sem consultar a aula. O feedback é imediato; volte ao trecho relevante antes de tentar de novo.

DI entrega uma dependência; DIP define:

Em Go, uma interface é satisfeita quando:

Quem deve possuir uma interface pequena?

Onde a implementação concreta é escolhida?

Para testar uma regra sem banco, injete:

Uma interface deve conter:

O detalhe PostgreSQL deve depender de:

Qual sinal pede uma interface?

Após a refatoração, a regra não deve importar: