Executando verificação de segurança...
1

Bom, primeiramente:

A monad is just a monoid in the category of endofunctors.

Brincadeiras a parte. Acredito que seu artigo é razoável no que tange ao que expressa, mas não responde nem a pergunta: 'exception é um vilão?' nem as objeções mais comuns contra exceptions (como o fato delas serem significativamente mais lentas que erros como valores, o fato de que elas introduzem certa imprevisibilidade na control-flow do código, etc...).

Dito isso, vou deixar meus 2¢ aqui sobre o assunto.

Utilize o padrão da linguagem

Razoável, completamente aceitável e uma boa dica. No entanto, como tudo, pra isso existem exceções claras. A mais óbvia delas é que nenhuma linguagem está de facto pronta. Mesmo C# depois de 20 anos continua a evoluir constantemente, e um fatores que faz com que linguagens mudem e se adaptem e por vezes se tornem melhores é justamente não seguir os padrões predeterminados da linguagem. Usarei C# como um exemplo (já que é minha linguagem de escolha geral, mas isso pode ser aplicado a qualquer uma, de Kotlin a Rust ou mesmo Go). C# se adaptou fortemente nos últimos anos a vários recursos presentes em linguagens de paradigma funcional, como pattern matching, suporte a sintaxes mais declarativas e afins, ele fez isso pois a própria comunidade tendia a utilizar ele de uma forma que levava a isso (e porque ela estava buscando por isso ao criar propostas pra serem incorporadas nas novas versões da linguagem; a própria Microsoft está de olho nisso e mesmo em linguagens como Rust [admitidamente]). Se todos seguissem determinado padrão de apenas programar de forma estrita e orientada a objetos (como é um dos paradigmas principais de quando C# foi criado), não teriamos nenhum desses desenvolvimentos. Então, apesar de eu também indicar manter-se no padrão, acredito que levar isso muito a sério será muito mais prejudicial do que benéfico no longo prazo pra adaptabilidade da linguagem (inclusive, se você sente a necessidade de fazer um "wrapper de alguma coisa" já é um indício de que isso é algo que falta na linguagem em questão, se muitas pessoas sentem isso isso se torna uma modificação efetiva com o tempo).

Da mesma forma, acredito que a dica de utilizar exceptions de acordo com as especificações da linguagem mina a própria maleabilidade da linguagem. Existe por exemplo um certo movimento relacionado a adicionar uniões discriminadas em C# já a algum tempo, motivado justamente por pessoas percebendo as vantagens desse tipo de estrutura e buscando trazer isso para complementar C# (da mesma forma, existem diversos "wrappers" disso, como o clássico OneOf ou o ErrorOr mencionado); na medida da existência dessas libraries e do fato de que elas possuem adopters podemos ver que existe uma busca por justamente quebrar com esse paradigma de tratar exceptions como erros, e disso algo bom pode surgir (ou pode ser barrado pelo time do C#, como tem sido até o momento, os desenvolvedores também são responsáveis por manter certos padrões mesmo com buscas da comunidade em alguns casos, pela própria visão que buscam criar da linguagem mesma, no entanto nada disso é imediatamente óbvio).

Então, discordo de que isso é "mais importante", mas acredito que o sentimento geral de que é benéfico em muitos casos que padrões sejam respeitados é perfeitamente válido.

Minha palhinha sobre exceptions e errors como valores

Acho que as questões acima resumem boa parte do que pensei sobre o artigo em geral, mas acredito que posso trazer algo pra esse campo também.

Penso que em termos gerais: Exceptions são funcionalmente equivalentes a erros como valores.
O que isso significa? Significa que, pelo menos no que posso perceber, você não tem vantagens ou desvantagens imediatamente óbvias e funcionalmente discrepantes entre as duas formas de escrever código (e nesse sentido, a discussão é fútil para a maior parte dos casos).

