¿Cómo evitar lanzar excepciones molestas?

21

Leer el artículo de Eric Lippert sobre excepciones fue definitivamente una revelación sobre cómo debería abordar las excepciones, tanto como productor como como consumidor. Sin embargo, todavía estoy luchando por definir una directriz sobre cómo evitar lanzar excepciones molestas.

Específicamente:

  • Suponga que tiene un método Save que puede fallar porque a) Alguien más modificó el registro antes que usted , ob) El valor que está intentando crear ya existe . Estas condiciones son de esperar y no excepcionales, por lo que en lugar de lanzar una excepción, decide crear una versión de prueba de su método, TrySave, que devuelve un valor booleano que indica si la operación de salvar se realizó correctamente. Pero si falla, ¿cómo sabrá el consumidor cuál fue el problema? ¿O sería mejor devolver una enumeración que indique el resultado, como Ok / RecordAlreadyModified / ValueAlreadyExists? Con integer. TryParse, este problema no existe, ya que solo hay una razón por la que el método puede fallar.
  • ¿Es el ejemplo anterior realmente una situación irritante? ¿O lanzar una excepción en este caso sería la forma preferida? Sé que así se hace en la mayoría de las bibliotecas y marcos, incluido el marco Entity.
  • ¿Cómo decide cuándo crear una versión de prueba de su método en lugar de proporcionar alguna forma de probar de antemano si el método funcionará o no? Actualmente estoy siguiendo estas pautas:
    • Si existe la posibilidad de una condición de carrera, cree una versión de prueba. Esto evita la necesidad de que el consumidor atrape una excepción exógena. Por ejemplo, en el método Guardar descrito anteriormente.
    • Si el método para probar la condición prácticamente haría todo lo que hace el método original, cree una versión de prueba. Por ejemplo, integer. TryParse ().
    • En cualquier otro caso, cree un método para probar la condición.
Micro
fuente
1
Su ejemplo de una salvación que puede fallar no es realmente una excepción terriblemente molesta. Es bastante común y probablemente debería ser simplemente una excepción.
S.Lott
@ S.Lott: ¿Qué quieres decir con que es bastante normal? La situación en sí, o lanzando una excepción en esta situación? De todos modos, estoy de acuerdo con usted en que no es evidente si esto es realmente una situación irritante. Actualizaré la pregunta.
Mike
"La situación en sí, o lanzando una excepción en esta situación" Ambos.
S.Lott

Respuestas:

24

Suponga que tiene un método Save que puede fallar porque a) Alguien más modificó el registro antes que usted, ob) El valor que está tratando de crear ya existe. Estas condiciones son de esperar y no excepcionales, por lo que en lugar de lanzar una excepción, decide crear una versión de prueba de su método, TrySave, que devuelve un valor booleano que indica si la operación de salvar se realizó correctamente. Pero si falla, ¿cómo sabrá el consumidor cuál fue el problema?

Buena pregunta.

La primera pregunta que me viene a la mente es: si los datos ya están allí, ¿en qué sentido falló el guardado ? Seguro que suena como si me hubiera sucedido. Pero supongamos, en aras del argumento, que realmente tiene muchas razones diferentes por las que una operación puede fallar.

La segunda pregunta que me viene a la mente es: ¿ es accionable la información que desea devolver al usuario ? Es decir, ¿van a tomar alguna decisión basándose en esa información?

Cuando se enciende la luz de "revisar motor", abro el capó, verifico que hay un motor en mi auto que no está en llamas y lo llevo al garaje. Por supuesto, en el garaje tienen todo tipo de equipos de diagnóstico para propósitos especiales que les dicen por qué la luz de verificación del motor está encendida, pero desde mi perspectiva, el sistema de advertencia está bien diseñado. No me importa si el problema se debe a que el sensor de oxígeno está registrando un nivel anormal de oxígeno en la cámara de combustión, o porque el detector de velocidad de ralentí está desconectado, o lo que sea. Voy a tomar la misma acción, es decir, dejar que alguien más resuelva esto .

¿A la persona que llama le importa por qué falló el guardado? ¿Van a hacer algo al respecto, aparte de rendirse o volver a intentarlo?

