Esta pergunta se aplica a qualquer linguagem de programação OO que suporte o tratamento de exceções; Estou usando C # apenas para fins ilustrativos.
Geralmente, as exceções devem ser levantadas quando surgir um problema que o código não pode tratar imediatamente e, então, serem capturadas em uma catch
cláusula em um local diferente (geralmente um quadro de pilha externo).
P: Existem situações legítimas em que as exceções não são lançadas e capturadas, mas simplesmente retornadas de um método e transmitidas como objetos de erro?
Essa pergunta surgiu para mim porque o System.IObserver<T>.OnError
método do .NET 4 sugere exatamente isso: exceções sendo passadas como objetos de erro.
Vamos olhar para outro cenário, validação. Digamos que estou seguindo a sabedoria convencional e, portanto, estou distinguindo entre um tipo de objeto de erro IValidationError
e um tipo de exceção separado ValidationException
que é usado para relatar erros inesperados:
partial interface IValidationError { }
abstract partial class ValidationException : System.Exception
{
public abstract IValidationError[] ValidationErrors { get; }
}
(O System.Component.DataAnnotations
espaço para nome faz algo bastante semelhante.)
Esses tipos podem ser empregados da seguinte maneira:
partial interface IFoo { } // an immutable type
partial interface IFooBuilder // mutable counterpart to prepare instances of above type
{
bool IsValid(out IValidationError[] validationErrors); // true if no validation error occurs
IFoo Build(); // throws ValidationException if !IsValid(…)
}
Agora estou me perguntando, não poderia simplificar o acima para isso:
partial class ValidationError : System.Exception { } // = IValidationError + ValidationException
partial interface IFoo { } // (unchanged)
partial interface IFooBuilder
{
bool IsValid(out ValidationError[] validationErrors);
IFoo Build(); // may throw ValidationError or sth. like AggregateException<ValidationError>
}
P: Quais são as vantagens e desvantagens dessas duas abordagens diferentes?
AggregateException
? Bom ponto.Respostas:
Se nunca for lançado, não será uma exceção. É um objeto
derived
de uma classe Exception, embora não siga o comportamento. Chamar isso de exceção é puramente semântica neste momento, mas não vejo nada de errado em não lançá-lo. Na minha perspectiva, não lançar uma exceção é exatamente a mesma coisa que uma função interna que a captura e impede que ela se propague.É válido que uma função retorne objetos de exceção?
Absolutamente. Aqui está uma pequena lista de exemplos onde isso pode ser apropriado:
Não está jogando mal?
Lançar uma exceção é como: "Vá para a cadeia. Não passe em Go!" no jogo de tabuleiro Monopólio. Ele diz ao compilador para pular todo o código-fonte até a captura sem executar nenhum código-fonte. Não tem nada a ver com erros, relatórios ou interrupção de bugs. Eu gosto de pensar em lançar uma exceção como uma declaração de "super retorno" para uma função. Retorna a execução do código para um lugar muito superior ao chamador original.
O importante aqui é entender que o verdadeiro valor das exceções está no
try/catch
padrão, e não na instanciação do objeto de exceção. O objeto de exceção é apenas uma mensagem de estado.Na sua pergunta, você parece confundir o uso dessas duas coisas: pular para um manipulador da exceção e o estado de erro que a exceção representa. Só porque você tomou um estado de erro e o envolveu em uma exceção não significa que você está seguindo o
try/catch
padrão ou seus benefícios.fonte
Exception
classe, mas não segue o comportamento." - E esse é exatamente o problema: é legítimo usar umException
objeto derivado em uma situação em que ele não será lançado, nem pelo produtor do objeto nem por qualquer método de chamada; onde ele serve apenas como uma "mensagem de estado" sendo transmitida, sem o fluxo de controle de "super retorno"? Ou estou violando tanto um contrato não ditotry
/ não ditocatch(Exception)
que nunca devo fazer isso e, em vez disso, usar umException
tipo não separado ?No
você não está quebrando nenhum contrato. Opopular opinion
seria que você não deveria fazer isso. A programação está cheia de exemplos em que seu código-fonte não faz nada errado, mas todo mundo não gosta. O código-fonte deve sempre ser escrito para que fique claro qual é a intenção. Você está realmente ajudando outro programador usando uma exceção dessa maneira? Se sim, faça-o. Caso contrário, não se surpreenda se não gostar.Retornar exceções em vez de lançá-las pode fazer sentido semântico quando você tem um método auxiliar para analisar a situação e retornar uma exceção apropriada que é lançada pelo chamador (você pode chamar isso de "fábrica de exceções"). Lançar uma exceção nessa função do analisador de erros significaria que algo deu errado durante a análise em si, enquanto retornar uma exceção significa que o tipo de erro foi analisado com êxito.
Um possível caso de uso pode ser uma função que transforma códigos de resposta HTTP em exceções:
Observe que lançar uma exceção significa que o método foi usado incorretamente ou teve um erro interno, enquanto retornar uma exceção significa que o código de erro foi identificado com êxito.
fonte
return
instruções supérfluas para fazer com que o compilador pare de reclamar.System.Exit()
. O código após a chamada da função não é executado. Isso não soa como um bom padrão de design para uma função.Conceitualmente, se o objeto de exceção for o resultado esperado da operação, então sim. Os casos em que consigo pensar, no entanto, sempre envolvem arremesso de peso em algum momento:
Variações no padrão "Try" (onde um método encapsula um segundo método de lançamento de exceção, mas captura a exceção e, em vez disso, retorna um booleano indicando sucesso). Você pode, em vez de retornar um booleano, retornar qualquer exceção lançada (nula se for bem-sucedida), permitindo ao usuário final mais informações, mantendo a indicação de sucesso ou falha sem captura.
Processamento de erros no fluxo de trabalho. Semelhante na prática ao "tentar o padrão" do encapsulamento do método, você pode ter uma etapa do fluxo de trabalho abstraída em um padrão de cadeia de comando. Se um link na cadeia lança uma exceção, geralmente é mais fácil capturar essa exceção no objeto de abstração e devolvê-lo como resultado da operação mencionada, para que o mecanismo do fluxo de trabalho e o código real sejam executados como uma etapa do fluxo de trabalho, não exigindo uma tentativa extensiva. lógica de captura por conta própria.
fonte
OperationResult
classe abstrata , com umabool
propriedade "Successful", umGetExceptionIfAny
método e umAssertSuccessful
método (que lançaria a exceçãoGetExceptionIfAny
se não for nulo). TendoGetExceptionIfAny
ser um método em vez de uma propriedade permitiria o uso de objetos de erro imutáveis estáticos para casos em que não havia muito útil a dizer.Digamos que você enquadrou alguma tarefa a ser executada em algum pool de threads. Se essa tarefa gerar exceção, será um encadeamento diferente para que você não o pegue. A linha onde foi executada simplesmente morre.
Agora considere que algo (o código nessa tarefa ou a implementação do conjunto de encadeamentos) captura essa exceção, armazene-o junto com a tarefa e considere a tarefa concluída (sem êxito). Agora você pode perguntar se a tarefa foi concluída (e perguntar se foi lançada ou se pode ser lançada novamente em seu encadeamento (ou melhor, nova exceção com o original como causa)).
Se você fizer isso manualmente, poderá perceber que está criando uma nova exceção, lançando-a e capturando-a e armazenando-a, em diferentes threads recuperando lançando, capturando e reagindo a ela. Portanto, pode fazer sentido pular o arremesso e a captura, armazená-lo e concluir a tarefa e depois perguntar se há exceção e reagir a ele. Mas isso pode levar a um código mais complicado, se no mesmo local houver realmente exceções.
PS: isso é escrito com experiência em Java, onde as informações de rastreamento de pilha são criadas quando a exceção é criada (ao contrário do C #, onde é criada ao lançar). Portanto, a abordagem de exceção não lançada em Java será mais lenta que em C # (a menos que seja pré-criada e reutilizada), mas terá informações de rastreamento de pilha disponíveis.
Em geral, eu evitaria criar exceções e nunca as lançaria (a menos que, como otimização de desempenho após a criação de perfil, aponte como gargalo). Pelo menos em java, onde a criação de exceções é cara (rastreamento de pilha). Em C #, é possível, mas a OMI é surpreendente e, portanto, deve ser evitada.
fonte
Depende do seu design, normalmente eu não retornaria exceções a um chamador, mas as jogaria e pegaria e deixaria assim. Normalmente, o código é escrito para falhar cedo e rapidamente. Por exemplo, considere o caso de abrir um arquivo e processá-lo (este é C # PsuedoCode):
Nesse caso, teríamos um erro e pararíamos de processar o arquivo assim que ele encontrasse um registro incorreto.
Mas digamos que o requisito é que queremos continuar processando o arquivo mesmo que uma ou mais linhas apresentem um erro. O código pode começar a ter algo parecido com isto:
Portanto, nesse caso, retornamos a exceção de volta ao responsável pela chamada, embora eu provavelmente não retornasse uma exceção de volta, mas algum tipo de objeto de erro, pois a exceção foi capturada e já foi tratada. Isso não faz mal ao retornar uma exceção de volta, mas outros chamadores podem repetir a exceção, o que pode não ser desejável, pois o objeto de exceção tem esse comportamento. Se você quiser que os chamadores tenham a capacidade de retornar novamente, retorne uma exceção; caso contrário, retire as informações do objeto de exceção e construa um objeto leve menor e, em vez disso, retorne-o. A falha rápida é geralmente um código mais limpo.
Para o seu exemplo de validação, eu provavelmente não herdaria de uma classe de exceção nem lançaria exceções porque os erros de validação podem ser bastante comuns. Seria menos caro retornar um objeto do que lançar uma exceção se 50% dos meus usuários falharem em preencher um formulário corretamente na primeira tentativa.
fonte
Sim. Por exemplo, recentemente encontrei uma situação em que descobri que exceções lançadas em um serviço da Web ASMX não incluíam um elemento na mensagem SOAP resultante, portanto tive que gerá-lo.
Código ilustrativo:
fonte
Na maioria dos idiomas (afaik), as exceções vêm com alguns itens adicionais. Principalmente, uma captura instantânea do rastreamento de pilha atual é armazenada no objeto. Isso é bom para depuração, mas também pode ter implicações de memória.
Como o @ThinkingMedia já disse, você realmente está usando a exceção como um objeto de erro.
No seu exemplo de código, parece que você faz isso principalmente para reutilizar o código e evitar a composição. Pessoalmente, não acho que essa seja uma boa razão para fazer isso.
Outro motivo possível seria enganar a linguagem para fornecer um objeto de erro com um rastreamento de pilha. Isso fornece informações contextuais adicionais ao seu código de tratamento de erros.
Por outro lado, podemos assumir que manter o rastreamento da pilha ao redor tem um custo de memória. Por exemplo, se você começar a coletar esses objetos em algum lugar, poderá ver um impacto desagradável na memória. Obviamente, isso depende muito de como os rastreamentos de pilha de exceção são implementados no respectivo idioma / mecanismo.
Então, é uma boa ideia?
Até agora, eu não o vi sendo usado ou recomendado. Mas isso não significa muito.
A pergunta é: o rastreamento de pilha é realmente útil para o tratamento de erros? Ou, de maneira mais geral, quais informações você deseja fornecer ao desenvolvedor ou administrador?
O padrão típico de throw / try / catch torna necessário um rastreamento de pilha, pois, caso contrário, você não tem idéia de onde ele é. Para um objeto retornado, ele sempre vem da função chamada. O rastreamento de pilha pode conter todas as informações que o desenvolvedor precisa, mas talvez algo menos pesado e mais específico seja suficiente.
fonte
A resposta é sim. Consulte http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Exceptions e abra a seta do guia de estilo C ++ do Google sobre exceções. Você pode ver lá os argumentos que eles usaram para decidir contra, mas diga que se eles tivessem que fazer isso novamente, provavelmente teriam decidido.
No entanto, vale ressaltar que o idioma Go também não usa exceções no idioma, por motivos semelhantes.
fonte