João Pedro

Command Palette

Search for a command to run...

Command Palette

Search for a command to run...

Blog

Dívida Técnica que Vale Pagar e a que Vale Ignorar, Como Decido no Dia a Dia

Nem toda dívida técnica precisa ser quitada. O critério prático que adotei tocando um SaaS meu pra decidir o que refatorar agora, o que adiar e o que deixar morrer junto com o código.

Balança equilibrando dois pratos: um cheio de cards de tickets de refatoração e outro com um foguete representando entrega de produto

No primeiro ano de um SaaS que comecei a desenvolver sozinho, gastei umas três semanas refatorando um módulo que praticamente ninguém usava. Era um pedaço de código feio, com classes longas, abstrações vazadas, lógica duplicada. Toda vez que eu passava por ali pra fazer outra coisa, sentia desconforto. Um dia decidi que ia consertar.

Quando terminei, o código estava bonito. Cobertura de testes melhor. Padrões consistentes. E ninguém, literalmente ninguém, percebeu a diferença. Não houve bug a menos, não houve feature mais rápida, não houve cliente mais feliz. Esse módulo foi descontinuado oito meses depois, junto com uma feature que perdeu prioridade.

Aquelas três semanas viraram minha lição mais cara sobre dívida técnica: nem toda dívida deve ser paga, e algumas devem ser deixadas em paz até o código morrer.

O critério que adotei depois desse erro

Eu não tenho um framework formal. Tenho três perguntas que faço antes de mexer em qualquer pedaço de código legado por motivo de "limpeza":

A primeira é quanto esse código muda?. Se eu olho o git log e o arquivo tem 80 commits no último ano, isso é código vivo. Vale arrumar. Se tem 2 commits em três anos, é código estável. Mesmo que seja feio, a feiura não está custando nada: ninguém precisa entender pra alterar.

A segunda é o quão arriscado é esse código quando ele falha?. Lógica de cobrança, autenticação, integrações de pagamento, código que quando quebra acorda alguém às 3 da manhã ou abre ticket de suporte caro. Esse merece atenção mesmo que esteja estável, porque quando alguém for mexer, vai ser sob pressão. Código que quando falha gera um log obscuro e ninguém percebe, esse pode esperar.

A terceira é alguém consegue entender isso em 30 minutos?. Se a resposta é não, e esse código está na rota de quem for pegar o projeto depois, inclusive eu daqui a seis meses, sem o contexto fresco na cabeça, a dívida tem custo composto. Cada vez que alguém precisa voltar ali, paga o pedágio de horas tentando entender. Aí o investimento se paga rápido.

Quando as três respostas convergem pra "sim, arruma", refatoro sem pensar duas vezes. Quando convergem pra "não", deixo. O problema é quando elas divergem.

A matriz que eu uso na cabeça

Pra deixar concreto, nesse projeto eu tenho uns 30 módulos. Eu mentalmente classifico cada um em quatro quadrantes:

Alta mudança + alto risco. Esse é o "paga já". Módulo de funil de vendas se encaixa aqui. Toda semana muda algo, e bug em funil corrompe o trabalho de meses do cliente. Quando eu vejo dívida acumulando aqui, paro o que estou fazendo.

Alta mudança + baixo risco. Esse é o "paga aos poucos, junto com cada feature". Tela de configurações, por exemplo. Muda muito, mas se quebrar é constrangedor e não catastrófico. Eu não abro PR de refatoração isolada: refatoro junto com a próxima feature que tocar ali.

Baixa mudança + alto risco. Esse é o mais traiçoeiro. Código que ninguém quer tocar (porque sabe que é arriscado), mas quando precisa tocar, é horrível. Aqui eu invisto em testes e documentação, não em refatoração. Se o código funciona há três anos, o risco de mexer pra "melhorar" é maior que o ganho. Mas eu quero, pelo menos, que a próxima pessoa que precisar mexer tenha rede.

Baixa mudança + baixo risco. Esse é o "deixa quieto". Foi exatamente onde eu errei nas minhas três semanas perdidas. Não importa o quanto seja feio, o custo de não mexer é zero.

Impacto em números

Horas/mês gastas em refatoração
~12

Antes eram 30+ sem critério claro

PRs de refatoração isolada
↓60%

A maioria virou refatoração junto com features

Bugs evitados em módulos críticos
4

