He sido ingeniero de software profesional durante aproximadamente un año, después de graduarme con un título de CS. Hace un tiempo que conozco las afirmaciones en C ++ y C, pero no tenía idea de que existían en C # y .NET hasta hace poco.
Nuestro código de producción no contiene afirmaciones de ningún tipo y mi pregunta es esta ...
¿Debo comenzar a usar Asserts en nuestro código de producción? Y si es así, ¿cuándo es su uso más apropiado? ¿Tendría más sentido hacer
Debug.Assert(val != null);
o
if ( val == null )
throw new exception();
language-agnostic
exception
testing
assertions
defensive-programming
Nicholas Mancuso
fuente
fuente
Respuestas:
Al depurar aplicaciones de Microsoft .NET 2.0, John Robbins tiene una gran sección sobre aserciones. Sus puntos principales son:
PD: Si te gustó Code Complete, te recomiendo seguirlo con este libro. Lo compré para aprender a usar WinDBG y volcar archivos, pero la primera mitad está repleta de consejos para ayudar a evitar errores en primer lugar.
fuente
Debug.Assert
vsTrace.Assert
. Este último se ejecuta en una versión de lanzamiento, así como una versión de depuración.Ponga
Debug.Assert()
en todas partes en el código donde desee tener controles de cordura para asegurar invariantes. Cuando compila una versión de lanzamiento (es decir, sinDEBUG
constante del compilador), las llamadas aDebug.Assert()
se eliminarán para que no afecten el rendimiento.Aún debe lanzar excepciones antes de llamar
Debug.Assert()
. La afirmación solo se asegura de que todo esté como se esperaba mientras aún se está desarrollando.fuente
Desde el código completo
fuente
FWIW ... Me parece que mis métodos públicos tienden a usar el
if () { throw; }
patrón para garantizar que el método se llame correctamente. Mis métodos privados tienden a usarDebug.Assert()
.La idea es que con mis métodos privados, yo soy el que está bajo control, así que si empiezo a llamar a uno de mis propios métodos privados con parámetros que son incorrectos, entonces he roto mi propia suposición en alguna parte: nunca debería haberme entendido en ese estado En producción, estas afirmaciones privadas deberían ser idealmente un trabajo innecesario, ya que se supone que debo mantener mi estado interno válido y consistente. Contraste con los parámetros dados a los métodos públicos, que cualquier persona puede invocar en tiempo de ejecución: todavía necesito imponer restricciones de parámetros lanzando excepciones.
Además, mis métodos privados aún pueden arrojar excepciones si algo no funciona en tiempo de ejecución (error de red, error de acceso a datos, datos incorrectos recuperados de un servicio de terceros, etc.). Mis afirmaciones solo están ahí para asegurarme de que no he roto mis propias suposiciones internas sobre el estado del objeto.
fuente
Utilice afirmaciones para verificar los supuestos del desarrollador y excepciones para verificar los supuestos ambientales.
fuente
Si yo fuera tú, haría:
O para evitar la verificación repetida de la condición
fuente
Si desea Asserts en su código de producción (es decir, versiones de lanzamiento), puede usar Trace.Assert en lugar de Debug.Assert.
Por supuesto, esto agrega gastos generales a su ejecutable de producción.
Además, si su aplicación se ejecuta en modo de interfaz de usuario, el cuadro de diálogo Afirmación se mostrará de forma predeterminada, lo que puede ser un poco desconcertante para sus usuarios.
Puede anular este comportamiento quitando DefaultTraceListener: consulte la documentación de Trace.Listeners en MSDN.
En resumen,
Utilice Debug.Assert generosamente para ayudar a detectar errores en las compilaciones de depuración.
Si usa Trace.Assert en modo de interfaz de usuario, probablemente desee eliminar DefaultTraceListener para evitar usuarios desconcertantes.
Si la condición que está probando es algo que su aplicación no puede manejar, probablemente sea mejor lanzar una excepción, para asegurarse de que la ejecución no continúe. Tenga en cuenta que un usuario puede elegir ignorar una aserción.
fuente
Las afirmaciones se utilizan para detectar el error del programador (su), no el error del usuario. Deben usarse solo cuando no hay posibilidad de que un usuario pueda hacer que se active la afirmación. Si está escribiendo una API, por ejemplo, las afirmaciones no deben usarse para verificar que un argumento no sea nulo en ningún método que pueda llamar un usuario de la API. Pero podría usarse en un método privado no expuesto como parte de su API para afirmar que SU código nunca pasa un argumento nulo cuando se supone que no debe hacerlo.
Por lo general, prefiero las excepciones a las afirmaciones cuando no estoy seguro.
fuente
En breve
Asserts
se utilizan para guardias y para verificar restricciones de Diseño por contrato, a saber:Asserts
debe ser solo para las versiones de depuración y no producción. Los compiladores suelen ignorar las afirmaciones en las versiones de lanzamiento.Asserts
puede verificar errores / condiciones inesperadas que ESTÁN en el control de su sistemaAsserts
NO son un mecanismo para la validación de primera línea de la entrada del usuario o las reglas comercialesAsserts
debe no ser utilizado para detectar las condiciones ambientales inesperados (que están fuera del control del código), por ejemplo de la memoria, fallo en la red, la insuficiencia de base de datos, etc. Aunque es raro, estas condiciones son de esperar (y su código de aplicación no puede solucionar problemas como falla de hardware o agotamiento de recursos). Por lo general, se generarán excepciones: su aplicación puede tomar medidas correctivas (por ejemplo, volver a intentar una base de datos o una operación de red, intentar liberar memoria en caché) o abortar con gracia si la excepción no se puede manejar.Asserts
, su código está operando en un territorio inesperado. Los seguimientos de pila y los volcados de memoria se pueden usar para determinar qué salió mal.Las afirmaciones tienen un enorme beneficio:
Debug
compilaciones.... Mas detalle
Debug.Assert
expresa una condición que se ha asumido sobre el estado por el resto del bloque de código dentro del control del programa. Esto puede incluir el estado de los parámetros proporcionados, el estado de los miembros de una instancia de clase o que el retorno de una llamada al método está en su rango contratado / diseñado. Por lo general, las afirmaciones deben bloquear el subproceso / proceso / programa con toda la información necesaria (Stack Trace, Crash Dump, etc.), ya que indican la presencia de un error o una condición no considerada para la que no se ha diseñado (es decir, no intente atrapar o manejar fallas de aserción), con una posible excepción de cuando una aserción en sí misma podría causar más daño que el error (por ejemplo, los controladores de tráfico aéreo no querrían un YSOD cuando un avión se hace submarino, aunque es discutible si se debe implementar una construcción de depuración en producción ...)Cuándo debería usarlo
Asserts?
: en cualquier punto de un sistema, API de biblioteca o servicio donde se supone que las entradas a una función o estado de una clase son válidas (por ejemplo, cuando la validación ya se ha realizado en la entrada del usuario en el nivel de presentación de un sistema , las clases de nivel empresarial y de datos generalmente suponen que ya se han realizado verificaciones nulas, verificaciones de rango, verificaciones de longitud de cadena, etc. en la entrada). - LasAssert
comprobaciones comunes incluyen cuando una suposición inválida resultaría en una desreferencia de objeto nulo, un divisor cero, un desbordamiento aritmético numérico o de fecha, y fuera de banda general / no diseñado para el comportamiento (por ejemplo, si se usó un int de 32 bits para modelar la edad de un humano , sería prudenteAssert
que la edad esté realmente entre 0 y 125 más o menos; los valores de -100 y 10 ^ 10 no fueron diseñados para).Contratos de código .Net
En la pila .Net, los contratos de código se pueden usar además de, o como una alternativa al uso
Debug.Assert
. Los contratos de código pueden formalizar aún más la verificación estatal y pueden ayudar a detectar violaciones de supuestos en el momento de la compilación (o poco después, si se ejecutan como verificación de antecedentes en un IDE).Los controles de diseño por contrato (DBC) disponibles incluyen:
Contract.Requires
- Condiciones previas contratadasContract.Ensures
- Postcondiciones contratadasInvariant
- Expresa una suposición sobre el estado de un objeto en todos los puntos de su vida útil.Contract.Assumes
- pacifica el verificador estático cuando se realiza una llamada a métodos decorados sin contrato.fuente
Sobre todo nunca en mi libro. En la gran mayoría de las ocasiones, si desea verificar si todo está sano, tírelo si no lo está.
Lo que no me gusta es el hecho de que hace que una compilación de depuración sea funcionalmente diferente a una compilación de lanzamiento. Si una afirmación de depuración falla pero la funcionalidad funciona en la versión, ¿cómo tiene sentido eso? Es incluso mejor cuando el afirmador ha dejado la compañía hace mucho tiempo y nadie conoce esa parte del código. Luego tienes que matar algo de tu tiempo explorando el problema para ver si realmente es un problema o no. Si es un problema, ¿por qué la persona no está tirando en primer lugar?
Para mí, esto sugiere usar Debug.Aserta que estás difiriendo el problema a otra persona, trata el problema tú mismo. Si se supone que algo es el caso y no lo es, entonces tirar.
Supongo que posiblemente existan escenarios críticos de rendimiento en los que desee optimizar sus afirmaciones y que sean útiles allí, sin embargo, todavía no he encontrado ese escenario.
fuente
System.Diagnostics.Trace.Assert()
ejecuta en una versión de lanzamiento, así como una versión de depuración.De acuerdo con el estándar IDesign , debe
Como descargo de responsabilidad, debo mencionar que no me ha resultado práctico implementar este IRL. Pero este es su estándar.
fuente
Utilice las aserciones solo en los casos en que desee que se elimine la verificación para las versiones de lanzamiento. Recuerde, sus afirmaciones no se dispararán si no compila en modo de depuración.
Dado su ejemplo de comprobación de nulo, si esto está en una API solo interna, podría usar una aserción. Si está en una API pública, definitivamente usaría el check y throw explícito.
fuente
System.Diagnostics.Trace.Assert()
para ejecutar una aserción en una compilación de lanzamiento (producción).null
cuando: "Un método visible externamente desreferencia uno de sus argumentos de referencia sin verificar si ese argumento es nulo ". En tal situación, el método o propiedad debe arrojarArgumentNullException
.Todas las afirmaciones deben ser código que pueda optimizarse para:
Porque está comprobando algo que ya asumiste que es cierto. P.ej:
En lo anterior, hay tres enfoques diferentes para los parámetros nulos. El primero lo acepta como permitido (simplemente no hace nada). El segundo arroja una excepción para que el código de llamada lo maneje (o no, lo que resulta en un mensaje de error). El tercero asume que posiblemente no puede suceder, y afirma que es así.
En el primer caso, no hay problema.
En el segundo caso, hay un problema con el código de llamada: no debería haber llamado
GetFirstAndConsume
con nulo, por lo que se devuelve una excepción.En el tercer caso, hay un problema con este código, porque ya debería haberse verificado
en != null
antes de que se lo llamara, por lo que no es cierto es un error. O, en otras palabras, debería ser un código que teóricamente podría optimizarseDebug.Assert(true)
, ¡sicneen != null
siempre debería serlotrue
!fuente
en == null
en producción? ¿Posiblemente dices queen == null
eso nunca puede suceder en producción (ya que el programa ha sido completamente depurado)? Si es así,Debug.Assert(en != null)
al menos sirve como alternativa a un comentario. Por supuesto, si se realizan cambios futuros, también sigue teniendo valor para detectar una posible regresión.Debug.Assert()
se eliminan en una versión de lanzamiento. Entonces, si está equivocado, en el tercer caso , no lo sabrá en producción (suponiendo el uso de una versión de lanzamiento en producción). Sin embargo, el comportamiento del primer y segundo caso es idéntico en las compilaciones Debug y Release.Pensé que agregaría cuatro casos más, donde Debug.Assert puede ser la opción correcta.
1) Algo que no he visto mencionado aquí es la cobertura conceptual adicional que los Activos pueden proporcionar durante las pruebas automatizadas . Como un simple ejemplo:
Cuando un autor que modifica un llamador de nivel superior cree que ha ampliado el alcance del código para manejar escenarios adicionales, idealmente (!) Escribirán pruebas unitarias para cubrir esta nueva condición. Entonces puede ser que el código totalmente integrado parece funcionar bien.
Sin embargo, en realidad se introdujo un defecto sutil, pero no se detectó en los resultados de la prueba. El destinatario de la llamada se ha convertido no determinista en este caso, y sólo ocurre para proporcionar el resultado esperado. O tal vez ha arrojado un error de redondeo que pasó desapercibido. O provocó un error que fue compensado igualmente en otros lugares. O no solo se le otorga el acceso solicitado, sino también privilegios adicionales que no se deben otorgar. Etc.
En este punto, las declaraciones Debug.Assert () contenidas en el destinatario junto con el nuevo caso (o caso límite) conducido por pruebas unitarias pueden proporcionar una notificación invaluable durante la prueba de que los supuestos del autor original han sido invalidados, y el código no debe ser lanzado sin revisión adicional. Las afirmaciones con pruebas unitarias son los socios perfectos.
2) Además, algunas pruebas son simples de escribir, pero de alto costo e innecesarias dados los supuestos iniciales . Por ejemplo:
Si solo se puede acceder a un objeto desde un determinado punto de entrada seguro, ¿se debe realizar una consulta adicional a una base de datos de derechos de red desde cada método de objeto para garantizar que la persona que llama tenga permisos? Seguramente no. Quizás la solución ideal incluye el almacenamiento en caché o alguna otra expansión de características, pero el diseño no lo requiere. Debug.Assert () mostrará inmediatamente cuando el objeto se haya adjuntado a un punto de entrada inseguro.
3) Luego, en algunos casos, su producto puede no tener una interacción de diagnóstico útil para todas o parte de sus operaciones cuando se implementa en modo de lanzamiento . Por ejemplo:
Supongamos que es un dispositivo incorporado en tiempo real. Lanzar excepciones y reiniciar cuando encuentra un paquete mal formado es contraproducente. En cambio, el dispositivo puede beneficiarse de la operación de mejor esfuerzo, incluso hasta el punto de generar ruido en su salida. Es posible que tampoco tenga una interfaz humana, un dispositivo de registro o incluso que un humano pueda acceder físicamente cuando se implementa en modo de liberación, y la mejor manera de obtener conocimiento de los errores es evaluando la misma salida. En este caso, las afirmaciones liberales y las pruebas exhaustivas previas al lanzamiento son más valiosas que las excepciones.
4) Por último, algunas pruebas son innecesarias solo porque la persona que llama se percibe como extremadamente confiable . En la mayoría de los casos, cuanto más reutilizable es el código, más esfuerzo se ha hecho para hacerlo confiable. Por lo tanto, es común a Exception para parámetros inesperados de las personas que llaman, pero Assert para resultados inesperados de personas que llaman. Por ejemplo:
Si una
String.Find
operación principal indica que devolverá un-1
cuando no se encuentran los criterios de búsqueda, es posible que pueda realizar una operación de forma segura en lugar de tres. Sin embargo, si realmente regresó-2
, es posible que no tenga un curso de acción razonable. Sería inútil reemplazar el cálculo más simple con uno que pruebe un-1
valor por separado , y que no sea razonable en la mayoría de los entornos de lanzamiento para ensuciar su código con pruebas que garanticen que las bibliotecas principales funcionen como se espera. En este caso, las afirmaciones son ideales.fuente
Cita tomada del programador pragmático: de oficial a maestro
fuente
Siempre debe usar el segundo enfoque (lanzando excepciones).
Además, si está en producción (y tiene una versión de compilación), es mejor lanzar una excepción (y dejar que la aplicación se bloquee en el peor de los casos) que trabajar con valores no válidos y tal vez destruir los datos de su cliente (lo que puede costar miles de dólares).
fuente
Debe usar Debug.Assert para probar errores lógicos en sus programas. El cumplidor solo puede informarle de errores de sintaxis. Por lo tanto, definitivamente debe usar declaraciones de afirmación para probar errores lógicos. Como por ejemplo, probar un programa que vende autos que solo los BMW que son azules deberían obtener un descuento del 15%. El cumplidor no podría decirle nada acerca de si su programa es lógicamente correcto al realizar esto, pero una declaración de afirmación podría hacerlo.
fuente
He leído las respuestas aquí y pensé que debería agregar una distinción importante. Hay dos formas muy diferentes en las que se utilizan afirmaciones. Uno es como un acceso directo de desarrollador temporal para "Esto realmente no debería suceder, así que si me avisa para que pueda decidir qué hacer", algo así como un punto de interrupción condicional, para los casos en los que su programa puede continuar. El otro, es una forma de poner suposiciones sobre estados de programa válidos en su código.
En el primer caso, las afirmaciones ni siquiera necesitan estar en el código final. Debe usarlo
Debug.Assert
durante el desarrollo y puede eliminarlo si / cuando ya no sea necesario. Si desea dejarlos o si olvida eliminarlos, no hay problema, ya que no tendrán ninguna consecuencia en las compilaciones de lanzamiento.Pero en el segundo caso, las afirmaciones son parte del código. Ellos, bueno, afirman que sus suposiciones son verdaderas, y también las documentan. En ese caso, realmente quieres dejarlos en el código. Si el programa está en un estado no válido, no se debe permitir que continúe. Si no pudiera permitirse el éxito en el rendimiento, no estaría usando C #. Por un lado, puede ser útil poder adjuntar un depurador si sucede. Por otro lado, no desea que el seguimiento de la pila aparezca en sus usuarios y quizás lo más importante es que no desea que puedan ignorarlo. Además, si está en un servicio, siempre se ignorará. Por lo tanto, en producción, el comportamiento correcto sería lanzar una Excepción y usar el manejo normal de excepciones de su programa, lo que podría mostrarle al usuario un buen mensaje y registrar los detalles.
Trace.Assert
tiene la manera perfecta de lograr esto. No se eliminará en producción y se puede configurar con diferentes oyentes usando app.config. Entonces, para el desarrollo, el controlador predeterminado está bien, y para la producción puede crear un TraceListener simple como el siguiente, que arroja una excepción y lo activa en el archivo de configuración de producción.Y en el archivo de configuración de producción:
fuente
No sé cómo es en C # y .NET, pero en C afirmará que () solo funcionará si se compila con -DDEBUG; el usuario final nunca verá una afirmación () si se compila sin él. Es solo para desarrolladores. Lo uso con mucha frecuencia, a veces es más fácil rastrear errores.
fuente
No los usaría en el código de producción. Lanza excepciones, atrapa y registra.
También debe tener cuidado en asp.net, ya que una afirmación puede aparecer en la consola y congelar las solicitudes.
fuente