12 de jan. de 2026·8 min de leitura

Evite falhas por dados nulos: restrições, defaults e backfills

Evite falhas por dados nulos com restrições de banco, defaults seguros, validação no app e backfills, para que protótipos com seed data funcionem de forma confiável em produção.

Evite falhas por dados nulos: restrições, defaults e backfills

Por que dados de seed escondem problemas com nulos

“Funciona com seed data” normalmente significa que o app foi testado com um conjunto pequeno e manual de registros que são limpos, completos e moldados para o caminho feliz. Todo usuário tem nome, todo pedido tem endereço e toda configuração existe. O código parece OK porque nunca encontra a bagunça real.

O uso real cria lacunas rápido. Pessoas abandonam formulários, planilhas chegam com colunas em branco e APIs de terceiros devolvem campos somente às vezes. Mesmo usuários cuidadosos fazem escolhas que deixam algo faltando.

Valores vazios costumam aparecer depois do lançamento quando:

  • Um fluxo de cadastro permite pular detalhes do perfil, e depois páginas assumem que eles existem
  • Uma importação CSV traz emails em branco, preços faltando ou datas inconsistentes
  • Um cliente mobile envia um payload parcial em uma conexão instável
  • Um webhook muda de formato, ou um campo está nulo durante uma queda
  • Um colega edita dados manualmente e deixa um campo requerido em branco

Uma falha por dados nulos acontece quando seu código espera um valor e recebe nada. O app tenta usar um campo vazio como se fosse dado real e lança um erro. Isso pode aparecer como “cannot read property of null”, uma inserção no banco que falha, uma página quebrada ou um job em background que tenta de novo para sempre.

Evitar falhas por dados nulos não é só tarefa de programação. É questão de regras de dados. Você precisa de duas coisas funcionando juntas:

  1. Impedir que dados ruins sejam armazenados (para que o banco seja confiável).

  2. Lidar com dados faltantes de forma segura quando isso for permitido (para que o app continue funcionando e o usuário saiba como corrigir).

Protótipos gerados por IA são especialmente vulneráveis aqui. A UI frequentemente assume entradas perfeitas, e o banco aceita qualquer coisa. Por isso um protótipo pode demonstrar bem e quebrar em produção assim que um usuário real pular um campo ou uma importação trouxer brancos.

Uma vez que você trata seed data como “melhor caso”, pode desenhar para o caso normal: incompleto, inconsistente e às vezes errado.

Comece com regras claras: o que pode faltar?

A maioria dos apps não quebra porque os dados são “ruins”. Quebra porque o app e o banco discordam sobre o que pode faltar. Antes de adicionar restrições ou escrever migrações, escreva as regras em inglês simples. Esse passo costuma prevenir mais falhas por nulos do que qualquer mudança de código isolada.

Comece decidindo “obrigatório vs opcional”:

  • Email: obrigatório para login e reset de senha, ou opcional se você só oferecer telefone ou SSO
  • Address line 2: opcional
  • Bio do perfil: opcional, mas a UI deve lidar com estados vazios
  • Data de nascimento: opcional, ou obrigatório apenas para funcionalidades com limite de idade
  • Nome da empresa: opcional para pessoas físicas, obrigatório para contas corporativas

Depois, separe três estados que exigem tratamento diferente na UI, na API e no banco de dados:

  • Desconhecido: você esperava o valor, mas ainda não o tem (muitas vezes temporário)
  • Não fornecido: o usuário optou por deixar em branco
  • Não aplicável: o campo não faz sentido para este registro

Exemplo: num fluxo de cadastro, profile.bio é opcional. billing_country pode ser desconhecido durante o cadastro, mas obrigatório antes de criar um pagamento. vat_id pode ser não aplicável para muitos usuários.

Então decida o que deve ser bloqueado vs permitido-e-tratado. Bloqueie quando o app não pode funcionar ou cumprir requisitos legais/segurança sem o valor (identificadores de login, papéis de permissão, IDs de propriedade). Permita-e-trate quando você pode mostrar um fallback seguro (bio vazia, avatar faltando) ou coletar depois.

