Quais exceções devem ser lançadas para parâmetros inválidos ou inesperados no .NET?

163

Que tipos de exceções devem ser lançadas para parâmetros inválidos ou inesperados no .NET? Quando eu escolheria um em vez de outro?

Acompanhamento:

Qual exceção você usaria se tivesse uma função esperando um número inteiro correspondente a um mês e passasse '42'? Isso se enquadra na categoria "fora de alcance", mesmo que não seja uma coleção?

Even Mien
fonte

Respostas:

249

Eu gosto de usar: ArgumentException, ArgumentNullException, e ArgumentOutOfRangeException.

Também há outras opções que não se concentram tanto no argumento em si, mas julgam a chamada como um todo:

  • InvalidOperationException- O argumento pode estar OK, mas não no estado atual do objeto. O crédito vai para o STW (anteriormente Yoooder). Vote sua resposta também.
  • NotSupportedException- Os argumentos transmitidos são válidos, mas simplesmente não são suportados nesta implementação. Imagine um cliente FTP e você passa um comando no qual o cliente não suporta.

O truque é lançar a exceção que melhor expressa por que o método não pode ser chamado do jeito que é. Idealmente, a exceção deve ser detalhada sobre o que deu errado, por que está errado e como corrigi-lo.

Adoro quando as mensagens de erro apontam para ajuda, documentação ou outros recursos. Por exemplo, a Microsoft deu um bom primeiro passo com seus artigos da Base de Dados de Conhecimento, por exemplo, “Por que recebo uma mensagem de erro" Operação interrompida "quando visito uma página da Web no Internet Explorer?” . Quando você encontra o erro, eles apontam para o artigo da KB na mensagem de erro. O que eles não fazem bem é que não dizem a você, por que especificamente falhou.

Obrigado a STW (ex Yoooder) novamente pelos comentários.


Em resposta ao seu acompanhamento, eu jogaria um ArgumentOutOfRangeException. Veja o que o MSDN diz sobre essa exceção:

ArgumentOutOfRangeExceptioné lançado quando um método é chamado e pelo menos um dos argumentos passados ​​para o método não é referência nula ( Nothingno Visual Basic) e não contém um valor válido.

Portanto, nesse caso, você está passando um valor, mas esse não é um valor válido, pois seu intervalo é de 1 a 12. No entanto, a maneira como você documenta isso deixa claro o que sua API lança. Porque, embora eu possa dizer ArgumentOutOfRangeException, outro desenvolvedor pode dizer ArgumentException. Facilite e documente o comportamento.

JoshBerke
fonte
Psst! Concordo que você respondeu a sua pergunta específica exatamente certo, mas ver minha resposta abaixo para ajuda rodada fora defensiva codificação e validação de parâmetros; -D
STW
+1, mas documentando que exceção é lançada e por que é mais importante do que escolher a opção 'certa'.
pipTheGeek
@pipTheGeek - Eu acho que é realmente um ponto discutível. Embora a documentação seja definitivamente importante, ela também espera que o desenvolvedor consumidor seja proativo ou defensivo e realmente leia a documentação em detalhes. Eu optaria por um erro amigável / descritivo em vez de boa documentação, pois o usuário final tem a chance de ver um deles e não o outro; há uma melhor chance de ter um usuário final comunicar um erro descritiva para um programador pobres do que um programador pobre lendo os documentos completos
STW
4
Observe que, se você capturar ArgumentException, ele também capturará ArgumentOutOfRange.
21119 Steven Evers
Que tal FormatException: a exceção lançada quando o formato de um argumento é inválido ou quando uma sequência de formato composto não está bem formada.
Anthony
44

Votei na resposta de Josh , mas gostaria de adicionar mais uma à lista:

System.InvalidOperationException deve ser lançado se o argumento for válido, mas o objeto estiver em um estado em que o argumento não deve ser usado.

Atualização obtida do MSDN:

InvalidOperationException é usado nos casos em que a falha na chamada de um método é causada por outros motivos que não argumentos inválidos.

Digamos que seu objeto tenha um método PerformAction (ação enmSomeAction), enmSomeActions válidas são Open e Close. Se você chamar PerformAction (enmSomeAction.Open) duas vezes seguidas, a segunda chamada lançará InvalidOperationException (uma vez que o artigo era válido, mas não para o estado atual do controle)

Como você já está fazendo a coisa certa ao programar defensivamente, tenho uma outra exceção a ser mencionada: ObjectDisposedException. Se seu objeto implementa IDisposable, você deve sempre ter uma variável de classe rastreando o estado disposto; se seu objeto foi descartado e um método é chamado, você deve aumentar o ObjectDisposedException:

public void SomeMethod()
{
    If (m_Disposed) {
          throw new ObjectDisposedException("Object has been disposed")
     }
    // ... Normal execution code
}

Atualização: Para responder ao seu acompanhamento: É uma situação um pouco ambígua e é um pouco mais complicada por um tipo de dados genérico (não no sentido do .NET Generics) sendo usado para representar um conjunto específico de dados; um enum ou outro objeto fortemente tipado seria um ajuste mais ideal - mas nem sempre temos esse controle.

Pessoalmente, eu me inclinaria para o ArgumentOutOfRangeException e forneceria uma mensagem indicando que os valores válidos são 1-12. Meu raciocínio é que, quando você fala sobre meses, assumindo que todas as representações inteiras de meses são válidas, você espera um valor no intervalo de 1 a 12. Se apenas alguns meses (como meses com 31 dias) fossem válidos, você não estaria lidando com um intervalo per se e eu lançaria uma ArgumentException genérica que indicava os valores válidos, e também os documentaria nos comentários do método.

