---
title: "40 minutos, 85% mais rápido: o que a IA fez na minha lib de CPF e CNPJ"
description: "Como atualizei minha biblioteca de validação de CPF e CNPJ para o novo formato alfanumérico usando TDD, skills e agentes de IA — e o que a revisão guiada por benchmark encontrou no caminho."
author: Bruno Germano
lang: pt-BR
canonical: https://egermano.com/artigos/2026-11-12-cnpj-alfanumerico-refatoracao-ia
date: "2026-11-12 09:00:00 -0300"
updated: "2026-11-12 09:00:00 -0300"
---

# 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](https://github.com/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:

```ts
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:

```sh
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ário | Antes (ns/chamada) | Depois | Melhoria |
| --- | ---: | ---: | ---: |
| `cnpjValidator` — CNPJ válido | 1.384 | 204 | **−85%** |
| `documentValidator` — CNPJ válido | 1.340 | 358 | −73% |
| `documentValidator` — CPF válido | 489 | 217 | −56% |
| `cpfValidator` — CPF válido | 329 | 277 | −16% |
| `cnpjValidator` — entrada de 100 mil chars | 122.866 | 6 | −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](https://github.com/egermano/brazilian-multidocument-validator), junto com a spec que guiou o trabalho e os dados brutos das medições.