Escreva suas regras como uma especificação curta antes de mexer no código:

“User.email é obrigatório e único. User.address_line_2 é opcional. Profile.bio é opcional e exibe como vazio. Payment.country é obrigatório antes de criar uma cobrança.”

Se você herdou um protótipo gerado por IA, essa especificação costuma ser o que falta. Auditorias frequentemente começam aqui porque revelam onde nulos são aceitáveis e onde devem ser impedidos desde o início.

Restrições de BD que impedem dados ruins de serem armazenados

Se você quer prevenir falhas por dados nulos, o banco de dados precisa dizer “não” quando o app tenta salvar algo que vai quebrar depois. Código de app muda com frequência, mas restrições permanecem. Elas são uma rede de segurança confiável, especialmente quando um protótipo “funcionou” apenas porque foi testado com seed data perfeito.

Use NOT NULL para campos realmente obrigatórios

Adicione NOT NULL apenas a campos sem os quais seu app não consegue funcionar. Um bom teste: “Um usuário real consegue completar um fluxo chave se isso estiver faltando?” Se não, torne obrigatório no banco.

Por exemplo, uma tabela orders geralmente não funciona sem user_id e created_at. Se esses forem nulos, você acabará com linhas que parecem ok na UI mas quebram relatórios, emails ou telas de admin.

Adicione regras simples com CHECK constraints

CHECK é ótimo para regras básicas e legíveis que impedem valores lixo:

  • Intervalos: quantity > 0, price_cents >= 0
  • Texto não vazio: trim(display_name) <> ''
  • Valores permitidos: status IN ('draft','active','canceled')
  • Datas que fazem sentido: end_date >= start_date

Essas regras evitam dados “tecnicamente não-nulos, ainda assim inúteis”. Uma string vazia num campo de nome pode causar a mesma UI quebrada que um null.

Unicidade é outra fonte comum de surpresas. Use UNIQUE para identificadores que nunca podem duplicar, como email, username ou um ID de provedor externo (por exemplo, google_sub). Sem isso, você pode acabar com duas contas que parecem a mesma pessoa, e o código downstream vai escolher a linha “errada”.

Chaves estrangeiras também importam. Se você permitir linhas filhas sem pai (como profile sem user), código que assume relacionamentos vai falhar de formas estranhas. Foreign keys evitam registros órfãos e mantêm deletes/updates coerentes.

Restrições são a última linha de defesa, não a única. Seu app ainda deve validar input e mostrar erros amigáveis, mas o banco deve reforçar as regras para que dados ruins não entrem via jobs de background, scripts de admin ou patches rápidos.

Defaults que tornam dados faltantes seguros (sem mascarar problemas)

Defaults ajudam quando um valor deve existir para cada linha, mas usuários e código não devem precisar fornecê‑lo. Eles podem prevenir falhas tornando “vazio” impossível onde “vazio” não é significativo.

Um bom default combina com o comportamento real do produto. Se você não consegue explicar o significado para alguém de suporte, provavelmente não é um bom default.

Bons defaults: monótonos, previsíveis e corretos

Use defaults para campos inevitáveis e que não são escolha do usuário:

  • timestamps created_at ou updated_at
  • um status que começa como pending, draft ou active
  • contadores como login_count começando em 0
  • flags simples como is_deleted começando em false

Esses defaults reduzem a superfície para bugs, especialmente em protótipos gerados por IA onde caminhos de código esquecem de definir campos.

Defaults que escondem bugs (e criam dados piores)

Defaults ficam perigosos quando encobrem relacionamentos faltantes ou inputs obrigatórios. Eles fazem o app parecer estável enquanto enchem o banco com nonsense.

Evite defaults como:

  • user_id = 0 ou account_id = 1 quando o dono real é desconhecido
  • email = '' (string vazia) para contornar falta de email
  • price = 0 quando um preço era obrigatório para cobrar corretamente
  • role = 'admin' porque a UI não enviou um papel

