Apostila · Engenharia

Código legado:
mudar o que
ninguém quer tocar

Código legado não é código velho: é código sem testes — e por isso código que ninguém tem confiança para mudar. A imagem do trabalho de programação como campo aberto descreve uma fração pequena do que se faz. O resto é acrescentar comportamento a sistemas que já existem, já têm clientes, e não podem parar.

12 capítulosdo primeiro teste à conversa com quem decide
1 demo ao vivode 300 linhas desmontadas em seis passos
12 exercíciospara fazer no sistema onde trabalha

↓ role para começar

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

Código legado é código sem testes

É 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 sintomaA 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 promessa da IA não muda isto — muda o que é escasso

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 é

Delimitação

É 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

Nunca mude comportamento e estrutura ao mesmo tempo

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.

Exercício 1.1 — O mapa do medo

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

Riscar código para o perceber

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):

PerguntaComo 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
O cruzamento que interessa

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

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.

Exercício 2.1 — O mapa de pontos quentes

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:

Não pergunta o que devia fazer. Regista o que faz.

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.

Uma consequência que confunde toda a gente

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.

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:

Exercício 3.1 — O primeiro teste de caracterização

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

Definição

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écnicaQuandoComo
Extrair e sobrepor chamadaUma linha problemática no meio de um método bomMover essa linha para um método próprio; na subclasse de teste, sobrepor esse método
Parametrizar construtorA classe cria as dependências no construtorAceitá-las como argumento, mantendo um construtor antigo que chama o novo com os valores reais — nada partido
Extrair interfaceDepende de uma classe concreta pesadaDefinir a interface com os poucos métodos usados e criar um duplo simples
Encapsular referência globalEstado global ou singleton por todo o ladoPassar tudo por um acesso só, que se pode substituir
Envolver métodoQuer acrescentar comportamento sem tocar no existenteRenomear o original e criar um novo com o nome antigo, que chama o original e faz o extra
Injetar o relógioQualquer coisa que use a hora atualPassar uma função que devolve a hora. Torna testáveis os casos de fim de mês, ano bissexto e fuso
Manter a assinatura antiga é a chave

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:

4.4 Não deixe a costura virar arquitetura

O erro do meio do caminho

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.

Exercício 4.1 — Encontre as três invisíveis

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çãoResolveRisco
RenomearNomes que mentem — o defeito mais caro e o mais fácil de corrigirNenhum, com a ferramenta do IDE
Extrair funçãoFunções longas; dá nome a um bloco e revela intençãoBaixo, se não houver estado partilhado
Extrair variávelCondições ilegíveis: if (a && b || c)Nenhum
InlineIndireções que não pagam a viagemBaixo
Substituir condicional por polimorfismoSwitch repetido em vários sítios sobre o mesmo tipoMédio — e às vezes o switch era mais legível. Não faça por reflexo
Introduzir objeto de parâmetrosFunções com sete argumentosBaixo
Separar consulta de comandoFunções que devolvem e alteram estado — a maior fonte de surpresasMé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

Verde a verde

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:

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".

O diagnóstico

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.

Exercício 5.1 — Trinta minutos, um ficheiro

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.

Porque funciona

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:

  1. Crie uma abstração sobre o que existe — uma interface com as operações usadas.
  2. Passe todos os chamadores a usá-la, ainda com a implementação antiga por trás. Nada mudou de comportamento.
  3. Escreva a implementação nova por trás da mesma abstração.
  4. Comute com uma bandeira de funcionalidade: primeiro internamente, depois 1% do tráfego, depois tudo (Feature Flags).
  5. 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.

6.4 O que decide entre eles

SituaçãoPadrão
Fronteira de rede clara (rotas, serviço)Figueira estranguladora
Componente no meio do códigoRamificar por abstração
Lógica crítica de cálculoComparação em paralelo, depois um dos dois acima
Esquema de dadosExpandir e contrair (cap. 7)
Nada disto é possívelProvavelmente a fronteira está mal escolhida — procure uma menor
Exercício 6.1 — A primeira fatia

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.

Onde as equipas se enganam

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

7.3 Os dados que já lá estão são piores do que o esquema diz

Olhe antes de assumir

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.

Exercício 7.1 — A renomeação em cinco passos

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çãoQuandoCusto típico
RefatorarMuda muito, é preciso, a lógica de negócio tem valorContínuo e pequeno; diluído no trabalho normal
Substituir aos poucosMuda muito, a tecnologia é um beco sem saídaAlto, mas distribuído e reversível (cap. 6)
CongelarFunciona, quase não muda, ninguém precisa de lá mexerQuase zero — e é uma decisão legítima, não uma desistência
ApagarNinguém usaNegativo: poupa manutenção, testes e atenção
Comece por procurar o que se apaga

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 conta que não se faz

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.

A lição

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.

Exercício 8.1 — Classifique cinco módulos

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.

Passo 0 — o inimigo

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.

Passo 1 — a rede

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.

Passo 2 — a costura

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.

Passo 3 — extrair

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.

Passo 4 — apertar

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.

Passo 5 — corrigir

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.

Exercício 9.1 — Os seis passos, na sua função

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.

Onde a metáfora se gasta

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 digaDiga
"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

10.4 Não peça um projeto: peça uma percentagem

A negociação que costuma resultar

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.

Exercício 10.1 — O caso em números

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

TarefaUtilidadePorquê
Explicar um bloco que ninguém percebeAltaUma explicação aproximada é um ponto de partida muito melhor do que nada — e é verificável contra o código
Gerar testes de caracterizaçãoAltaÉ trabalho mecânico e volumoso, e os testes são verificados ao correr: se passam, valem
Traduzir de uma linguagem mortaMédia-altaBom primeiro rascunho, mas exige a comparação em paralelo do cap. 6
Aplicar uma refatoração mecânica em 200 sítiosMédiaRápido, e cada sítio tem de ser revisto — o volume é que torna a revisão difícil
Decidir a arquitetura de destinoBaixaDepende 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 é seguraNenhumaIsso não se opina: prova-se com testes

11.2 O perigo específico

O contexto que falta é exatamente o que interessa

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

11.4 A assimetria que fica

O que a IA torna escasso

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.

Exercício 11.1 — A explicação verificada

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:

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:

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

Exercício 12.1 — A primeira hora de segunda-feira

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.