Por que não devemos lançar essas exceções?

111

Me deparei com esta página do MSDN que afirma:

Não lance Exception , SystemException , NullReferenceException ou IndexOutOfRangeException intencionalmente de seu próprio código-fonte.

Infelizmente, não se preocupa em explicar o porquê. Posso adivinhar as razões, mas espero que alguém com mais autoridade no assunto possa oferecer sua visão.

Os dois primeiros fazem algum sentido óbvio, mas os dois últimos parecem aqueles que você gostaria de empregar (e na verdade, eu usei).

Além disso, essas são as únicas exceções que devemos evitar? Se houver outros, quais são e por que também deveriam ser evitados?

DonBoitnott
fonte
22
De msdn.microsoft.com/en-us/library/ms182338.aspx : Se você lançar um tipo de exceção geral, como Exception ou SystemException em uma biblioteca ou estrutura, ele força os consumidores a capturar todas as exceções, incluindo exceções desconhecidas que eles fazem não sei como lidar.
Henrik de
3
Por que você lançaria uma NullReferenceException?
Rik
5
@Rik: semelhante ao NullArgumentExceptionque algumas pessoas podem confundir os dois.
Muçulmano Ben Dhaou
2
Métodos de extensão @Rik, conforme minha resposta
Marc Gravell
3
Outro que você não deve jogar éApplicationException
Matthew Watson

Respostas:

98

Exceptioné o tipo base para todas as exceções e, como tal, terrivelmente inespecífico. Você nunca deve lançar essa exceção porque ela simplesmente não contém nenhuma informação útil. A captura do código de chamada para exceções não pode eliminar a ambigüidade da exceção lançada intencionalmente (de sua lógica) de outras exceções do sistema que são totalmente indesejadas e apontam falhas reais.

O mesmo motivo também se aplica a SystemException. Se você olhar a lista de tipos derivados, poderá ver um grande número de outras exceções com semânticas muito diferentes.

NullReferenceExceptione IndexOutOfRangeExceptionsão de um tipo diferente. Essas são exceções muito específicas, portanto, pode ser bom jogá-las . No entanto, você ainda não vai querer jogá-los, pois eles geralmente significam que existem alguns erros reais em sua lógica. Por exemplo, a exceção de referência nula significa que você está tentando acessar um membro de um objeto que é null. Se isso for uma possibilidade em seu código, você deve sempre verificar explicitamente nulle lançar uma exceção mais útil (por exemplo ArgumentNullException). Da mesma forma, IndexOutOfRangeExceptions ocorrem quando você acessa um índice inválido (em matrizes - não em listas). Você deve sempre certificar-se de não fazer isso em primeiro lugar e verificar os limites de, por exemplo, um array primeiro.

Existem algumas outras exceções como essas duas, por exemplo InvalidCastExceptionou DivideByZeroException, que são lançadas para falhas específicas em seu código e geralmente significam que você está fazendo algo errado ou não está verificando alguns valores inválidos primeiro. Ao descartá-los conscientemente de seu código, você está apenas tornando mais difícil para o código de chamada determinar se eles foram lançados devido a alguma falha no código ou apenas porque você decidiu reutilizá-los para algo em sua implementação.

Claro, existem algumas exceções (hah) a essas regras. Se você estiver construindo algo que pode causar uma exceção que corresponda exatamente a uma existente, sinta-se à vontade para usar isso, especialmente se estiver tentando corresponder a algum comportamento integrado. Apenas certifique-se de escolher um tipo de exceção muito específico.

Em geral, porém, a menos que você encontre uma exceção (específica) que preencha sua necessidade, você deve sempre considerar a criação de seus próprios tipos de exceção para exceções esperadas específicas. Especialmente quando você está escrevendo código de biblioteca, isso pode ser muito útil para separar as fontes de exceção.

