
Uma PR chamada "limpeza" renomeia funções, troca o arredondamento de preços e muda campos da resposta. Os testes passam porque foram atualizados junto com a implementação. Quando um cliente percebe uma diferença, a equipe precisa descobrir qual comportamento pretendia preservar. A revisão perdeu uma informação essencial: onde acabou a reorganização e começou uma mudança funcional.
Recomendo Refactoring para desenvolver essa disciplina. A proposta de movimentos pequenos permite melhorar uma estrutura enquanto mantemos uma referência para avaliar o resultado. O objetivo é tornar a próxima alteração mais simples sem esconder mudanças de contrato dentro de um diff aparentemente cosmético. Isso exige escolher o que observar e reconhecer os limites da evidência disponível.
Esta resenha avalia Refactoring: Improving the Design of Existing Code, segunda edição de 2018. A página do próprio Fowler registra o crédito "Martin Fowler, with Kent Beck" e descreve a organização: exemplo inicial, discussão de fundamentos, sinais de problemas e um catálogo de transformações com motivação, mecânica e exemplos.
Os exemplos da segunda edição usam JavaScript, como explica o site oficial de refatoração. O exemplo desta resenha será em Python e é original. Minha recomendação não depende de trabalhar com a linguagem do livro: o retorno está em acompanhar uma sequência controlada, identificar o motivo de cada movimento e transportar o raciocínio para o ambiente em que você programa.
O formato de catálogo também muda como eu sugiro ler. Primeiro entenda o processo; depois consulte uma transformação ligada a um problema real. Ler os nomes de todas as técnicas oferece menos retorno que praticar uma delas preservando um contrato que você consegue explicar.
O cenário didático abaixo calcula um preço com benefício de associação. Valores são centavos inteiros; membros pagam 90% do valor, com divisão inteira. Quantias negativas geram erro. A primeira versão contém tudo; a segunda versão extrai apenas o cálculo do preço de membro. Nada aqui vem do código do livro.
Use Python 3, sem dependências externas. Salve o bloco como exemplo.py e execute python3 exemplo.py. A saída esperada é 7 value cases and 2 error checks passed.
def price_before(cents, member):
if cents < 0:
raise ValueError("negative amount")
return cents * 90 // 100 if member else cents
def member_price(cents):
return cents * 90 // 100
def price_after(cents, member):
if cents < 0:
raise ValueError("negative amount")
return member_price(cents) if member else cents
cases = [
(0, False, 0), (0, True, 0), (1, True, 0),
(99, True, 89), (100, True, 90),
(1001, True, 900), (1001, False, 1001),
]
for cents, member, expected in cases:
assert price_before(cents, member) == expected
assert price_after(cents, member) == expected
for function in (price_before, price_after):
try:
function(-1, True)
except ValueError as error:
assert str(error) == "negative amount"
else:
raise AssertionError("negative amount was accepted")
print("7 value cases and 2 error checks passed")
O caso de um centavo é deliberado: o preço resultante é zero. Isso pode provocar uma conversa comercial importante, mas a extração deve preservar a decisão existente. Trocar a regra por arredondamento diferente seria uma alteração funcional. Mesmo uma correção desejável precisa de uma justificativa e de exemplos que expressem o novo resultado.
As expectativas fixas também importam. Comparar apenas as duas versões permitiria que ambas carregassem o mesmo erro sem questionamento. Os sete resultados escolhidos fornecem uma referência independente para esse recorte. Eles não demonstram equivalência para qualquer entrada, nem validam todos os tipos que Python permite passar para essas funções.
Produtos gratuitos e pagos para transformar ideias em uma base que você consegue executar.
13 produtos disponíveisContinue explorando tópicos similares

Aprenda com Kent Beck a transformar requisitos em passos pequenos de feedback, sem confundir testes verdes com produto correto.

Use Michael Feathers para começar uma mudança em código sem testes, localizar dependências e registrar o comportamento existente.

