exception design assert design-by-contract

exception - ¿Diseña por contrato usando aserciones o excepciones?



design assert (14)

Deberías usar ambos. Las afirmaciones son para su conveniencia como desarrollador. Las excepciones detectan cosas que se perdieron o no esperaban durante el tiempo de ejecución.

Me he encariñado con las funciones de informe de errores de glib en lugar de antiguas aseveraciones simples. Se comportan como afirmaciones afirmativas, pero en lugar de detener el programa, simplemente devuelven un valor y permiten que el programa continúe. Funciona sorprendentemente bien, y como bonificación puede ver lo que ocurre con el resto de su programa cuando una función no devuelve "lo que se supone que debe". Si se bloquea, sabrá que su comprobación de errores es laxa en algún otro lugar en el futuro.

En mi último proyecto, utilicé este estilo de funciones para implementar la verificación de condiciones previas, y si una de ellas falla, imprimiría un seguimiento de pila en el archivo de registro pero seguiría funcionando. Me ahorró muchísimo tiempo de depuración cuando otras personas encontraban un problema al ejecutar mi compilación de depuración.

#ifdef DEBUG #define RETURN_IF_FAIL(expr) do { / if (!(expr)) / { / fprintf(stderr, / "file %s: line %d (%s): precondition `%s'' failed.", / __FILE__, / __LINE__, / __PRETTY_FUNCTION__, / #expr); / ::print_stack_trace(2); / return; / }; } while(0) #define RETURN_VAL_IF_FAIL(expr, val) do { / if (!(expr)) / { / fprintf(stderr, / "file %s: line %d (%s): precondition `%s'' failed.", / __FILE__, / __LINE__, / __PRETTY_FUNCTION__, / #expr); / ::print_stack_trace(2); / return val; / }; } while(0) #else #define RETURN_IF_FAIL(expr) #define RETURN_VAL_IF_FAIL(expr, val) #endif

Si necesitaba verificar los argumentos en tiempo de ejecución, haría esto:

char *doSomething(char *ptr) { RETURN_VAL_IF_FAIL(ptr != NULL, NULL); // same as assert(ptr != NULL), but returns NULL if it fails. // Goes away when debug off. if( ptr != NULL ) { ... } return ptr; }

Cuando se programa por contrato, una función o método primero verifica si se cumplen sus condiciones previas, antes de comenzar a trabajar en sus responsabilidades, ¿verdad? Las dos formas más destacadas de hacer estos controles son por assert y por exception .

  1. assert solo falla en el modo de depuración. Para asegurarse de que es crucial para (unidad) probar todas las condiciones previas del contrato por separado para ver si realmente fallan.
  2. la excepción falla en el modo de depuración y liberación. Esto tiene la ventaja de que el comportamiento de depuración probado es idéntico al comportamiento de liberación, pero incurre en una penalización de rendimiento en el tiempo de ejecución.

¿Cuál crees que es preferible?

Vea la pregunta releated here


El principio que sigo es el siguiente: si una situación puede evitarse de manera realista codificando, entonces use una afirmación. De lo contrario, use una excepción.

Las afirmaciones son para garantizar que se cumpla el Contrato. El contrato debe ser justo, por lo que el cliente debe estar en condiciones de garantizar que cumpla. Por ejemplo, puede indicar en un contrato que una URL debe ser válida porque las reglas sobre lo que es y lo que no es una URL válida son conocidas y consistentes.

Las excepciones son para situaciones que están fuera del control tanto del cliente como del servidor. Una excepción significa que algo salió mal y no se pudo haber hecho nada para evitarlo. Por ejemplo, la conectividad de red está fuera del control de las aplicaciones, por lo que no se puede hacer nada para evitar un error de red.

Me gustaría agregar que la distinción Aserción / Excepción no es realmente la mejor manera de pensar sobre eso. En lo que realmente quieres pensar es en el contrato y cómo se puede aplicar. En mi ejemplo de URL anterior, lo mejor que puedo hacer es tener una clase que encapsule una URL y que sea nula o una URL válida. Es la conversión de una cadena en una URL que hace cumplir el contrato, y se lanza una excepción si no es válida. Un método con un parámetro de URL es mucho más claro que un método con un parámetro de cadena y una aserción que especifica una URL.


Esbocé mi punto de vista sobre el estado del asunto aquí: ¿cómo se valida el estado interno de un objeto? . En general, haga valer sus reclamos y arroje la violación por otros. Para desactivar aseveraciones en compilaciones de lanzamiento, puede hacer:

  • Inhabilitar afirmaciones para cheques costosos (como comprobar si se ordena un rango)
  • Mantenga las comprobaciones triviales habilitadas (como buscar un puntero nulo o un valor booleano)

Por supuesto, en las versiones de lanzamiento, las aserciones fallidas y las excepciones no detectadas deberían manejarse de otra manera que en las compilaciones de depuración (donde simplemente podría llamar a std :: abortar). Escriba un registro del error en algún lugar (posiblemente en un archivo), informe al cliente que ocurrió un error interno. El cliente podrá enviarle el archivo de registro.


Hubo un thread enorme sobre la habilitación / inhabilitación de aserciones en compilaciones de lanzamiento en comp.lang.c ++. Moderado, que si tiene algunas semanas puede ver qué variadas son las opiniones al respecto. :)