Quando você depois aplica restrições, relatórios, permissões e lógica de billing quebram de formas confusas.

Se algo pode faltar por um tempo, use um estado explícito “não pronto ainda”. Por exemplo, um perfil pode começar com status = 'incomplete' até o usuário adicionar nome e telefone. Isso torna o estado faltante visível, testável e fácil de tratar na UI.

Um cenário rápido: um fluxo de cadastro cria a linha do usuário primeiro e pede detalhes do perfil na tela seguinte. Um default seguro é profile_status = 'incomplete', não name = 'Unknown'. “Unknown” parece dado real, então ninguém corrige.

Validação no app: capturar problemas antes de chegar ao banco

Roll out DB constraints safely
We’ll add safe constraints, defaults, and backfills without breaking live users.

Falhas por nulos frequentemente começam antes do banco ver a requisição. Um formulário envia um campo vazio, um cliente de API esquece uma propriedade ou um webhook envia um payload ligeiramente diferente do testado. Validação é sua primeira linha de defesa. Ela transforma “erro misterioso do servidor” em uma mensagem clara e impede que dados ruins entrem no sistema.

Valide na borda, o mais perto possível da entrada. Isso significa formulários no navegador, requisições da API e integrações que postam dados no seu app (como provedores de pagamento ou ferramentas de email). Se você aceita input em três lugares, precisa checagens nos três. O caminho mais fraco é o que quebra em produção.

Boa validação não é só “obrigatório ou não”. Também normaliza dados para armazenar o que você espera. Remova espaços extras, defina se emails devem ficar em minúsculas e trate strings vazias de forma consistente (muitas vezes como null, mas só se suas regras permitirem). Normalizar cedo evita bugs sutis como contas duplicadas por um espaço no final do email.

Quando a validação falha, retorne mensagens humanas, não stack traces. Usuários devem saber o que corrigir, e suporte deve conseguir reproduzir o problema. Uma resposta 400 com uma frase clara bate um erro 500.

Uma abordagem simples que funciona para a maioria dos apps:

  • Valide no servidor para toda escrita, mesmo que a UI já valide
  • Normalize campos antes de salvar (trim, casing, tratamento de string vazia)
  • Rejeite campos desconhecidos para que erros de digitação não virem nulos silenciosos
  • Use mensagens de erro claras ligadas ao nome do campo
  • Logue falhas de validação com contexto suficiente (mas nunca segredos)

Para evitar divergência de regras, coloque validação em um lugar e reutilize. Um modo comum de erro em produção de protótipos gerados por IA é um conjunto de regras na UI, outro na API e nenhum no handler do webhook. Escolha uma fonte de verdade (frequentemente um validador server-side ou esquema compartilhado) e faça outras camadas referenciá‑la.

Exemplo concreto: seu formulário de cadastro exige nome completo, mas o cliente mobile só envia email e senha. Se o servidor não validar, você pode criar um usuário com name = null, e a primeira página “Welcome, {name}” quebra. Com validação server-side, a requisição falha rápido com “Full name is required” e você nunca armazena o registro quebrado.

Passo a passo: adicionar restrições com segurança em um app existente

Para prevenir falhas por nulos, trate restrições como um rollout, não um interruptor. A abordagem mais segura é aprender onde já existem nulos, consertar linhas antigas e só então bloquear novas más inserções.

Comece inventariando o que pode ser null hoje. Olhe suas tabelas e onde cada campo é usado: respostas da API, renderização na UI, emails, jobs em background e exports. Uma coluna inofensiva numa tela pode quebrar outra que assume presença.

Um rollout prático:

  1. Encontre áreas de risco. Liste colunas que permitem null e procure no código lugares que assumem um valor (por exemplo, chamar .toLowerCase() em um nome).
  2. Decida a regra. Para cada coluna, escolha: deve existir, pode faltar, ou pode faltar só em certos momentos (por exemplo, “até o onboarding ser completo”).
  3. Backfill das linhas existentes. Atualize registros antigos para que correspondam à regra. Se não conseguir backfill correto, use um estado temporário e claramente marcado mais um plano para coletar o valor real depois.
  4. Adicione restrições em pequenas partes. Mude uma coluna (ou uma tabela) por release. Mantenha migrações pequenas para que falhas sejam fáceis de diagnosticar.
  5. Ative a restrição e monitore. Adicione NOT NULL, CHECK, FOREIGN KEY ou regras de unicidade só depois que seus dados e comportamento do app estiverem prontos.

