Introdução
Esse cluster é diferente dos outros três: ele não previne um tipo de bug, ele sustenta a capacidade de manter os outros nove princípios ao longo do tempo. A segunda lei de Lehman, a Lei da Complexidade Crescente, diz que a complexidade de um sistema aumenta por padrão a menos que alguém trabalhe ativamente para reduzir-la. Refatoração não é uma tarefa opcional de “polimento”, é a única força que contrabalança uma tendência que existe independente da sua vontade. Mas refatorar só é seguro se você consegue confirmar rapidamente que nada quebrou — e é aí que testes lentos ou dependentes de ordem matam a disciplina inteira: se rodar a suíte demora 20 minutos ou falha de forma inconsistente, o time para de rodar, para de confiar, e para de refatorar, e a lei da complexidade crescente passa a valer sem oposição. Esse não é um risco teórico: o relatório The Developer Coefficient (Stripe, 2018), baseado em pesquisa com centenas de desenvolvedores e executivos de engenharia, estimou que uma fração substancial da semana de trabalho — na casa de um terço a 40% — é perdida lidando com dívida técnica e código malfeito, com impacto econômico global estimado em centenas de bilhões de dólares por ano. Esse número é, em essência, o preço agregado de organizações que pararam de fazer #10 e #13 e deixaram a lei de Lehman correr sem freio.
#10 Testes Rápidos e Independentes
A base conceitual mais direta aqui é o acrônimo FIRST, documentado por Robert C. Martin em Clean Code (2008) e creditado a Tim Ottinger e Jeff Langr: um teste de unidade deveria ser Fast (rápido), Independent (independente de outros testes), Repeatable (repetível em qualquer ambiente), Self-Validating (retorna verdadeiro/falso, sem exigir inspeção manual de log) e Timely (escrito no momento certo, perto do código que testa). O título desse princípio no artigo pega exatamente as duas primeiras letras — porque são, na prática, as duas que mais diretamente disparam a espiral de morte descrita na introdução: um teste lento drena paciência a cada execução; um teste dependente de ordem drena confiança a cada falha que não devia ter acontecido.
Exemplo — teste lento e acoplado vs. teste rápido e isolado:
// Lento e acoplado — bate no banco de dados real a cada execução
public class RetificacaoServiceTests
{
[Fact]
public void DeveRetificarMovimento_QuandoValorDivergente()
{
var context = new FiscalDbContext(_connectionStringReal); // conexão real com SQL Server
var service = new RetificacaoService(context);
var resultado = service.Retificar(movimentoId: 123);
Assert.True(resultado.Sucesso);
// Cada execução: abre conexão de rede real, e depende de que a linha 123
// já exista no banco com o estado "certo" — herdado de uma execução anterior
}
}
// Rápido e isolado — dependência trocada por um fake em memória
public class RetificacaoServiceTests
{
[Fact]
public void DeveRetificarMovimento_QuandoValorDivergente()
{
var repositorioFake = new RepositorioMovimentoFake(
new Movimento(id: 123, valor: 500m, status: StatusMovimento.Pendente));
var service = new RetificacaoService(repositorioFake);
var resultado = service.Retificar(movimentoId: 123);
Assert.True(resultado.Sucesso);
// Roda em memória, sem I/O — milissegundos. O estado de entrada é 100%
// definido dentro do próprio teste, não herdado de nada externo
}
}
Exemplo — teste dependente de ordem vs. independente:
// Frágil — os dois testes compartilham uma lista estática entre execuções
public class NumeracaoNotaFiscalTests
{
private static readonly List<int> _numerosGerados = new();
[Fact]
public void PrimeiroNumero_DeveSerUm()
{
var numero = GerarProximoNumero(_numerosGerados);
Assert.Equal(1, numero);
_numerosGerados.Add(numero);
}
[Fact]
public void SegundoNumero_DeveSerDois()
{
var numero = GerarProximoNumero(_numerosGerados);
Assert.Equal(2, numero);
// Só passa se PrimeiroNumero_DeveSerUm já rodou antes, e nessa ordem.
// Rodar sozinho, em paralelo, ou com a suíte embaralhada: falha.
}
}
// Independente — cada teste monta seu próprio estado, do zero
public class NumeracaoNotaFiscalTests
{
[Fact]
public void PrimeiroNumero_DeveSerUm()
{
var numero = GerarProximoNumero(new List<int>());
Assert.Equal(1, numero);
}
[Fact]
public void SegundoNumero_DeveSerDois()
{
var numero = GerarProximoNumero(new List<int> { 1 }); // pré-condição explícita, não herdada
Assert.Equal(2, numero);
}
}
O segundo exemplo é o padrão mais comum de "flakiness" na prática: dois testes que passam perfeitamente na ordem em que foram escritos, e falham de forma aparentemente aleatória assim que uma pipeline de CI paraliza a execução, ou quando algum framework de teste decide randomizar a ordem deliberadamente — justamente pra expor esse tipo de acoplamento escondido antes que ele vire um problema em produção.
#13 Simplicidade e Refatoração Contínua
A base conceitual mais direta vem de Kent Beck, em Extreme Programming Explained (1999): as Quatro Regras do Design Simples, nessa ordem de prioridade — (1) passa em todos os testes, (2) revela a intenção, (3) sem duplicação, (4) menor número de elementos possível. A ordem importa: só faz sentido buscar "menos elementos" depois de garantir que a intenção está clara e que não há duplicação — simplicidade não é sinônimo de "menos código a qualquer custo", é a ausência de tudo que não precisa estar ali.
A ligação com dívida técnica, que a introdução já traz pelo relatório da Stripe, vem de Ward Cunningham, num relato de experiência na OOPSLA de 1992: código que corta caminho pra entregar mais rápido hoje é, literalmente, um empréstimo — você recebe velocidade agora e paga juros (retrabalho, fricção, tempo de entendimento) em cada mudança futura, até que alguém pague o principal de volta via refatoração. Cunningham não estava fazendo uma crítica moral ao atalho — dívida técnica pode ser escolha racional e deliberada —, o ponto dele é que ela tem que ser paga de algum jeito, consciente ou não.
A técnica pra pagar essa dívida com segurança é formalizada por Martin Fowler em Refactoring: Improving the Design of Existing Code (1999): uma sequência de pequenas transformações, cada uma preservando o comportamento observável do sistema, verificada por testes a cada passo — nunca uma reescrita de uma vez só. Robert C. Martin resume o hábito do dia a dia nisso com a Regra do Escoteiro: deixe o código um pouco mais limpo do que você encontrou, toda vez que mexer nele — não precisa de um projeto formal de refatoração, é disciplina contínua aplicada a cada Pull Request.
Exemplo — duplicação que se acumula em silêncio:
// Complexo — a mesma validação de CPF/CNPJ duplicada em dois lugares do sistema
public class ValidadorNotaFiscal
{
public bool Validar(NotaFiscal nota)
{
var doc = nota.Cliente.Documento.Replace(".", "").Replace("-", "").Replace("/", "");
if (doc.Length == 11) { /* ~20 linhas de validação de CPF */ }
else if (doc.Length == 14) { /* ~25 linhas de validação de CNPJ */ }
return true;
}
}
public class ValidadorTransmissao
{
public bool PodeTransmitir(ArquivoEFinanceira arquivo)
{
var doc = arquivo.Titular.Documento.Replace(".", "").Replace("-", "").Replace("/", "");
if (doc.Length == 11) { /* a mesma validação de CPF, copiada e colada */ }
// quem corrigir um bug aqui precisa lembrar de corrigir no outro lugar também
}
}
// Simples — a regra vive em um único lugar; os dois validadores delegam
public static class ValidadorDocumento
{
public static bool EhValido(string documento)
{
var limpo = Limpar(documento);
return limpo.Length switch
{
11 => ValidarCpf(limpo),
14 => ValidarCnpj(limpo),
_ => false
};
}
private static string Limpar(string doc) => doc.Replace(".", "").Replace("-", "").Replace("/", "");
}
public class ValidadorNotaFiscal
{
public bool Validar(NotaFiscal nota) => ValidadorDocumento.EhValido(nota.Cliente.Documento);
}
Essa é a Regra 3 de Beck (sem duplicação) na prática. O perigo da versão duplicada não é só o volume de código — é que um bug corrigido num lugar continua vivo no outro, esperando quieto até alguém notar a divergência, geralmente meses depois, geralmente em produção.
Top comments (0)