¿Por qué no vamos a lanzar estas excepciones?

111

Encontré esta página de MSDN que dice:

No arrojes Exception , SystemException , NullReferenceException o IndexOutOfRangeException intencionalmente desde tu propio código fuente.

Desafortunadamente, no se molesta en explicar por qué. Puedo adivinar las razones, pero espero que alguien más autorizado en el tema pueda ofrecer su visión.

Los dos primeros tienen un sentido obvio, pero los dos últimos parecen ser los que le gustaría emplear (y de hecho, lo tengo).

Además, ¿son estas las únicas excepciones que se deben evitar? Si hay otros, ¿cuáles son y por qué deberían evitarse también?

DonBoitnott
fuente
22
De msdn.microsoft.com/en-us/library/ms182338.aspx : si lanza un tipo de excepción general, como Exception o SystemException en una biblioteca o marco, obliga a los consumidores a detectar todas las excepciones, incluidas las excepciones desconocidas que hacen no se como manejar.
Henrik
3
¿Por qué arrojaría una NullReferenceException?
Rik
5
@Rik: similar a lo NullArgumentExceptionque algunas personas pueden confundir a ambos.
Moslem Ben Dhaou
2
Métodos de extensión de @Rik, según mi respuesta
Marc Gravell
3
Otro que no debes lanzar esApplicationException
Matthew Watson

Respuestas:

98

Exceptiones el tipo base para todas las excepciones y, como tal, es terriblemente inespecífico. Nunca debería lanzar esta excepción porque simplemente no contiene ninguna información útil. Llamar a la captura de código para las excepciones no pudo eliminar la ambigüedad de la excepción lanzada intencionalmente (de su lógica) de otras excepciones del sistema que son completamente indeseables y señalan fallas reales.

La misma razón también se aplica a SystemException. Si observa la lista de tipos derivados, puede ver una gran cantidad de otras excepciones con semánticas muy diferentes.

NullReferenceExceptiony IndexOutOfRangeExceptionson de un tipo diferente. Ahora bien, estas son excepciones muy específicas, por lo que lanzarlas podría estar bien. Sin embargo, todavía no querrás lanzarlos, ya que generalmente significan que hay algunos errores reales en tu lógica. Por ejemplo, la excepción de referencia nula significa que está intentando acceder a un miembro de un objeto que es null. Si esa es una posibilidad en su código, entonces siempre debe buscar explícitamente nully lanzar una excepción más útil en su lugar (por ejemplo ArgumentNullException). De manera similar, IndexOutOfRangeExceptionlos correos electrónicos ocurren cuando accede a un índice no válido (en matrices, no en listas). Siempre debe asegurarse de no hacer eso en primer lugar y verificar primero los límites de, por ejemplo, una matriz.

Hay algunas otras excepciones como esas dos, por ejemplo InvalidCastExceptiono DivideByZeroException, que se lanzan por fallas específicas en su código y generalmente significan que está haciendo algo mal o que no está verificando primero algunos valores no válidos. Al arrojarlos a sabiendas de su código, solo está dificultando que el código de llamada determine si se lanzaron debido a alguna falla en el código, o simplemente porque decidió reutilizarlos para algo en su implementación.

Por supuesto, hay algunas excepciones (ja) a estas reglas. Si está creando algo que puede causar una excepción que coincida exactamente con una existente, no dude en usarlo, especialmente si está tratando de igualar algún comportamiento incorporado. Solo asegúrese de elegir un tipo de excepción muy específico.

Sin embargo, en general, a menos que encuentre una excepción (específica) que satisfaga sus necesidades, siempre debe considerar la posibilidad de crear sus propios tipos de excepción para excepciones esperadas específicas. Especialmente cuando está escribiendo código de biblioteca, esto puede ser muy útil para separar las fuentes de excepción.