Depois do deploy, espere algumas falhas. Isso normalmente é sinal de que o sistema finalmente está capturando problemas que antes ficavam escondidos.

Adicione monitoramento simples nos novos pontos de falha: conte erros de validação, monitore erros de restrição no banco e logue o payload (sem dados sensíveis) para ver o que está faltando e de onde veio. Por exemplo, se você torna users.email NOT NULL, observe picos vindos de um caminho de cadastro específico ou de uma versão antiga do app mobile.

Tenha um plano de rollback para migrações que falharem em produção:

  • Saiba como reverter a restrição (ou desabilitar a checagem) rapidamente.
  • Tenha um script pronto para reexecutar o backfill em lotes menores.
  • Garanta que seu app suporte ambos os esquemas por um curto período.

Backfills: consertar linhas existentes sem quebrar usuários

Get a free code audit
Start with a free code audit and get a clear list of what to fix next.

Um backfill é um update planejado que preenche valores faltantes em linhas já no banco. Importa porque adicionar um NOT NULL (ou tornar uma coluna obrigatória no app) vai falhar se linhas antigas ainda tiverem nulls. Seed data costuma parecer perfeito. Dados reais raramente são.

Antes de mexer, decida qual deve ser o valor correto para campos faltantes. Às vezes um placeholder seguro é suficiente (como "Unknown" para um rótulo de exibição), desde que o app trate isso claramente. Outras vezes você precisa derivar um valor real. Por exemplo, se users.timezone estiver faltando, você pode derivar pela timezone mais comum na organização do usuário ou por uma região de signup registrada.

Placeholders mantêm tudo andando, mas podem esconder problemas se você escolher valores que parecem reais. Um placeholder ruim pode fazer dashboards mentirem ou enviar emails para o nome errado. Em dúvida, use um valor que seja obviamente “incompleto” ou adicione uma flag separada como profile_incomplete = true para facilitar encontrar e corrigir depois.

Se a tabela for grande, faça backfills em lotes. Lotes pequenos reduzem tempo de bloqueio e baixam a chance de impactar o app durante pico:

  • Atualize um número limitado de linhas por execução (por exemplo 1.000 a 10.000)
  • Rode em horários de baixa carga e pare se erros ou carga do BD subirem
  • Mantenha cada lote idempotente (seguro para tentar de novo)

Logue o que mudou. Debug fica mais fácil quando você responde: “Quais linhas foram tocadas, quando e por qual versão do script?” No mínimo, registre contagens e IDs. Se os dados são sensíveis, logue apenas IDs e um resumo.

Trate backfills como duas coisas: um script one-time e um job repetível. O script corrige nulos de hoje. O job repetível pega dados atrasados (de workers antigos, imports ou retries instáveis) até você ter confiança nas novas regras.

Exemplo: um fluxo de cadastro que falha com perfis incompletos

Um bug comum “funciona com seed data” é assim: um usuário se cadastra, uma linha de conta é criada, mas a linha de perfil fica só parcialmente preenchida. Em dados de teste, todo usuário tem perfil completo, então ninguém nota.

Cenário realista: seu signup cria users e depois profiles. O profile deveria ter display_name e status, mas o código às vezes pula display_name (por exemplo, o usuário fecha a aba no meio do onboarding). Mais tarde, o app renderiza um cabeçalho que assume que o nome existe.

A falha costuma aparecer como:

  • Um erro de servidor ao renderizar uma página (tentando formatar ou uppercase de um nome nulo)
  • Uma resposta API quebrada (um serializer espera string, recebe null)
  • Um erro front-end confuso (um componente lê profile.display_name.length e falha)

