O Portão
門 — A FRONTEIRA — DISCIPLINA

門 番MonbanO Portão · onde o contrato é forjado

Toda rota declara um contrato. Toda função recebe pré-condição. Toda chamada de API espera um shape. Quando o contrato é declarado e não validado no portão, todo cômodo abaixo herda a obrigação de defender contra estado inválido — e cada um trata mal do seu jeito.

Quem cuida do portão decide o que entra na forja.

Quando a promessa do nome não é validada na entrada.

Quatro casos onde o sistema declara um pré-requisito mas o esquece de checar. Cada um vira fogo no chão dos componentes downstream — que nem sabiam ter sido eleitos guardiões.

  1. 01

    Rota protegida sem porteiro

    A pasta se chama `(protected)`. O nome promete sessão autenticada. Mas o arquivo `page.tsx` renderiza sem checar cookie, dispara fetch que volta 403, e cada componente downstream herda a obrigação de defender o que o portão devia ter recusado.

  2. 02

    Função pública que confia no chamador

    Assinatura diz `(user: User) => void`, mas em runtime aceita `undefined` porque algum call site passa o resultado de um `find()` sem checar. O TS mente — a peneira é só visual.

  3. 03

    Prop required no tipo, opcional no runtime

    `pocketType: { key: string; value: string }` no tipo. O componente lê `pocketType.value` sem hesitar. Em alguma rota patológica, o pai renderiza com `pocketType={undefined}` — e o React explode em produção sem aviso.

  4. 04

    Migration que assume FK existente

    O DBA escreveu `ALTER TABLE orders ADD CONSTRAINT fk_account FOREIGN KEY (account_id) REFERENCES accounts(id)` sem rodar antes `SELECT ... WHERE account_id NOT IN (SELECT id FROM accounts)`. A linha quebra no deploy às 3 da manhã.

As patrulhas mal pagas que nascem da peneira faltante.

Quatro anti-padrões que aparecem quando o portão falhou e cada cômodo tenta proteger sozinho. Parecem cuidado. São silêncio. Defendem a estética, não a verdade do dado.

  1. 01

    Optional chaining como tapete

    `a?.b?.c?.d` em toda leitura, sem nunca perguntar por que `a` poderia ser undefined. A tela não quebra mas mostra dado errado em silêncio. O usuário acha que clicou no botão certo. O ferreiro não percebeu que a lâmina amassou.

  2. 02

    Fallback fictício

    Quando dado falta, retornar `{ key: "", value: "" }` ou `0` ou `[]` — um valor que parece neutro mas envenena cálculo abaixo. Total vira R$ 0,00 mentindo. Lista vazia esconde erro de carregamento. Default fictício é impureza com gravata de robustez.

  3. 03

    try/catch que engole

    `} catch (e) { console.log(e); }`. O erro vira ruído no console e a função retorna como se nada tivesse acontecido. A UI segue. O bug some. Quem investigar daqui a três meses não vai achar a origem — porque ela foi enterrada no momento que aconteceu.

  4. 04

    Estado inicial mentiroso

    `useState({ name: "", orders: [] })` num componente que precisa do servidor pra ter sentido. O primeiro render mostra "0 pedidos" e o usuário acha que comprou nada. O dado real chega 200ms depois e a UI pisca. O default mentiu durante 200ms — e em conexões ruins, durante muito mais.

Quatro andares que aceitam o porteiro.

A defesa de contrato tem domicílio. Não é todo lugar — é o primeiro ponto da cadeia que pode dizer não com autoridade. Saber qual dispensa todo o código abaixo de checagem redundante.

  1. 01

    O middleware do framework

    Next, Remix, SvelteKit, Rails — todos têm um lugar antes do render onde dá pra cortar a requisição. `if (!session) redirect("/entrar")` em uma linha resolve o que cinquenta `?.` espalhados nunca resolveriam. O portão é o lugar mais barato pra bater o martelo.

  2. 02

    Schema na borda do dado externo

    Toda resposta de API, todo payload de webhook, todo arquivo lido do disco — passa por `schema.parse(input)` antes de virar tipo do domínio. Zod, Yup, ou um parser feito à mão. O contrato fica no parser. Depois disso, o tipo é verdade — não promessa.

  3. 03

    Newtype que recusa criação inválida

    `type Email = string & { __email: never }` com `function asEmail(s: string): Email | null`. Se o construtor recusou, o resto do programa não vê o caso inválido. A defesa fica no construtor — não em cada função que recebe um Email. Dá pra fazer o mesmo com OrderId, UserId, qualquer coisa cujo formato importa.

  4. 04

    Gate antes do import dinâmico

    Feature flag, role, ambiente — checar antes de carregar o módulo, não depois. `if (flagOff) return null; const Mod = await import(...)`. Dispensa todo o componente de saber que pode ser desligado. A peneira fica antes da forja, não dentro.

Quatro perguntas antes de cravar a defesa.

O ferreiro que defende em cada cômodo não tem portão.
Quem tem portão raramente precisa defender em cômodo.

  1. 01

    Quem foi o primeiro a tocar nesse dado?

    Suba a stack. Rastreia até a origem — fetch, parse de URL, leitura de cookie, deserialização de localStorage. Se o ponto de entrada não validou, todo mundo abaixo tá patrulhando o que devia ser garantido.

  2. 02

    Quem deveria tê-lo recusado?

    O middleware? O parser? O type system? O construtor? Pelo menos um desses tinha jurisdição pra dizer não. Identificar qual e mover a defesa pra lá. O resto do código relaxa.

  3. 03

    Se eu apagar todos os ?. deste arquivo, onde a tela explode?

    Experimento mental brutal. Cada explosão é um contrato esquecido lá em cima. Cada `?.` que sobreviver é uma cobertura legítima de caso opcional. Os outros são ruído defensivo herdado.

  4. 04

    A peneira atual fica em qual andar?

    Mapeie a cadeia: portão, parser, tipo, componente. A peneira efetiva é a primeira que recusa entrada inválida — todas as outras são redundância (boa ou má, depende). Se a peneira tá no quinto andar e a impureza entrou no térreo, sobrou poeira em todos os cômodos do meio.

A peneira mais barata é a primeira da cadeia.
O Portãocontrato no porteiro