¿Qué excepciones se deben lanzar para parámetros inválidos o inesperados en .NET?

163

¿Qué tipos de excepciones deberían lanzarse para parámetros inválidos o inesperados en .NET? ¿Cuándo elegiría uno en lugar de otro?

Seguimiento:

¿Qué excepción usarías si tienes una función que espera un número entero correspondiente a un mes y pasaste '42'? ¿Caería esto en la categoría "fuera de rango" aunque no sea una colección?

Incluso Mien
fuente

Respuestas:

249

Me gusta usar: ArgumentException, ArgumentNullException, y ArgumentOutOfRangeException.

También hay otras opciones que no se centran tanto en el argumento en sí, sino que juzgan la llamada en su conjunto:

  • InvalidOperationException- El argumento puede estar bien, pero no en el estado actual del objeto. El crédito va a STW (anteriormente Yoooder). Vota su respuesta también.
  • NotSupportedException- Los argumentos pasados ​​son válidos, pero simplemente no son compatibles con esta implementación. Imagine un cliente FTP y le pasa un comando que el cliente no admite.

El truco consiste en lanzar la excepción que mejor exprese por qué el método no puede llamarse como está. Idealmente, la excepción debe ser detallada sobre lo que salió mal, por qué está mal y cómo solucionarlo.

Me encanta cuando los mensajes de error apuntan a ayuda, documentación u otros recursos. Por ejemplo, Microsoft dio un buen primer paso con sus artículos de KB, por ejemplo, "¿Por qué recibo un mensaje de error" Operación cancelada "cuando visito una página web en Internet Explorer?" . Cuando encuentra el error, lo señalan al artículo de KB en el mensaje de error. Lo que no hacen bien es que no te dicen por qué falló específicamente.

Gracias a STW (ex Yoooder) nuevamente por los comentarios.


En respuesta a su seguimiento, lanzaría un ArgumentOutOfRangeException. Mire lo que dice MSDN sobre esta excepción:

ArgumentOutOfRangeExceptionse lanza cuando se invoca un método y al menos uno de los argumentos pasados ​​al método no es una referencia nula ( Nothingen Visual Basic) y no contiene un valor válido.

Entonces, en este caso, está pasando un valor, pero ese no es un valor válido, ya que su rango es 1–12. Sin embargo, la forma en que lo documenta deja en claro qué arroja su API. Porque aunque podría decir ArgumentOutOfRangeException, otro desarrollador podría decir ArgumentException. Hazlo fácil y documenta el comportamiento.

JoshBerke
fuente
Psst! Estoy de acuerdo en que respondió a su pregunta específica en lo cierto, pero veo mi respuesta a continuación a la redonda ayuda a cabo defensiva parámetros de codificación y validación; -D
STW
+1 pero documentando qué excepción se produce y por qué es más importante que elegir la "correcta".
pipTheGeek
@pipTheGeek - Creo que es realmente un punto discutible. Si bien la documentación es definitivamente importante, también espera que el desarrollador consumidor sea proactivo o defensivo y realmente lea la documentación en detalle. Optaría por un error descriptivo / descriptivo sobre una buena documentación, ya que el usuario final tiene la posibilidad de ver uno de ellos y no el otro; hay una mejor posibilidad de que un usuario final comunique un error descriptivo a un programador pobre que un programador pobre que lee los documentos completos
STW
44
Tenga en cuenta que si detecta ArgumentException, también detectará ArgumentOutOfRange.
Steven Evers
Qué tal FormatException: la excepción que se produce cuando el formato de un argumento no es válido o cuando una cadena de formato compuesto no está bien formada.
Anthony
44

Voté por la respuesta de Josh , pero me gustaría agregar una más a la lista:

Se debe lanzar System.InvalidOperationException si el argumento es válido, pero el objeto está en un estado en el que no se debe usar el argumento.

Actualización tomada de MSDN:

InvalidOperationException se usa en los casos en que el incumplimiento de invocar un método se debe a razones distintas de argumentos no válidos.

Digamos que su objeto tiene un método PerformAction (acción enmSomeAction), las enmSomeActions válidas son Abrir y Cerrar. Si llama a PerformAction (enmSomeAction.Open) dos veces seguidas, la segunda llamada debería arrojar la InvalidOperationException (ya que la resolución era válida, pero no para el estado actual del control)

Como ya está haciendo lo correcto al programar a la defensiva, tengo otra excepción que mencionar es ObjectDisposedException. Si su objeto implementa IDisposable, siempre debe tener una variable de clase que rastree el estado desechado; si su objeto ha sido desechado y se llama a un método, debe activar la excepción ObjectDisposedException:

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

Actualización: para responder a su seguimiento: es una situación un poco ambigua, y se complica un poco más por un tipo de datos genérico (no en el sentido de .NET Generics) que se utiliza para representar un conjunto específico de datos; una enumeración u otro objeto fuertemente tipado sería un ajuste más ideal, pero no siempre tenemos ese control.

Me inclinaría personalmente hacia ArgumentOutOfRangeException y proporcionaría un mensaje que indica que los valores válidos son 1-12. Mi razonamiento es que cuando habla de meses, suponiendo que todas las representaciones enteras de meses son válidas, entonces espera un valor en el rango de 1-12. Si solo ciertos meses (como los meses que tenían 31 días) fueran válidos, entonces no estaría lidiando con un Rango per se y arrojaría una Excepción Argument genérica que indicara los valores válidos, y también los documentaría en los comentarios del método.

