Voltar para artigos
12 de novembro de 2026
Atualizado em 12 de novembro de 2026
6 min
🇧🇷 PT-BR
#ia#tdd#refactor#typescript#node

40 minutos, 85% mais rápido: o que a IA fez na minha lib de CPF e CNPJ

Eu tenho uma biblioteca que acompanha eu não sei quantos projetos: validação de CPF e CNPJ em Node.js. É daquelas que você escreve uma vez, publica no npm e passa a arrastar de projeto em projeto. Funcionava, tinha teste, ninguém mexia.

Só que a Receita Federal mudou as regras. Desde julho de 2026, o CNPJ de novas inscrições pode ter letras, além de números. Ou seja: aquela validação que assumia 14 dígitos numéricos parou de cobrir os documentos novos. Hora de mexer na lib.

E aqui vai o detalhe que me interessava contar: foi a primeira vez que eu atualizei essa biblioteca com ajuda de IA, de ponta a ponta. Feature nova, revisão completa do código e uma rodada de otimização. Tudo isso em cerca de 40 minutos. O que eu quero mostrar aqui é como foi feito — porque o número bonito sem o processo não prova nada.

A feature: CNPJ alfanumérico

Eu não mandei a IA “implementar CNPJ alfanumérico” e torcer. Escrevi uma especificação primeiro, descrevendo o que mudou no formato e no cálculo do dígito verificador. No repositório egermano/skills eu mantenho skills de TDD e XP, e foram elas que guiaram o trabalho: primeiro os testes com os exemplos do manual oficial da Receita (como o 12.ABC.345/01DE-35), depois a implementação mínima pra fazer eles passar.

O resultado foi o esperado: feature entregue, os 109 testes verdes, cobertura em 100%. Até aqui, qualquer sessão de TDD bem conduzida — com ou sem IA — teria chegado no mesmo lugar.

A revisão: onde apareceu o dinheiro

Aí eu pedi outra coisa: revisar o código inteiro e propor melhorias. E foi essa segunda etapa que rendeu mais que a primeira. A revisão apontou trabalho em três eixos, nesta ordem de prioridade: corrigir comportamento, reduzir complexidade e melhorar performance.

Um bug de verdade

Rodando o código, apareceu isso:

cpfValidator("77325121890123"); // retornava true (!)

A implementação antiga extraía os caracteres numéricos, ignorava qualquer excedente e validava só os 11 primeiros dígitos. Ou seja: uma string de 14 dígitos cujo prefixo é um CPF válido passava como CPF. A correção, em TDD estrito — teste novo vermelho, correção mínima, verde — foi rejeitar entradas com mais de 11 dígitos depois da limpeza.

Três decisões que simplificaram tudo

O código legado tinha variáveis chamadas a, b, c, x, y e a função de CNPJ fazia até quatro varreduras da mesma string por chamada. A refatoração se apoiou em três decisões:

  1. Checar comprimento antes de qualquer processamento. Um CNPJ só pode ter 14 caracteres (sem máscara) ou 18 (com máscara). Uma string de 100 mil caracteres é rejeitada num teste O(1) — sem regex, sem cópias.
  2. Normalizar uma vez. No fluxo antigo, a função que identifica o documento convertia caixa, testava formato e chamava o validador específico — que repetia tudo de novo. Agora a normalização acontece uma única vez e o resultado atravessa o fluxo.
  3. Um laço, dois somatórios. Os dígitos verificadores de CPF e CNPJ são calculados em módulo 11 com pesos deslocados. Em vez de dois reduce com array temporário, um único for acumula as duas somas.

Nomes legíveis (firstSum, secondDigit, verifierDigit) substituíram as letras soltas, e charCodeAt(i) - 48 entrou no lugar de parseInt(charAt(i)).

Uma decisão que eu destaco porque é rara em refatoração guiada por IA: não criar uma abstração genérica de “documento” pra duas funções pequenas. Complexidade de design também é custo, e a IA propôs isso por conta própria.

Como eu provei que ficou mais rápido

“Achamos que ficou mais rápido” não vale nada num post meu. Cada alegação de performance foi medida, não presumida — e o baseline foi capturado antes de tocar no código:

npm run build
cp bin/multidocument_validator.js /tmp/cnpj-before-improvements.mjs

Com o build antigo salvo, as duas versões rodam no mesmo processo, na mesma máquina, no mesmo Node, como módulos independentes. O script de benchmark descarta uma rodada de aquecimento pro JIT estabilizar, roda 7 amostras por cenário e reporta a mediana. E tem um detalhe que eu achei elegante: o resultado de cada chamada é acumulado num checksum. As duas versões produziram exatamente o mesmo valor — 3.200.000. Ou seja, a comparação de performance não esconde nenhuma mudança de comportamento por baixo dos panos.

O teste de comportamento também não ficou pra trás: 109 testes com casos-limite do módulo 11, entradas de 100 mil caracteres, até Unicode (ſ não pode virar S). E um teste que empacota a lib com npm pack e instala o tarball num diretório limpo, garantindo que um consumidor JS e outro TypeScript conseguem usar o pacote publicado de verdade.

Resultados

CenárioAntes (ns/chamada)DepoisMelhoria
cnpjValidator — CNPJ válido1.384204−85%
documentValidator — CNPJ válido1.340358−73%
documentValidator — CPF válido489217−56%
cpfValidator — CPF válido329277−16%
cnpjValidator — entrada de 100 mil chars122.8666−99,99%

O caso da entrada longa é o meu favorito. A versão antiga aplicava toUpperCase() e .replace() — copiando 100 KB — antes de qualquer rejeição. A nova olha o length e devolve false. Na prática, isso é o que protege sua API quando alguém cola um arquivo inteiro no campo de formulário.

E o CPF com −16%? Pois é, o ganho menor ficou explícito também. A função antiga já era razoável; a melhoria veio do laço único e do charCodeAt no lugar do parseInt. Não tudo precisa ser 85%.

As ressalvas, porque credibilidade se constrói com elas

Antes de você sair refatorando tudo, o que esse número não diz:

  • Foi medido em uma máquina (Linux, Node 24, um Core Ultra 7). O benchmark é reproduzível com npm run benchmark — rode na sua e compare.
  • Em nanossegundos por chamada, a diferença absoluta no dia a dia é pequena. O valor real aparece em volume: lotes, ETL, upload de planilhas, e na resistência a inputs patológicos.
  • Mudou comportamento, sim — de forma intencional: dígitos excedentes no CPF agora são rejeitados, e isso está coberto por teste e documentado no CHANGELOG.

Valeu a pena?

Pra mim, o aprendizado não foi “IA escreveu minha lib”. A spec foi minha, as skills de TDD e XP eram minhas, o critério de “medir antes de mudar” veio do processo que eu já seguia. O que a IA fez foi executar esse processo numa velocidade que eu não conseguiria sozinho — e, na revisão, enxergar no meu código antigo coisas que eu tinha deixado passar por anos, inclusive um bug de validação.

Quarenta minutos. Feature nova, um bug corrigido, código mais simples e um benchmark com prova. Se você tem aquela lib sua que ninguém mexe há anos, talvez seja uma boa sessão de testes.

O benchmark é reproduzível no repositório egermano/brazilian-multidocument-validator, junto com a spec que guiou o trabalho e os dados brutos das medições.