dar un toque
fuente
3
La tercera parte tiene poco sentido para mí. Claro, debería evitar causar estos errores al principio, pero cuando, por ejemplo, escribe una IListimplementación, no está en su poder afectar los índices solicitados, es un error lógico de la persona que llama cuando un índice no es válido, y solo puede informarles de este error lógico lanzando una excepción apropiada. ¿Por qué IndexOutOfRangeExceptionno es apropiado?
7
@delnan Si está implementando IList, lanzará un ArgumentOutOfRangeExceptioncomo sugiere la documentación de la interfaz . IndexOutOfRangeExceptiones para matrices y, hasta donde yo sé, no se pueden volver a implementar matrices.
empuje el
2
Lo que también podría estar algo relacionado, NullReferenceExceptiongeneralmente se lanza internamente como un caso especial de un AccessViolationException(IIRC, la prueba es algo así cmp [addr], addr, es decir, intenta desreferenciar el puntero y si falla con una violación de acceso, maneja la diferencia entre NRE y AVE en el manejador de interrupciones resultante). Entonces, aparte de las razones semánticas, también hay algunas trampas involucradas. También podría ayudarlo a desanimarlo de verificar nullmanualmente cuando no sea útil; si de todos modos va a lanzar una NRE, ¿por qué no dejar que .NET lo haga?
Luaan
Con respecto a su última declaración, sobre las excepciones personalizadas: nunca sentí la necesidad de hacer esto. Quizás me estoy perdiendo algo. ¿En qué condiciones se necesitaría crear un tipo de excepción personalizado en lugar de algo del marco?
DonBoitnott
1
Bueno, para aplicaciones más pequeñas puede que no sea necesario. Pero tan pronto como crea algo más complejo en el que las partes individuales funcionan como "componentes" independientes, a menudo tiene sentido introducir excepciones personalizadas para situaciones de error personalizadas. Por ejemplo, si tiene alguna capa de control de acceso e intenta ejecutar algún servicio aunque no está autorizado para hacerlo, puede lanzar una "excepción de acceso denegado" personalizada o algo así. O si tiene un analizador que analiza algún archivo, es posible que tenga sus propios errores de analizador para informar al usuario.
empuje el
36

Sospecho que la intención con los últimos 2 es evitar la confusión con las excepciones incorporadas que tienen un significado esperado. Sin embargo, soy de la opinión de que si está preservando la intención exacta de la excepción : es la correcta para throw. Por ejemplo, si está escribiendo una colección personalizada, parece completamente razonable usarlo IndexOutOfRangeException: más claro y más específico, en mi opinión, que ArgumentOutOfRangeException. Y aunque List<T>podría elegir lo último, hay al menos 41 lugares (cortesía del reflector) en el BCL (sin incluir los arreglos) que se lanzan a medida IndexOutOfRangeException, ninguno de los cuales es de "nivel bajo" lo suficientemente como para merecer una exención especial. Así que sí, creo que se puede argumentar con justicia que esa directriz es una tontería. Igualmente,NullReferenceException es un poco útil en métodos de extensión, si desea preservar la semántica que:

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

lanza un NullReferenceExceptioncuando objes null.

Marc Gravell
fuente
2
"si está preservando la intención exacta de la excepción", seguramente si ese fuera el caso, la excepción se lanzaría sin que usted tuviera que probarla en primer lugar. Y si ya lo ha probado, ¿no es realmente una excepción?
PugFugly
5
@PugFugly toma 2 segundos para ver el ejemplo del método de extensión: no, eso no se lanzará sin que tengas que probarlo. Si SomeMethod()no necesita realizar el acceso de miembros, es incorrecto forzarlo. Del mismo modo: tome ese punto con los 41 lugares en el BCL que crean personalizado IndexOutOfRangeExceptiony los 16 lugares que crean personalizadoNullReferenceException
Marc Gravell
16
Yo diría que un método de extensión debería lanzar un en ArgumentNullExceptionlugar de un NullReferenceException. Incluso si el azúcar sintáctico de los métodos de extensión permite la misma sintaxis que el acceso a miembros normales, todavía funciona de manera muy diferente. Y obtener una NRE de MyStaticHelpers.SomeMethod(obj)sería simplemente incorrecto.
empuje el
1
@PugFugly BCL es una "biblioteca de clases base", básicamente el material principal en .NET.
empuje el
1
@PugFugly: Hay muchos escenarios en los que no detectar de forma preventiva una condición daría lugar a que se lanzara una excepción en un momento "inconveniente". Si una operación no va a tener éxito, lanzar una excepción temprano es mejor que comenzar la operación, llegar a la mitad y luego tener que limpiar el desorden resultante parcialmente procesado.
supercat
5