cutucar
fonte
3
A terceira parte faz pouco sentido para mim. Claro, você deve evitar causar esses erros para começar, mas quando você, por exemplo, escreve uma IListimplementação, não está em seu poder afetar os índices solicitados, é o erro lógico do chamador quando um índice é inválido, e você só pode informá-los desse erro lógico lançando uma exceção apropriada. Por que IndexOutOfRangeExceptionnão é apropriado?
7
@delnan Se você estiver implementando IList, irá lançar um ArgumentOutOfRangeExceptionconforme sugere a documentação da interface . IndexOutOfRangeExceptioné para matrizes e, até onde eu sei, você não pode reimplementar matrizes.
cutucar
2
O que também pode estar relacionado, NullReferenceExceptiongeralmente é lançado internamente como um caso especial de um AccessViolationException(IIRC, o teste é algo como cmp [addr], addr, isto é, ele tenta desreferenciar o ponteiro e se falhar com uma violação de acesso, ele trata a diferença entre NRE e AVE no manipulador de interrupção resultante). Portanto, além das razões semânticas, também há algum engano envolvido. Também pode ajudar a desencorajá-lo de verificar nullmanualmente quando não é útil - se você vai lançar um NRE de qualquer maneira, por que não deixar o .NET fazer isso?
Luaan
Em relação à sua última declaração, sobre exceções personalizadas: Nunca senti a necessidade de fazer isso. Talvez eu esteja perdendo alguma coisa. Sob quais condições seria necessário criar um tipo de exceção personalizado em vez de algo da estrutura?
DonBoitnott
1
Bem, para aplicativos menores, pode não haver necessidade disso. Mas assim que você cria algo mais complexo, onde peças individuais funcionam como “componentes” independentes, geralmente faz sentido introduzir exceções personalizadas para situações de erro personalizadas. Por exemplo, se você tiver alguma camada de controle de acesso e tentar executar algum serviço embora não esteja autorizado a fazê-lo, poderá lançar uma “exceção de acesso negado” ou algo assim. Ou se você tiver um analisador que analisa algum arquivo, pode ter seus próprios erros do analisador para relatar ao usuário.
cutucar
36

Suspeito que a intenção com os 2 últimos é evitar confusão com exceções embutidas que têm um significado esperado. No entanto, sou da opinião que, se você está preservando a intenção exata da exceção : é a correta para throw. Por exemplo, se você estiver escrevendo uma coleção personalizada, parece totalmente razoável usar IndexOutOfRangeException- mais claro e mais específico, IMO, do que ArgumentOutOfRangeException. E embora você List<T>possa escolher o último, há pelo menos 41 lugares (cortesia do refletor) no BCL (sem incluir matrizes) que são projetadas sob medida IndexOutOfRangeException- nenhum dos quais é de "nível baixo" o suficiente para merecer isenção especial. Então, sim, acho que você pode argumentar com justiça que essa diretriz é boba. Da mesma forma,NullReferenceException é bastante útil em métodos de extensão - se você deseja preservar a semântica que:

obj.SomeMethod(); // this is actually an extension method

joga um NullReferenceExceptionquando objé null.

Marc Gravell
fonte
2
"se você estiver preservando a intenção exata da exceção" - certamente, se fosse esse o caso, a exceção seria lançada sem que você tivesse que testá-la em primeiro lugar? E se você já testou, não é realmente uma exceção?
PugFugly
5
@PugFugly leva 2 segundos para olhar o exemplo do método de extensão: não, isso não será lançado sem que você tenha que fazer um teste. Se SomeMethod()não for necessário realizar o acesso de membro, é incorreto forçá-lo. Da mesma forma: considere esse ponto com os 41 locais no BCL que criam personalizados IndexOutOfRangeExceptione os 16 locais que criam personalizadosNullReferenceException
Marc Gravell
16
Eu diria que um método de extensão ainda deve lançar um em ArgumentNullExceptionvez de um NullReferenceException. Mesmo se o açúcar sintático dos métodos de extensão permitir a mesma sintaxe do acesso de membro normal, ele ainda funciona de maneira muito diferente. E obter um NRE de MyStaticHelpers.SomeMethod(obj)seria simplesmente errado.
cutucar
1
@PugFugly BCL é uma “biblioteca de classes base”, basicamente o núcleo do .NET.
toque em
1
@PugFugly: Existem muitos cenários em que a falha na detecção preventiva de uma condição resultaria em uma exceção sendo lançada em um momento "inconveniente". Se uma operação não for bem-sucedida, lançar uma exceção antecipadamente é melhor do que iniciar a operação, chegar na metade e depois ter que limpar a bagunça parcialmente processada resultante.
supercat de
5