STW
fuente
1
Buen punto. Esto podría explicar la diferencia entre una entrada no válida e inesperada. +1
Daniel Brückner
Psst, estoy de acuerdo contigo, no iba a robarte el trueno. Pero como lo señaló, actualicé mi respuesta
JoshBerke
38

Dependiendo del valor real y qué excepción se ajusta mejor:

Si esto no es lo suficientemente preciso, simplemente deriva tu propia clase de excepción ArgumentException.

La respuesta de Yoooder me iluminó. Una entrada no es válida si no es válida en cualquier momento, mientras que una entrada es inesperada si no es válida para el estado actual del sistema. Entonces, en el último caso, una InvalidOperationExceptiones una opción razonable.

Daniel Brückner
fuente
66
Tomado de la página de MSDN en InvalidOperationException: "InvalidOperationException se usa en los casos en que el incumplimiento de invocar un método se debe a razones distintas de argumentos no válidos".
STW
3

ArgumentException :

Se genera ArgumentException cuando se invoca un método y al menos uno de los argumentos pasados ​​no cumple con la especificación de parámetros del método llamado. Todas las instancias de ArgumentException deben llevar un mensaje de error significativo que describa el argumento no válido, así como el rango de valores esperado para el argumento.

También existen algunas subclases para tipos específicos de invalidez. El enlace tiene resúmenes de los subtipos y cuándo deben aplicarse.

Ben S
fuente
1

Respuesta corta:
ninguno

Respuesta más larga:
usar Argument * Exception (excepto en una biblioteca que es un producto encendido, como la biblioteca de componentes) es un olor. Las excepciones son manejar situaciones excepcionales, no errores, y no fallas del usuario (es decir, consumidor de API).

Respuesta más larga:
Lanzar excepciones para argumentos no válidos es grosero, a menos que escriba una biblioteca.
Prefiero usar aserciones, por dos (o más) razones:

  • Las aserciones no necesitan ser probadas, mientras que las aserciones de lanzamiento sí, y la prueba contra ArgumentNullException parece ridícula (pruébelo).
  • Las afirmaciones comunican mejor el uso previsto de la unidad y están más cerca de ser documentación ejecutable que una especificación de comportamiento de clase.
  • Puede cambiar el comportamiento de la violación de la aserción. Por ejemplo, en la compilación de depuración, un cuadro de mensaje está bien, de modo que su control de calidad lo golpeará de inmediato (también obtendrá su IDE rompiendo en la línea donde sucede), mientras que en la prueba unitaria puede indicar el fallo de la aserción como un fallo de la prueba .

Así es como se ve el manejo de la excepción nula (siendo sarcástico, obviamente):

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

Las excepciones se utilizarán cuando la situación se espera pero es excepcional (suceden cosas que están fuera del control del consumidor, como una falla de E / S). Argumento * La excepción es una indicación de un error y será (en mi opinión) manejada con pruebas y asistida con Debug.

Por cierto: en este caso particular, podría haber usado el tipo de mes, en lugar de int. C # se queda corto cuando se trata de escribir con seguridad (¡Aspecto # rulez!), Pero a veces puedes evitar (o detectar en el momento de la compilación) todos esos errores.

Y sí, MicroSoft está equivocado al respecto.

THX-1138
fuente
66
En mi humilde opinión, las excepciones también se deben lanzar cuando el método llamado no puede proceder razonablemente. Eso incluye el caso cuando la persona que llama ha pasado argumentos falsos. ¿Qué harías en su lugar? Regresar -1?
John Saunders
1
Si los argumentos inválidos causan el fallo de una función interna, ¿cuáles son los pros y los contras de probar la validez de los argumentos, en lugar de detectar una InvalidArgumentException de la función interna y envolverla con una más informativa? El último enfoque parecería mejorar el rendimiento en el caso común, pero no lo he visto hacer mucho.
supercat
Una búsqueda rápida en Google sobre esta pregunta indica que lanzar la Excepción general es la peor práctica de todas. En lo que respecta a una afirmación del argumento, veo mérito en esto en pequeños proyectos personales, pero no en aplicaciones empresariales donde los argumentos no válidos se deben probablemente a una mala configuración o una mala comprensión de la aplicación.
Max
0

Existe una excepción ArgumentException estándar que podría usar, o podría subclasificar y crear la suya propia. Hay varias clases específicas de ArgumentException:

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

Cualquiera que funcione mejor.

Scott M.
fuente
1
No estoy de acuerdo con casi todos los casos; las clases de excepción .NET Argument * proporcionadas se usan muy comúnmente y le brindan la capacidad de proporcionar suficiente información específica para notificar al consumidor sobre el problema.
STW
Para aclarar: no estoy de acuerdo con casi todos los casos de derivación de las clases Argument * Exception. El uso de una de las excepciones de argumentos de .NET más un mensaje descriptivo y claro proporciona suficientes detalles para más o menos cada situación en la que los argumentos no son válidos.
STW
de acuerdo, pero solo estaba describiendo las opciones disponibles. Definitivamente debería haber sido más claro sobre el método "preferido".
Scott M.