Estimativa baseada em near-misses no último trimestre

O sinal que aprendi a ler: a mesma pessoa reescrevendo a mesma coisa

Tem um sinal específico que eu aprendi a ler como gatilho real para refatoração. Quando vejo o mesmo PR no mesmo arquivo, da mesma pessoa, pela terceira vez no ano, com mudanças pequenas e pontuais: esse arquivo está pedindo socorro.

Não é o tamanho do arquivo, não é a complexidade ciclomática, não é a métrica chique do SonarQube. É a frequência com que alguém precisa modificar ali, combinada com a dificuldade aparente de fazer cada mudança. O segundo sinal é o tempo que leva pra fazer cada modificação. Se um campo novo num formulário leva 20 minutos pra adicionar quando deveria levar 5, alguma coisa está mal.

Quando bate esses dois sinais juntos, eu paro e olho com cuidado. Quase sempre é uma abstração que estava certa pra um momento e ficou errada pra outro. A solução nunca é "refazer tudo". É identificar a abstração que perdeu o sentido e desfazer só ela, deixando o resto.

A regra do escoteiro, com asterisco

Tem uma regra famosa que diz "deixe o código mais limpo do que você encontrou". Eu segui ela à risca por uns dois anos, e o resultado foi PRs gigantes, com 30% de feature nova e 70% de refatoração não solicitada. Revisar minhas próprias mudanças virava um inferno, conflitos de merge horríveis, bugs introduzidos em código que eu nem precisava ter tocado.

Hoje sigo uma versão mais modesta: deixe o código tão limpo quanto precisa estar pra que sua mudança seja segura. Se eu precisei renomear uma variável pra entender o método, fica o rename. Se a função tinha 200 linhas e eu adicionei mais 10, eu não extraio 5 sub-funções "de brinde". Eu adiciono minhas 10 linhas e sigo em frente. A função continua tendo 210 linhas. Vai esperar.

A diferença é sutil mas importante: a regra antiga me fazia carregar peso que não era meu. A nova me faz ser responsável pela qualidade do que eu adicionei, não pela qualidade de tudo que eu passei.

Quando dívida vira hipoteca

Tem um momento, raro, em que dívida acumulada deixa de ser ruído e vira impedimento real. Eu já vi isso acontecer duas vezes nesse projeto. Os dois sinais foram parecidos:

O primeiro foi quando estimar features virou impossível. Toda task que tocava o módulo de relatórios voltava com "esse aqui é 2 ou 5 dias, depende de quanto eu vou encontrar quebrado". Não dava mais pra planejar nada com antecedência. Esse foi o momento em que parei de adicionar features por uma semana inteira pra reescrever a base do módulo.

O segundo foi quando voltar no módulo depois de algumas semanas longe virou um exercício de arqueologia. Num projeto que toco sozinho, o bus factor já é 1 por definição, mas tem um nível pior: quando nem eu, que escrevi cada linha, conseguia reconstruir o raciocínio sem parar tudo pra reler do zero. No dia em que percebi que estava com medo de mexer no meu próprio código porque não lembrava mais como ele funcionava, a dívida tinha virado risco real. Aí não é sobre código bonito, é sobre conseguir continuar entregando sem travar a cada mudança.

Esses dois momentos justificam parar tudo e pagar a dívida. Tudo que não chega nesse nível, eu adio. E adiar dívida não é covardia: é alocação de recurso. Tempo gasto refatorando um módulo morto é tempo não gasto entregando algo que mexe o ponteiro.

O que mudou na minha cabeça

A pergunta que eu me fazia antes era "esse código está bom?". Era uma pergunta estética, e como toda pergunta estética, tinha resposta fácil: quase nunca. Quase nenhum código está "bom" pelo critério do dev que olha de fora.

A pergunta que eu me faço hoje é "esse código está bom o suficiente pro que ele precisa fazer e pra quem precisa mantê-lo?". É uma pergunta de engenharia, e tem resposta mais incômoda mas mais útil. Na maior parte das vezes a resposta é "está, pode deixar". E é essa resposta que liberou meu tempo pra mexer no que realmente importa.

Dívida técnica não é um erro a ser corrigido. É um instrumento. Usado com critério, ela acelera a entrega. Pagar dívida que não precisa ser paga é tão caro quanto deixar dívida crítica acumulando: só que é mais difícil perceber, porque parece trabalho de qualidade.