Estou lutando com uma pergunta muito simples:
Agora estou trabalhando em um aplicativo de servidor e preciso inventar uma hierarquia para as exceções (algumas exceções já existem, mas é necessária uma estrutura geral). Como eu começo a fazer isso?
Estou pensando em seguir esta estratégia:
1) O que está errado?
- Algo é perguntado, o que não é permitido.
- Algo é solicitado, é permitido, mas não funciona, devido a parâmetros incorretos.
- Algo é perguntado, é permitido, mas não funciona, devido a erros internos.
2) Quem está iniciando a solicitação?
- O aplicativo cliente
- Outro aplicativo de servidor
3) Entrega de mensagens: como estamos lidando com um aplicativo de servidor, trata-se de receber e enviar mensagens. E daí se o envio de uma mensagem der errado?
Dessa forma, podemos obter os seguintes tipos de exceção:
- ServerNotAllowedException
- ClientNotAllowedException
- ServerParameterException
- ClientParameterException
- InternalException (caso o servidor não saiba de onde vem a solicitação)
- ServerInternalException
- ClientInternalException
- MessageHandlingException
Essa é uma abordagem muito geral para definir a hierarquia de exceções, mas receio que esteja faltando alguns casos óbvios. Você tem idéias sobre as áreas que não estou abordando, conhece alguma desvantagem desse método ou existe uma abordagem mais geral para esse tipo de pergunta (no último caso, onde posso encontrá-lo)?
desde já, obrigado
fonte
catch
blocos que uso, não tenho muito mais uso para a exceção do que a mensagem de erro que ela contém. Eu realmente não tenho nada diferente que eu possa fazer por uma exceção envolvida na falha na leitura de um arquivo, como uma falha na alocação de memória durante o processo de leitura, por isso eu apenas captostd::exception
e relate a mensagem de erro que ele contém, talvez decorando com"Failed to open file: %s", ex.what()
um buffer de pilha antes de imprimi-lo.catch
blocos diferentes em um único site de recuperação, mas geralmente é apenas para ignorar a mensagem dentro da exceção e imprimir uma mensagem mais localizada ...Respostas:
Observações gerais
(um pouco tendencioso)
Eu normalmente não iria para uma hierarquia de exceção detalhada.
O mais importante: uma exceção informa ao chamador que seu método falhou ao concluir seu trabalho. E seu interlocutor deve receber um aviso sobre isso, para que ele simplesmente não continue. Isso funciona com qualquer exceção, independentemente da classe de exceção que você escolher.
O segundo aspecto é o log. Você deseja encontrar entradas de log significativas sempre que algo der errado. Isso também não precisa de classes de exceção diferentes, apenas mensagens de texto bem projetadas (suponho que você não precise de um automático para ler seus logs de erros ...).
Terceiro aspecto é a reação do seu interlocutor. O que seu interlocutor pode fazer quando recebe uma exceção? Aqui, pode fazer sentido ter classes de exceção diferentes, para que o chamador possa decidir se deseja repetir a mesma chamada, usar uma solução diferente (por exemplo, usar uma fonte de fallback) ou desistir.
E talvez você queira usar suas exceções como base para informar o usuário final sobre o problema. Isso significa criar uma mensagem amigável ao usuário além do texto do administrador para o arquivo de log, mas não precisa de diferentes classes de exceção (embora talvez isso possa facilitar a geração de texto ...).
Um aspecto importante para o log (e para as mensagens de erro do usuário) é a capacidade de alterar a exceção com informações de contexto, capturando-a em alguma camada, adicionando algumas informações de contexto, por exemplo, parâmetros de método e lançando-a novamente.
Sua hierarquia
Quem está iniciando a solicitação? Acho que você não precisará das informações que estavam iniciando a solicitação. Não consigo nem imaginar como você sabe disso profundamente em uma pilha de chamadas.
Tratamento de mensagens : esse não é um aspecto diferente, mas apenas casos adicionais para "O que está acontecendo de errado?".
Em um comentário, você fala sobre um sinalizador " sem registro " ao criar uma exceção. Eu não acho que no local em que você cria e lança uma exceção, você pode tomar uma decisão confiável se deve ou não registrar essa exceção.
A única situação que posso imaginar é que uma camada superior usa sua API de uma maneira que às vezes produz exceções, e essa camada sabe que não precisa incomodar nenhum administrador com a exceção, portanto, silenciosamente, engole a exceção. Mas isso é um cheiro de código: uma exceção esperada é uma contradição em si, uma dica para alterar a API. E é a camada superior que deve decidir, não o código gerador de exceção.
fonte
O principal aspecto a ser lembrado ao projetar um padrão de resposta a erros é garantir que seja útil para os chamadores. Isso se aplica se você estiver usando exceções ou códigos de erro definidos, mas nos restringiremos a ilustrar com exceções.
Se sua linguagem ou estrutura já fornecer classes de exceção gerais, use-as onde forem apropriadas e onde devem ser razoavelmente esperadas . Não defina suas próprias classes
ArgumentNullException
ouArgumentOutOfRange
exceções. As pessoas que ligam não esperam capturá-las.Defina uma
MyClientServerAppException
classe base para abranger os erros exclusivos no contexto do seu aplicativo. Nunca jogue uma instância da classe base. Uma resposta de erro ambígua é a pior coisa de todos os tempos . Se houver um "erro interno", explique qual é esse erro.Na maior parte, a hierarquia abaixo da classe base deve ser ampla, não profunda. Você só precisa aprofundá-lo em situações em que seja útil para o chamador. Por exemplo, se houver 5 razões pelas quais uma mensagem pode falhar do cliente para o servidor, você poderá definir uma
ServerMessageFault
exceção e definir uma classe de exceção para cada um desses 5 erros abaixo. Dessa forma, o chamador pode simplesmente capturar a superclasse se precisar ou quiser. Tente limitar isso a casos específicos e razoáveis.Não tente definir todas as suas classes de exceção antes que elas sejam realmente usadas. Você acabará refazendo a maior parte. Quando você encontrar um caso de erro ao escrever um código, decida qual a melhor forma de descrever esse erro. Idealmente, ele deve ser expresso no contexto do que o chamador está tentando fazer.
Relacionado ao ponto anterior, lembre-se de que apenas porque você usa exceções para responder a erros, isso não significa que você deve usar somente exceções para estados de erro. Lembre-se de que lançar exceções é geralmente caro, e o custo de desempenho pode variar de um idioma para outro. Em alguns idiomas, o custo é mais alto, dependendo da profundidade da pilha de chamadas; portanto, se um erro for profundo em uma pilha de chamadas, verifique se você não pode usar tipos primitivos simples (códigos de erro inteiro ou sinalizadores booleanos) para pressionar o erro faz backup da pilha, para que possa ser jogado mais perto da chamada do chamador.
Se você estiver incluindo o log como parte da resposta a erro, será fácil para os chamadores anexar informações de contexto a um objeto de exceção. Comece de onde as informações são usadas no código de log. Decida quanta informação é necessária para que o log seja útil (sem ser uma parede gigante de texto). Em seguida, trabalhe para trás para garantir que as classes de exceção possam ser facilmente fornecidas com essas informações.
Por fim, a menos que seu aplicativo possa lidar absolutamente com um erro de falta de memória, não tente lidar com eles ou outras exceções de tempo de execução catastróficas. Apenas deixe o sistema operacional lidar com isso, porque, na realidade, é tudo o que você pode fazer.
fonte
Bem, eu recomendo que você crie, em primeiro lugar, uma
Exception
classe base para todas as exceções verificadas que possam ser lançadas pelo seu aplicativo. Se seu aplicativo foi chamadoDuckType
, faça umaDuckTypeException
classe base.Isso permite capturar qualquer exceção de sua
DuckTypeException
classe base para manipulação. A partir daqui, suas exceções devem se ramificar com nomes descritivos que possam destacar melhor o tipo de problema. Por exemplo, "DatabaseConnectionException".Deixe-me esclarecer, todas essas exceções devem ser verificadas para situações que possam acontecer que você provavelmente desejaria manipular normalmente em seu programa. Em outras palavras, você não pode se conectar ao banco de dados; portanto,
DatabaseConnectionException
é lançado um que você pode pegar para aguardar e tentar novamente após um período de tempo.Você não veria uma exceção verificada para um problema muito inesperado, como uma consulta SQL inválida ou uma exceção de ponteiro nulo, e eu encorajo você a permitir que essas exceções transcendam a maioria das cláusulas catch (ou capturadas e reconfiguradas conforme necessário) até chegar à página principal. próprio controlador, que pode ser capturado
RuntimeException
puramente para fins de registro.Minha preferência pessoal é não repetir uma desmarcada
RuntimeException
como outra exceção, pois, por natureza de uma exceção desmarcada, você não esperaria e, portanto, recontar novamente em outra exceção está ocultando informações. No entanto, se essa for a sua preferência, você ainda pode pegar umRuntimeException
e lançar umDuckTypeInternalException
que, ao contrário,DuckTypeException
deriva deRuntimeException
e é, portanto, desmarcado.Se preferir, você pode subcategorizar suas exceções para fins organizacionais, como
DatabaseException
para qualquer coisa relacionada ao banco de dados, mas eu ainda encorajo essas subceções a derivarem da sua exceção baseDuckTypeException
e serem abstratas e, portanto, derivadas com nomes descritivos explícitos.Como regra geral, suas tentativas de captura devem ser cada vez mais genéricas à medida que você sobe na pilha de chamadas para lidar com exceções e, novamente, em seu controlador principal, você não lidaria com um,
DatabaseConnectionException
mas com o simplesDuckTypeException
de que todas as exceções verificadas derivam.fonte
Tente simplificar isso.
A primeira coisa que o ajudará a pensar em outra estratégia é: capturou muitas exceções é muito semelhante ao uso de exceções verificadas do Java (desculpe, eu não sou desenvolvedor de C ++). Isso não é bom por vários motivos, por isso sempre tento não usá-los, e sua estratégia de exceção de hierarquia lembra muito de mim.
Portanto, recomendo outra estratégia flexível: use exceções não verificadas e erros de código.
Por exemplo, veja este código Java:
E sua exceção única :
Encontrei esta estratégia neste link e você pode encontrar a implementação Java aqui , onde você pode ver mais detalhes, porque o código acima é simplificado.
Como você precisa separar as diferentes exceções entre os aplicativos "cliente" e "outro servidor", você pode ter várias classes de código de erro implementando a interface ErrorCode.
fonte
Exceções são gotos irrestritos e devem ser usadas com cuidado. A melhor estratégia para eles é restringi-los. A função de chamada deve lidar com todas as exceções geradas pelas funções que chama ou o programa morre. Somente a função de chamada possui o contexto correto para manipular a exceção. Tendo as funções mais adiante na árvore de chamadas, os manipula são gotos irrestritos.
Exceções não são erros. Eles ocorrem quando as circunstâncias impedem que o programa conclua uma ramificação do código e indicam outra ramificação para seguir.
Exceções devem estar no contexto da função chamada. Por exemplo: uma função que resolve equações quadráticas . Ele deve manipular duas exceções: division_by_zero e square_root_of_negative_number. Mas essas exceções não fazem sentido para funções que chamam esse solucionador de equações quadráticas. Elas acontecem por causa do método usado para resolver equações e simplesmente repeti-las novamente expõe o interior e quebra o encapsulamento. Em vez disso, division_by_zero deve ser mostrado novamente como not_quadratic e square_root_of_negative_number e no_real_roots.
Não há necessidade de uma hierarquia de exceções. Uma enum das exceções (que as identifica) lançadas por uma função é suficiente porque a função de chamada deve tratá-las. Permitir que eles processem a árvore de chamadas é um passo fora do contexto (irrestrito).
fonte