Aplique Michael Nygard a timeouts, isolamento e recuperação com um exercício de falha controlada.
Checklist de 47 pontos para encontrar bugs, riscos de segurança e problemas de performance antes do lançamento.
Templates testados em produção, usados por desenvolvedores. Economize semanas de setup no seu próximo projeto.
Fowler apresenta a ideia de reorganizar o código antes de acrescentar uma capacidade em seu exemplo de refatoração preparatória. Minha aplicação aqui seria primeiro extrair member_price, manter os resultados e revisar o diff. Depois, se o negócio decidir criar outra modalidade de benefício, acrescentar os novos casos e alterar a política em um passo identificável.
Essa separação oferece uma vantagem para quem revisa. Na extração, a pergunta é se o comportamento permaneceu igual e se o nome ajuda. Na nova modalidade, a pergunta é se a regra solicitada foi implementada. Misturar as intenções exige reconstruir as duas ao mesmo tempo, justamente quando a estrutura está mudando.
Isso não obriga abrir uma PR para cada linha movida. Passos pequenos podem existir durante o desenvolvimento, acompanhados por testes frequentes, e aparecer como commits ou diferenças revisáveis conforme o contexto. O cuidado é manter pontos de referência compreensíveis e evitar passar longos períodos com várias transformações incompletas.
O exemplo verifica retorno e um erro específico. Em uma aplicação, outras propriedades podem ser observáveis: ordem de eventos, quantidade de gravações, campos de uma resposta e momento em que uma exceção é lançada. Extrair uma expressão que lê um relógio ou consome um iterador exige cuidado para não mudar quantas vezes ela é avaliada.
Também existem compromissos operacionais. Uma transformação que passa nos testes de resultado pode aumentar consultas ao banco ou uso de memória. Se latência ou consumo de recursos fazem parte das condições de uso, selecione verificações representativas para essas propriedades. Testes funcionais e medição de desempenho respondem perguntas diferentes; nenhum dos dois deve ser apresentado como substituto universal do outro.
Eu evitaria atualizar expectativas apenas para fazer a suíte ficar verde durante uma mudança estrutural. Quando uma expectativa falha, é preciso investigar se houve regressão, se o teste depende de um detalhe interno ou se o contrato estava mal compreendido. Cada diagnóstico leva a uma decisão diferente. Um teste frágil pode precisar de ajuste, mas esse ajuste exige explicação.
A fronteira da verificação também precisa ser escolhida. Testar cada auxiliar extraído pode acoplar a suíte à decomposição que ainda pretendemos melhorar. Prefira resultados observáveis quando oferecem proteção suficiente e teste um auxiliar isoladamente quando seu contrato justificar isso. A estratégia de testes afeta o custo da próxima refatoração.
Uma função longa, uma regra repetida e um nome vago são bons pontos de partida para revisão. Minha leitura das heurísticas do livro é tratá-las como perguntas sobre custo de mudança. Uma função longa pode representar uma sequência coesa; um trecho duplicado pode pertencer a regras que evoluem separadamente. A aparência oferece suspeitas, sem comprovar qual transformação será útil.
No exemplo, a extração é defensável se o preço de membro se torna um conceito que precisamos explicar ou modificar isoladamente. Se não há essa necessidade, o ganho pode ser pequeno. Acrescentar vários auxiliares que apenas repitam operadores aumentaria navegação sem oferecer uma ideia melhor. Um catálogo de técnicas precisa vir acompanhado da capacidade de decidir quando deixar o código como está.
Essa é uma força do método quando praticado criticamente: cada movimento pode ser avaliado antes do próximo. Se a nova estrutura torna a regra mais difícil de encontrar, é possível reconsiderar sem já ter misturado uma funcionalidade adicional. A reversão fica mais compreensível porque o passo tinha uma intenção delimitada.
Renomear uma função privada e renomear um campo consumido por outros sistemas têm impactos diferentes. No segundo caso, mudar todos os pontos conhecidos no repositório não demonstra que todos os clientes foram atualizados. Documentação, integrações externas e dados persistidos podem carregar o contrato antigo.
Eu faria uma transição gradual: adicionar a nova entrada mantendo a antiga, fazer ambas delegarem à mesma implementação, migrar consumidores e só então retirar a compatibilidade em uma etapa acordada. Essa abordagem de expandir e depois contrair cria trabalho adicional e precisa de prazo para não manter duas interfaces indefinidamente. Não é licença para chamar uma quebra pública de simples reorganização.
O mesmo cuidado vale para banco e efeitos externos. Depois de publicar um novo formato ou gravar dados irreversíveis, voltar o código pode ser insuficiente. A revisão precisa considerar estados mistos, compatibilidade e procedimentos de recuperação. A pequena extração do exemplo não oferece evidência para essas situações; ela serve para aprender o método em um contexto controlado.
Eu não iniciaria uma reorganização ampla durante um incidente sem entender como ela ajuda a estabilizar o serviço. Também adiaria melhorias em código prestes a ser removido quando o benefício provável não paga o custo. Se o comportamento é desconhecido e faltam testes, o primeiro trabalho pode ser criar uma observação confiável, não aplicar imediatamente a transformação escolhida.
A limitação de um catálogo é não decidir essas prioridades pela equipe. Ele oferece movimentos e motivos, mas não conhece a janela de entrega, os consumidores externos nem o risco operacional do seu sistema. Minha recomendação exige justificar o investimento pela alteração que está sendo dificultada. "Está feio" é insuficiente para autorizar uma migração extensa.
Quem consegue acompanhar funções, condicionais e testes básicos tem a base para começar. A leitura rende mais com código existente e uma alteração concreta em mente. Quem ainda está aprendendo sintaxe pode praticar exemplos menores antes de encadear várias técnicas. Pessoas experientes podem usar o catálogo para melhorar a precisão de revisão e mentoria.
Na série, eu priorizaria Working Effectively with Legacy Code quando o obstáculo é estabelecer proteção em código difícil de testar. Test-Driven Development: By Example oferece outra prática para quem precisa desenvolver o ciclo de feedback. Clean Code ajuda a discutir legibilidade; Refactoring acrescenta uma maneira de conduzir a transformação preservando uma referência.
Comece pelo exemplo inicial do livro e reproduza uma transformação por vez. Depois escolha uma função sua, escreva os contratos observáveis e selecione um movimento pequeno. Registre o teste usado, o resultado e o custo de navegação antes e depois. Acrescente uma nova regra apenas quando a reorganização estiver compreendida e verificada.
Meu veredito é favorável porque o livro torna o processo de mudança ensinável e discutível. Seu valor depende menos de chegar a uma estrutura ideal que de manter confiança durante o percurso. Pequenos passos não removem todo risco, mas deixam mais claro o que foi preservado, o que mudou de propósito e qual evidência sustenta essa afirmação.