Refatorações guiadas por tipos que valeram a pena
1 min
“Tornar estados ilegais irrepresentáveis” parece um slogan até um union type eliminar uma classe inteira de bugs. Três antes/depois de um código real.
O primeiro foi um objeto de request com cinco campos opcionais e um comentário explicando quais combinações eram de fato válidas. Ninguém lia o comentário. Trocá-lo por uma union discriminada sobre um campo status fez o compilador impor as combinações que o comentário só descrevia, e uma categoria inteira de bugs do tipo “por que esse campo está undefined” parou de acontecer.
O segundo foi um estado de carregamento modelado como três booleanos separados — isLoading, isError, hasData — que podiam, e às vezes podiam mesmo, ser todos verdadeiros ao mesmo tempo. Juntar tudo numa única union com tag, um estado por vez, eliminou o cenário em que a interface desenhava um spinner por cima de uma mensagem de erro, um bug real em produção durante meses antes de alguém rastrear a causa.
O terceiro foi mais discreto: uma função que aceitava uma string qualquer para um valor monetário, o que significava que “10”, “10.00” e “R$10” eram todos entradas válidas do ponto de vista do sistema de tipos. Um tipo com marca (branded type), que só podia ser construído por um parser validado, empurrou a validação para a borda uma única vez, em vez de repeti-la em quatro pontos de chamada com quatro regexes ligeiramente diferentes.
Nenhuma dessas mudanças levou mais que uma tarde. O padrão nas três: achar o comentário que explica uma restrição que os tipos não impõem, e perguntar se o sistema de tipos pode simplesmente impor.