Contrariamente a la coppro , creo que si no está seguro de que una afirmación se pueda desactivar en una compilación de lanzamiento, entonces no debería haber sido una afirmación. Las afirmaciones son para proteger contra las invariantes del programa que se rompen. En tal caso, en lo que respecta al cliente de su código, habrá uno de dos posibles resultados:

  1. Morir con algún tipo de falla del tipo de sistema operativo, lo que resulta en una llamada para abortar. (Sin afirmar)
  2. Morir a través de una llamada directa para abortar. (Con assert)

No hay diferencia para el usuario, sin embargo, es posible que las aserciones agreguen un costo de rendimiento innecesario en el código que está presente en la gran mayoría de las ejecuciones donde el código no falla.

La respuesta a la pregunta en realidad depende mucho más de quiénes serán los clientes de la API. Si está escribiendo una biblioteca que proporciona una API, entonces necesita algún tipo de mecanismo para notificar a sus clientes que han utilizado la API de forma incorrecta. A menos que proporcione dos versiones de la biblioteca (una con afirmaciones y otra sin), entonces afirmar es muy poco probable que sea la elección adecuada.

Personalmente, sin embargo, tampoco estoy seguro de que acepte excepciones para este caso. Las excepciones son más adecuadas para que pueda tener lugar una forma adecuada de recuperación. Por ejemplo, puede ser que estés tratando de asignar memoria. Cuando detecta una excepción ''std :: bad_alloc'', es posible liberar memoria e intentar nuevamente.


Inhabilitar la afirmación en compilaciones de lanzamiento es como decir "Nunca tendré problemas en absoluto en una compilación de lanzamiento", que a menudo no es el caso. Por lo tanto, assert no se debe deshabilitar en una compilación de lanzamiento. Pero no querrás que la versión de lanzamiento falle cada vez que se produzcan errores, ¿verdad?

Entonces usa excepciones y úsalos bien. Use una buena y sólida jerarquía de excepciones y asegúrese de atrapar y puede ponerle un gancho a la excepción lanzada en su depurador para atraparla, y en el modo de lanzamiento puede compensar el error en lugar de un choque directo. Es la forma más segura de ir.


La regla de oro es que debe usar las aserciones cuando intenta detectar sus propios errores y las excepciones cuando intenta detectar los errores de otras personas. En otras palabras, debe usar excepciones para verificar las condiciones previas para las funciones API públicas, y cada vez que obtenga datos que sean externos a su sistema. Debe usar afirmaciones para las funciones o datos que son internos a su sistema.


La regla de oro para mí es que use expresiones de afirmación para encontrar errores internos y excepciones para errores externos. Puede beneficiarse mucho de la siguiente discusión de Greg desde here .

Las expresiones de aserción se usan para encontrar errores de programación: errores en la lógica del programa mismo o errores en su implementación correspondiente. Una condición de afirmación verifica que el programa permanece en un estado definido. Un "estado definido" es básicamente uno que está de acuerdo con las suposiciones del programa. Tenga en cuenta que un "estado definido" para un programa no necesita ser un "estado ideal" o incluso "un estado habitual", o incluso un "estado útil", sino más sobre ese punto importante más adelante.

Para comprender cómo las aserciones se ajustan a un programa, considere una rutina en un programa de C ++ que está por desreferenciar un puntero. Ahora, ¿debería la rutina probar si el puntero es NULL antes de la desreferenciación, o debería afirmar que el puntero no es NULL y luego continuar y eliminar la referencia independientemente?