Supongamos, en aras del argumento, que la persona que llama realmente va a tomar diferentes acciones dependiendo de la razón por la cual la operación falló.

La tercera pregunta que viene a la mente es: ¿ es excepcional el modo de falla ? Creo que puede ser confuso posible con excepcional . Pensaría en dos usuarios que intentan modificar el mismo registro al mismo tiempo que una situación excepcional pero posible, no una situación común.

Supongamos, por el bien del argumento, que no es excepcional.

La cuarta pregunta que viene a la mente es: ¿hay alguna manera de detectar de manera confiable la mala situación antes de tiempo?

Si la mala situación está en mi cubo "exógeno", entonces no. No hay forma de decir de manera confiable "¿otro usuario modificó este registro?" porque podrían modificarlo después de hacer la pregunta . La respuesta es obsoleta tan pronto como se produce.

La quinta pregunta que viene a la mente es: ¿hay alguna forma de diseñar la API para evitar la mala situación?

Por ejemplo, puede hacer que la operación "guardar" requiera dos pasos. Paso uno: adquiera un bloqueo en el registro que se está modificando. Esa operación tiene éxito o falla y, por lo tanto, puede devolver un booleano. La persona que llama puede tener una política sobre cómo lidiar con el fracaso: espere un momento e intente nuevamente, renunciar, lo que sea. Paso dos: una vez que se adquiere el bloqueo, guarde y suelte el bloqueo. Ahora el guardado siempre tiene éxito, por lo que no hay necesidad de preocuparse por ningún tipo de manejo de errores. Si el guardado falla, eso es realmente excepcional.

Eric Lippert
fuente
Todos muy buenos puntos, gracias. Ahora, aquí hay una pregunta retórica que probablemente resume mi publicación: Si rediseñara File.Open (), ¿crearía un File. TryOpen () en su lugar? ¿Cómo le comunicaría al consumidor la razón del fracaso? ¿O lanzar una excepción exógena es realmente el mejor compromiso aquí?
Mike
10
@ Mike: los sistemas de archivos son un buen ejemplo del uso de excepciones exógenas. Fallan raramente, por lo que el fracaso es excepcional. Fallan de manera impredecible y por razones completamente fuera del control de la persona que llama (no hay un "bloqueo" que pueda tomar que mantenga el cable de Ethernet enchufado), y las fallas son diversas y procesables (es decir, una falla porque el archivo es no se encuentra el archivo vs se encuentra pero no tiene acceso de escritura, ambos son accionables de diferentes maneras). Todas estas son razones para representar una falla como una excepción.
Eric Lippert
Considero que la pregunta debe ser respondida ahora, pero tengo curiosidad;) Si el método puede fallar por 2 o más razones, las fallas son accionables, las fallas no son excepcionales y las fallas no pueden detectarse con anticipación ni prevenirse, ¿qué harías?
Mike
@ Mike, no puedo hablar por Eric, pero eso parece un buen lugar para los códigos de error. Devolver un miembro de una enumeración, tal vez.
Matthew leyó el
1

En su ejemplo, si la situación de ValueAlreadyExists puede verificarse fácilmente, debería verificarse y podría generarse una excepción antes de intentar Guardar, no creo que sea necesario un Try en esta situación. La condición de la carrera es más difícil de verificar con anticipación, por lo que, en este caso, envolver el Guardar en un intento es probablemente una muy buena idea.

En general, si hay una condición que creo que es muy probable (como NoDataReturned, DivideByZero, etc.) O es muy fácil de verificar (como una colección vacía o un valor NULL), trato de verificar antes de llegar al punto en el que tendría que atrapar una excepción. Admito que no siempre es fácil conocer estas condiciones con anticipación, a veces solo aparecen cuando el código está bajo pruebas rigurosas.

FrustratedWithFormsDesigner
fuente
0

El save()método debe arrojar una excepción.

La capa superior debe atrapar e informar al usuario , sin terminar el programa, a menos que sea un Unix como los programas de línea de comandos, en cuyo caso está bien terminar.

Los valores de retorno no son una buena forma de gestionar las excepciones.

Tulains Córdova
fuente