Renan Montanari Blog
Voltar ao blog
Banco de DadosMigração

Quando Entram os Dados de Verdade: Por Que Metade Para de Funcionar

O sistema passa em todos os testes, mas quebra na migração. Dado de teste é sempre educado; dado real não. Como auditar, validar e testar com a base de verdade antes de migrar.

Renan Montanari

Renan Montanari

Engenheiro de Software Full-Stack

9 min de leitura

Introdução: O Teste Que Ninguém Quer Fazer

O sistema passou em todos os testes. Testes unitários, testes de integração, testes de performance com dados de exemplo. Demo aprovada. Cliente pronto para migrar.

Aí entram os dados de verdade.

E metade para de funcionar.

Isso não é azar. Não é "ah, encontramos um bug". É que dado de teste é sempre educado.

João Silva. Telefone com onze dígitos exatos. E-mail válido segundo a RFC 5322. Uma compra por cliente. Data de nascimento em 1990. Três cadastros, todos bem comportados, todos criados por quem construiu o sistema.

Dado real não tem nada disso.

O Que Realmente Está na Sua Base de Dados

Você acha que tem nomes como "João Silva". Tem também:

Nomes

✓ "João Silva" (educado)
✗ "Joãããããã" (caractere repetido)
✗ "O'Brien" (apóstrofo)
✗ "Jr" (só duas letras)
✗ "JOÃO DA SILVA SANTOS PEREIRA DOS SANTOS..." (120 caracteres)
✗ "" (vazio)
✗ "123456" (só números, porque o sistema anterior permitia)
✗ " " (espaço em branco)
✗ "---" (hífens como preenchimento falso)

Endereços

✓ "Rua das Flores, 123, São Paulo"
✗ "s/n" (sem número)
✗ "Lote 5 / Apt 302 / Casa azul / Próximo ao açude / Comunidade XXX" (180 caracteres)
✗ "SP" (só o estado, sem rua)
✗ "undefined" (literalmente a string "undefined")
✗ "Na casa da mãe" (endereço inválido)
✗ "00000000000" (CEP falso, preenchimento inválido)

Telefones

✓ "(11) 98765-4321"
✗ "11987654321" (sem formatação)
✗ "1198765432" (faltou um dígito)
✗ "119876543210" (dígito a mais)
✗ "9876543210" (sem DDD)
✗ "11 98765-4321 ramal 201" (ramal no campo)
✗ "11987654321 / 1133334444" (dois telefones no mesmo campo)
✗ "000-000-0000" (telefone falso do teste)

E-mails

✓ "joao@example.com"
✗ "joao@" (sem domínio)
✗ "joao" (sem @)
✗ "joao @example.com" (espaço no meio)
✗ "JOAO@EXAMPLE.COM" (maiúsculas, duplica cadastro se não normalizar)
✗ "joao+tag@example.com" (com +, que é válido, mas quebra parsers ruins)
✗ "joao..silva@example.com" (ponto duplo)
✗ " joao@example.com " (espaços ao redor)

Datas

✓ "01/01/1990"
✗ "1900" (nascimento em 1900, pessoa com 126 anos)
✗ "2087" (data no futuro)
✗ "32/13/2020" (dia 32, mês 13)
✗ "1990" (só o ano, sem mês e dia)
✗ "" (vazio)
✗ "29/02/2023" (29 de fevereiro em ano não bissexto)

Volume

✓ 10 registros (abre instantaneamente)
✗ 40.000 registros (demora 15 segundos)
✗ 500.000 registros (timeout)
✗ 2.000.000 registros (derruba o servidor)

Por Que Ninguém Pensa Nisso

1. Os testes são construídos por quem faz o sistema

O dev cria os dados de teste. Naturalmente, cria dados que ele sabe que funcionam. Dados "felizes". Dados que passam em todos os validadores que ele escreveu.

Isso não é negligência. É que o dev não é criativo da forma certa: ele é criativo para resolver problemas, não para quebrar o sistema de propósito.

2. Casos extremos não aparecem no teste

Se você tem 1.000 usuários e um deles tem um nome com 180 caracteres, aquele bug aparece de vez em quando, sempre com um cliente real. No teste, nunca aparece.

3. Os dados crescem devagar

O produto começa com 1.000 registros e a query é instantânea. Com 100.000 registros, começa a ficar lenta. Com 1 milhão, trava.