Para prevenir falhas por nulos, banco e app devem concordar nas mesmas regras, e registros antigos precisam correspondê‑las também.

Um plano de correção simples

Comece decidindo o que significa “seguro” para um cadastro incompleto. Depois aplique a mesma intenção em quatro frentes:

  • Restrição no BD: exija uma linha de profile para cada user e torne status NOT NULL.
  • Default: defina status com default incomplete.
  • Validação no app: se display_name é exigido para finalizar onboarding, bloqueie “Continuar” até que esteja presente, com mensagem clara.
  • Backfill: atualize usuários existentes com status nulo (ou perfis faltantes) para que se encaixem nas novas regras.

Após a correção, a experiência melhora. Em vez de crash no dashboard, o usuário vê um aviso amigável “Finalize seu perfil” e um formulário curto para adicionar o nome. Sua API fica previsível e o suporte recebe menos relatos “ontem funcionou”.

Erros comuns que fazem as falhas por nulo voltarem

Turn prototype code into production
Send the repo and we’ll repair the spots that break when real data is messy.

A maioria das equipes repete o mesmo padrão: o app parece ok com seed data, então um usuário real pula um campo, uma importação deixa brancos ou uma integração envia payloads parciais. Se você quer evitar falhas por nulos de forma permanente, precisa de consistência entre banco, app e dados existentes.

Um erro comum é adicionar NOT NULL cedo demais. Em um app em produção, quase sempre existem linhas antigas que não batem com a nova regra. Resultado: migração falha, ou um hotfix apressado remove a restrição e te deixa de volta ao ponto inicial.

Outro problema sutil é usar strings vazias como substituto de dados faltantes sem definir o que isso significa. Uma string vazia pode significar “desconhecido”, “não fornecido” ou “intencionalmente em branco”. Se ninguém definir, filtros e relatórios ficam confusos, e você ainda quebra quando o código assume que o valor é significativo.

Padrões que fazem os problemas reaparecerem:

  • Validação só no navegador enquanto chamadas da API, scripts e webhooks ainda podem enviar payloads ruins
  • Defaults que parecem úteis mas mais tarde quebram billing, impostos ou analytics
  • Backfills que só cobrem o caminho feliz, deixando linhas raras até que um usuário bata nelas
  • Testes que cobrem apenas inputs perfeitos
  • Seed data que fica limpa demais e nunca inclui nulos, strings vazias ou relacionamentos faltantes

Defaults mágicos merecem cuidado extra. Se você define created_at como “agora” para linhas históricas, gráficos de retenção ficarão errados. Se defaulta preço faltante para 0, pode acabar dando recursos pagos de graça ou distorcendo receita. Defaults devem tornar o app seguro, não inventar verdades de negócio.

Cheque de realidade rápido: um fluxo de cadastro pode permitir profile.bio opcional, mas depois um template de email assume bio presente e quebra. Isso não é só problema de banco. É quebra de contrato entre regras, código e templates.

Se você herdou um código gerado por IA (de ferramentas como Lovable, Bolt, v0, Cursor ou Replit), esses erros costumam aparecer juntos: validação server-side fraca, tratamento inconsistente de nulos e migrações nunca testadas contra dados bagunçados.

Checklist rápido e próximos passos

Se você quer prevenir falhas por dados nulos, trate dados faltantes como algo normal, não raro. Uma boa correção costuma ser focada: regras claras, algumas restrições e um backfill cuidadoso.

Checklist rápido

Comece listando o que deve existir e o que pode ficar em branco. Depois garanta que a mesma regra seja aplicada no banco e no app.

  • Liste campos realmente obrigatórios por tabela (e por request da API) e o que “faltando” significa (NULL vs string vazia).
  • Escolha defaults apenas onde fazem sentido (por exemplo, status = 'draft') e evite defaults que escondem fluxos quebrados.
  • Confirme onde a validação acontece (form, API, job) e garanta mensagens claras aos usuários.
  • Planeje um backfill para linhas existentes antes de ativar novos NOT NULL.
  • Escolha um dono para as regras (produto decide o permitido; engenharia aplica) e documente tudo em um lugar.

