LGMdevs
Back to blog
Equipe LGMdevs

TypeScript strict: por que proibimos `any`

Um `any` não é só um tipo frouxo — ele desliga o verificador e o silêncio se espalha pelo código. O que usar no lugar: unknown, narrowing e generics.

TypeScriptDXArquitetura

Adotar TypeScript e encher o código de any é como instalar um alarme e deixar a bateria fora. O compilador continua lá, mas parou de te avisar. Por isso, nos nossos projetos, any é proibido por convenção — e a proibição não é preciosismo. É o que mantém a promessa do TypeScript de pé conforme o time e a base crescem.

any não afrouxa a checagem: ele a desliga

A documentação oficial é direta sobre o que any faz: qualquer operação sobre um valor any é aceita, sem verificação. Você pode chamar um método que não existe, acessar uma propriedade fantasma, passar o valor para onde quiser — o compilador não reclama de nada.

function processa(dado: any) {
  dado.metodoQueNaoExiste()   // sem erro
  return dado.preco * dado.qtd // sem erro, mesmo que 'dado' seja uma string
}

Não é que a checagem fique mais tolerante nesse ponto. Ela simplesmente não acontece. E o erro que o TypeScript existiria para pegar reaparece onde dói mais: em runtime, na frente do usuário.

O problema real: any é contagioso

Se o estrago ficasse contido numa variável, seria administrável. Não fica. O any vaza para tudo que encosta nele. O retorno de uma função any é any; o campo que você lê dele é any; a variável que recebe esse campo é any. Uma única brecha desliga a verificação de um rastro inteiro de código — silenciosamente.

const resposta: any = await fetch("/api/pedido").then((r) => r.json())
const total = resposta.itens.reduce((s, i) => s + i.valor, 0)
// 'resposta', 'itens', 'i' e 'total' são todos any.
// Nenhum erro de digitação em 'itens'/'valor' será pego aqui.

É por isso que uma regra de projeto vale mais que a disciplina caso a caso: basta um any esquecido num ponto de entrada de dados para a segurança de tipos evaporar rio abaixo, sem nenhum aviso.

O que usar no lugar

unknown na fronteira

Para valores que você realmente não conhece — resposta de API, JSON.parse, entrada externa — o tipo certo é unknown, não any. Ele representa qualquer valor, como o any, mas é seguro: o TypeScript não deixa você fazer nada com um unknown antes de provar o que ele é.

function f(a: unknown) {
  a.b()   // Erro: 'a' é do tipo 'unknown'
}

unknown te obriga a checar antes de usar — que é exatamente o comportamento que você quer numa fronteira de dados.

Narrowing para estreitar o tipo

De posse de um unknown, você o estreita com verificações que o compilador entende (typeof, in, checagem de propriedades) ou com um validador de schema. A partir daí, o tipo é conhecido e checado de novo:

function precoFormatado(dado: unknown): string {
  if (
    typeof dado === "object" && dado !== null &&
    "preco" in dado && typeof dado.preco === "number"
  ) {
    return dado.preco.toFixed(2)   // aqui 'dado.preco' é number, garantido
  }
  throw new Error("payload inválido")
}

Em código real, esse trabalho normalmente fica com uma biblioteca de validação (Zod e afins), que faz o narrowing e ainda valida em runtime de uma vez só.

Generics quando o tipo é "o que você me passar"

Muita gente recorre a any quando na verdade quer dizer "isto funciona com qualquer tipo, mas o mesmo tipo entra e sai". Isso é um generic, e ele preserva a informação que o any jogaria fora:

// any: você perde o tipo — o retorno vira any
function primeiroRuim(lista: any[]): any { return lista[0] }

// generic: o tipo entra e volta intacto
function primeiro<T>(lista: T[]): T | undefined { return lista[0] }

const n = primeiro([1, 2, 3])       // number | undefined
const s = primeiro(["a", "b"])      // string | undefined

A ressalva honesta

Ser dogmático a ponto de mentir também não ajuda. Há dois casos legítimos:

  • Migração de código JS para TS. Num porte grande, any temporário destrava o trabalho. Mesmo aí, prefira unknown nas bordas e trate os any como dívida a pagar, não como estado final.
  • Um tipo genuinamente dinâmico de uma lib mal tipada. Acontece. A saída é isolar o any no menor escopo possível — idealmente convertendo para unknown na fronteira — e nunca deixá-lo vazar para o resto do código.

Quando um escape é mesmo necessário, ele fica explícito e localizado (um // eslint-disable-next-line com justificativa), não espalhado. A diferença entre um any pontual e assumido e um any por comodismo é toda a diferença entre um projeto que você consegue refatorar sem medo e um que você reza para não quebrar.


Começando um projeto e quer a base de tipos certa desde o primeiro commit? Fale com a gente.

Fontes