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.
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,
anytemporário destrava o trabalho. Mesmo aí, prefiraunknownnas bordas e trate osanycomo 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
anyno menor escopo possível — idealmente convertendo paraunknownna 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.