Você só descobre quando está em produção.

4. Dados legados são caóticos

Cada sistema anterior tinha suas próprias regras (ou não tinha nenhuma). Quando você importa dados de 2005, 2010, 2015 e 2020, cada época deixou sua marca.

O campo de telefone tem formatos diferentes dependendo de quando foi criado. O endereço tem estruturas que ninguém mais usa. O nome tem caracteres estranhos porque a codificação era outra.

O Custo Real Dessa Negligência

Um Cenário Comum

Projeto: migrar uma base de 500.000 clientes do sistema legado.

O que foi testado:

  • 100 registros de exemplo ✓
  • Validações de campo ✓
  • Performance com 10.000 registros ✓

O que não foi testado:

  • Dados reais dos últimos 15 anos
  • Caracteres especiais, acentos, símbolos
  • Registros duplicados com grafias diferentes
  • Campos obrigatórios preenchidos com valores "falsos"

O que acontece:

  • No dia da migração, 15% dos registros falham
  • Causa: nomes com caracteres inválidos, endereços malformados, telefones sem padrão
  • Tempo para arrumar: 2 semanas de trabalho manual
  • Custo: R$ 40.000 em horas, mais o atraso no projeto
  • Negócio: clientes não conseguem fazer login e começam a ir para o concorrente

Como Validar de Verdade

1️⃣ Validação no banco, sempre

Nunca confie apenas no frontend. Os dados chegam ao banco por vários caminhos: API, importação, cron jobs, integrações.

// ❌ Amador: valida só no frontend, só aceita um formato
export const isValidPhone = (phone: string) => {
  return /^\(\d{2}\) \d{4,5}-\d{4}$/.test(phone);
};

// ✅ Profissional: normaliza e valida no backend...
export const validatePhone = (phone: string) => {
  const cleaned = phone.replace(/\D/g, ""); // remove tudo que não é número

  if (cleaned.length < 10 || cleaned.length > 11) {
    throw new Error("Telefone deve ter 10 ou 11 dígitos");
  }

  if (!/^[1-9]\d{9,10}$/.test(cleaned)) {
    throw new Error("Formato de telefone inválido");
  }

  return cleaned;
};
-- ...e o banco garante a regra, venha o dado de onde vier (PostgreSQL)
CREATE TABLE customers (
  id    UUID PRIMARY KEY,
  phone VARCHAR(11),
  CHECK (phone ~ '^[1-9][0-9]{9,10}$') -- só números, 10 ou 11 dígitos, sem 0 na frente
);

2️⃣ Normalização de entrada

Normalizar não é "limpar tudo". Cada campo tem sua regra, e o nome da pessoa não pode perder o acento.