Como você assinalou, no artigo Criando e lançando exceções (Guia de programação C #) sob o tópico Coisas a serem evitadas ao lançar exceções , a Microsoft realmente lista System.IndexOutOfRangeExceptioncomo um tipo de exceção que não deve ser lançado intencionalmente de seu próprio código-fonte.

Em contraste, no entanto, no lançamento do artigo (Referência C #) , a Microsoft parece violar suas próprias diretrizes. Aqui está um método que a Microsoft incluiu em seu exemplo:

static int GetNumber(int index)
{
    int[] nums = { 300, 600, 900 };
    if (index > nums.Length)
    {
        throw new IndexOutOfRangeException();
    }
    return nums[index];
}

Portanto, a própria Microsoft não está sendo consistente ao demonstrar o lançamento de IndexOutOfRangeExceptionem sua documentação para throw!

Isso me leva a acreditar que, pelo menos para o caso de IndexOutOfRangeException, pode haver ocasiões em que esse tipo de exceção pode ser lançado pelo programador e considerado uma prática aceitável.

DavidRR
fonte
1

Quando li sua pergunta, me perguntei em que condições alguém gostaria de lançar os tipos de exceção NullReferenceException , InvalidCastExceptionou ArgumentOutOfRangeException.

Na minha opinião, ao encontrar um desses tipos de exceção, eu (o desenvolvedor) fico preocupado com o aviso no sentido de que o compilador está falando comigo. Portanto, permitir que você (o desenvolvedor) lance tais tipos de exceção é equivalente a (o compilador) vender a responsabilidade. Por exemplo, isso sugere que o compilador deve agora permitir que o desenvolvedor decida se um objeto é null. Mas fazer essa determinação deve ser realmente trabalho do compilador.

PS: Desde 2003, venho desenvolvendo minhas próprias exceções para poder lançá-las como quiser. Eu acho que é considerada uma prática recomendada fazer isso.

Bellash
fonte
Bons pontos. No entanto, acho que seria mais preciso dizer que o programador deve deixar o runtime do .NET Framework lançar esses tipos de exceções (e que o programador deve tratá-las de maneira apropriada).
DavidRR
0

Colocando a discussão sobre NullReferenceExceptione de IndexOutOfBoundsExceptionlado:

Que tal pegar e jogar System.Exception. Eu lancei muito esse tipo de exceção no meu código e nunca me incomodei com isso. Da mesma forma, muitas vezes eu pego o não específicoException tipo , e também funcionou muito bem para mim. Então, por que isso?

Normalmente, os usuários argumentam que devem ser capazes de distinguir as causas dos erros. Pela minha experiência, existem apenas algumas situações em que você deseja lidar com diferentes tipos de exceção de maneira diferente. Para aqueles casos, onde você espera que os usuários tratem os erros programaticamente, você deve lançar um tipo de exceção mais específico. Para outros casos, não estou convencido pela diretriz geral de melhores práticas.

Portanto, em relação ao lançamento Exception, não vejo razão para proibir em todos os casos.

EDITAR: também na página MSDN:

As exceções não devem ser usadas para alterar o fluxo de um programa como parte da execução normal. As exceções devem ser usadas apenas para relatar e tratar condições de erro.

Exagerar cláusulas catch com lógica individual para diferentes tipos de exceção também não é uma prática recomendada.

uebe
fonte
A mensagem de exceção ainda está em vigor para contar o que aconteceu. Não consigo imaginar que você vá e crie um novo tipo de exceção para cada erro diferente, apenas no caso de um consumidor querer distinguir esses erros, programaticamente.
uebe
O que aconteceu com meu comentário anterior?
DonBoitnott