Como señala, en el artículo Creando y lanzando excepciones (Guía de programación de C #) bajo el tema Cosas que evitar al lanzar excepciones , Microsoft de hecho enumera System.IndexOutOfRangeExceptioncomo un tipo de excepción que no debe lanzarse intencionalmente desde su propio código fuente.

En contraste, sin embargo, en el artículo throw (Referencia de C #) , Microsoft parece violar sus propias pautas. Aquí hay un método que Microsoft incluyó en su ejemplo:

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

Por lo tanto, Microsoft en sí no está siendo consistente, ya que demuestra el lanzamiento de IndexOutOfRangeExceptionsu documentación para throw!

Esto me lleva a pensar que, al menos para el caso de IndexOutOfRangeException, puede haber ocasiones en las que el tipo de excepción puede ser lanzado por el programador y se considera una práctica aceptable.

DavidRR
fuente
1

Cuando leí su pregunta, me pregunté en qué condiciones uno querría lanzar los tipos de excepción NullReferenceException, InvalidCastExceptiono ArgumentOutOfRangeException.

En mi opinión, cuando me encuentro con uno de esos tipos de excepciones, yo (el desarrollador) me preocupa la advertencia en el sentido de que el compilador me está hablando. Entonces, permitirle a usted (el desarrollador) lanzar tales tipos de excepción es equivalente a (el compilador) vender la responsabilidad. Por ejemplo, esto sugiere que el compilador ahora debería permitir al desarrollador decidir si un objeto es null. Pero hacer tal determinación debería ser realmente el trabajo del compilador.

PD: Desde 2003 he estado desarrollando mis propias excepciones para poder lanzarlas como desee. Creo que se considera una buena práctica hacerlo.

Bellash
fuente
Buenos puntos. Sin embargo, creo que sería más exacto decir que el programador debería permitir que el tiempo de ejecución de .NET Framework arroje este tipo de excepciones (y que el programador debería manejarlas de manera apropiada).
DavidRR
0

Dejando la discusión sobre NullReferenceExceptiony a IndexOutOfBoundsExceptionun lado:

¿Qué hay de atrapar y lanzar System.Exception? He lanzado mucho este tipo de excepción en mi código y nunca me ha fastidiado. De manera similar, muy a menudo capto el Exceptiontipo inespecífico , y también funcionó bastante bien para mí. Entonces, ¿por qué es eso?

Por lo general, los usuarios argumentan que deberían poder distinguir las causas de los errores. Desde mi experiencia, hay muy pocas situaciones en las que le gustaría manejar diferentes tipos de excepciones de manera diferente. Para esos casos, en los que espera que los usuarios manejen los errores mediante programación, debe lanzar un tipo de excepción más específico. Para otros casos, no me convence la guía general de mejores prácticas.

Entonces, con respecto al lanzamiento Exception, no veo una razón para prohibir esto en todos los casos.

EDITAR: también desde la página de MSDN:

No se deben utilizar excepciones para cambiar el flujo de un programa como parte de la ejecución ordinaria. Las excepciones solo deben usarse para informar y manejar condiciones de error.

Exagerar las cláusulas catch con lógica individual para diferentes tipos de excepciones tampoco es una buena práctica.

uebe
fuente
El mensaje de excepción todavía está en su lugar para contar lo que sucedió. No puedo imaginarme que vaya y cree un nuevo tipo de excepción para cada error diferente, en caso de que un consumidor quiera distinguir esos errores, mediante programación.
uebe
¿Qué pasó con mi comentario anterior?
DonBoitnott