Separar um arquivo utils com limites de módulo iterativos e claros
Separe um arquivo de utils sem uma reescrita arriscada: extraia módulos pequenos passo a passo, defina fronteiras claras e mantenha mudanças seguras para enviar.

Por que um único arquivo utils vira um problema
Um único arquivo de utils geralmente começa com boas intenções: colocar pequenos helpers em um lugar. Aí vêm os prazos, a equipe muda, e qualquer coisa sem um lar óbvio acaba indo para utils. Com o tempo, “dividir um arquivo utils” deixa de ser uma tarefa de limpeza agradável e vira um bloqueador para mudanças seguras.
O problema central é propriedade. Quando tudo vive junto, nada parece ser responsabilidade de alguém. As pessoas adicionam “só mais um helper” sem pensar em nome, dependências ou se aquilo pertence a uma parte específica da aplicação. Logo, uma mudança que deveria ser simples vira um jogo de adivinhação: quem usa isso e o que vai quebrar se eu mexer?
Sintomas típicos aparecem rápido:
- Responsabilidades não relacionadas misturadas (formatação, chamadas de API, auth, banco de dados, ajustes de UI)
- Imports circulares porque muitas partes do app dependem do mesmo arquivo
- Funções “pequenas” com efeitos colaterais escondidos (ler vars de ambiente, escrever em storage, mutar estado global)
- Testes difíceis de escrever porque importar um helper puxa metade do app
- Lógica sensível à segurança espalhada por helpers aleatórios (parsing de tokens, regras de senha, tratamento de segredos)
O objetivo não é estética. É permitir mudanças mais seguras, testes mais fáceis e fronteiras claras para que você possa atualizar uma área sem surpreender outra.
Defina expectativas desde o início: isto é uma limpeza iterativa, não uma reescrita. Você está movendo código em pequenos passos mantendo o comportamento igual.
Como saber quando seu arquivo utils virou um catch-all
Um arquivo utils vira um catch-all quando silenciosamente se torna uma dependência para todo o resto. O maior sinal de aviso não é a contagem de linhas. É que uma alteração em um “helper” pode quebrar metade do app.
Fique atento a estes sinais:
- Mistura tarefas não relacionadas como checks de auth, queries ao banco, formatação e chamadas de rede.
- Helpers importam módulos específicos do app (models, routes, controllers) em vez de permanecer genéricos.
- Funções têm efeitos colaterais, mas parecem inofensivas pelo nome.
- Você edita utils para entregar features que não têm nada a ver com utilitários.
- Cria atrito constante: conflitos de merge e surpresas “por que isso quebrou aquilo?”.
Tamanho é apenas um sintoma. Acoplamento é o problema real. Quando um arquivo sabe sobre regras de auth, schema de dados e o cliente HTTP, dependências ocultas se acumulam. Cada pequena mudança exige testes amplos.
Helpers puros como capitalize(), clamp(), ou toSlug() são ok para permanecer como utilitários. Se uma função depende do banco de dados, do estado de auth ou de segredos, não é um utilitário. É um módulo esperando para existir.
Escolha fronteiras de módulo claras antes de mover código
Uma fronteira de módulo é uma promessa simples: tudo aqui trata de um tipo de trabalho, com entradas e saídas previsíveis. Você não precisa de um design perfeito. Você precisa que o próximo passo seja óbvio e seguro.
Comece com categorias que as pessoas já entendem. Para muitos apps, esses baldes cobrem a maior parte da bagunça:
- strings
- dates
- validation
- api
- auth
A fronteira mais importante é entre helpers puros e helpers com efeitos colaterais.
Helpers puros são previsíveis: mesma entrada, mesma saída, sem efeitos externos. São os mais fáceis de mover primeiro porque você pode testá-los com exemplos simples.
Helpers com efeitos colaterais tocam o mundo exterior: variáveis de ambiente, local storage, cookies, tempo, aleatoriedade, chamadas de rede, bancos de dados e estado global do app. Também podem logar, mutar objetos ou lançar exceções de maneiras que outro código depende. Trate-os como de maior risco e mantenha-os agrupados por responsabilidade (por exemplo, não misture auth e comportamento do cliente API no mesmo módulo).
Mantenha a nomeação consistente, mesmo que seja chata. Decida cedo se quer pastas ou arquivos planos, e siga uma convenção simples para que revisores consigam identificar módulos “misc” antes que cresçam.
Faça um mapa rápido do que está dentro (sem planejar demais)
Antes de dividir um arquivo utils, faça uma imagem rápida e honesta do que há nele. Você não precisa documentar perfeitamente. Precisa de clareza suficiente para mover código sem adivinhar.
Comece com um inventário das exportações. Liste cada função exportada e onde ela é importada. Se um helper é usado em 30 lugares, precisa de cuidado extra. Se é usado uma vez, é um ótimo candidato inicial.
Um inventário leve costuma ser suficiente:
- Nome da função e uma linha de descrição
- Onde é importada (alguns chamadores principais)
- Efeitos colaterais (env, storage, logging, network)
- Onde deveria viver (date, auth, formatting, db)
- Nível de risco: baixo (puro), médio (config), alto (stateful ou segurança)
Quando você conseguir ver a lista, procure um primeiro cluster para extração: funções puras que não leem estado global e não fazem I/O. Mover essas primeiro dá ganhos rápidos e um padrão limpo para repetir.
Passo a passo: extraia módulos iterativamente com baixo risco
A maneira mais segura de dividir um arquivo utils é progredir em fatias pequenas que sejam fáceis de revisar e reverter.
Escolha um tema (dates, formatting, validation, helpers de auth) e trate-o como piloto. Você quer uma rotina repetível, não uma arquitetura perfeita.
Um loop de extração de baixo risco
- Crie um novo arquivo de módulo para o tema escolhido.
- Mova apenas 1 a 3 funções intimamente relacionadas. Se algo estiver meio relacionado, deixe para depois.
- Mantenha as exportações estáveis re-exportando temporariamente as funções movidas do arquivo utils original.
- Atualize imports em uma pequena área do app (uma página, uma feature, um serviço). Faça commit.
- Remova os re-exports temporários somente depois que a maioria dos imports tiver sido migrada.
Isso mantém cada commit pequeno: mova algumas funções, atualize alguns imports e pare.
Exemplo concreto
Se utils.ts inclui formatMoney, parseCurrency, roundToCents, além de helpers não relacionados como slugify, sleep e fetchWithRetry, comece pelo dinheiro. Crie utils/money.ts, mova as funções de dinheiro e re-exporte-as de utils.ts para que nada quebre. Depois migre imports no fluxo de checkout primeiro e expanda a partir daí.
Mantenha o comportamento igual enquanto refatora
O maior risco não é mover código. É os edits “já que estou aqui” que mudam o resultado silenciosamente. Trate isso como mover caixas para um novo cômodo: etiquete, carregue, coloque. Não redecore no meio do transporte.
Comece com funções puras. Antes de mover uma, escreva um teste pequeno que congele o comportamento atual, mesmo que seja estranho. Conhecido-e-stranho vence limpo-e-diferente.
Helpers de formatação são bons para tabelas simples de entrada-saída. Escolha alguns casos representativos, incluindo casos de borda como valores vazios, espaços extras e caracteres não ASCII.
Se você não tem infraestrutura de testes, use checagens rápidas em runtime em vez de adivinhar. Uma asserção temporária ou um log curto ao redor de um ponto de chamada pode confirmar que a saída não mudou após a movimentação. Remova quando tiver confiança.
Uma armadilha comum: “melhorar” formatPrice(amount) enquanto o move. Se esse helper afeta faturas ou emails, uma mudança de arredondamento ou símbolo pode criar totais divergentes ou confusão para clientes. Congele a saída, mova, e depois agende melhorias em uma mudança separada e explícita.
Lide com efeitos colaterais e helpers sensíveis à segurança com cuidado
As partes mais arriscadas de um arquivo utils raramente são os helpers de string. São as funções que tocam o mundo exterior: chamadas de rede, storage, bancos, tempo, IDs aleatórios e variáveis de ambiente.
Mantenha helpers puros separados do código com efeitos colaterais. Misturá-los é como obter bugs de chamadas de API duplicadas, headers faltando ou dados salvos no lugar errado.
Coloque código com efeitos colaterais em módulos com nomes óbvios. Por exemplo: auth (leitura/gravação de token, refresh, logout), configuração do cliente API (base URL, retries, headers), wrappers de storage e acesso a env.
Para manter chamadores estáveis enquanto você move o código, introduza pequenas interfaces. Ao invés de chamar localStorage.getItem('token') em todo lugar, crie um tokenStore com getToken() e setToken(). Depois, você pode mudar como tokens são armazenados sem editar metade do app.
Trate esses helpers como de alto risco mesmo que pareçam pequenos:
- Manipulação de tokens (parsing de JWT, refresh, checagem de expiração)
- Segredos (API keys, tokens de serviço, URLs privadas)
- Lógica de senha (hashing, comparações, fluxos de reset)
- Sanitização de input e construção de queries
Se tiver dúvida sobre uma movimentação, mantenha a função antiga como um wrapper fino que chama o novo módulo e remova-o apenas depois de ter enviado com segurança.
Armadilhas comuns que transformam uma refatoração em reescrita
Refactors explodem quando as mudanças ficam grandes demais para raciocinar. O objetivo é extrações pequenas em que você pode confiar.
Padrões de falha comuns:
- Movimentos big-bang: mover dezenas de helpers de uma vez arruina sua capacidade de isolar quebras.
- Misturar limpeza com mudanças de comportamento: renomear e alterar lógica no mesmo commit esconde regressões.
- Criar um novo depósito de despejo:
shared/oucommon/cedo demais vira o utils v2. - Quebrar imports sem plano de migração: prefira um período de ponte com re-exports ou aliases.
- Dependências ocultas: helpers que leem vars de ambiente ou singletons globais podem quebrar devido a ordem de inicialização alterada.
Um exemplo simples: formatDate() parece inofensivo, mas depende de uma configuração global de locale e de uma var de ambiente de timezone. Depois de movê-lo, testes locais passam mas produção roda com outro env, e agora recibos mostram o dia errado.
Duas guardrails ajudam: mantenha cada extração pequena o suficiente para revisar em minutos, e mantenha o comportamento idêntico até que a fronteira esteja estável.
Checagens rápidas antes de enviar cada extração
Trate cada extração como um pequeno release.
Garanta que o novo módulo tenha um trabalho e uma surface area pequena. Se você não consegue descrever o que faz em uma frase, ainda faz demais. Seja deliberado sobre exports. Muitos helpers eram “públicos por acidente” quando viviam em um arquivo grande.
Uma checklist pré-merge curta:
- Propósito claro: nome e exports batem com um domínio
- Exports intencionais: exporte só o que os chamadores precisam
- Sem imports circulares
- Sem segredos puxados para bundles cliente
- Build passa sem warnings que sugiram paths duplicados ou imports mortos
Depois faça um smoke test pequeno em uso real. Mesmo com testes unitários, vale verificar se seu fluxo principal ainda funciona (auth, a principal ação de criar/salvar, e uma página que chama a API e renderiza dados reais).
Exemplo: dividir um arquivo utils misto em um app real
Uma startup tem um único utils.ts que começou pequeno e cresceu para 900 linhas. Ele guarda helpers de auth (armazenamento de token, checagens de sessão), chamadas de API (fetchWithAuth) e formatação de UI (datas, moeda, nomes exibidos). Bugs aparecem em lugares estranhos porque tudo importa tudo.
Eles mantêm o trabalho de features em movimento extraindo pequenos módulos em ordem segura:
- Mover helpers de formatação puros primeiro.
- Extrair o wrapper do cliente API em seguida e manter as exportações antigas como wrappers finos.
- Dividir auth em módulos separados de token e sessão, isolando qualquer coisa que toque storage.
- Finalizar com uma passada de limpeza: remover re-exports, renomear funções confusas e deletar helpers mortos que você provar estar não usados.
O que melhora rápido é prático: menos regressões, propriedade mais clara (mudanças em auth não afetam formatação), revisão de segurança mais fácil e onboarding mais rápido porque os arquivos batem com conceitos reais do app.
Próximos passos: transforme o plano em progresso constante
A maneira mais rápida de dividir um arquivo utils é tratá-lo como uma série de pequenos movimentos seguros.
Um plano simples de uma semana:
- Dia 1: Escolha uma fronteira (date/time, strings, money, validation) e mova 3 a 5 funções puras. Adicione testes rápidos.
- Dia 2: Mova as próximas 3 a 5 no mesmo domínio.
- Dia 3: Extraia um módulo com efeitos colaterais (storage, cookies, wrappers de fetch). Mantenha wrappers finos.
- Dia 4: Migre imports em alguns arquivos. Adicione uma regra simples para desencorajar novos imports do catch-all antigo.
- Dia 5: Limpeza: renomeie módulos, adicione comentários curtos e documente o que ainda vive no arquivo antigo.
Pare quando o arquivo utils restante for principalmente wrappers de compatibilidade e cola realmente compartilhada, não o lugar onde tudo acaba indo.
Se você herdou um app gerado por IA e o utils parece possuir todo o produto, FixMyMess (fixmymess.ai) pode ajudar diagnosticando a base de código, isolando helpers arriscados (auth, segredos, builders de query propensos à injeção) e transformando a separação em uma sequência segura e enviável em vez de uma reescrita.
Perguntas Frequentes
How do I know my utils file is actually a problem and not just “big”?
Se o arquivo mistura tarefas não relacionadas e uma pequena alteração quebra muitas funcionalidades, já é um catch-all. O sinal real é o acoplamento: helpers importam módulos específicos do app, têm efeitos colaterais ocultos ou são usados por toda parte.
A contagem de linhas importa menos do que quantas partes do app dependem dele.
What module boundaries should I pick before I start moving code?
Aposte em categorias de “uma tarefa” simples que correspondam à forma como as pessoas pensam sobre o app, como strings, dates, validation, api e auth. Separe helpers puros (sem I/O, sem estado global) dos que têm efeitos colaterais (storage, env, network, database).
Se você não consegue descrever um módulo em uma frase, a fronteira ainda está muito vaga.
What should I move out of utils first?
Comece por helpers puros e de baixo risco, fáceis de testar e que não leem estado global. Priorize também helpers com poucos pontos de chamada, porque você pode migrá-los rapidamente.
Deixe peças de alto risco como auth, manipulação de tokens, acesso a env e construção de queries para depois, quando tiver um padrão de extração seguro.
How do I refactor without accidentally changing behavior?
Não mude o comportamento enquanto move o código. Escreva um pequeno teste (ou uma checagem de runtime rápida) que fixe a saída atual, mesmo que ela seja estranha.
Trate a movimentação como mudar caixas de cômodo: mova primeiro, melhore depois em uma mudança separada e explícita.
What’s the safest step-by-step way to split a utils file?
Crie o novo módulo, mova 1–3 funções relacionadas e mantenha as exportações antigas funcionando re-exportando temporariamente do arquivo utils original. Em seguida, migre imports em uma pequena parte do app e faça commit.
Quando a maioria dos pontos de chamada for atualizada, remova os re-exports temporários e repita com o próximo grupo.
How do I avoid circular imports when I extract modules?
Imports circulares geralmente significam que o arquivo “utils” não é realmente utilitário; está fazendo trabalho de domínio e dependendo de internos do app. Separe por responsabilidade e mantenha dependências unidirecionais.
Se necessário, crie um módulo de nível inferior (helpers puros) que módulos de nível superior possam importar, em vez de todo mundo importar todo mundo.
Which utils functions are the most security-sensitive?
Qualquer coisa que toque tokens, segredos, senhas, variáveis de ambiente, storage ou construção de queries deve ser tratada como alto risco. Mova esses itens para módulos com nomes óbvios (por exemplo auth, env, storage, db) em vez de deixá-los espalhados entre helpers aparentemente inofensivos.
Também garanta que código que lê segredos não acabe empacotado no bundle cliente por engano.
Should I wrap things like localStorage, cookies, and env access?
Sim — mas faça isso intencionalmente. Crie um pequeno wrapper como tokenStore.getToken() e tokenStore.setToken() para que o restante do app não chame localStorage ou cookies diretamente.
Isso facilita mudanças futuras e reduz a chance de tratamento inconsistente do token pelo app.
How do I migrate imports without breaking everything?
Prefira um período de ponte curto: mantenha os imports antigos funcionando via re-exports ou wrappers finos enquanto migra os pontos de chamada gradualmente. Mantenha cada extração pequena o bastante para que um revisor a entenda rapidamente.
Evite movimentos tipo big-bang ou commits mistos que renomeiem, movam e alterem lógica tudo de uma vez.
When should I get help breaking up a messy utils file in an AI-generated app?
Quando você herda código gerado por IA, um arquivo utils gigante muitas vezes esconde fluxos de auth quebrados, segredos expostos e efeitos laterais imprevisíveis que tornam cada mudança arriscada. Se você precisa dividir sem transformar em uma reescrita, FixMyMess (fixmymess.ai) pode ajudar com uma auditoria gratuita do código e, em seguida, isolar e reparar os helpers arriscados (auth, cliente API, storage, query builders) com correções verificadas por humanos.
A maioria dos projetos pode ser estabilizada em 48–72 horas depois de identificar e separar os helpers de maior risco.