Recentemente, comecei a trabalhar em um local com desenvolvedores muito mais antigos (com mais de 50 anos). Eles trabalharam em aplicativos críticos que lidam com a aviação, onde o sistema não podia cair. Como resultado, o programador mais velho tende a codificar dessa maneira.
Ele tende a colocar um booleano nos objetos para indicar se uma exceção deve ser lançada ou não.
Exemplo
public class AreaCalculator
{
AreaCalculator(bool shouldThrowExceptions) { ... }
CalculateArea(int x, int y)
{
if(x < 0 || y < 0)
{
if(shouldThrowExceptions)
throwException;
else
return 0;
}
}
}
(Em nosso projeto, o método pode falhar porque estamos tentando usar um dispositivo de rede que não pode estar presente no momento. O exemplo da área é apenas um exemplo do sinalizador de exceção)
Para mim, isso parece um cheiro de código. Escrever testes de unidade se torna um pouco mais complexo, pois você precisa testar o sinalizador de exceção a cada vez. Além disso, se algo der errado, você não gostaria de saber imediatamente? Não deveria ser responsabilidade do interlocutor determinar como continuar?
Sua lógica / raciocínio é que nosso programa precisa fazer uma coisa, mostrar dados ao usuário. Qualquer outra exceção que não nos impeça de fazê-lo deve ser ignorada. Concordo que eles não devem ser ignorados, mas devem aparecer e ser manipulados pela pessoa apropriada, e não precisam lidar com bandeiras para isso.
Essa é uma boa maneira de lidar com exceções?
Edit : Apenas para dar mais contexto sobre a decisão de design, suspeito que seja porque, se esse componente falhar, o programa ainda poderá operar e executar sua tarefa principal. Portanto, não queremos lançar uma exceção (e não lidar com isso?) E interromper o programa quando, para o usuário, estiver funcionando bem
Edit 2 : Para dar ainda mais contexto, no nosso caso, o método é chamado para redefinir uma placa de rede. O problema surge quando a placa de rede é desconectada e reconectada, é atribuído um endereço IP diferente; portanto, a redefinição lançará uma exceção, pois estaríamos tentando redefinir o hardware com o antigo IP.
Respostas:
O problema dessa abordagem é que, embora as exceções nunca sejam lançadas (e, portanto, o aplicativo nunca trava devido a exceções não capturadas), os resultados retornados não são necessariamente corretos e o usuário pode nunca saber que há um problema com os dados (ou qual é esse problema e como corrigi-lo).
Para que os resultados sejam corretos e significativos, o método de chamada deve verificar o resultado em busca de números especiais - ou seja, valores de retorno específicos usados para indicar problemas que surgiram durante a execução do método. Números negativos (ou zero) retornados para quantidades definidas positivas (como área) são um excelente exemplo disso no código antigo. Se o método de chamada não souber (ou esquecer!) Verificar esses números especiais, o processamento poderá continuar sem nunca perceber um erro. Os dados são exibidos ao usuário mostrando uma área 0, que o usuário sabe que está incorreta, mas eles não têm indicação do que deu errado, onde ou por quê. Eles então se perguntam se algum dos outros valores está errado ...
Se a exceção fosse lançada, o processamento seria interrompido, o erro (idealmente) seria registrado e o usuário poderá ser notificado de alguma forma. O usuário pode então corrigir o que estiver errado e tentar novamente. O manuseio adequado das exceções (e testes!) Garantirá que aplicativos críticos não travem ou acabem em um estado inválido.
fonte
Não, acho que é uma prática muito ruim. Lançar uma exceção x retornar um valor é uma alteração fundamental na API, alterar a assinatura do método e fazer com que o método se comporte de maneira bem diferente da perspectiva da interface.
Em geral, quando projetamos classes e suas APIs, devemos considerar que
pode haver várias instâncias da classe com configurações diferentes flutuando no mesmo programa ao mesmo tempo e,
devido à injeção de dependência e a qualquer número de outras práticas de programação, um cliente consumidor pode criar os objetos e entregá-los a outro uso - tantas vezes temos uma separação entre criadores e usuários de objetos.
Considere agora o que o responsável pela chamada do método deve fazer para usar uma instância que lhe foi entregue, por exemplo, para chamar o método de cálculo: o responsável pela chamada teria que verificar se a área é zero e capturar exceções - ai! As considerações de teste vão não apenas para a classe em si, mas também para o tratamento de erros dos chamadores ...
Devemos sempre facilitar as coisas o mais fácil possível para o cliente consumidor; essa configuração booleana no construtor que altera a API de um método de instância é o oposto de fazer com que o programador cliente consumidor (talvez você ou seu colega) caia no poço do sucesso.
Para oferecer as duas APIs, você é muito melhor e mais normal para fornecer duas classes diferentes - uma que sempre gera erro e outra que sempre retorna 0 em erro ou fornece dois métodos diferentes com uma única classe. Dessa forma, o cliente consumidor pode facilmente saber exatamente como verificar e manipular erros.
Usando duas classes diferentes ou dois métodos diferentes, é possível usar os IDEs para encontrar usuários do método e refatorar recursos, etc. muito mais facilmente, pois os dois casos de uso não estão mais conflitantes. A leitura, escrita, manutenção, revisões e testes de código também são mais simples.
Em outra nota, eu pessoalmente acho que não devemos aceitar parâmetros de configuração booleanos, onde todos os chamadores reais simplesmente passam uma constante . Essa parametrização de configuração combina dois casos de uso separados, sem benefício real.
Dê uma olhada na sua base de código e veja se uma variável (ou expressão não constante) é usada para o parâmetro de configuração booleana no construtor! Eu duvido.
E uma consideração mais aprofundada é perguntar por que a área de computação pode falhar. O melhor seria lançar o construtor, se o cálculo não puder ser feito. No entanto, se você não souber se o cálculo pode ser feito até que o objeto seja inicializado ainda mais, talvez considere usar classes diferentes para diferenciar esses estados (não está pronto para calcular a área versus pronto para calcular a área).
Eu li que sua situação de falha é orientada à comunicação remota, portanto, pode não se aplicar; apenas algum alimento para o pensamento.
Sim eu concordo. Parece prematuro para o receptor decidir que a área 0 é a resposta certa em condições de erro (especialmente porque 0 é uma área válida, portanto, não há como diferenciar o erro do zero real, embora possa não se aplicar ao seu aplicativo).
fonte
Essa é uma introdução interessante, que me dá a impressão de que a motivação por trás desse design é evitar lançar exceções em alguns contextos "porque o sistema pode ficar inativo" nessa época. Mas se o sistema "pode cair por causa de uma exceção", isso é uma indicação clara de que
Portanto, se o programa que usa o
AreaCalculator
é buggy, seu colega prefere não interromper o programa mais cedo, mas retornar algum valor errado (esperando que ninguém perceba ou que ninguém faça algo importante). Na verdade, isso está ocultando um erro e, na minha experiência, mais cedo ou mais tarde levará a erros de acompanhamento para os quais fica difícil encontrar a causa raiz.IMHO escrever um programa que não trava em nenhuma circunstância, mas mostra dados incorretos ou resultados de cálculos geralmente não é melhor do que deixar o programa travar. A única abordagem correta é dar ao chamador a chance de perceber o erro, lidar com ele, deixar que ele decida se o usuário deve ser informado sobre o comportamento errado e se é seguro continuar o processamento ou se é mais seguro para parar o programa completamente. Assim, eu recomendaria um dos seguintes:
dificulta a negligência do fato de que uma função pode gerar uma exceção. Os padrões de documentação e codificação são seus amigos aqui, e as revisões regulares de código devem oferecer suporte ao uso correto dos componentes e ao tratamento adequado de exceções.
treine a equipe a esperar e lidar com exceções quando usarem componentes da "caixa preta" e tiver em mente o comportamento global do programa.
se, por algum motivo, você acha que não pode obter o código de chamada (ou os desenvolvedores que o escrevem) para usar o tratamento de exceções corretamente, como último recurso, você pode criar uma API com variáveis de saída de erro explícitas e sem exceções, como
então fica muito difícil para o chamador ignorar a função pode falhar. Mas isso é IMHO extremamente feio em C #; é uma técnica de programação defensiva antiga de C onde não há exceções e, normalmente, não é necessário trabalhar isso hoje em dia.
fonte
try
/catch
dentro de um loop se precisar processar para continuar se um elemento falhar.Qualquer função com n parâmetros será mais difícil de testar do que uma função com parâmetros n-1 . Estenda isso ao absurdo, e o argumento é que as funções não devem ter parâmetros, porque isso as torna mais fáceis de testar.
Embora seja uma ótima idéia escrever código fácil de testar, é uma péssima idéia colocar a simplicidade do teste acima da escrita útil para as pessoas que precisam chamá-lo. Se o exemplo da pergunta tiver um comutador que determine se uma exceção será lançada ou não, é possível que o número de chamadores que desejam esse comportamento tenha merecido adicioná-lo à função. Onde está a linha entre complexo e complexo demais, é uma decisão judicial; qualquer pessoa que tente lhe dizer que existe uma linha brilhante que se aplica a todas as situações deve ser vista com desconfiança.
Isso depende da sua definição de errado. O exemplo da pergunta define errado como "dada uma dimensão menor que zero e
shouldThrowExceptions
é verdadeira". Receber uma dimensão se menos de zero não estiver errado quandoshouldThrowExceptions
for falso, porque a opção induz um comportamento diferente. Isso simplesmente não é uma situação excepcional.O problema real aqui é que a opção foi mal nomeada porque não é descritiva do que faz a função. Se tivesse recebido um nome melhor
treatInvalidDimensionsAsZero
, você teria feito essa pergunta?O chamador não determinar como continuar. Nesse caso, ele é feito antecipadamente, definindo ou limpando
shouldThrowExceptions
e a função se comporta de acordo com seu estado.O exemplo é patologicamente simples porque faz um único cálculo e retorna. Se você o tornar um pouco mais complexo, como calcular a soma das raízes quadradas de uma lista de números, lançar exceções pode causar problemas aos chamadores que eles não podem resolver. Se eu passar em uma lista de
[5, 6, -1, 8, 12]
e a função lança uma exceção sobre o-1
, eu não tenho como dizer à função para continuar, porque ela já terá abortado e jogado fora a soma. Se a lista for um conjunto enorme de dados, gerar uma cópia sem números negativos antes de chamar a função pode ser impraticável, por isso sou forçado a dizer com antecedência como os números inválidos devem ser tratados, na forma de "apenas ignore-os" "alterne ou talvez forneça um lambda que é chamado para tomar essa decisão.Novamente, não existe uma solução única para todos. No exemplo, a função foi presumivelmente escrita para uma especificação que diz como lidar com dimensões negativas. A última coisa que você quer fazer é diminuir a proporção sinal-ruído de seus logs, preenchendo-os com mensagens que dizem "normalmente, uma exceção seria lançada aqui, mas o chamador disse para não se incomodar".
E como um desses programadores muito mais velhos, eu pediria que você gentilmente se afastasse do meu gramado. ;-)
fonte
our program needs to do 1 thing, show data to user. Any other exception that doesn't stop us from doing so should be ignored
mentalidade pode levar a decisão do usuário com base nos dados errados (porque, na verdade, o programa precisa fazer 1 coisa - ajudar o usuário a tomar decisões informadas) 2. casos semelhantes, comobool ExecuteJob(bool throwOnError = false)
geralmente são extremamente propensos a erros e levam a códigos difíceis de raciocinar, apenas lendo-os.Um código crítico e "normal" de segurança pode levar a idéias muito diferentes de como é a "boa prática". Há muita sobreposição - algumas coisas são arriscadas e devem ser evitadas em ambas -, mas ainda existem diferenças significativas. Se você adicionar um requisito com garantia de resposta, esses desvios serão bastante substanciais.
Eles geralmente se relacionam com o que você esperaria:
Para o git, a resposta errada pode ser muito ruim em relação a: demorar / interromper / travar ou até travar (que são efetivamente não-problemas em relação a, digamos, alterar o código de check-in acidentalmente).
No entanto: para um painel de instrumentos com um cálculo de força-g parado e impedindo que um cálculo de velocidade do ar seja feito talvez inaceitável.
Alguns são menos óbvios:
Se você testou bastante , os resultados de primeira ordem (como respostas corretas) não são uma preocupação tão grande em termos relativos. Você sabe que seu teste terá coberto isso. No entanto, se houvesse estado oculto ou fluxo de controle, você não sabe que isso não será a causa de algo muito mais sutil. É difícil descartar os testes.
Ser comprovadamente seguro é relativamente importante. Poucos clientes se perguntam se a fonte que estão comprando é segura ou não. Se você está no mercado de aviação, por outro lado ...
Como isso se aplica ao seu exemplo:
Eu não sei. Há vários processos de pensamento que podem ter regras de liderança como "Ninguém lance código de produção" sendo adotados em códigos críticos de segurança que seriam bastante tolos em situações mais comuns.
Alguns se relacionam com a integração, alguns com segurança e talvez outros ... Alguns são bons (eram necessários limites apertados de desempenho / memória) outros são ruins (não lidamos com exceções adequadamente, então é melhor não arriscar). Na maioria das vezes, mesmo sabendo por que eles fizeram isso, realmente não responde à pergunta. Por exemplo, se tem a ver com a facilidade de auditar mais o código do que realmente torná-lo melhor, é uma boa prática? Você realmente não pode dizer. Eles são animais diferentes e precisam tratar de maneira diferente.
Tudo isso dito, parece um pouco suspeito para mim, MAS :
As decisões críticas sobre software e software de segurança provavelmente não devem ser tomadas por estranhos na troca de pilha de engenharia de software. Pode haver uma boa razão para fazer isso, mesmo que faça parte de um sistema ruim. Não leia muito sobre isso, a não ser como "alimento para o pensamento".
fonte
Às vezes, lançar uma exceção não é o melhor método. Além disso, devido ao desenrolamento da pilha, mas às vezes porque a captura de uma exceção é problemática, principalmente ao longo de costuras de idioma ou interface.
Uma boa maneira de lidar com isso é retornar um tipo de dados enriquecido. Esse tipo de dados possui estado suficiente para descrever todos os caminhos felizes e todos os caminhos infelizes. O ponto é que, se você interagir com esta função (membro / global / caso contrário), será forçado a lidar com o resultado.
Dito isto, esse tipo de dados enriquecido não deve forçar a ação. Imagine em seu exemplo da área algo como
var area_calc = new AreaCalculator(); var volume = area_calc.CalculateArea(x, y) * z;
. Parece útilvolume
deve conter a área multiplicada pela profundidade - que pode ser um cubo, cilindro, etc ...Mas e se o serviço area_calc estivesse inoperante? Em seguida,
area_calc .CalculateArea(x, y)
retornou um tipo de dados rico contendo um erro. É legal multiplicar isso porz
? É uma boa pergunta. Você pode forçar os usuários a manipular a verificação imediatamente. No entanto, isso quebra a lógica com o tratamento de erros.vs
A lógica essencialmente é espalhada por duas linhas e dividida pelo tratamento de erros no primeiro caso, enquanto o segundo caso possui toda a lógica relevante em uma linha seguida pelo tratamento de erros.
Nesse segundo caso,
volume
é um tipo de dados rico. Não é apenas um número. Isso aumenta o armazenamento evolume
ainda precisará ser investigado quanto a uma condição de erro. Além disso,volume
pode alimentar outros cálculos antes que o usuário decida manipular o erro, permitindo que ele se manifeste em vários locais diferentes. Isso pode ser bom ou ruim, dependendo das especificidades da situação.Como alternativa,
volume
pode ser apenas um tipo de dados simples - apenas um número, mas o que acontece com a condição de erro? Pode ser que o valor converta implicitamente se estiver em uma condição feliz. Se estiver em uma condição infeliz, poderá retornar um valor padrão / erro (para a área 0 ou -1 pode parecer razoável). Como alternativa, isso poderia gerar uma exceção neste lado do limite da interface / idioma.vs.
Ao distribuir um valor ruim ou possivelmente ruim, ele coloca muito ônus no usuário para validar os dados. Esta é uma fonte de erros, porque, no que diz respeito ao compilador, o valor de retorno é um número inteiro legítimo. Se algo não foi verificado, você descobrirá quando as coisas derem errado. O segundo caso combina o melhor dos dois mundos, permitindo que exceções tratem de caminhos infelizes, enquanto caminhos felizes seguem o processamento normal. Infelizmente, ele força o usuário a lidar com exceções com sabedoria, o que é difícil.
Só para esclarecer que um caminho infeliz é um caso desconhecido da lógica de negócios (o domínio de exceção), deixar de validar é um caminho feliz porque você sabe como lidar com isso pelas regras de negócios (o domínio de regras).
A solução final seria aquela que permita todos os cenários (dentro do razoável).
Algo como:
fonte
Eu vou responder do ponto de vista do C ++. Tenho certeza de que todos os conceitos principais são transferíveis para c #.
Parece que seu estilo preferido é "sempre lance exceções":
Isso pode ser um problema para o código C ++ porque o tratamento de exceções é pesado - faz com que o caso de falha seja executado lentamente e aloca memória (que às vezes nem sequer está disponível) e geralmente torna as coisas menos previsíveis. O peso pesado de EH é uma das razões pelas quais você ouve pessoas dizendo coisas como "Não use exceções para controlar o fluxo".
Portanto, algumas bibliotecas (como
<filesystem>
) usam o que o C ++ chama de "API dupla" ou o que o C # chama deTry-Parse
padrão (obrigado Peter pela dica!)Você pode ver o problema com as "APIs duplas" imediatamente: muita duplicação de código, nenhuma orientação para os usuários sobre qual API é a "correta" a ser usada, e o usuário deve fazer uma escolha difícil entre mensagens de erro úteis (
CalculateArea
) e speed (TryCalculateArea
) porque a versão mais rápida pega nossa"negative side lengths"
exceção útil e a torna inútilfalse
- "algo deu errado, não me pergunte o que ou onde". (Alguns dupla APIs usar um tipo de erro mais expressivo, comoint errno
ou C ++ 'sstd::error_code
, mas que ainda não lhe diz onde o erro ocorreu - só que ele fez ocorrer em algum lugar.)Se você não consegue decidir como o seu código deve se comportar, sempre pode levar a decisão até o chamador!
Isso é essencialmente o que seu colega de trabalho está fazendo; exceto que ele está fatorando o "manipulador de erros" em uma variável global:
Mover parâmetros importantes de parâmetros explícitos de função para o estado global é quase sempre uma má ideia. Eu não recomendo. (O fato de não ser um estado global no seu caso, mas simplesmente um estado membro em toda a instância atenua um pouco a maldade, mas não muito.)
Além disso, seu colega de trabalho está desnecessariamente limitando o número de possíveis comportamentos de manipulação de erros. Em vez de permitir qualquer lambda de manipulação de erros, ele decidiu apenas dois:
Este é provavelmente o "ponto azedo" de qualquer uma dessas estratégias possíveis. Você tirou toda a flexibilidade do usuário final, forçando-o a usar um de seus exatamente dois retornos de chamada de tratamento de erros; e você tem todos os problemas do estado global compartilhado; e você ainda está pagando por esse ramo condicional em qualquer lugar.
Finalmente, uma solução comum em C ++ (ou qualquer linguagem com compilação condicional) seria forçar o usuário a tomar a decisão de todo o programa, globalmente, em tempo de compilação, para que o caminho de código não utilizado possa ser totalmente otimizado:
Um exemplo de algo que funciona dessa maneira é a
assert
macro em C e C ++, que condiciona seu comportamento na macro do pré-processadorNDEBUG
.fonte
std::optional
deTryCalculateArea()
, é simples unificar a implementação de ambas as partes da interface dupla em um único modelo de função com um sinalizador de tempo de compilação.std::expected
. Com apenasstd::optional
, a menos que eu não entenda sua solução proposta, ela ainda sofreria com o que eu disse: o usuário deve fazer uma escolha difícil entre mensagens de erro e velocidade, porque a versão mais rápida pega nossa"negative side lengths"
exceção útil e a transforma em inútilfalse
- " algo deu errado, não me pergunte o que ou onde ".std::error_code *ec
percorre todos os níveis da API e, na parte inferior, faz o equivalente moral deif (ec == nullptr) throw something; else *ec = some error code
. (Ele abstrai o realif
em algo chamadoErrorHandler
, mas é a mesma idéia básica.)Eu acho que deve ser mencionado de onde seu colega obteve o padrão deles.
Hoje em dia, C # tem o padrão TryGet
public bool TryThing(out result)
. Isso permite que você obtenha seu resultado, enquanto ainda informa se esse resultado é mesmo um valor válido. (Por exemplo, todos osint
valores são resultados válidos paraMath.sum(int, int)
, mas se o valor exceder, esse resultado específico pode ser lixo). Este é um padrão relativamente novo.Antes da
out
palavra - chave, você tinha que lançar uma exceção (cara, e o chamador tinha que capturá-la ou matar o programa inteiro), criar uma estrutura especial (classe antes da classe ou genéricos realmente ser uma coisa) para cada resultado para representar o valor e possíveis erros (demorado para criar e inchar software) ou retornar um valor "erro" padrão (que pode não ter sido um erro).A abordagem que seu colega usa fornece a eles o privilégio de falha antecipada de exceções ao testar / depurar novos recursos, enquanto oferece segurança e desempenho em tempo de execução (o desempenho era uma questão crítica o tempo todo ~ 30 anos atrás) de apenas retornar um valor de erro padrão. Agora, esse é o padrão em que o software foi escrito e o padrão esperado avançando, por isso é natural continuar fazendo dessa maneira, mesmo que haja maneiras melhores agora. Muito provavelmente, esse padrão foi herdado da era do software, ou um padrão em que suas faculdades nunca cresceram (é difícil quebrar os velhos hábitos).
As outras respostas já abordam por que isso é considerado uma má prática, por isso, acabarei recomendando que você leia o padrão TryGet (talvez também encapsule o que as promessas que um objeto deve fazer a quem está chamando).
fonte
out
palavra - chave, você escreveria umabool
função que leva um ponteiro para o resultado, ou seja, umref
parâmetro. Você poderia fazer isso no VB6 em 1998. Aout
palavra-chave apenas compra a certeza em tempo de compilação de que o parâmetro é atribuído quando a função retorna, isso é tudo o que existe. Ele é um teste padrão agradável e útil embora.Há momentos em que você deseja fazer a abordagem dele, mas eu não consideraria a situação "normal". A chave para determinar em que caso você está é:
Verifique os requisitos. Se seus requisitos realmente disserem que você tem um trabalho, que é mostrar dados ao usuário, ele está certo. No entanto, na minha experiência, na maioria das vezes o usuário também se importa com os dados mostrados. Eles querem os dados corretos. Alguns sistemas só querem falhar silenciosamente e permitem que um usuário descubra que algo deu errado, mas eu os consideraria a exceção à regra.
A principal pergunta que eu faria após uma falha é "O sistema está em um estado em que as expectativas do usuário e os invariantes do software são válidos?" Se assim for, então por todos os meios apenas retorne e continue. Na prática, isso não é o que acontece na maioria dos programas.
Quanto ao próprio sinalizador, o sinalizador de exceções geralmente é considerado cheiro de código, porque o usuário precisa saber de alguma forma em que modo o módulo está para entender como a função funciona. Se está em
!shouldThrowExceptions
modo, o usuário precisa saber que eles são responsáveis pela detecção de erros e manter as expectativas e invariantes quando eles ocorrem. Eles também são responsáveis imediatamente, na linha em que a função é chamada. Uma bandeira como essa geralmente é altamente confusa.No entanto, isso acontece. Considere que muitos processadores permitem alterar o comportamento do ponto flutuante no programa. Um programa que deseja ter padrões mais relaxados pode fazê-lo simplesmente alterando um registro (que é efetivamente um sinalizador). O truque é que você deve ser muito cauteloso, para evitar pisar acidentalmente nos dedos dos outros. O código geralmente verifica o sinalizador atual, define-o para a configuração desejada, realiza operações e, em seguida, volta a defini-lo. Dessa forma, ninguém fica surpreso com a mudança.
fonte
Este exemplo específico tem um recurso interessante que pode afetar as regras ...
O que vejo aqui é uma verificação de pré - condição . Uma falha na verificação de pré-condição implica um bug mais alto na pilha de chamadas. Assim, a questão é: esse código é responsável por relatar erros localizados em outro lugar?
Parte da tensão aqui é atribuída ao fato de que essa interface exibe obsessão primitiva -
x
ey
presume-se que represente medidas reais de comprimento. Em um contexto de programação em que tipos específicos de domínios são uma escolha razoável, na verdade, aproximaríamos a verificação de pré-condição da fonte dos dados - em outras palavras, aumentaria a responsabilidade pela integridade dos dados na pilha de chamadas, onde temos um melhor senso para o contexto.Dito isto, não vejo nada de fundamentalmente errado em ter duas estratégias diferentes para gerenciar uma verificação com falha. Minha preferência seria usar a composição para determinar qual estratégia está sendo usada; o sinalizador do recurso seria usado na raiz da composição, e não na implementação do método da biblioteca.
O Conselho Nacional de Trânsito e Segurança é muito bom; Eu posso sugerir técnicas alternativas de implementação para os barbas cinzentas, mas não estou inclinado a discutir com elas sobre o design de anteparas no subsistema de relatórios de erros.
Mais amplamente: qual é o custo para os negócios? É muito mais barato travar um site do que um sistema essencial para a vida.
fonte
Os métodos lidam com exceções ou não, não há necessidade de sinalizadores em idiomas como C #.
Se algo der errado no código ... essa exceção precisará ser tratada pelo chamador. Se ninguém lida com o erro, o programa será encerrado.
Nesse caso, se algo de ruim acontecer no código ..., o Method1 está lidando com o problema e o programa deve continuar.
Você lida com as exceções. Certamente você pode ignorá-los pegando e não fazendo nada. Mas, garanto que você esteja ignorando apenas certos tipos específicos de exceções que você pode esperar que ocorram. Ignorar (
exception ex
) é perigoso porque algumas exceções que você não deseja ignorar, como exceções do sistema relacionadas à falta de memória, etc.fonte
Essa abordagem quebra a filosofia "falhe rápido, falhe muito".
Por que você deseja falhar rapidamente:
Desvantagens de não falhar rápido e difícil:
Por fim, o tratamento de erros com base em exceção real tem o benefício de que você pode fornecer uma mensagem ou objeto de erro "fora da banda", pode facilmente conectar o log de erros, alertar etc. sem escrever uma única linha extra no código de domínio .
Portanto, existem não apenas razões técnicas simples, mas também razões de "sistema" que tornam muito útil a falha rápida.
No final do dia, não falhar com força e rapidez nos nossos tempos atuais, onde o tratamento de exceções é leve e muito estável, é apenas meio criminoso. Entendo perfeitamente de onde vem o pensamento de que é bom suprimir exceções, mas não é mais aplicável.
Especialmente no seu caso particular, onde você ainda dá a opção de lançar exceções ou não: isso significa que o chamador deve decidir de qualquer maneira . Portanto, não há nenhuma desvantagem em fazer com que o chamador capture a exceção e lide com ela adequadamente.
Um ponto que aparece em um comentário:
Falha rápida e difícil não significa que seu aplicativo inteiro trava. Isso significa que, no ponto em que o erro ocorre, está falhando localmente. No exemplo do OP, o método de baixo nível que calcula alguma área não deve substituir silenciosamente um erro por um valor errado. Deve falhar claramente.
Alguns chamadores da cadeia obviamente precisam capturar esse erro / exceção e manipulá-lo adequadamente. Se esse método foi usado em um avião, isso provavelmente deve acender algum LED de erro ou, pelo menos, exibir "área de cálculo de erro" em vez de uma área errada.
fonte