Capítulo 01 Fundamento
O que é código legado, e porque é quase todo o trabalho
A definição útil não tem nada a ver com idade nem com linguagem antiga. Tem a ver com medo — e o medo tem uma causa técnica identificável.
1.1 A definição que serve
É a definição de Michael Feathers, e é útil porque é acionável. Não diz "código velho" (que não se pode mudar) nem "código mau" (que é uma opinião): diz que falta uma coisa concreta, que se pode acrescentar.
Código sem testes é código que não se pode mudar com confiança. Sem confiança, ninguém mexe. Sem mexer, apodrece. É por isso que a definição explica o comportamento das equipas melhor do que a idade explica.
Uma definição complementar, mais crua: código legado é código que você tem medo de mudar. Repare que ambas admitem a mesma coisa incómoda — que se pode escrever código legado esta tarde, e a maior parte das equipas escreve.
1.2 O que se sente e o que é
| O sintoma | A causa técnica |
|---|---|
| "Ninguém percebe este módulo" | Nomes que mentem, funções longas, e a lógica de negócio misturada com a de infraestrutura |
| "Mudar aqui parte ali" | Acoplamento por estado partilhado ou por dependências construídas dentro das próprias funções |
| "Não dá para testar" | Não há costuras: a função cria as suas dependências em vez de as receber (cap. 4) |
| "Só o João é que sabe" | Conhecimento por transmissão oral. É um risco de negócio, não um traço de personalidade |
| "Cada entrega é uma noite em claro" | Sem rede de segurança automática, a validação é manual e incompleta |
1.3 A escala do assunto
A imagem do trabalho de programação — campo aberto, projeto novo, decisões de raiz — descreve uma fração pequena do que se faz. O resto é acrescentar comportamento a sistemas que já existem, que já têm clientes, e que não podem parar. É esse o trabalho, e é sobre ele que menos material existe.
A geração automática de código torna barato escrever. Não torna barato perceber um sistema com quinze anos, nem garantir que uma alteração não parte nada. Se produzir passa a custar pouco e verificar continua a custar, a verificação torna-se o gargalo — que é exatamente o argumento de O Futuro da Engenharia de Software. Esta apostila é o lado prático dessa tese.
1.4 O que esta apostila é
É sobre mudar código que já existe sem o partir: caracterizar, cobrir, quebrar dependências, refatorar em passos pequenos, migrar em produção e negociar o tempo para isso. Testing & Automação ensina a testar; aqui os testes entram como ferramenta de resgate, com uma finalidade diferente e regras diferentes. Arquitetura & System Design desenha o destino; aqui trata-se do caminho a partir de onde se está.
1.5 A regra que organiza tudo o resto
Refatorar é mudar a estrutura sem mudar o comportamento. Acrescentar funcionalidade é mudar o comportamento sem reorganizar a estrutura. Fazer os dois no mesmo commit torna impossível saber qual dos dois causou a falha — e é a origem de quase todos os desastres de refatoração.
Duas mãos, nunca ao mesmo tempo. É uma regra chata e é a que salva o resto.
Liste os cinco ficheiros do seu sistema onde ninguém gosta de mexer. Ao lado de cada um escreva a causa técnica da tabela de 1.2. Guarde a lista: é o material de trabalho dos próximos onze capítulos.
Capítulo 02 Método
Antes de mexer: caracterizar o sistema
A pressa de melhorar é o instinto errado. A primeira semana num sistema desconhecido é para o ler, medir e mapear — e a leitura tem técnicas.
2.1 Ler código que não se percebe
- Comece pelas fronteiras, não pelo meio. Onde entram os pedidos, o que sai para a base de dados, que ficheiros se leem. As fronteiras são poucas e definem o sistema melhor do que o núcleo.
- Siga um caso real do princípio ao fim. Um único pedido, desde o clique até à linha gravada. Aprende-se mais nisto do que a ler cinquenta ficheiros por ordem alfabética.
- Desenhe enquanto lê. Um diagrama feito à mão, mesmo errado, obriga a decidir o que é importante — e as correções ao desenho são a aprendizagem.
- Anote as perguntas, não as respostas. A lista de "porque é que isto está aqui?" é o inventário do que ainda não se sabe, e vai ser útil no capítulo 3.
Uma técnica subestimada: copie o ficheiro para outro sítio e apague tudo o que não interessa — tratamento de erros, registos, casos-limite —, até restar o esqueleto da lógica. Não se compromete nada e o código fica legível. Depois deite fora a cópia. O que se leva é o entendimento.
2.2 O que o histórico do repositório diz
O git é o instrumento de arqueologia mais subaproveitado que existe (Git & GitHub):
| Pergunta | Como responder |
|---|---|
| Que ficheiros mudam mais? | Contagem de alterações por ficheiro no último ano. São os pontos quentes — e é onde a refatoração se paga |
| Que ficheiros mudam juntos? | Pares que aparecem no mesmo commit repetidamente: acoplamento que a estrutura de pastas não mostra |
| Onde estão as correções urgentes? | Mensagens com "hotfix" ou "corrige": as zonas frágeis autodenunciam-se |
| Porque é que esta linha existe? | git log da linha, até ao commit original e ao seu contexto |
| Quem sabe disto? | Quem escreveu mais neste ficheiro — mesmo que já não esteja na empresa, sabe-se a quem perguntar |
Um ficheiro complicado que ninguém toca há três anos não é um problema — é um sistema estável e a refatorá-lo só se ganha risco. O alvo é a interseção: muda muito × é complicado. É aí que a dificuldade se converte em custo todas as semanas, e é a única priorização de refatoração que se defende com dados.
2.3 Fazer o sistema falar
- Registo temporário e generoso. Acrescente registos em pontos-chave, corra um caso real, leia a sequência, e retire-os. Vale mais que qualquer diagrama desatualizado.
- Meça antes de otimizar. A intuição sobre onde está a lentidão erra quase sempre. Um profiler uma vez poupa uma semana de refatoração do sítio errado.
- Instrumente antes de mudar. Se não consegue ver o comportamento atual em produção, não vai conseguir provar que a mudança não o alterou.
- Conte o que existe. Quantas rotas, quantas tabelas, quantos jobs agendados. O número costuma ser diferente do que toda a gente diz.
2.4 As perguntas às pessoas
Parte do sistema não está no código: está nas cabeças, e sai com as perguntas certas. "Porque é que isto está assim?" põe as pessoas na defensiva; "o que é que isto estava a resolver na altura?" devolve a história. Quase toda a estranheza teve uma boa razão — um cliente grande, um prazo, uma limitação que já não existe. Saber qual é o que permite decidir se a razão ainda se aplica.
Corra a contagem de alterações por ficheiro no último ano e cruze com o tamanho de cada ficheiro. Os cinco do canto superior direito são o seu plano de trabalho para os próximos meses — e é a lista que se leva a uma reunião de prioridades, porque tem números.
Capítulo 03 Prática
A rede de segurança: testes de caracterização
Para mudar código com segurança é preciso testes. Para escrever testes é preciso perceber o código. A saída deste círculo tem nome e é a técnica central do ofício.
3.1 O teste que não sabe o que devia acontecer
Um teste normal afirma o comportamento correto. Num sistema legado ninguém sabe qual é — e às vezes o comportamento errado já é aquilo de que os clientes dependem. O teste de caracterização resolve isto invertendo o objetivo:
1. Escreva um teste que chama o código com uma entrada qualquer e afirma um resultado obviamente falso.
2. Corra. O teste falha e a mensagem de erro diz-lhe o valor verdadeiro.
3. Ponha esse valor no teste. Agora passa.
4. Repita para mais entradas, sobretudo as estranhas.
Não está a documentar o que o sistema devia fazer. Está a construir um alarme que dispara se o comportamento mudar. É tudo o que é preciso para refatorar.
Se o sistema tem um bug, o teste de caracterização regista o bug. Isto é correto e deliberado: neste momento o objetivo não é corrigir, é conseguir mexer sem partir. Marque-o com um comentário — "regista comportamento atual, provavelmente errado, ver ficha #482" — e corrija depois de a rede estar montada, num commit só para isso, onde a mudança do teste é a prova de que o comportamento mudou de propósito.
3.2 Testes de aprovação, para quando a saída é grande
Quando o resultado é um relatório, um HTML, um ficheiro ou uma estrutura enorme, escrever afirmações campo a campo é impraticável. A variante: guarde a saída inteira num ficheiro de referência aprovado e, a cada execução, compare tudo. Qualquer diferença aparece como diff.
- É desproporcionalmente eficaz em código legado: cobre milhares de campos com uma linha de teste.
- Exige eliminar o que varia sozinho — datas, identificadores aleatórios, ordem de coleções. Normalize antes de comparar, senão o teste falha todos os dias por nada.
- Perigo: aprovar um diff sem o ler. Um teste de aprovação vale exatamente o cuidado com que se aprovam as diferenças.
3.3 Por onde começar a cobertura
Não tente cobrir tudo
Um sistema grande sem testes não se cobre por inteiro — o projeto morre a meio e fica a sensação de fracasso. Cubra o que vai mexer, imediatamente antes de mexer.
Comece de fora
Testes na fronteira mais externa que conseguir — uma rota HTTP, um comando de linha — cobrem muito com pouco esforço e não exigem quebrar dependências. Lentos e grosseiros, mas é rede a sério, hoje.
Depois aperte para dentro
Com a rede externa a segurar, quebre dependências (cap. 4) e escreva testes menores e rápidos. Os externos vão-se tornando redundantes e podem reduzir-se.
Meça o que interessa
Cobertura global de um sistema legado é um número deprimente e inútil. Meça a cobertura das linhas que mudaram neste commit — essa sim é acionável, e sobe sozinha.
3.4 Quando nem isso dá
Há código que não se consegue chamar de teste nenhum sem o mudar antes — e mudá-lo sem rede é o que se queria evitar. Três saídas legítimas:
- Refatorações comprovadamente seguras da ferramenta. Renomear ou extrair método com o refactor automático do IDE, sem escrever nada à mão, é seguro o bastante para abrir caminho.
- Comparação em paralelo. Corra o código antigo e o novo lado a lado com tráfego real, sem usar o resultado do novo, e compare. Caro, e às vezes é a única prova possível.
- Aceitar o risco, em pequeno. Uma alteração minúscula, revista por outra pessoa, com plano de reversão. Melhor do que ficar paralisado — desde que seja decisão consciente e não hábito.
Escolha a função mais assustadora da sua lista de 1.1. Escreva um teste que a chama e afirma = 0. Corra. Ponha o valor verdadeiro. Já tem mais rede do que tinha há dez minutos — e nenhuma linha de produção mudou.
Capítulo 04 Prática
Costuras: onde se corta sem partir
A razão técnica pela qual um código não se testa quase nunca é a lógica. É a forma como obtém as coisas de que precisa.
4.1 O que é uma costura
Uma costura é um ponto onde se pode alterar o comportamento do programa sem editar o código nesse ponto. É onde se consegue entrar para pôr um duplo no lugar de uma dependência real.
Código impossível de testar é código sem costuras: cria dentro de si tudo aquilo de que depende, e não deixa entrada por lado nenhum.
Sem costura
function calcularFatura(id) {
const bd = new LigacaoBD(CONFIG.url);
const email = new ServidorEmail();
const hoje = new Date();
// ... 200 linhas
}
Testar isto exige uma base de dados, um servidor de email e uma máquina do tempo.
Com costura
function calcularFatura(id, deps) {
const { bd, email, agora } = deps;
// ... as mesmas 200 linhas
}
A lógica não mudou. Agora é testável, porque as dependências entram por fora.
A transformação acima chama-se parametrizar dependências e é a mais rentável do ofício: não muda comportamento nenhum, o compilador ou os testes existentes apanham os erros, e desbloqueia tudo o resto.
4.2 O catálogo de quebra de dependências
| Técnica | Quando | Como |
|---|---|---|
| Extrair e sobrepor chamada | Uma linha problemática no meio de um método bom | Mover essa linha para um método próprio; na subclasse de teste, sobrepor esse método |
| Parametrizar construtor | A classe cria as dependências no construtor | Aceitá-las como argumento, mantendo um construtor antigo que chama o novo com os valores reais — nada partido |
| Extrair interface | Depende de uma classe concreta pesada | Definir a interface com os poucos métodos usados e criar um duplo simples |
| Encapsular referência global | Estado global ou singleton por todo o lado | Passar tudo por um acesso só, que se pode substituir |
| Envolver método | Quer acrescentar comportamento sem tocar no existente | Renomear o original e criar um novo com o nome antigo, que chama o original e faz o extra |
| Injetar o relógio | Qualquer coisa que use a hora atual | Passar uma função que devolve a hora. Torna testáveis os casos de fim de mês, ano bissexto e fuso |
Em todas estas técnicas, o método antigo continua a existir e a funcionar. Nenhum chamador é obrigado a mudar, portanto a alteração é minúscula e revisível. Quem tenta mudar todos os chamadores de uma vez está a preparar um merge impossível e uma revisão que ninguém faz a sério.
4.3 O relógio, o acaso e a rede
Três dependências invisíveis que estragam mais testes do que todas as outras juntas, porque não parecem dependências:
- A hora atual. Um teste que passa às 10h e falha às 23h59 do dia 31 tem aqui a causa.
- Números aleatórios e identificadores. Injete o gerador; nos testes, um determinista.
- Chamadas de rede. Além de lentas, tornam os testes dependentes de coisas fora do seu controlo. Substitua na fronteira, não em cada sítio onde são usadas.
4.4 Não deixe a costura virar arquitetura
Quebrar dependências produz, temporariamente, código mais feio: parâmetros a mais, interfaces com um só implementador, construtores duplicados. Isso é aceitável como estado de passagem. O problema é parar aí e a feiura instalar-se — as interfaces com um implementador multiplicam-se e ninguém percebe porque existem.
Regra: cada quebra de dependência tem de vir com o teste que a justifica, no mesmo pull request. Sem o teste, é complexidade acrescentada por nada.
Procure no seu sistema todas as chamadas diretas à hora atual, ao gerador aleatório e à rede. Conte. Escolha a mais central e parametrize-a, mantendo a assinatura antiga a funcionar.
Capítulo 05 Prática
Refatorar em passos pequenos de mais
O erro clássico não é escolher a refatoração errada: é dar passos grandes. As transformações são simples e a disciplina é que é difícil.
5.1 As transformações que resolvem quase tudo
| Refatoração | Resolve | Risco |
|---|---|---|
| Renomear | Nomes que mentem — o defeito mais caro e o mais fácil de corrigir | Nenhum, com a ferramenta do IDE |
| Extrair função | Funções longas; dá nome a um bloco e revela intenção | Baixo, se não houver estado partilhado |
| Extrair variável | Condições ilegíveis: if (a && b || c) | Nenhum |
| Inline | Indireções que não pagam a viagem | Baixo |
| Substituir condicional por polimorfismo | Switch repetido em vários sítios sobre o mesmo tipo | Médio — e às vezes o switch era mais legível. Não faça por reflexo |
| Introduzir objeto de parâmetros | Funções com sete argumentos | Baixo |
| Separar consulta de comando | Funções que devolvem e alteram estado — a maior fonte de surpresas | Médio, e vale quase sempre a pena |
Não é preciso saber cinquenta nomes. Renomear e extrair função resolvem a maioria dos casos reais, e são as duas que a ferramenta faz sozinha sem risco.
5.2 O tamanho do passo
O sistema deve estar a funcionar ao fim de cada passo, não ao fim da sessão. Na prática: um passo, correr os testes, commit. Um passo, correr os testes, commit.
O sinal de que o passo foi grande demais é conhecido: "agora não compila e não sei porquê". Nessa altura a resposta certa é deitar fora e recomeçar mais pequeno — não continuar a martelar. Custa vinte minutos; insistir custa a tarde.
5.3 A regra do escuteiro, e o seu limite
"Deixe o acampamento mais limpo do que o encontrou" — melhore ligeiramente o código por onde passar. É bom conselho e tem duas armadilhas:
- A limpeza vai no mesmo commit que a funcionalidade e o diff fica ilegível. Separe: um commit só de refatoração, outro só de comportamento (a regra de 1.5).
- A limpeza cresce. Começa em renomear uma variável e acaba a reorganizar um módulo. Ponha um limite de tempo antes de começar; o que ficar por fazer vai para a lista, não para o pull request.
5.4 Quando a refatoração dá para trás
Caso · A grande limpeza de três semanas
Uma equipa dedicou três semanas a reorganizar o módulo de faturação: nova estrutura de pastas, camadas separadas, nomes coerentes. Fizeram-no num ramo próprio. Ao fim de três semanas, o merge tinha 400 ficheiros e conflitos com dez semanas de trabalho dos colegas. Levaram mais uma semana a resolver, introduziram dois bugs em produção, e a conclusão interna foi "refatorar não compensa".
Nada do que fizeram estava tecnicamente errado. O erro foi de método: um ramo longo transforma refatoração numa aposta com data de liquidação. Os mesmos três semanas, entregues em trinta pull requests pequenos ao longo de dois meses, teriam produzido o mesmo resultado sem um único conflito — e sem a conclusão errada que a equipa agora carrega.
E se os passos pequenos deixarem o código pior a meio?
Deixam, e é normal — durante horas ou dias há duas formas de fazer a mesma coisa. O que torna isto seguro é que o sistema funciona em cada passo, portanto pode-se parar em qualquer ponto sem estragar nada. Um estado intermédio feio mas funcional é infinitamente melhor do que um estado intermédio elegante que não compila. Se a passagem for demorar semanas, use uma bandeira ou uma abstração explícita (cap. 6) para que ninguém fique confuso sobre qual é o caminho novo.
Escolha um ficheiro coberto por testes. Durante trinta minutos, faça só renomeações e extrações, com commit a cada passo. Conte os commits. Menos de seis significa que os passos foram grandes.
Capítulo 06 Estratégia
Substituir o que não se pode desligar
Nos sistemas grandes o problema não é uma função difícil: é substituir uma parte inteira enquanto ela continua a servir clientes. Há três padrões e resolvem quase tudo.
6.1 Figueira estranguladora
A metáfora é botânica: a figueira cresce à volta da árvore hospedeira, ramo a ramo, até a substituir por completo — e nunca há um momento em que não haja árvore.
1. Ponha uma fachada à frente do sistema antigo. Todo o tráfego passa a atravessá-la; nada mais muda. Este passo sozinho já é uma entrega.
2. Escolha uma operação — a mais simples ou a mais valiosa — e implemente-a no sistema novo.
3. A fachada encaminha essa operação para o novo e tudo o resto para o antigo.
4. Repita. Uma operação de cada vez, cada uma com o seu deploy e a sua reversão.
5. Quando não sobrar tráfego no antigo, apague-o — e este passo tem de ser mesmo dado, senão fica com dois sistemas para sempre.
Entrega valor desde a segunda semana, é reversível a cada passo, e — o mais importante em termos organizacionais — não exige que ninguém aprove uma reescrita de dois anos. Sobrevive a mudanças de prioridade, que é a causa de morte mais comum das grandes migrações.
6.2 Ramificar por abstração
Quando a coisa a substituir está no meio do código e não atrás de uma fronteira de rede, não há onde pôr uma fachada. A alternativa faz-se toda no ramo principal:
- Crie uma abstração sobre o que existe — uma interface com as operações usadas.
- Passe todos os chamadores a usá-la, ainda com a implementação antiga por trás. Nada mudou de comportamento.
- Escreva a implementação nova por trás da mesma abstração.
- Comute com uma bandeira de funcionalidade: primeiro internamente, depois 1% do tráfego, depois tudo (Feature Flags).
- Apague a implementação antiga e a abstração, se já não servir para nada.
Vantagem decisiva sobre o ramo longo: todo o trabalho está no ramo principal desde o primeiro dia. Não há merge, não há conflitos, e qualquer pessoa pode continuar o trabalho.
6.3 A comparação em paralelo
Para lógica crítica onde um erro é caro — cálculo de preços, de impostos, de risco —, há um passo intermédio que compensa: correr as duas implementações com tráfego real, devolver o resultado da antiga, e registar as diferenças.
- Dá uma prova empírica em dados reais, com todos os casos-limite que ninguém imaginou.
- Cada diferença é uma pergunta: qual das duas está certa? Frequentemente descobre-se que a antiga tinha um bug que ninguém notara.
- Custa recursos e complexidade, e por isso é temporário. Defina de antemão quando desliga: "duas semanas sem diferenças por explicar".
6.4 O que decide entre eles
| Situação | Padrão |
|---|---|
| Fronteira de rede clara (rotas, serviço) | Figueira estranguladora |
| Componente no meio do código | Ramificar por abstração |
| Lógica crítica de cálculo | Comparação em paralelo, depois um dos dois acima |
| Esquema de dados | Expandir e contrair (cap. 7) |
| Nada disto é possível | Provavelmente a fronteira está mal escolhida — procure uma menor |
Escolha um subsistema que gostaria de substituir. Identifique a operação mais pequena que poderia mudar de casa sozinha, esta semana. Se não conseguir encontrar nenhuma, o problema não é a migração — é que não existe fronteira, e é isso que tem de criar primeiro.
Capítulo 07 Estratégia
Mudar dados sem parar o sistema
O código volta atrás com uma reversão. Os dados não. É por isso que a migração de esquema é a parte do trabalho legado onde os erros custam mais.
7.1 Expandir, migrar, contrair
A regra que atravessa tudo: nunca faça uma alteração de esquema que exija que o código antigo e o novo parem ao mesmo tempo. Durante um deploy, as duas versões coexistem — e durante uma reversão também.
Expandir. Acrescente o novo — coluna, tabela, campo — sem tirar nada. O código antigo continua a funcionar porque não sabe que existe.
Escrever nos dois. O código novo passa a escrever no antigo e no novo. Ainda lê do antigo. Reversível a qualquer momento.
Preencher o histórico. Copie os dados existentes em lotes pequenos, em segundo plano, com possibilidade de parar e retomar.
Ler do novo. Comute a leitura, atrás de uma bandeira. Continue a escrever nos dois — é isto que mantém a reversão possível.
Contrair. Só quando estiver confiante: pare de escrever no antigo, espere, e só então apague a coluna. Este passo é o único irreversível, e deve ser o mais aborrecido de todos.
Renomear uma coluna não existe como operação segura: é acrescentar uma nova, copiar, comutar e apagar a velha — cinco entregas, não uma. O mesmo vale para mudar o tipo de uma coluna ou tornar um campo obrigatório. A operação "simples" no editor de base de dados é a que derruba o sistema durante o deploy.
7.2 Migrações que não bloqueiam
- Nunca corra uma migração longa dentro de uma transação de deploy. Separe: a alteração de esquema é uma entrega, o preenchimento de dados é um trabalho em segundo plano.
- Lotes pequenos, com pausa. Mil registos de cada vez, com intervalo. Uma atualização de dez milhões de linhas de uma vez bloqueia a tabela e derruba a aplicação.
- Retomável e idempotente. O processo vai ser interrompido. Tem de poder voltar a correr sem duplicar nada.
- Índices criados sem bloqueio, onde o motor o permita. Um índice criado à moda antiga numa tabela grande é uma paragem não anunciada.
- Ensaie com um volume realista. Uma migração que corre em 2 segundos com mil linhas pode demorar seis horas com dez milhões, e isso não se descobre em produção.
7.3 Os dados que já lá estão são piores do que o esquema diz
Todo o sistema antigo tem dados que violam as regras que hoje se dão por garantidas: emails vazios, datas em 1970, referências para linhas apagadas, o mesmo campo com três formatos de três épocas diferentes, e registos de teste de 2014 em produção.
Antes de qualquer migração, conte: quantas linhas violam a regra que quer impor? A resposta decide entre "corrigir e avançar" e "a regra não pode ser imposta a todo o histórico". Descobrir isto a meio da migração, à meia-noite, é a versão cara da mesma pergunta.
7.4 Uma cópia de segurança que ninguém testou não existe
Antes de qualquer alteração irreversível: confirme que existe cópia, que é recente, e — o passo que quase ninguém dá — que já foi restaurada com sucesso pelo menos uma vez. Uma cópia por testar é uma esperança com nome técnico. E escreva o plano de reversão antes, não durante o incidente.
Escolha uma coluna do seu sistema com nome errado. Escreva as cinco entregas necessárias para a renomear em segurança, cada uma revertível sozinha. Depois compare com o que teria feito por instinto.
Capítulo 08 Decisão
Refatorar, reescrever, congelar ou apagar
Nem todo o código mau merece atenção. A decisão é económica, não estética — e a opção que toda a gente quer é quase sempre a errada.
8.1 As quatro opções
| Opção | Quando | Custo típico |
|---|---|---|
| Refatorar | Muda muito, é preciso, a lógica de negócio tem valor | Contínuo e pequeno; diluído no trabalho normal |
| Substituir aos poucos | Muda muito, a tecnologia é um beco sem saída | Alto, mas distribuído e reversível (cap. 6) |
| Congelar | Funciona, quase não muda, ninguém precisa de lá mexer | Quase zero — e é uma decisão legítima, não uma desistência |
| Apagar | Ninguém usa | Negativo: poupa manutenção, testes e atenção |
Em qualquer sistema com anos há funcionalidades que ninguém usa, rotas sem tráfego, opções ligadas por três clientes que já saíram, e ramos de código inalcançáveis. Instrumente e meça. Apagar é a única intervenção com retorno imediato e risco decrescente — e é a que menos vezes se considera, porque não parece trabalho.
Rede de segurança para apagar: registe a utilização durante um período que cubra os ciclos (um fecho de mês, um fecho de ano), avise, desligue com uma bandeira antes de apagar, e só depois remova o código.
8.2 A reescrita total: porque falha
A reescrita do zero é a opção que toda a gente quer e é a que falha mais. As razões são estruturais, não de execução:
- O sistema antigo tem anos de correções de casos que ninguém se lembra. Cada linha estranha é um bug corrigido. A reescrita começa por perdê-las todas e volta a encontrá-las uma a uma — em produção.
- Durante a reescrita, o antigo não para. Continua a receber funcionalidades novas, que a reescrita tem de perseguir. É um alvo em movimento.
- Zero valor até ao fim. Nada é entregue durante meses, o que a torna o primeiro alvo de qualquer mudança de prioridades.
- O sistema novo também vai envelhecer. Se a causa do estado atual foi falta de testes e de tempo, a mesma organização vai produzir o mesmo resultado outra vez.
A reescrita justifica-se quando a plataforma não tem futuro — linguagem sem suporte, dependência morta, restrição legal — e mesmo aí faz-se por fatias, com a figueira estranguladora.
8.3 A conta que decide
O custo de não fazer
Quanto tempo por mês se perde ali? Quantos incidentes? Quanto demora uma alteração pequena? Meça durante um mês antes de propor seja o que for.
O custo de fazer
Estime, e depois multiplique — a estimativa de refatoração é otimista por natureza, porque as surpresas só aparecem lá dentro.
A vida restante
A pergunta mais esquecida: este sistema vai existir daqui a três anos? Se está a caminho de ser descontinuado, refatorar é gastar num ativo que vai ser abatido.
O risco de não fazer
Diferente do custo. Uma parte estável e feia tem custo alto e risco baixo. Uma parte que gere pagamentos e que ninguém percebe tem risco que não se mede em horas.
8.4 Um caso de congelamento bem decidido
Caso · O módulo de impressão
Um módulo de geração de etiquetas, escrito em 2011, sem testes, com 4.000 linhas e um único ficheiro. Toda a gente queria reescrevê-lo. A medição mostrou: 2 alterações em 4 anos, zero incidentes, e o formato das etiquetas é definido por uma norma que não muda.
Decisão: congelar. Escreveram testes de aprovação sobre as saídas (uma tarde), documentaram como se corre localmente (uma página), e deixaram-no em paz. Os três meses que a reescrita ia custar foram para o módulo de faturação, que mudava todas as semanas.
Código feio que não muda não é dívida — é uma decisão já amortizada. Dívida técnica só é dívida quando cobra juros, e os juros cobram-se em alterações.
Pegue nos cinco módulos da sua lista de 1.1 e atribua a cada um uma das quatro opções de 8.1, com a razão em números. Se todos ficarem em "refatorar", provavelmente não mediu — em qualquer sistema real há pelo menos um para congelar e um para apagar.
Capítulo 09 Método aplicado · Demo
Uma função de 300 linhas, desmontada
Role devagar. Uma função real de faturação — impossível de testar, com um bug conhecido — resgatada em seis movimentos, sem nunca parar de funcionar.
Trezentas linhas, cinco responsabilidades, zero testes
É a função típica: cresceu por acumulação, cada acrescento foi razoável, e o resultado não é. Faz cinco coisas distintas — descontos, IVA, multa, PDF, persistência — e não se consegue chamar nenhuma isoladamente, porque a função constrói a ligação à base de dados e lê o relógio lá dentro.
Há ainda um bug conhecido na multa de atraso, no primeiro dia do mês. Está registado desde 2021 e ninguém o corrigiu — não por ser difícil, mas porque ninguém consegue provar que a correção não parte a faturação toda.
Testar de fora custa uma tarde e não toca em nada
Antes de mudar uma linha: doze faturas reais entram pela rota HTTP, e guarda-se tudo o que sai — o PDF e as linhas gravadas — como ficheiros de referência aprovados (cap. 3). É preciso normalizar o que varia sozinho: a data de emissão e o número de documento.
Estes testes são lentos e grosseiros. São também uma rede verdadeira, hoje, e obtida com zero linhas de produção alteradas — o que os torna a primeira coisa a fazer sempre.
Um valor por omissão poupa mudar todos os chamadores
As dependências passam a entrar por parâmetro, mas com um valor por omissão que reproduz exatamente o comportamento anterior. Nenhum dos 40 sítios que chamam esta função muda — o diff tem quatro linhas e a revisão demora um minuto (cap. 4).
O que se ganhou não é elegância: é que o relógio deixou de ser um facto do universo e passou a ser um argumento. Sem isto, o passo 4 é impossível.
Cinco extrações, cinco commits, testes verdes em cada um
Uma extração de cada vez, com a ferramenta do IDE sempre que possível — quatro das cinco foram automáticas e portanto sem risco. Correr os testes e fazer commit entre cada uma (a disciplina do cap. 5).
A função principal fica com 22 linhas que se leem como um índice: lê a fatura, calcula desconto, IVA e multa, monta o PDF, grava e envia. Ninguém decidiu qual devia ser a estrutura — ela estava lá, escrita nos comentários, à espera que alguém lhe desse nomes.
O bug de 2021 passa a ser um teste vermelho
calcularMulta é agora uma função pura: recebe dados e o relógio, devolve um número. Vinte e sete testes unitários escrevem-se em quatro minutos e correm em milissegundos — fim de mês, 29 de fevereiro, pagamento no próprio dia, valor zero.
Um falha. É o bug de 2021, que durante quatro anos foi uma história contada em reuniões e é agora uma linha reproduzível. A diferença entre as duas coisas é toda a diferença.
A ordem importa: primeiro a rede, só depois a correção
A correção é um caractere: > passa a >=. Vai num commit sozinho, sem nenhuma refatoração à mistura (a regra de 1.5). Uma das doze faturas aprovadas muda de resultado — e aprovar essa diferença é a prova de que o comportamento mudou onde se queria, e só aí. As outras onze ficam intactas, o que prova o resto.
Repare no que foi feito e no que não foi: nenhuma reescrita, nenhum ramo longo, nenhuma reunião de aprovação. Uma tarde de testes, oito commits pequenos, e uma função que passou de intocável a coberta — com um bug de quatro anos resolvido pelo caminho, quase como efeito secundário.
Aplique esta sequência à pior função do seu sistema, e pare onde for preciso parar. Mesmo chegar ao passo 1 — testes de aprovação na fronteira, zero linhas alteradas — já muda o que é possível fazer amanhã.
Capítulo 10 Operação
A dívida técnica como conversa, não como queixa
A frase "precisamos de tempo para refatorar" perde sempre. Não porque quem decide seja insensível, mas porque a frase não contém nenhuma informação que sirva para decidir.
10.1 O que a metáfora da dívida diz e o que esconde
Ward Cunningham cunhou a metáfora com um sentido preciso: entregar depressa com uma estrutura imperfeita é contrair um empréstimo — legítimo, útil, e com juros a pagar em cada alteração futura. A metáfora funciona porque explica a decisão a quem pensa em termos financeiros.
Passou a servir de rótulo para tudo o que se desgosta no código — incluindo código simplesmente mau, escolhas que não se percebem, e diferenças de gosto. Quando "dívida técnica" cobre tudo, deixa de significar alguma coisa e passa a ser ouvida como "os programadores querem tempo para arrumar".
Vale a pena reservar o termo para o que é mesmo dívida: uma escolha deliberada de velocidade sobre estrutura, com um custo recorrente identificável. O resto tem outros nomes, e é útil dizê-los.
10.2 Traduzir para a linguagem de quem decide
| Não diga | Diga |
|---|---|
| "O código de faturação está horrível." | "Cada alteração à faturação leva 3 dias em vez de meio. Fizemos 14 este ano: são 35 dias perdidos." |
| "Precisamos de refatorar." | "Duas semanas aqui fazem as próximas alterações passarem a meio dia. Paga-se em cinco meses." |
| "Isto não tem testes." | "Três dos cinco incidentes deste trimestre vieram deste módulo. A média de resolução foi 6 horas." |
| "É impossível manter isto." | "Só uma pessoa consegue mexer aqui. Se estiver de férias numa semana de fecho, não temos resposta." |
A diferença não é retórica: é que a coluna da direita tem números que podem ser confrontados e permite a quem decide compará-la com outras coisas. A coluna da esquerda pede confiança; a da direita apresenta um caso.
10.3 Onde arranjar os números
- Tempo por alteração, por área. Mesmo grosseiro, do sistema de tarefas.
- Incidentes por módulo. Já está registado; quase nunca é agregado assim.
- Alterações por ficheiro (cap. 2) — mostra onde o custo se repete.
- Alterações falhadas. Quantas correções precisaram de correção. É o indicador mais eloquente que existe e quase ninguém o conta.
- Fator autocarro. Quantas pessoas conseguem mexer em cada área crítica. Quando é 1, é risco de negócio e fala-se dele como tal.
10.4 Não peça um projeto: peça uma percentagem
Pedir "três meses para refatorar" é pedir a suspensão da entrega de valor, e perde. Pedir "20% de cada sprint para trabalho de estrutura, nas áreas que mais mexemos" costuma ganhar, por três razões: não pára nada, é reversível, e os resultados aparecem em semanas em vez de meses.
E ligue sempre ao trabalho em curso: "vamos mexer na faturação nas próximas três semanas; dois dias de preparação fazem as três semanas render mais". Refatoração ligada a trabalho já aprovado quase nunca é recusada.
10.5 Quando a resposta é não
Às vezes a decisão de não investir é correta e não foi tomada por ignorância: uma ronda de financiamento em curso, um cliente decisivo, um sistema a caminho de ser descontinuado. Registe o que disse e siga, sem transformar o assunto numa causa pessoal.
O que se faz nesses períodos: as intervenções de custo quase nulo — cobrir com testes o que se vai mexer, escrever o que se descobre, não piorar. E manter o registo, porque a decisão vai ser revista quando o contexto mudar, e nessa altura os números estarão prontos.
Escolha o módulo mais doloroso e reúna quatro números de 10.3. Escreva três frases na coluna da direita de 10.2. Leve-o a quem decide sem pedir nada — só para mostrar. A conversa que se segue é diferente de todas as anteriores.
Capítulo 11 Prática
IA em código legado: onde ajuda e onde é perigosa
É o contexto em que os assistentes de código são simultaneamente mais úteis e mais arriscados — e a razão das duas coisas é a mesma.
11.1 O que muda mesmo
| Tarefa | Utilidade | Porquê |
|---|---|---|
| Explicar um bloco que ninguém percebe | Alta | Uma explicação aproximada é um ponto de partida muito melhor do que nada — e é verificável contra o código |
| Gerar testes de caracterização | Alta | É trabalho mecânico e volumoso, e os testes são verificados ao correr: se passam, valem |
| Traduzir de uma linguagem morta | Média-alta | Bom primeiro rascunho, mas exige a comparação em paralelo do cap. 6 |
| Aplicar uma refatoração mecânica em 200 sítios | Média | Rápido, e cada sítio tem de ser revisto — o volume é que torna a revisão difícil |
| Decidir a arquitetura de destino | Baixa | Depende de restrições de negócio, de equipa e de história que não estão no código |
| Dizer se uma alteração é segura | Nenhuma | Isso não se opina: prova-se com testes |
11.2 O perigo específico
Um assistente vê o código e não vê aquilo que faz o código legado ser difícil: que aquela condição estranha protege um cliente grande; que aquele campo tem três formatos históricos; que o comportamento aparentemente errado é aquilo de que dependem 200 integrações.
Por isso a sugestão típica é limpar a estranheza — que é precisamente a alteração que causa o incidente. E a sugestão vem escrita com confiança e boa forma, o que a torna mais difícil de recusar numa revisão do que o mesmo erro escrito por uma pessoa hesitante.
Há um segundo risco, mais silencioso: refatorações grandes ficaram baratas de produzir e continuam caras de rever. Um pull request de 3.000 linhas gerado em cinco minutos consome duas horas de revisão atenta — que ninguém tem — e é aprovado por cansaço. O gargalo mudou de sítio, e é preciso desenhar o processo para o novo sítio.
11.3 Como usar sem se magoar
- Primeiro a rede, depois a IA. Testes de caracterização antes de aceitar qualquer alteração sugerida. Sem eles, não há como distinguir uma boa sugestão de uma catástrofe elegante.
- Peça explicações, não reescritas. "O que faz esta função e que casos-limite trata?" é uma boa pergunta, e a resposta verifica-se. "Reescreve isto melhor" é um convite ao problema de 11.2.
- Peça-lhe para gerar entradas de teste estranhas. É onde brilha: casos-limite que a pessoa não pensaria. E o resultado é validado ao correr.
- Limite o tamanho do que aceita de uma vez — a mesma disciplina de passos pequenos do cap. 5, agora aplicada à revisão em vez da escrita.
- Desconfie da simplificação. Quando o assistente propõe eliminar uma condição por parecer redundante, essa é a hora de ir ao histórico perceber quando é que ela apareceu (cap. 2).
11.4 A assimetria que fica
Se escrever código fica quase de graça e garantir que ele está certo continua a custar o mesmo, o valor desloca-se para quem sabe garantir: quem monta redes de segurança, quem desenha migrações reversíveis, quem sabe medir se o comportamento mudou. Nada disso é novo — é o conteúdo dos capítulos 3, 6 e 7 desta apostila —, mas passa de higiene a competência central.
É o argumento desenvolvido em O Futuro da Engenharia de Software. Sobre trabalhar com assistentes no dia a dia, veja Aceleração de Código com IA.
Peça a um assistente que explique a função mais opaca do seu sistema. Depois verifique cada afirmação contra o código e conte os erros. O número que encontrar diz-lhe quanto pode confiar nas explicações sobre este sistema — que é diferente de quanto se pode confiar em geral.
Capítulo 12 Ofício
Trabalhar assim, todos os dias
Nada disto é um projeto com data de fim. É um modo de trabalhar que faz a diferença entre um sistema que melhora devagar e um que piora devagar.
12.1 As sete regras que sobram de tudo
1 · Rede antes de mexer
Testes de caracterização primeiro, sempre. É o único capítulo que não tem exceção.
2 · Estrutura e comportamento, nunca juntos
Commits separados. Torna cada revisão possível e cada reversão precisa.
3 · Passos pequenos de mais
Se não compila e não sabe porquê, o passo foi grande. Deite fora e recomece.
4 · Ramo principal, sempre
Abstrações e bandeiras em vez de ramos longos. O ramo de três semanas é a forma mais fiável de fazer a organização concluir que refatorar não compensa.
5 · Refatore onde mexe
Não onde dói mais na leitura. O cruzamento muda-muito × é-complicado paga-se; o resto é gosto.
6 · Apague antes de melhorar
É a única intervenção com retorno imediato, e é sempre a última a ser considerada.
7 · Meça antes de argumentar
Números modestos ganham a discussões que a indignação perde.
E uma oitava, informal
Não julgue quem escreveu. Tinha outro prazo, outra informação e outra equipa — e o código que está a escrever hoje vai ser legado de alguém.
12.2 Escrever o que se descobriu
Muito do custo do código legado é conhecimento perdido: alguém já percebeu aquele módulo três vezes, e três vezes esse entendimento saiu pela porta. Duas práticas baratas resolvem a maior parte:
- Registos de decisão. Uma página por decisão relevante: contexto, opções, escolha, consequências. Escrita quando se decide, não depois. O valor não está na decisão — está no contexto, que é a parte que se perde e a que faz a decisão parecer absurda cinco anos depois.
- Um comentário quando o código não pode ser óbvio. Não a explicar o que faz — isso reescreve-se —, mas porquê: "o cliente X envia a data neste formato desde 2019; ver ficha 4471". É o comentário que impede a próxima pessoa de "limpar" a estranheza.
12.3 Deixar de produzir legado novo
A pergunta que fecha o assunto: porque é que este sistema ficou assim? Se a resposta for "não havia tempo para testes" e nada mudou, o sistema novo vai ficar igual. As causas são quase sempre organizacionais e conhecidas:
- Prazos negociados sem quem faz o trabalho.
- Nenhuma área com dono — o que não é de ninguém degrada-se.
- Revisão de código como formalidade, não como leitura.
- Rotatividade sem transferência de conhecimento.
- Nenhum espaço regular para trabalho de estrutura (a percentagem do cap. 10).
Melhorar o código sem tocar nestas causas é limpar uma casa com a torneira aberta. É trabalho útil e não resolve.
12.4 Para onde ir a seguir
- Testing & Automação — a rede do capítulo 3, com profundidade e com as ferramentas.
- Arquitetura & System Design — para decidir o destino das migrações do capítulo 6.
- Git & GitHub — a arqueologia do capítulo 2 e a disciplina de commits pequenos.
- DevOps, Docker & Kubernetes — entregas pequenas e reversíveis são o que torna tudo isto possível.
- Aceleração de Código com IA e O Futuro da Engenharia de Software — o capítulo 11, dos dois lados.
Na próxima alteração que fizer a código sem testes, gaste a primeira hora a escrever um teste de caracterização antes de mudar o que quer que seja. Faça isto cinco vezes. Ao fim das cinco, o hábito está instalado — e é o único hábito desta apostila que muda tudo o resto.