Me imagino que la mayoría de los desarrolladores querrían hacer ambas cosas, agregar la afirmación, pero también verificar el puntero para obtener un valor NULO, para no bloquearse si la condición afirmada falla. En la superficie, realizar la prueba y el control puede parecer la decisión más sabia

A diferencia de sus condiciones afirmadas, el manejo de errores (excepciones) de un programa no se refiere a errores en el programa, sino a las entradas que el programa obtiene de su entorno. A menudo, estos son "errores" por parte de alguien, como un usuario que intenta ingresar a una cuenta sin ingresar una contraseña. Y a pesar de que el error puede evitar una finalización exitosa de la tarea del programa, no hay falla del programa. El programa no puede iniciar sesión en el usuario sin una contraseña debido a un error externo, un error por parte del usuario. Si las circunstancias eran diferentes, y el usuario ingresaba la contraseña correcta y el programa no la reconocía; luego, aunque el resultado seguiría siendo el mismo, el fracaso ahora pertenecería al programa.

El objetivo del manejo de errores (excepciones) es doble. El primero es comunicarle al usuario (u otro cliente) que se ha detectado un error en la entrada del programa y lo que significa. El segundo objetivo es restaurar la aplicación después de que se detecta el error, a un estado bien definido. Tenga en cuenta que el programa en sí no tiene errores en esta situación. De acuerdo, el programa puede estar en un estado no ideal, o incluso un estado en el que no puede hacer nada útil, pero no hay ningún error de programación. Por el contrario, dado que el estado de recuperación de errores es uno anticipado por el diseño del programa, es uno que el programa puede manejar.

PD: es posible que desee verificar la pregunta similar: Excepción Vs Afirmación .


Las afirmaciones son para atrapar algo que un desarrollador ha hecho mal (no solo usted mismo, otro desarrollador en su equipo también). Si es razonable que un error del usuario pueda crear esta condición, entonces debería ser una excepción.

Del mismo modo pensar en las consecuencias. Una afirmación generalmente apaga la aplicación. Si hay alguna expectativa realista de que la condición pueda recuperarse, probablemente deba usar una excepción.

Por otro lado, si el problema solo puede deberse a un error del programador, utilice una afirmación, ya que desea saberlo lo antes posible. Una excepción podría ser capturada y manejada, y nunca se enteraría al respecto. Y sí, debe desactivar las afirmaciones en el código de lanzamiento porque allí quiere que la aplicación se recupere si existe la menor posibilidad de que ocurra. Incluso si el estado de su programa se rompe profundamente, es posible que el usuario pueda guardar su trabajo.


Las respuestas anteriores son correctas: use excepciones para funciones API públicas. El único momento en que puede querer doblar esta regla es cuando el cheque es computacionalmente costoso. En ese caso, puedes ponerlo en una afirmación.

Si cree que es probable que se produzca una violación de esa condición previa, consérvela como una excepción o reordene la precondición.


No es exactamente cierto que "assert solo falla en el modo de depuración".

En Construcción de software orientado a objetos, segunda edición de Bertrand Meyer, el autor deja una puerta abierta para comprobar las condiciones previas en el modo de lanzamiento. En ese caso, lo que sucede cuando una afirmación falla es que ... ¡se lanza una excepción de violación de afirmación! En este caso, no hay recuperación de la situación: algo útil podría hacerse, y es generar automáticamente un informe de error y, en algunos casos, reiniciar la aplicación.

La motivación detrás de esto es que las precondiciones son típicamente más baratas de probar que las invariantes y las postcondiciones, y que en algunos casos la corrección y la "seguridad" en la compilación de lanzamiento son más importantes que la velocidad. es decir, para muchas aplicaciones, la velocidad no es un problema, pero la solidez (la capacidad del programa para comportarse de manera segura cuando su comportamiento no es correcto, es decir, cuando se rompe un contrato) sí lo es.

¿Deberías siempre dejar las verificaciones de precondición habilitadas? Depende. Tu decides. No hay una respuesta universal. Si está creando software para un banco, sería mejor interrumpir la ejecución con un mensaje alarmante que transferir $ 1,000,000 en lugar de $ 1,000. Pero, ¿y si estás programando un juego? Tal vez necesites toda la velocidad que puedas obtener, y si alguien obtiene 1000 puntos en lugar de 10 debido a un error que las condiciones previas no captaron (porque no están habilitadas), mala suerte.