STW
fonte
1
Bom ponto. Isso pode explicar a diferença entre entradas inválidas e inesperadas. +1
Daniel Brückner
Psiu, eu concordo com você simplesmente não iria roubar seu trovão. Mas desde que você apontou, eu atualizei a minha resposta
JoshBerke
38

Dependendo do valor real e qual exceção se encaixa melhor:

Se isso não for preciso, basta derivar sua própria classe de exceção ArgumentException.

A resposta de Yoooder me iluminou. Uma entrada é inválida se não for válida a qualquer momento, enquanto uma entrada é inesperada se não for válida para o estado atual do sistema. Portanto, no caso posterior, a InvalidOperationExceptioné uma escolha razoável.

Daniel Brückner
fonte
6
Retirado da página do MSDN em InvalidOperationException: "InvalidOperationException é usado nos casos em que a falha na chamada de um método é causada por outros motivos que não argumentos inválidos".
217 de STW
4

exceção de argumento.

  • System.ArgumentException
  • System.ArgumentNullException
  • System.ArgumentOutOfRangeException
Syed Tayyab Ali
fonte
3

ArgumentException :

ArgumentException é lançada quando um método é chamado e pelo menos um dos argumentos passados ​​não atende à especificação de parâmetro do método chamado. Todas as instâncias de ArgumentException devem conter uma mensagem de erro significativa descrevendo o argumento inválido, bem como o intervalo esperado de valores para o argumento.

Também existem algumas subclasses para tipos específicos de invalidez. O link possui resumos dos subtipos e quando eles devem ser aplicados.

Ben S
fonte
1

Resposta curta:
nem

Resposta mais longa:
usar Argumento * A exceção (exceto em uma biblioteca que é um produto, como a biblioteca de componentes) é um cheiro. As exceções são para lidar com situações excepcionais, não com bugs e com deficiências do usuário (ou seja, consumidor da API).

Resposta mais longa:
Lançar exceções para argumentos inválidos é rude, a menos que você escreva uma biblioteca.
Prefiro usar asserções, por dois (ou mais) motivos:

  • As asserções não precisam ser testadas, enquanto as assertivas o fazem, e o teste contra ArgumentNullException parece ridículo (tente).
  • As asserções comunicam melhor o uso pretendido da unidade e estão mais próximas de ser uma documentação executável do que uma especificação de comportamento de classe.
  • Você pode alterar o comportamento da violação de asserção. Por exemplo, na compilação de depuração, uma caixa de mensagem é adequada, para que seu controle de qualidade o atinja imediatamente (você também obtém seu IDE interrompido na linha em que ocorre), enquanto no teste de unidade você pode indicar falha de asserção como falha de teste .

Aqui está como é o tratamento da exceção nula (sendo sarcástico, obviamente):

try {
    library.Method(null);
}
catch (ArgumentNullException e) {
    // retry with real argument this time
    library.Method(realArgument);
}

Exceções devem ser usadas quando a situação é esperada, mas excepcional (acontecem coisas que estão fora do controle do consumidor, como falha de E / S). Argumento * Exceção é uma indicação de um bug e deve (na minha opinião) ser tratado com testes e assistido com Debug.Assert

BTW: Nesse caso específico, você poderia ter usado o tipo Month, em vez de int. O C # fica aquém quando se trata de digitar safety (Aspect # rulez!), Mas às vezes você pode impedir (ou capturar em tempo de compilação) esses erros todos juntos.

E sim, a MicroSoft está errada sobre isso.

THX-1138
fonte
6
IMHO, exceções também devem ser lançadas quando o método chamado não puder razoavelmente prosseguir. Isso inclui o caso em que o chamador passou por argumentos falsos. O que você faria em vez disso? Retornar -1?
John Saunders
1
Se argumentos inválidos causam uma falha na função interna, quais são os prós e os contras dos testes de validade, em vez de capturar uma InvalidArgumentException da função interna e envolvê-la com uma mais informativa? A última abordagem parece melhorar o desempenho no caso comum, mas eu não a vi fazer muito.
Supercat
Uma rápida pesquisa no Google em torno dessa pergunta indica que a exceção geral é a pior prática de todas. Em relação a uma afirmação do argumento, vejo mérito nisso em pequenos projetos pessoais, mas não em aplicativos corporativos em que argumentos inválidos provavelmente são devidos a uma configuração incorreta ou a um entendimento ruim do aplicativo.
Max
0

Existe uma ArgumentException padrão que você pode usar ou pode criar uma subclasse e criar a sua própria. Existem várias classes ArgumentException específicas:

http://msdn.microsoft.com/en-us/library/system.argumentexception(VS.71).aspx

Qualquer um que funcione melhor.

Scott M.
fonte
1
Discordo de quase todos os casos; as classes fornecidas.
217 de STW
Para esclarecer - eu discordo de quase todos os casos decorrentes das classes Argument * Exception. O uso de uma das exceções de argumento do .NET, além de uma mensagem clara e descritiva, fornece detalhes suficientes para mais ou menos todas as situações em que os argumentos são inválidos.
STW
concordou, mas eu estava apenas descrevendo as opções disponíveis. Eu definitivamente deveria ter sido mais claro sobre o método "preferido".
217 Scott Scott M.