Faça um rápido teste de “input ruim” em seguida. Isso pega caminhos surpresa que nunca existiram com seed data.

Testes básicos para dados faltantes

Execute essas checagens nas jornadas principais: signup, checkout, edição de perfil, imports e telas de admin.

  • Envie formulários com campos opcionais faltando e com campos obrigatórios vazios.
  • Reproduza uma importação com algumas colunas em branco e espaçamento estranho.
  • Chame endpoints chave com chaves JSON faltando (não apenas chaves com valor null).
  • Carregue páginas de contas antigas que podem ter linhas incompletas.

Para evitar regressões, adicione um pequeno conjunto de testes “nulo e vazio” para seus endpoints e jobs principais. Mesmo 5–10 casos podem evitar que mudanças futuras reintroduzam crashes.

Se você está lidando com uma base de código gerada por IA que desmanda quando dados reais aparecem, FixMyMess (fixmymess.ai) pode começar com uma auditoria de código gratuita para encontrar caminhos de null arriscados, depois aplicar correções direcionadas como rollouts de restrições, consertos de validação e backfills para deixar o app pronto para produção.

Perguntas Frequentes

Why does my app work with seed data but crash in production?

Seed data is usually hand-picked to be complete and consistent, so your code only sees the “happy path.” Real users, imports, and integrations quickly introduce missing fields, blank strings, and partial records that your code wasn’t written to handle.

What’s the first step to stop null-related crashes?

Start by writing plain-English rules for each field: required, optional, or required only at certain stages (like “before charging a card”). The goal is to make the app and database agree on what can be missing and when.

Is an empty string basically the same as NULL?

NULL usually means “no value at all,” while an empty string is still a value that often behaves differently in queries, validations, and UI rendering. Pick one meaning for “missing” per field and normalize input so you don’t end up with both forms mixed together.

When should I add NOT NULL constraints?

Use NOT NULL for fields the product truly can’t function without, like ownership IDs, login identifiers, or required timestamps. If you’re not sure, treat it as optional first, add app handling, and only enforce NOT NULL once you’re confident it’s required across every write path.

What kinds of problems do CHECK constraints prevent?

Use CHECK constraints for simple rules that prevent unusable values even when they’re not null, like negative quantities, impossible date ranges, or “blank but not null” text. They’re especially useful to stop “looks filled” data that later breaks billing, reporting, or UI assumptions.

Which database defaults are safe, and which ones are dangerous?

Defaults are good for values that should always exist but shouldn’t rely on the client to send them, like timestamps, initial statuses, or counters. Avoid defaults that invent business truth (like setting a missing price to 0 or assigning a fake owner), because they hide bugs and pollute your data.

Where should validation live to prevent nulls from slipping in?

Validate on the server for every write, even if your UI validates, because scripts, webhooks, imports, and older clients can bypass the browser. Return a clear 400-level error with a field-specific message so you fail fast instead of storing broken rows and crashing later.

How do I add constraints safely in a live app without breaking it?

Audit existing nulls first, decide the rule per column, backfill old rows to match the rule, then add constraints in small releases. If you turn on constraints before cleaning old data, migrations can fail or force rushed rollbacks that put you back where you started.

What is a backfill, and why do I need one before adding NOT NULL?

A backfill updates existing rows that already violate your new rules so you can enforce constraints reliably. Use correct derived values when possible, and if you must use placeholders, make them obviously “incomplete” so they don’t get mistaken for real user data later.

Why are AI-generated prototypes especially prone to null-data crashes, and what can I do about it?

AI-generated prototypes often assume perfect inputs and miss consistent server-side validation, schema constraints, and safe defaults, so messy real data triggers crashes quickly. If you inherited a broken AI-built app, FixMyMess can start with a free code audit and then apply targeted fixes like constraint rollouts, validation repairs, and backfills to make it production-ready in days, often 48–72 hours.