mas travar o software do seu cliente ainda não é uma coisa boa
Certamente é uma coisa boa.
Você deseja que qualquer coisa que deixe o sistema em um estado indefinido pare o sistema, porque um sistema indefinido pode fazer coisas desagradáveis, como dados corrompidos, formatar o disco rígido e enviar e-mails ameaçadores ao presidente. Se você não conseguir recuperar e colocar o sistema novamente em um estado definido, a falha é a coisa responsável a fazer. É exatamente por isso que criamos sistemas que falham, em vez de se separarem silenciosamente. Agora, com certeza, todos nós queremos um sistema estável que nunca trava, mas só queremos isso quando o sistema permanece em um estado seguro previsível definido.
Ouvi dizer que exceções como fluxo de controle são consideradas um sério antipadrão
Isso é absolutamente verdade, mas geralmente é mal compreendido. Quando eles inventaram o sistema de exceção, tinham medo de interromper a programação estruturada. Programação estruturada é por isso que temos for
, while
, until
, break
, e continue
quando tudo o que precisamos, para fazer tudo isso, é goto
.
Dijkstra nos ensinou que usar o goto informalmente (ou seja, pular onde você quiser) faz da leitura de código um pesadelo. Quando eles nos deram o sistema de exceção, eles temiam que estavam se reinventando. Então eles nos disseram para não "usá-lo para controle de fluxo", esperando que entendêssemos. Infelizmente, muitos de nós não.
Estranhamente, não costumamos abusar de exceções para criar código espaguete, como costumávamos fazer com o goto. O conselho em si parece ter causado mais problemas.
Fundamentalmente, as exceções são sobre rejeitar uma suposição. Quando você solicita que um arquivo seja salvo, assume que o arquivo pode e será salvo. A exceção que você recebe quando não pode pode ser o nome ser ilegal, o HD estar cheio ou o rato roer seu cabo de dados. Você pode lidar com todos esses erros de maneira diferente, pode lidar com todos da mesma maneira ou pode deixar que eles interrompam o sistema. Há um caminho feliz no seu código em que suas suposições devem ser verdadeiras. De uma maneira ou de outra, as exceções o afastam desse caminho feliz. Estritamente falando, sim, isso é uma espécie de "controle de fluxo", mas não é sobre isso que eles estavam avisando. Eles estavam falando sobre bobagens como esta :
"Exceções devem ser excepcionais". Essa pequena tautologia nasceu porque os projetistas de sistemas de exceção precisam de tempo para criar rastreamentos de pilha. Comparado a pular, isso é lento. Consome tempo de CPU. Mas se você estiver prestes a registrar e interromper o sistema ou pelo menos interromper o processamento intensivo de tempo atual antes de iniciar o próximo, você terá algum tempo para matar. Se as pessoas começarem a usar exceções "para controle de fluxo", essas suposições sobre o tempo sairão pela janela. Portanto, "Exceções devem ser excepcionais" foi realmente dada a nós como uma consideração de desempenho.
Muito mais importante do que isso não está nos confundindo. Quanto tempo você levou para identificar o loop infinito no código acima?
NÃO retorne códigos de erro.
... é um bom conselho quando você está em uma base de código que normalmente não usa códigos de erro. Por quê? Porque ninguém vai se lembrar de salvar o valor de retorno e verificar seus códigos de erro. Ainda é uma boa convenção quando você está em C.
OneOf
Você está usando outra convenção. Tudo bem, desde que você esteja definindo a convenção e não apenas lutando contra outra. É confuso ter duas convenções de erro na mesma base de código. Se, de alguma forma, você se livrou de todo o código que usa a outra convenção, vá em frente.
Eu também gosto da convenção. Uma das melhores explicações que encontrei aqui * :
Mas, por mais que eu goste, ainda não vou misturá-lo com as outras convenções. Escolha um e fique com ele. 1 1
1: Com o que quero dizer, não me faça pensar em mais de uma convenção ao mesmo tempo.
Pensamentos subsequentes:
Nesta discussão, o que estou retirando atualmente é o seguinte:
- Se você espera que o chamador imediato capte e lide com a exceção na maioria das vezes e continue seu trabalho, talvez por outro caminho, provavelmente deve fazer parte do tipo de retorno. Opcional ou OneOf pode ser útil aqui.
- Se você espera que o chamador imediato não capte a exceção na maioria das vezes, lance uma exceção, para economizar a bobagem de passá-la manualmente para a pilha.
- Se você não tiver certeza do que o chamador imediato fará, talvez forneça os dois, como Parse e TryParse.
Realmente não é assim tão simples. Uma das coisas fundamentais que você precisa entender é o que é um zero.
Quantos dias restam em maio? 0 (porque não é maio. Já é junho).
Exceções são uma maneira de rejeitar uma suposição, mas não são a única. Se você usar exceções para rejeitar a suposição, deixa o caminho feliz. Mas se você escolher valores para enviar o caminho feliz que sinaliza que as coisas não são tão simples como foi assumido, você poderá permanecer nesse caminho enquanto puder lidar com esses valores. Às vezes, 0 já é usado para significar algo, então você precisa encontrar outro valor para mapear sua suposição de rejeitar a ideia. Você pode reconhecer essa idéia pelo seu uso em boa e velha álgebra . Mônadas podem ajudar com isso, mas nem sempre precisa ser uma mônada.
Por exemplo 2 :
IList<int> ParseAllTheInts(String s) { ... }
Você pode pensar em alguma boa razão para isso ter que ser planejado para que deliberadamente jogue alguma coisa? Adivinha o que você ganha quando nenhum int pode ser analisado? Eu nem preciso te contar.
Isso é um sinal de um bom nome. Desculpe, mas TryParse não é minha idéia de um bom nome.
Muitas vezes evitamos lançar uma exceção ao receber nada quando a resposta pode ser mais do que uma coisa ao mesmo tempo, mas por algum motivo, se a resposta for uma coisa ou nada, ficamos obcecados em insistir que ela nos dê uma coisa ou jogue:
IList<Point> Intersection(Line a, Line b) { ... }
As linhas paralelas realmente precisam causar uma exceção aqui? É realmente tão ruim se essa lista nunca conter mais de um ponto?
Talvez semanticamente você não consiga aceitar isso. Se assim for, é uma pena. Mas talvez as mônadas, que não têm um tamanho arbitrário como o List
fazem, farão com que você se sinta melhor.
Maybe<Point> Intersection(Line a, Line b) { ... }
As Mônadas são pequenas coleções de finalidades especiais que devem ser usadas de maneiras específicas que evitam a necessidade de testá-las. Devemos encontrar maneiras de lidar com eles, independentemente do que eles contêm. Dessa forma, o caminho feliz permanece simples. Se você abrir e testar todas as mônadas que tocar, estará usando errado.
Eu sei, é estranho. Mas é uma nova ferramenta (bem, para nós). Então, dê um tempo. Os martelos fazem mais sentido quando você para de usá-los nos parafusos.
Se você me quiser, gostaria de abordar este comentário:
Como é que nenhuma das respostas esclarece que a mônada Either não é um código de erro e o OneOf também não é? Eles são fundamentalmente diferentes, e a pergunta, consequentemente, parece se basear em um mal-entendido. (Embora de forma modificada ainda seja uma pergunta válida.) Konrad Rudolph 4 de junho de 18 às 14:08
Isto é absolutamente verdade. As mônadas estão muito mais próximas das coleções do que as exceções, sinalizadores ou códigos de erro. Eles fazem bons recipientes para essas coisas quando usados com sabedoria.
Result
;-)