En ambos casos, idealmente debería haber atrapado ese error durante la prueba, y debería hacer una parte importante de su prueba con las aserciones habilitadas. Lo que se discute aquí es cuál es la mejor política para aquellos casos excepcionales en los que las condiciones previas fallan en el código de producción en un escenario que no se detectó antes debido a pruebas incompletas.

Para resumir, puede tener aserciones y obtener las excepciones automáticamente , si las deja activadas, al menos en Eiffel. Creo que para hacer lo mismo en C ++ debes escribirlo tú mismo.

Ver también: ¿ Cuándo deberían permanecer las afirmaciones en el código de producción?


Traté de sintetizar varias de las otras respuestas aquí con mis propios puntos de vista.

Use aserciones para los casos en que desee desactivarlo en producción, errando al dejarlos. La única razón real para desactivar en producción, pero no en desarrollo, es acelerar el programa. En la mayoría de los casos, esta aceleración no será significativa, pero a veces el código es crítico en el tiempo o la prueba es computacionalmente costosa. Si el código es crítico para la misión, entonces las excepciones pueden ser mejores a pesar de la desaceleración.

Si hay alguna posibilidad real de recuperación, use una excepción ya que las aserciones no están diseñadas para ser recuperadas. Por ejemplo, el código rara vez está diseñado para recuperarse de errores de programación, pero está diseñado para recuperarse de factores como fallas de red o archivos bloqueados. Los errores no deben manejarse como excepciones simplemente por estar fuera del control del programador. Por el contrario, la previsibilidad de estos errores, en comparación con los errores de codificación, los hace más amables con la recuperación.

Re argumento de que es más fácil depurar las aserciones: el seguimiento de la pila de una excepción con nombre propio es tan fácil de leer como una aserción. El código bueno solo debe detectar tipos específicos de excepciones, por lo que las excepciones no deben pasar desapercibidas debido a que son atrapadas. Sin embargo, creo que Java a veces te obliga a atrapar todas las excepciones.


Ver también esta pregunta :

En algunos casos, las afirmaciones están deshabilitadas cuando se crea para el lanzamiento. Es posible que no tenga control sobre esto (de lo contrario, podría construir con afirmaciones), por lo que podría ser una buena idea hacerlo así.

El problema con "corregir" los valores de entrada es que la persona que llama no obtendrá lo que espera, y esto puede provocar problemas o incluso bloqueos en partes completamente diferentes del programa, haciendo que la depuración sea una pesadilla.

Normalmente lanzo una excepción en el enunciado if para encargarme del rol del assert en caso de que estén deshabilitados

assert(value>0); if(value<=0) throw new ArgumentOutOfRangeException("value"); //do stuff


Yo prefiero el segundo. Si bien es posible que sus pruebas hayan sido correctas, Murphy dice que algo inesperado saldrá mal. Entonces, en lugar de obtener una excepción en la llamada al método erróneo real, terminas rastreando una NullPointerException (o equivalente) 10 cuadros más profundos.


está preguntando sobre la diferencia entre el tiempo de diseño y los errores de tiempo de ejecución.

Las afirmaciones son notificaciones de "hey programmer, this is broken", están ahí para recordarte los errores que no habrías notado cuando sucedieron.

las excepciones son las notificaciones de "¡Oiga, algunas cosas salieron mal!" (obviamente, puede codificarlas para que el usuario nunca se entere), pero están diseñadas para que se ejecuten en el momento de la ejecución cuando el usuario Joe está usando la aplicación.

Por lo tanto, si cree que puede obtener todos sus errores, use excepciones solamente. Si crees que no puedes ... usa excepciones. Todavía puede usar afirmaciones de depuración para disminuir el número de excepciones, por supuesto.

No olvide que muchas de las condiciones previas serán datos proporcionados por el usuario, por lo que necesitará una buena forma de informar al usuario que sus datos no eran buenos. Para hacerlo, a menudo tendrá que devolver los datos de error en la pila de llamadas a los bits con los que está interactuando. Asserts no será útil entonces, doblemente si tu aplicación es n-tier.

Por último, no utilizaría ninguno de los dos: los códigos de error son muy superiores para los errores que usted cree que ocurrirán regularmente. :)