Eu sempre me perguntei quantas classes de exceção diferentes eu deveria implementar e lançar para várias partes do meu software. Meu desenvolvimento específico geralmente é relacionado a C ++ / C # / Java, mas acredito que essa seja uma pergunta para todas as linguagens.
Quero entender o que é um bom número de exceções diferentes e o que a comunidade de desenvolvedores espera de uma boa biblioteca.
As compensações que vejo incluem:
- Mais classes de exceção podem permitir níveis muito detalhados de tratamento de erros para usuários da API (propensos a configuração do usuário ou erros de dados ou arquivos não encontrados)
- Mais classes de exceção permitem que informações específicas sobre erros sejam incorporadas à exceção, em vez de apenas uma mensagem de string ou código de erro
- Mais classes de exceção podem significar mais manutenção de código
- Mais classes de exceção podem significar que a API é menos acessível aos usuários
Os cenários em que desejo entender o uso de exceções incluem:
- Durante o estágio 'configuração', que pode incluir o carregamento de arquivos ou a definição de parâmetros
- Durante uma fase do tipo 'operação', em que a biblioteca pode estar executando tarefas e realizando algum trabalho, talvez em outro encadeamento
Outros padrões de relatório de erros sem usar exceções ou menos exceções (como comparação) podem incluir:
- Menos exceções, mas incorporando um código de erro que pode ser usado como uma pesquisa
- Retornando códigos de erro e sinalizadores diretamente de funções (às vezes não é possível a partir de threads)
- Implementou um sistema de evento ou retorno de chamada em caso de erro (evita o desenrolamento da pilha)
Como desenvolvedores, o que você prefere ver?
Se houver MUITAS exceções, você se incomoda em lidar com elas separadamente?
Você prefere os tipos de tratamento de erros, dependendo do estágio da operação?
Respostas:
Eu mantenho isso simples.
Uma biblioteca possui um tipo de exceção base estendido de std ::: runtime_error (que é do C ++ aplicável conforme apropriado a outros idiomas). Essa exceção recebe uma sequência de mensagens para que possamos registrar; todo ponto de lançamento tem uma mensagem única (geralmente com um ID exclusivo).
É sobre isso.
Nota 1 : Nas situações em que alguém que está capturando a exceção pode corrigi-las e reiniciar a ação. Adicionarei exceções derivadas para coisas que podem ser potencialmente corrigidas exclusivamente em um local remoto. Mas isso é muito, muito raro (lembre-se que é improvável que o apanhador esteja próximo do ponto de lançamento, assim, corrigir o problema será difícil (mas tudo depende da situação)).
Nota 2 : Às vezes, a biblioteca é tão simples que não vale a pena dar sua própria exceção e o std :: runtime_error fará. É importante apenas ter uma exceção se a capacidade de diferenciá-lo de std :: runtime_error puder fornecer ao usuário informações suficientes para fazer algo com ele.
Nota 3 : Dentro de uma classe, geralmente prefiro códigos de erro (mas eles nunca escapam pela API pública da minha classe).
Observando suas vantagens e desvantagens:
As compensações que vejo incluem:
Mais exceções realmente oferecem controle de grão mais fino? A questão é: o código de captura pode realmente corrigir o erro com base na exceção? Estou certo de que existem situações como essa e, nesses casos, você deve ter outra exceção. Mas todas as exceções listadas acima, a única correção útil, são gerar um grande aviso e interromper o aplicativo.
Esse é um ótimo motivo para usar exceções. Mas as informações devem ser úteis para a pessoa que as está armazenando em cache. Eles podem usar as informações para executar alguma ação corretiva? Se o objeto for interno à sua biblioteca e não puder ser usado para influenciar nenhuma API, as informações serão inúteis. Você precisa ser muito específico, pois as informações lançadas têm um valor útil para a pessoa que pode obtê-las. A pessoa que a captura geralmente está fora da API pública, portanto adapte suas informações para que possam ser usadas com itens da API pública.
Se tudo o que eles podem fazer é registrar a exceção, é melhor lançar apenas uma mensagem de erro em vez de muitos dados. Como o coletor geralmente cria uma mensagem de erro com os dados. Se você criar a mensagem de erro, ela será consistente em todos os coletores; se você permitir que o coletor crie a mensagem de erro, poderá obter o mesmo erro relatado de maneira diferente, dependendo de quem está ligando e capturando.
Você deve determinar o clima, o código de erro pode ser usado de maneira significativa. Se puder, você deve ter sua própria exceção. Caso contrário, seus usuários agora precisam implementar instruções de troca dentro de catch (o que anula todo o ponto de ter catch manipular automaticamente as coisas).
Se não puder, por que não usar uma mensagem de erro na exceção (não é necessário dividir o código e a mensagem torna difícil procurar).
O retorno de códigos de erro é ótimo internamente. Ele permite que você corrija erros de vez em quando e tenha certeza de corrigir todos os códigos de erro e prestar contas deles. Mas vazá-los pela API pública é uma má ideia. O problema é que os programadores geralmente esquecem de verificar os estados de erro (pelo menos, com uma exceção, um erro não verificado forçará o aplicativo a sair de um erro não tratado geralmente corromperá todos os seus dados).
Esse método é frequentemente usado em conjunto com outro mecanismo de tratamento de erros (não como alternativa). Pense no seu programa do Windows. Um usuário inicia uma ação selecionando um item de menu. Isso gera uma ação na fila de eventos. A fila de eventos eventualmente atribui um encadeamento para manipular a ação. O encadeamento deve lidar com a ação e, eventualmente, retornar ao conjunto de encadeamentos e aguardar outra tarefa. Aqui, uma exceção deve ser capturada na base pelo thread encarregado do trabalho. O resultado da captura da exceção geralmente resulta na geração de um evento para o loop principal, o que acabará resultando na exibição de uma mensagem de erro para o usuário.
Mas, a menos que você possa continuar diante da exceção, a pilha se descontrairá (pelo menos para o encadeamento).
fonte
Normalmente começo com:
Como as classes dos três primeiros casos são auxiliares de depuração, elas não devem ser tratadas pelo código. Em vez disso, eles devem ser capturados apenas por um manipulador de nível superior que exiba as informações para que o usuário possa copiá-las e colá-las no desenvolvedor (ou melhor ainda: pressione o botão "enviar relatório"). Portanto, inclua informações úteis para o desenvolvedor: arquivo, função, número da linha e alguma mensagem que identifique claramente qual verificação falhou.
Como os três primeiros casos são os mesmos para cada projeto, em C ++ eu normalmente apenas os copio do projeto anterior. Como muitos estão fazendo exatamente o mesmo, os designers de C # e Java adicionaram classes padrão para esses casos à biblioteca padrão. [ATUALIZAÇÃO:] Para programadores preguiçosos: uma classe pode ser suficiente e, com alguma sorte, sua biblioteca padrão já possui uma classe de exceção adequada. Prefiro adicionar informações como nome do arquivo e número da roupa, que as classes padrão no C ++ não fornecem. [Fim da atualização]
Dependendo da biblioteca, o quarto caso pode ter apenas uma classe ou pode se tornar um punhado de classes. Eu prefiro a abordagem ágil para começar simples, adicionando subclasses quando necessário.
Para uma discussão detalhada sobre o meu quarto caso, veja a resposta de Loki Astari . Eu concordo totalmente com sua resposta detalhada.
fonte