Um dos maiores problemas que vejo nas exceptions é também um problema que vejo no próprio tratamento de erros de Go, e que eu não veria em relação a Swift e Rust. Que é a questão de 'checagem em tempo de compilação'.
Tanto C# quanto Go tratam seus erros como questões opcionais de serem lidadas, e tratam os valores como o principal. Ambos dão espaço para erros bobos que podem se alastrar pela aplicação, ambos possuem um ponto de falha na representação dos próprios estados possíveis da aplicação que me parece um erro em si mesmo. Em C# você pode simplesmente não checar as exceptions com um try-catch, e com isso seu programa vai crashar e você vai ter erros e, em alguns casos, pode ter problemas com recursos não fechados ou o que for. Em Go, a situação é a mesma, se não verificamos os erros adquirimos a chance de acessar dados corrompidos ou inválidos, crashar nosso programa e incorrer em diversos problemas.
Acredito que isso é semanticamente problemático do ponto de vista da utilização das linguagens, e nesse sentido eu diria que Rust e Swift satisfazem esse problema de uma forma muito superior mesmo adotando formas diferentes de lidar com erros.
Em Rust, você tem uma certa verbosidade natural de lidar com os erros, mas garante que o estado X em que seu programa estará vai conter toda a informação que você precisa saber sobre os valores que estão sendo operados: i.e. se você receber um Result<User, Error> ao chamar a função login(...), você sabe que pode estar lidando ou com um User (login bem sucedido) ou com um problema ao fazer login (Error), e terá certeza de estar acessando o estado apropriado dos seus dados na medida em que fizer o 'match' neles (ou usar algo como let-else ou if-let, ou o ?). Rust induz uma abordagem de transformação dos estados, induz a irrepresentabilidade de erros em valores no código na medida em que os problemas que aparecem precisam ser tratados pra consumir os valores que estão sendo contidos nos erros, com isso ele remove a própria possibilidade de você simplesmente ignorar o 'err' e usar o valor como se nada tivesse acontecido.
Swift, na mesma medida, consegue uma proeza semelhante ao exigir que você sempre esteja imediatamente verificando se uma função levanta uma exception ou não, e exigindo que você marque suas funções como potenciais geradoras de erros caso você utilize 'throw' no código. Dessa forma, você remove novamente a possibilidade de incorrer em problemas de representação dos estados do programa, ao exigir que os erros sejam tratados de imediato caso ocorra algum problema (ou, como em Rust, ao 'levantar' os erros para que funções anteriores na hierarquia de chamadas lidem com os erros). Uma vez que esse mecanismo está colocado na própria corelib da linguagem (e em todas as utilizações da linguagem), ele também garante que essa integridade de estados seja propagada em todas as libraries que você possa consumir e, com isso, lhe adiciona segurança no código que escreve (é claro que isso não é uma garantia 100% certeira, mesmo em Rust você tem coisas como 'panic' que podem evadir do comportamento tradicional de gerenciamento de erros; mas nessas linguagens recursos do tipo são realmente tratados como 'exceções' ao invés da regra, justamente por possuírem melhores recursos pra lidar com os erros de forma primária).

Então, eu diria que tanto erro como valor quanto exceptions podem e foram implementadas de forma errada e/ou problemática em muitas linguagens, mas acredito que uma vez que fossem corretamente implementadas, não haveria diferença prática na utilização de ambas que não a sintaxe para tal.

Conclusão

Apenas use o que lhe for mais conveniente, se precisar utilizar erros como valores em C# porque quer levar seu código a ter um nível maior de garantias faça isso, se quiser se prender ao padrão e manter um nível mais imediatamente compreensível de código faça isso, mas qualquer que seja sua decisão faça questão de manter isso de forma clara e pública pra todos que forem utilizar seu código possam entender, consumir e escrever na medida dos padrões que você adotar no tratamento de erros (ou em outras áreas, que seja). Em geral discussões de internet são sem sentido, e a maioria das dicas encontradas são inúteis ou pedantes, em muitos casos será melhor que você estude os benefícios e malefícios de cada forma de fazer as coisas e entenda-os bem antes de entrar em qualquer empreitada específica que tenha uma disrupção com os padrões estabelecidos de uma linguagem ou ambiente no qual você está programando. Fora isso, você é um engenheiro de software, tome as benditas decisões que façam seu código ser o melhor possível dentro dos seus limites.

Cheers.

Carregando publicação patrocinada...