// Nome: tira espaços sobrando, mas preserva acentos, apóstrofos e hífens
const normalizeName = (input: string) => {
  return input
    .trim()
    .replace(/\s+/g, " ") // espaços múltiplos → um só
    .replace(/[^\p{L}\s'-]/gu, ""); // mantém letras de qualquer idioma
};

// E-mail: aqui sim, minúsculas e sem espaços
const normalizeEmail = (input: string) => {
  return input.trim().toLowerCase();
};

normalizeName("  JOÃO  DA  SILVA  "); // "JOÃO DA SILVA"
normalizeName("O'Brien"); // "O'Brien"
normalizeEmail(" JOAO@EXAMPLE.COM "); // "joao@example.com"

3️⃣ Queries otimizadas para volume

-- ❌ Amador: sem índice, sem paginação
SELECT * FROM customers;

-- ✅ Profissional: índice, paginação, filtros
CREATE INDEX idx_customer_email ON customers (email);
CREATE INDEX idx_customer_created_at ON customers (created_at DESC);

-- Query com paginação
SELECT id, name, email
FROM customers
WHERE created_at > NOW() - INTERVAL '30 days'
ORDER BY created_at DESC
LIMIT 50 OFFSET 0;

-- Índice composto para as queries mais comuns
CREATE INDEX idx_customer_status_date
ON customers (status, created_at DESC);

4️⃣ Testes com dados reais

// Teste de importação com as variações que existem na base de verdade
describe("Customer Import", () => {
  it("should handle real-world data variations", async () => {
    const realWorldData = [
      { name: "JOÃO SILVA", phone: "11 987654321", email: "joao@email.com" },
      { name: "María José", phone: "21987654321", email: "maria@email.com" },
      { name: "O'Brien", phone: "1198765432", email: "obrien@email.com" },
      { name: "s/n", phone: "119876543210", email: "invalido" },
      { name: "", phone: "", email: "" },
    ];

    const results = await importCustomers(realWorldData);

    expect(results.successful).toBe(3); // só os válidos
    expect(results.failed).toBe(2); // com mensagens de erro claras
    expect(results.errors[0].reason).toContain("E-mail inválido");
  });
});

O Teste de Uma Hora Que Economiza Semanas

Antes de lançar qualquer sistema novo ou migração:

  1. Extraia uma amostra (1 a 5%) dos dados reais
  2. Teste a importação ou migração com essa amostra
  3. Não corrija erros manualmente: registre todos
  4. Analise os padrões de erro
  5. Implemente validadores para cada padrão encontrado

Exemplo do que você vai encontrar:

❌ 12% dos nomes têm mais de 50 caracteres
❌ 3% dos telefones estão em outro formato
❌ 0,5% das datas são inválidas (ano 1900, data no futuro)
❌ 8% dos endereços não têm número
❌ 0,2% dos e-mails têm caracteres válidos, mas raros
❌ Queries com 40 mil registros levam 12 segundos (falta índice)

Cada um desses é um problema que você resolve antes de migrar 500.000 registros.

Checklist: Dados de Verdade

Antes de qualquer importação ou migração, você testou com:

  • Amostra real dos dados legados (1 a 5%)
  • Validação robusta de todos os campos
  • Normalização de entrada por tipo de campo
  • Índices no banco para as queries que vão crescer
  • Testes de performance com o volume esperado (100x o tamanho atual)
  • Relatório de erros por padrão (quais dados quebram e por quê)
  • Plano de correção para os dados problemáticos
  • Rollback caso algo dê errado

Menos de 5 itens marcados: não está pronto para produção.

Qual Foi o Dado Mais Estranho?

Em bases com mais de 20 anos de histórico, existem pérolas:

  • Cliente com ID negativo (porque alguém resetou a sequência)
  • Nome só com emojis (❤️❤️❤️❤️)
  • Data de nascimento em 1800 (alguém clicou errado e nunca voltou para corrigir)
  • E-mail com 500 caracteres (copiar e colar acidental)
  • Telefone com 50 dígitos (alguém digitou a senha no campo errado)
  • Endereço com SQL injection (alguém mandou de propósito)
  • O mesmo cliente cadastrado 3 vezes com grafias diferentes

Se você nunca encontrou algo estranho, é porque nunca olhou.

Como Começar: Audite Seus Dados

Hoje (1 hora):

-- Quais valores se repetem de forma suspeita?
SELECT name, COUNT(*)
FROM customers
GROUP BY name
ORDER BY COUNT(*) DESC
LIMIT 50;

-- Qual o maior tamanho de cada campo?
SELECT MAX(LENGTH(name)) AS max_name_length,
       MAX(LENGTH(address)) AS max_address_length
FROM customers;

-- Quantos registros têm valores nulos ou vazios?
SELECT COUNT(*) FROM customers WHERE name IS NULL OR TRIM(name) = '';

Amanhã (3 horas):

  • Implementar validadores robustos
  • Adicionar índices nas queries lentas
  • Criar testes com dados reais

Próxima semana:

  • Documentar os padrões de erro encontrados
  • Treinar o time sobre casos extremos
  • Revisar a lógica de importação

Próximos Passos

Se você vai migrar de sistema ou integrar dados legados, não pule essa etapa.

O teste com dados reais é a forma mais barata de descobrir um problema caro.

Uma tarde testando com dados verdadeiros economiza semanas de investigação em produção.

Precisa de Ajuda Para Auditar e Validar Seus Dados?

Se você está migrando de sistema, integrando dados legados ou escalando o produto para volume real, posso ajudar com:

  • Auditoria de dados: análise de padrões, anomalias e casos extremos
  • Validação robusta: validadores prontos para produção
  • Otimização para volume: índices, queries eficientes e testes de performance
  • Estratégia de migração: planejamento, testes, rollback e comunicação

Vamos auditar seus dados →

Gostou? Compartilhe