Runtime Type Checking: Por que você deveria fazer?
Erros são inevitáveis, mas é importante estar sempre preparado para lidar com eles.
Na computação, os dados são armazenados em memória. Eles podem ser de diferentes tipos, como números, textos, booleanos etc. Cada tipo ocupa um espaço diferente na memória.
Veja alguns exemplos de tipos e o tamanho da memória que eles ocupam:
int a = 1; // 4 bytes
float b = 2.0; // 4 bytes
char c = 'a'; // 1 byte
Se você tentar alocar uma variável com um valor que exige mais espaço do que o reservado para ela, certamente haverá um erro.
Por isso, os tipos são tão importantes: eles indicam quanto de memória uma variável irá ocupar.
Durante a etapa de compilação, o compilador verifica se o tipo de uma variável é compatível com o valor atribuído. Esse processo é chamado de compile-time type checking.
Um outro cenário é quando precisamos validar dados enquanto o software está em execução (runtime).
Um formulário no frontend pode receber dados do usuário; um backend pode receber dados por uma API.
Como garantir que os dados recebidos estão sendo manipulados corretamente? Como garantir que o usuário não está enviando um número como um texto, por exemplo?
Para isso, a resposta é: runtime type checking.
Bibliotecas de runtime type checking
Para fazer essa validação de tipos em runtime, existem bibliotecas que nos ajudam. Normalmente, você define um schema (formato esperado dos dados) e a biblioteca valida se os dados recebidos estão de acordo com esse schema.
Dentre as bibliotecas mais populares, a que eu mais gosto é o Zod. Aqui vão as features que eu mais uso:
Schema definition:
import { z } from "zod";
const userSchema = z.object({
name: z.string(),
age: z.number(),
});
const user = userSchema.parse({
name: "John Doe",
age: 20,
});
console.log(user); // { name: 'John Doe', age: 20 }
✅ O schema define um objeto com as propriedades name (string) e age (number).
Type inference:
import { z } from "zod";
const userSchema = z.object({
name: z.string(),
age: z.number(),
});
const User = z.infer<typeof userSchema>;
✅ O Zod infere o tipo com base no schema definido, resultando em:
type User = { name: string; age: number }
Transform:
import { z } from "zod";
const userSchema = z.object({
name: z.string(),
age: z.number(),
}).transform((data) => ({
name: data.name.toUpperCase(),
age: data.age * 2,
}));
const user = userSchema.parse({
name: "John Doe",
age: 20,
});
console.log(user); // { name: 'JOHN DOE', age: 40 }
✅ O transform modifica os dados após a validação, transformando name para uppercase e age para o dobro.
Parse:
import { z, ZodError } from "zod";
const registerBodySchema = z.object({
name: z.string(),
age: z.number(),
});
const registerController = (request, response) => {
try {
const { name, age } = request.body;
const registerBody = registerBodySchema.parse({
name,
age,
});
return response.status(201).json(registerBody);
} catch (error) {
if (error instanceof ZodError) {
return response.status(400).json({
message: "Invalid body",
});
}
return response.status(500).json({
message: "Internal server error",
});
}
};
✅ Neste caso, parse lança um erro automaticamente se os dados não estiverem de acordo com o schema.
Safe Parse:
import { z } from "zod";
const envSchema = z.object({
NODE_ENV: z.enum(["development", "production"]),
});
const env = envSchema.safeParse(process.env);
if (!env.success) {
throw new Error("Invalid environment variables");
}
export const env = env.data;
✅ safeParse retorna um objeto com success: true | false, permitindo tratar erros de forma segura e manual.
Por que usar runtime type checking?
Erros são inevitáveis, mas é importante estar sempre preparado para lidar com eles.
Uma excelente forma de evitar falhas em tempo de execução é utilizando runtime type checking. Isso permite:
- Enviar mensagens de erro claras para o usuário, por exemplo: mostrando campos inválidos ou não preenchidos;
- Exibir logs de erro no console para facilitar o debug e a identificar a causa do erro.
Conclusão
Os tipos nos ajudam a garantir que os dados sejam manipulados corretamente.
Em tempo de compilação, o compilador verifica se o tipo de uma variável é compatível com o valor atribuído, isso é compile-time type checking.
O runtime type checking é uma excelente forma de lidar com erros, pois nos ajuda a identificar problemas com precisão e corrigi-los mais rapidamente.