O problema dessa abordagem é que ela considera que os dados sempre virão por uma única fonte.
Quando os dados são lidos de um banco de dados que já existia antes da aplicação ou do uso dessa abordagem, você pode ter problemas em reconstruir objetos a partir desses dados.
Por exemplo, um new CPF('123.456.789-01')
que lance uma exceção* em caso de dados inválidos é muito aceitável quando estamos falando de uma inclusão de um registro. Mas se ao recuperar esse dado do banco de dados e ele não for válido, sua aplicação vai ter que lidar com isso e ter if
espalhado para todo lado.
Já vi problemas como esse surgirem com aplicações que herdaram bancos de dados legados, bancos de dados compartilhados com outras aplicações e com integrações.
Por conta disso, a abordagem que mais praticamos aqui é validar os dados na entrada antes da construção do objeto, com um zod
da vida ou similar, e ter na classe CPF
algo como isValid() : boolean
para indicar se o valor contido ali é válido ou não.
*Sobre as exceções, é uma coisa que a gente também não usa aqui para validar dados. Nós encaramos exceções como exceções 🙃, o que é previsível a gente trata normalmente, por exemplo a entrada de dados de ambientes externos não serem válidas é algo totalmente previsível e facilmente checadas, então não lançamos exceção. No caso do TS/JS, uma das formas de aplicar isso é usando Either Monad
.
Como em Go, a funções podem retornar diversos valores e err
é uma coisa natural do uso da linguagem, não faz sentido usar Either Monad
em Go. (e nem existe exceptions em Go).
Tenho visto muita gente usando exceções como controle de fluxo, principalmente a galera do PHP, quase como um goto
.