Refatorar uma base de código bagunçada sem quebrar a produção
Refatore uma base de código bagunçada com um plano prático para limites de pastas, nomeação, extração de módulos e PRs pequenos que mantêm o comportamento estável.

O que uma estrutura de base de código bagunçada realmente significa
Uma estrutura de base de código bagunçada não é "muitos arquivos." É quando o layout deixa de corresponder a como o produto funciona. Você não consegue dizer onde uma mudança pertence, então toda alteração parece arriscada.
Em repositórios reais, isso costuma aparecer como responsabilidades misturadas: código de UI falando diretamente com o banco de dados, checagens de auth espalhadas pelas páginas e pastas de "utils" que silenciosamente guardam metade da lógica de negócio. Outro cheiro comum são importações circulares (A importa B, B importa A). Elas podem criar comportamento estranho em tempo de execução e empurrar as pessoas para hacks só para a aplicação iniciar.
A nomeação é o problema mais silencioso. Você verá três maneiras diferentes de nomear a mesma coisa (userService, users.service, user_manager), ou pastas que não significam nada (misc, temp, old2). Pessoas duplicam código porque não conseguem encontrar o lugar certo, e a bagunça se compõe.
Você sente problemas de estrutura no trabalho do dia a dia mais do que em diagramas de arquitetura:
- Mudanças pequenas levam horas porque você persegue dependências por todo o repositório.
- Bugs se repetem porque correções caem em um lugar enquanto o comportamento real vive em outro.
- Releases dão medo porque uma edição "minúscula" pode quebrar algo não relacionado.
Uma limpeza vale a pena quando a estrutura é o principal fator que te desacelera ou causa bugs evitáveis. Se o produto está estável, mudanças são raras e a equipe entende o layout atual, uma grande refatoração pode esperar. Frequentemente a melhor ação é "toque e arrume": melhorar a estrutura apenas na área que você já está mudando para uma feature ou bug.
"Não quebrar tudo" significa que o comportamento permanece o mesmo enquanto a estrutura muda. Usuários não devem notar. O ganho é mudanças mais seguras: diffs menores, limites mais claros e menos surpresas durante testes e release.
Defina objetivos e guardrails antes de mexer no código
Refatorações vão mais rápido quando você consegue responder uma pergunta: o que estamos tentando melhorar? Escolha um alvo principal, como acelerar mudanças, reduzir bugs ou facilitar o onboarding. Se você tentar consertar tudo ao mesmo tempo, vai gastar uma semana movendo arquivos sem deixar o sistema mais fácil de trabalhar.
Em seguida, escreva o que precisa permanecer estável. Estabilidade não é só "os testes passam." É o contrato real que seu app tem com usuários e outros sistemas.
Combine guardrails antes do primeiro commit:
- Não negociáveis: APIs públicas, esquema do banco, rotas e fluxos-chave de UI que não podem mudar.
- Escopo: o que está dentro do alcance e o que está fora (por enquanto).
- Time box: um limite claro (por exemplo, 2–3 dias) antes de reavaliar.
- Prova: quais checagens precisam estar verdes (testes, lint, build, mais um teste rápido de fumaça).
- Feito significa: "essas pastas estão limpas e documentadas", não "o repositório todo está perfeito."
Exemplo: você herdou um app gerado por IA onde o auth funciona "na maioria das vezes", rotas estão duplicadas e arquivos vivem em lugares aleatórios. Seus guardrails podem ser: não alterar o fluxo de login, cookies de sessão ou tabelas do banco esta semana. O objetivo é apenas colocar o auth atrás de um módulo e mover arquivos relacionados para uma pasta única com nomes consistentes. Isso te dá uma vitória que pode ser entregue sem criar um novo bug misterioso.
Se estiver trabalhando com outros, coloque os guardrails por escrito e consiga um “sim” rápido. Isso evita debates no meio da refatoração e mantém as reviews focadas em resultados, não em opiniões.
Mapa rápido da estrutura atual (30 a 60 minutos)
Antes de mover pastas, passe uma hora focada fazendo um mapa grosseiro. Isso reduz risco porque você para de adivinhar onde o comportamento começa e onde ele se espalha.
Comece pelos pontos de entrada: onde o sistema acorda e começa a trabalhar. Muitos apps têm mais de um: um servidor web, um worker agendado, um consumidor de fila, um script CLI e às vezes um runner de migração.
Depois encontre a dor: os arquivos e pastas que as pessoas tocam constantemente. Alto churn frequentemente significa que esses arquivos fazem demais ou tudo depende deles.
Mantenha um mapa simples aberto enquanto trabalha:
- 3 a 6 pontos de entrada (o que roda primeiro e como começa)
- 5 a 10 arquivos ou pastas de alto churn (onde as mudanças continuam caindo)
- 3 a 5 pontos quentes de dependência (importados por muitos outros módulos)
- Uma frase para o propósito de cada pasta de topo (mesmo que seja feio)
Um ponto quente de dependência é onde refatores vão morrer. Um sinal rápido é um único arquivo "utils" que contém helpers de auth, formatação de data, código de banco e chamadas de API. Se é importado por todo lado, toda mudança é arriscada.
Mantenha o mapa honesto, não lisonjeiro. "src/helpers: coisas aleatórias que não sabíamos onde colocar" é útil. Depois você pode transformar essa frase em um plano.
Defina limites de pasta que reduzam acoplamento
Se você quer reorganizar sem quebrar a produção, comece desenhando fronteiras claras. O objetivo é simples: uma mudança em uma área não deveria forçar edições por todo o repositório.
Escolha um modelo de pasta que você consiga explicar em uma frase
Escolha o modelo mais simples que combine com a forma de pensar da sua equipe:
- Por feature: tudo relacionado a "faturamento" fica junto (UI, lógica, dados).
- Por camada:
ui/,domain/,data/são separados e as features ficam dentro deles. - Híbrido: pastas de feature no topo, com
ui/,domain/,data/dentro de cada feature.
Qualquer um desses pode funcionar. O que importa é que "Onde este arquivo deve ir?" tenha uma resposta óbvia.
Escreva regras simples para o que pertence onde
Defina fronteiras com linguagem do dia a dia. Por exemplo:
- UI: componentes, telas, formulários e lógica de exibição.
- Domain: regras e decisões de negócio (cálculo de preço, checagens de elegibilidade).
- Acesso a dados: clientes de API, queries ao banco e persistência.
Depois acrescente uma curta lista de "não cruza" para prevenir acoplamentos acidentais enquanto refatora:
- UI não importa direto de acesso a dados.
- Domain não importa UI.
- Nenhuma chamada direta ao BD fora do módulo de data access.
- Não ler segredos de env fora de um único módulo de config.
- Nenhuma pasta de feature alcança internamente outra feature.
Um cenário rápido: se uma tela de checkout precisa do total do pedido, ela deve chamar uma função de domínio (como calculateTotal). Essa função pode chamar o acesso a dados por uma interface pequena, não por SQL cru ou um cliente de API direto.
Finalmente, decida propriedade. Nomeie um revisor (ou um pequeno grupo) para cada área para que quebras de fronteira sejam pegas cedo.
Convenções de nomeação e regras pequenas que mantêm a ordem
Refatores frequentemente falham por um motivo chato: as pessoas não sabem onde colocar o próximo arquivo, então a bagunça volta a crescer. Convenções de nomeação soam chatas, mas removem decisões diárias e impedem exceções do tipo "só desta vez".
Escolha convenções que sua equipe realmente seguirá. Se uma regra precisa de debate sempre que aparece, é rígida demais ou esperta demais. O objetivo é consistência, não perfeição.
Alguns básicos para anotar:
- Nomes de arquivos e pastas: escolha kebab-case (
user-profile.ts) ou camelCase (userProfile.ts) e mantenha. - Singular vs plural: por exemplo, use singular para pastas de módulo (
invoice/) e plural apenas para coleções (invoices/). - Exports: prefira exports nomeados para código compartilhado; evite default exports salvo razão clara.
- Index files: ou proíba, ou limite-os a re-exportar a API pública, para que imports fiquem previsíveis.
- Um conceito por arquivo: se um arquivo cresce além de uma tela ou duas, divida por responsabilidade.
Um pequeno "exemplo dourado" de pasta ajuda mais que um documento longo. Mantenha-o pequeno e perto do que você constrói com mais frequência:
features/
auth/
api.ts
routes.ts
components/
LoginForm.tsx
index.ts
Quando alguém adicionar uma nova tela de auth, pode copiar o padrão sem adivinhar.
Uma regra leve para código novo ajuda a manter a refatoração mesmo se a limpeza não estiver concluída:
- Arquivos novos seguem as novas regras de nomeação e vivem nas novas pastas.
- "Toque e arrume": se você modificar um arquivo, mova-o para o lugar certo ou corrija o nome.
- Nada de novas gavetas de tralha como
misc.
Se isso parecer difícil de aplicar, geralmente é sinal de que a estrutura ainda não combina com como o app funciona.
Passo a passo: extrair um módulo com segurança
Escolha um alvo pequeno e de baixo risco primeiro. Um bom começo é um utilitário compartilhado que muitos arquivos importam (formatação de data, feature flags, validação de entrada) ou uma única feature que é em grande parte autocontida. Se tentar puxar uma área central de domínio no primeiro dia, você vai passar o tempo perseguindo surpresas.
Uma refatoração segura muda a forma do código sem alterar o que ele faz. O truque é criar uma fronteira, então mover o código por trás dela aos poucos.
Sequência segura de extração
Comece escrevendo a promessa do novo módulo em uma frase: o que ele faz e o que não faz. Depois:
- Congele o comportamento atrás de uma interface. Uma função ou classe exportada é suficiente. Mantenha os internos feios por enquanto; deixe a parte externa simples.
- Mova em commits pequenos. Atualize imports conforme avança. Se precisar renomear coisas, faça depois do movimento para que os diffs fiquem claros.
- Cheque após cada movimento. Rode a app e os testes. Se não tiver testes, faça uma checagem manual rápida do fluxo que usa o módulo.
- Delete o código antigo por último. Só depois de provar que nada depende dele (procure imports, verifique logs de runtime, confirme que não restam cópias duplicadas).
- Adicione alguns testes focados na fronteira. Um happy path, um caso de borda e um caso de falha geralmente são suficientes.
Exemplo: você nota três arquivos diferentes respondendo "o usuário está logado?" de formas ligeiramente diferentes. Crie um módulo authSession com getSession() e requireUser(). Primeiro, faça essas funções chamar o código antigo. Depois mova a lógica para dentro do módulo, atualize um chamador por vez e adicione 2 a 3 testes que assegurem os resultados esperados.
A extração também expõe acoplamentos escondidos: globais, responsabilidades misturadas, valores secretos em arquivos aleatórios e helpers "temporários" que viraram permanentes.
Como entregar a refatoração usando PRs incrementais
Esse trabalho é mais seguro quando tratado como uma série de pequenas entregas, não uma grande reescrita. PRs incrementais mantêm o raio de impacto pequeno. Quando algo quebra, você identifica rápido e reverte sem pânico.
Mantenha PRs pequenos e sem surpresas
Mire em um tipo de mudança por PR: mover uma pasta, renomear um grupo de arquivos ou extrair um módulo. Se sentir vontade de "arrumar mais coisas enquanto está aqui", anote e faça depois.
Faça mudanças mecânicas primeiro: moves, renames, formatação, atualização de caminhos de import. Elas são fáceis de revisar porque o comportamento permanece o mesmo.
Depois que a estrutura estiver estável, faça mudanças de comportamento em PRs separados (correção de lógica, mudanças de formato de dados, tratamento de erros). Misturar estrutura e comportamento é como refatores viram bugs misteriosos.
Escreva descrições de PR que ajudem os revisores a verificar
Facilite a revisão incluindo:
- O que mudou (uma frase)
- O risco (o que pode quebrar, e onde)
- Como verificar (um teste manual curto mais checagens automatizadas chave)
- Plano de rollback (geralmente "reverter este PR")
Exemplo: "Moveu código relacionado a auth para modules/auth e atualizou imports. Risco: ligação da rota de login. Verificar: signup, login, logout, refresh de sessão.".
Se mais de uma pessoa estiver trabalhando, combinem uma ordem que evite conflitos. Regras: mescle PRs de boundary primeiro, depois extrações que dependem do novo layout. Atribua ownership para que duas pessoas não editem os mesmos arquivos de alto churn ao mesmo tempo.
Mantenha o comportamento estável enquanto a estrutura muda
O objetivo é entediante: a app deve se comportar do mesmo jeito para os usuários. A maioria das refatorações falha porque pequenas mudanças de comportamento escorregam sem ser notadas.
Comece com checagens de fumaça que correspondam a como o produto é usado. Mantenha a lista curta para realmente rodá-la sempre:
- Signup, login, logout (e reset de senha)
- Criar o objeto principal do app (pedido, post, projeto, ticket)
- Atualizar e deletar esse objeto
- Uma ação financeira (checkout, fatura, mudança de assinatura), se houver
- Uma ação de mensagem (email, notificação, webhook), se houver
Testes são armadilhas, não um projeto de perfeição. Se a cobertura for fraca, escreva 2 a 5 testes de alto valor ao redor dos fluxos acima e pare por aí. Alguns testes end-to-end ou de integração costumam pegar mais erros de refatoração do que dezenas de unit tests pequenos.
Fique atento a falhas silenciosas. Auth pode quebrar sem erros óbvios (cookies, armazenamento de sessão, caminhos de redirect). Pagamentos e emails podem falhar “com sucesso” se callbacks não estiverem ligados ou jobs em background não estiverem rodando. Filas podem perder tarefas se nomes de jobs ou caminhos de import mudarem durante a extração.
Faça as falhas visíveis rápido. Confirme que você consegue ver logs e erros em um único lugar, e que os erros têm contexto suficiente (user ID, request ID, nome do job). Durante a refatoração, adicione algumas linhas de log focadas em fronteiras críticas (auth, billing, emails de saída) e remova-as quando as coisas estabilizarem.
Armadilhas comuns que fazem a refatoração descarrilar
A maioria das refatorações falha quando pequenas mudanças se acumulam até que ninguém tenha certeza do que é seguro. Fique de olho nessas armadilhas cedo.
A armadilha do "ainda compila"
Mover arquivos pode parecer inofensivo, especialmente quando os testes estão verdes. Mas se você mover coisas sem decidir qual é a interface pública, acaba quebrando imports pelo app ou forçando todo mundo a "só atualizar o caminho" em dezenas de lugares.
Um padrão mais seguro é manter pontos de entrada estáveis (por exemplo, um arquivo index por módulo que define a API pública) e mudar caminhos internos por trás dessa interface.
Também evite criar uma nova gaveta de tralha como solução rápida. Um misc ou utils novo vira o mesmo problema em uma semana. Se algo não tem um lar claro, trate isso como sinal de que suas fronteiras estão confusas.
Portas dos fundos escondidas entre módulos
A extração não termina quando os arquivos são movidos. Termina quando o módulo pode ficar de pé sozinho.
As portas dos fundos usuais são cross-imports (módulo A importa secretamente B) e globais compartilhadas (objetos de config, singletons, caches mutáveis) que deixam o código atravessar fronteiras.
Armadilhas que comumente derrubam a qualidade da revisão:
- Renomear muitos arquivos e símbolos ao mesmo tempo que move pastas
- Misturar refatoração com novas features
- Deixar a API antiga e a nova no ar "temporariamente" e nunca remover o caminho antigo
- Fazer um PR gigante que os revisores só conseguem folhear
- Mudar comportamento acidentalmente (por exemplo, mover código de auth e alterar a ordem de middlewares)
Exemplo: uma equipe extrai um módulo auth, mas algumas telas ainda importam um global currentUser da pasta antiga. Tudo parece bem até a produção, onde uma cold start carrega módulos em ordem diferente.
Checklist rápido para cada PR de refatoração
PRs pequenos são como você reorganiza estrutura sem transformar a produção em um jogo de adivinhação.
Antes de abrir o PR
Se você não consegue explicar a mudança em duas frases, o PR provavelmente está grande demais.
- Mantenha o escopo em uma correção de boundary, um conjunto de renomeações ou uma movimentação de módulo.
- Liste os pontos de entrada que pode afetar (rotas, comandos CLI, jobs background, imports de outros pacotes).
- Note um plano de rollback (geralmente "reverter este PR"; às vezes um export de compatibilidade por uma release).
- Rode lint e testes localmente e escreva 2 a 3 checagens de fumaça.
- Confirme que não há mudança de comportamento a menos que o título do PR diga isso.
Exemplo concreto: se você está movendo helpers de auth para um novo módulo, destaque rota de login, refresh de token e imports de middleware como pontos de entrada. Checagens de fumaça: signup, sign in, sign out, refresh de token, carregar página protegida.
Depois de abrir e mesclar o PR
Faça uma checagem rápida antes da próxima fatia:
- Verifique que não introduziu importações circulares (um build mais checagem de dependências geralmente revela).
- Remova pastas temporárias criadas durante o movimento, ou renomeie-as para o lar final.
- Garanta que a equipe concorda onde novos arquivos vão (um comentário curto no PR costuma ser suficiente).
- Atualize notas simples: um README curto na pasta ou um comentário breve explicando o propósito da pasta.
- Confirme que o PR não mudou silenciosamente APIs públicas (exports, caminhos de arquivos que outros importam).
Próximos passos: manter o momentum (e saber quando pedir ajuda)
Escolha uma feature pequena e real e use-a como seu caminho de prova. Se você trabalha a partir de um protótipo gerado por IA, uma fatia inicial comum é: checagens de auth puxadas para fora de componentes de UI, acesso a dados reunido em um módulo e a UI reduzida à renderização.
Refatore uma fatia vertical primeiro e adie o resto. Por exemplo: pegue "Sign in -> carregar conta -> mostrar dashboard" e torne isso chato e previsível. Mova checagens de auth para um lugar só, puxe acesso a dados para um módulo único e mantenha a UI focada em exibir.
Guarde debates maiores (renomeações completas de pastas, troca de framework, modelagem de domínio perfeita) para depois, quando tiver momentum e uma rede de segurança.
É frequentemente mais rápido pedir ajuda quando o código está profundamente emaranhado (tudo importa tudo), você vê bandeiras de segurança (chaves expostas, queries inseguras) ou a autenticação já é pouco confiável.
Se sua base veio de ferramentas como Lovable, Bolt, v0, Cursor ou Replit e você passa mais tempo desembaraçando do que construindo, FixMyMess (fixmymess.ai) pode rodar uma auditoria gratuita de código para identificar as áreas mais arriscadas e a ordem mais segura de correção antes de você começar a mover tudo.
Perguntas Frequentes
Como eu sei se minha base de código é realmente “bagunçada” ou apenas grande?
Uma estrutura bagunçada aparece quando você não consegue identificar onde uma mudança deve ser feita, então cada edição parece arriscada. Sinais comuns incluem responsabilidades misturadas (UI tocando direto no banco de dados), lógica de negócio escondida em “utils”, nomes inconsistentes para o mesmo conceito e importações circulares que criam comportamento estranho em tempo de execução.
Quando vale a pena fazer uma refatoração estrutural, e quando devo esperar?
Faça quando a estrutura estiver claramente retardando a entrega ou causando bugs recorrentes, não só porque está feia. Se o produto estiver estável, as mudanças forem raras e a equipe conseguir trabalhar com segurança, uma grande limpeza pode esperar enquanto você aplica a abordagem “toque e arrume” nas áreas que já modifica.
Quais guardrails devo definir antes de começar a mover arquivos?
Escolha um objetivo principal, como “as mudanças em auth devem ser previsíveis”, e escreva os não negociáveis, como rotas, esquema do banco e fluxos críticos de usuário. Acrescente um time box curto e uma definição clara de “feito” para garantir que você entregue melhorias em vez de mover arquivos para sempre.
Qual a forma mais rápida de entender um repositório antes de refatorá-lo?
Gaste 30–60 minutos mapeando pontos de entrada e hotspots antes de mexer na estrutura. Encontre onde o sistema inicia, quais arquivos mudam com mais frequência e que módulos são importados por todo lado. Escreva uma frase honesta sobre o propósito de cada pasta de topo.
Como escolho limites de pastas que não virem motivo de discussão?
Comece com uma regra simples que dê para aplicar sempre, como “UI não chama o banco” e “segredos só vêm de um módulo de config”. Depois escolha um modelo de pastas (por feature, por camada, ou híbrido) com base em como sua equipe naturalmente fala sobre o produto, não no que esteja na moda.
Quais convenções de nomeação realmente evitam que a bagunça volte?
Mantenha as convenções entediantes e consistentes para que ninguém precise pensar toda vez que criar um arquivo. Escolha um estilo de nomes, defina como usar index files e mantenha um pequeno “exemplo dourado” que mostre o padrão a ser repetido.
Qual é uma forma segura de extrair um módulo sem quebrar a produção?
Extraia um módulo pequeno atrás de uma interface limpa primeiro e mova os chamadores um a um. Mantenha o comportamento igual, verifique após cada movimento e delete o código antigo só depois de provar que nada depende dele.
Quão pequenos devem ser os PRs de refatoração e o que devo evitar misturar?
Limite cada PR a um tipo de mudança e mantenha refatorações separadas de mudanças de comportamento. Se um PR misturar movimentação de arquivos com alterações de lógica, os revisores não conseguem avaliar o risco e é mais provável que surja um “bug misterioso”.
E se eu não tiver bons testes — como mantenho o comportamento estável?
Comece com um curto teste de fumaça que reflita fluxos reais de usuário e rode-o sempre que mover estrutura. Se os testes forem fracos, adicione algumas verificações de alto valor nas fronteiras de módulo (auth, cobrança, email) para que erros de refatoração apareçam rápido.
Quando devo pedir ajuda em vez de tentar refatorar uma base gerada por IA por conta própria?
Se tudo importa tudo, a autenticação já é instável, segredos estão expostos ou há padrões de consulta inseguros, você vai perder tempo desembaraçando sozinho. FixMyMess pode rodar uma auditoria gratuita em bases geradas por IA (por exemplo, de Lovable, Bolt, v0, Cursor ou Replit) e identificar rapidamente as áreas mais arriscadas e a ordem mais segura para consertá-las, frequentemente deixando o projeto pronto para produção em 48–72 horas.