en
· 4 min de leitura

TypeScript como ferramenta de design, não como linter

Se seus tipos só pegam erro de digitação, você usa um décimo da ferramenta. Tipos são onde eu desenho o sistema primeiro.

fullstacktypescript

A maioria dos times usa TypeScript como corretor ortográfico. Ele pega uma propriedade digitada errado, um argumento faltando, um undefined que escapou. Isso é útil, e é um décimo do que a ferramenta consegue fazer.

Eu uso TypeScript do jeito que uso um quadro branco. Antes de escrever o corpo de uma função, escrevo os tipos. Antes de decidir como uma feature se comporta, decido em quais estados ela pode estar e faço o compilador recusar todo estado que não listei.

Torne o impossível irrepresentável

Aqui está a diferença na prática. Um tipo ingênuo de pedido tem uma string de status, uma data opcional paidAt, uma data opcional shippedAt, um cancellationReason opcional. Toda combinação é legal para o compilador, incluindo um pedido cancelado que foi enviado e nunca foi pago.

A versão desenhada é uma discriminated union. Um pedido pendente não tem datas. Um pedido pago tem paidAt e nada mais. Um pedido enviado tem as duas datas. Um pedido cancelado tem um motivo e um timestamp. Agora uma função que renderiza a página de rastreio nem pode ser escrita para um pedido cancelado, porque o campo shippedAt não existe naquele ramo.

Isso não é melhoria de lint. É uma decisão de design, codificada em um lugar de onde ela não pode se descolar do código. O próximo engenheiro que adicionar um status de reembolso recebe um erro de compilação em cada switch que esqueceu dele, se você fechar esses switches com uma checagem exaustiva de never.

Tipos como primeiro rascunho do contrato

Quando começo uma feature, escrevo o tipo da entrada, o tipo da saída e a união de tudo que pode dar errado. Uma função que retorna um result type com uma união explícita de erros diz mais para quem chama do que qualquer docstring. Diz que um pagamento pode ser recusado, que o cartão pode estar vencido, que o provedor pode dar timeout, e obriga a tratar cada caso ou ignorá-lo explicitamente.

Faço isso antes de saber como a função será implementada. Muitas vezes os tipos revelam que a feature é mais complicada do que o ticket sugeria, e esse é o momento de ter essa conversa, não depois de duas semanas de código.

Branded types são a ferramenta seguinte. Um id de usuário e um id de pedido são strings em tempo de execução, e já encontrei bugs reais onde foram trocados. Um brand é uma etiqueta de custo zero no nível dos tipos que transforma essa troca em erro de compilação. Uma linha de definição de tipo, e uma categoria inteira de erros desaparece.

Onde o compilador se paga

As flags strict não são opcionais nos meus projetos. Só noUncheckedIndexedAccess já pegou mais bugs em auditorias do que qualquer suíte de testes que já li, porque obriga você a admitir que um acesso a array pode falhar.

O operador satisfies me deixa checar que um objeto de configuração bate com um schema sem alargar o tipo dele, o que mantém o autocomplete preciso. Template literal types me deixam tipar strings de rota e nomes de evento, de modo que um erro de digitação em um nome de evento vira erro de compilação em vez de um no-op silencioso em produção.

Mantenho os tipos perto dos dados que descrevem, e derivo tipos da fonte da verdade em vez de duplicá-los. Se o schema do banco é a verdade, gere os tipos a partir dele. Se um schema de validação é a verdade, infira o tipo do schema. Duas definições do mesmo formato vão discordar mais cedo ou mais tarde.

O custo, e quando parar

Design no nível dos tipos tem um teto. Quando um tipo leva mais tempo para ser lido do que a função que ele protege, você passou desse teto. Conditional types profundamente recursivos deixam o compilador lento, confundem o time e raramente evitam um bug que um tipo mais simples não evitaria.

Minha regra é que um tipo deve ser legível por um engenheiro que está no time há uma semana. Se precisa de comentário explicando o truque, é um truque, e truques não sobrevivem à rotatividade.

Mas dentro desse limite, o compilador é o revisor mais barato que você vai contratar. Ele lê cada linha, nunca cansa e não se importa que o prazo seja sexta-feira. Desenhe com ele, não ao redor dele.