¿Cuándo debo usar Debug.Assert ()?

220

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();
Nicholas Mancuso
fuente
2
La dicotomía que configura es la pista. No se trata de, o para excepciones y afirmaciones, es de ambos, y para el código defensivo. Cuándo hacer qué es lo que estás buscando entender.
Casper Leon Nielsen
55
Una vez leí que alguien sugirió que una excepción u otro método de bloqueo es apropiado para condiciones donde "No hay forma de que pueda recuperarme sensiblemente de esto", y además una afirmación es apropiada para condiciones donde "Esto nunca debería suceder, nunca". Pero, ¿qué circunstancias realistas satisfacen las últimas condiciones sin también satisfacer las primeras? Viniendo de un fondo de Python donde las afirmaciones permanecen en producción, nunca he entendido el enfoque Java / C # de desactivar parte de su validación en producción. El único caso que realmente puedo ver es si la validación es costosa.
Mark Amery
2
Personalmente, uso excepciones para métodos públicos y afirmaciones para métodos privados.
Fred

Respuestas:

230

Al depurar aplicaciones de Microsoft .NET 2.0, John Robbins tiene una gran sección sobre aserciones. Sus puntos principales son:

  1. Afirmar generosamente. Nunca puedes tener demasiadas afirmaciones.
  2. Las afirmaciones no reemplazan las excepciones. Las excepciones cubren las cosas que exige su código; Las afirmaciones cubren las cosas que asume.
  3. Una afirmación bien escrita puede decirle no solo qué sucedió y dónde (como una excepción), sino también por qué.
  4. Un mensaje de excepción a menudo puede ser críptico, lo que requiere que trabaje hacia atrás a través del código para recrear el contexto que causó el error. Una aserción puede preservar el estado del programa en el momento en que ocurrió el error.
  5. Las afirmaciones funcionan como documentación, indicando a otros desarrolladores de qué suposiciones implícitas depende su código.
  6. El cuadro de diálogo que aparece cuando falla una aserción le permite adjuntar un depurador al proceso, por lo que puede hurgar en la pila como si hubiera puesto un punto de interrupción allí.

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.

Rory MacLeod
fuente
3
+1 para el resumen conciso y útil. Muy directamente aplicable. Sin embargo, lo que más me falta es cuándo usar Trace.Assert vs. Trace.Assert. Es decir, cuando quieres o no los quieres en tu código de producción.
Jon Coombs
2
JonCoombs es "Trace.Assert vs. Trace.Assert" un error tipográfico?
thelem
1
Tal vez @thelem Jon significaba Debug.Assertvs Trace.Assert. Este último se ejecuta en una versión de lanzamiento, así como una versión de depuración.
DavidRR
¿Por qué debería preferir Debug.Assert a lanzar una excepción?
Barış Akkurt
86

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, sin DEBUGconstante del compilador), las llamadas a Debug.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.

Mark Cidade
fuente
35
¿Podría aclarar por qué hacer una afirmación si aún arroja una excepción antes de llamarla? ¿O entendí mal tu respuesta?
Roman Starkov
2
@romkyns Aún debe incluirlos porque si no lo hace, cuando construya su proyecto en modo Release , todas las validaciones / verificación de errores desaparecerán.
Oscar Mederos
28
@Oscar Pensé que ese era el objetivo de usar aserciones en primer lugar ... Bien, entonces, pones las excepciones antes que ellas, entonces ¿por qué poner las aserciones después?
Roman Starkov
44
@superjos: Tengo que estar en desacuerdo: el punto n. ° 2 de la respuesta de MacLeod dice que realmente necesitas afirmación Y excepciones, pero no en el mismo lugar. Es inútil lanzar un NullRefEx en una variable y justo después de hacer un Assert en él (el método de aserción nunca mostrará un cuadro de diálogo en este caso, que es el punto completo de aserción). Lo que MacLeod quiere decir es que en algunos lugares necesitarás una excepción, en otros un Assert será suficiente.
David
1
Puede llegar a ser complicado para interpretar mi interpretación de otra persona respuesta :) De todos modos yo con usted en las siguientes: se necesita tanto de ellos, y se debe no poner la excepción antes de la aserción. No estoy seguro sobre el significado de "no en el mismo lugar". Nuevamente, negándome a interpretar, solo expresaré mis pensamientos / preferencias: ponga una o más afirmaciones para verificar las condiciones previas antes de que comience alguna operación, o para verificar las condiciones posteriores después de la operación. Además de las afirmaciones, y después de ellas de todos modos, verifique si algo sale mal y necesita lanzar excepciones.
superjos
52

Desde el código completo

8 Programación defensiva

8.2 Afirmaciones

Una aserción es un código que se usa durante el desarrollo, generalmente una rutina o macro, que permite que un programa se verifique a sí mismo mientras se ejecuta. Cuando una afirmación es verdadera, eso significa que todo está funcionando como se esperaba. Cuando es falso, eso significa que ha detectado un error inesperado en el código. Por ejemplo, si el sistema supone que un archivo de información del cliente nunca tendrá más de 50,000 registros, el programa podría contener una afirmación de que el número de registros es menor o igual a 50,000. Mientras el número de registros sea menor o igual a 50,000, la afirmación será silenciosa. Sin embargo, si encuentra más de 50,000 registros, "afirmará" en voz alta que hay un error en el programa.

Las afirmaciones son especialmente útiles en programas grandes y complicados y en programas de alta confiabilidad. Permiten a los programadores eliminar más rápidamente los supuestos de la interfaz que no coinciden, los errores que aparecen cuando se modifica el código, etc.

Una aserción generalmente toma dos argumentos: una expresión booleana que describe la suposición que se supone que es verdadera y un mensaje para mostrar si no lo es.

(...)

Normalmente, no desea que los usuarios vean mensajes de afirmación en el código de producción; Las afirmaciones son principalmente para uso durante el desarrollo y mantenimiento. Las afirmaciones normalmente se compilan en el código en el momento del desarrollo y se compilan fuera del código para la producción. Durante el desarrollo, las afirmaciones eliminan suposiciones contradictorias, condiciones inesperadas, valores incorrectos pasados ​​a las rutinas, etc. Durante la producción, se compilan del código para que las afirmaciones no degraden el rendimiento del sistema.

juan
fuente
77
Entonces, ¿qué sucede si un archivo de información del cliente encontrado en producción contiene más de 50,000 registros? Si la afirmación se compila a partir del código de producción y esta situación no se maneja de otra manera, ¿no significa esto un problema?
DavidRR
1
@DavidRR Sí, de hecho. Pero tan pronto como la producción indique un problema y algún desarrollador (que tal vez no conozca bien este código) solucione el problema, la afirmación fallará y el desarrollador sabrá de inmediato que el sistema no se utiliza como se esperaba.
Marc
48

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 usar Debug.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.

Nicholas Piasecki
fuente
3
Esta es una descripción muy clara de una buena práctica y da una respuesta muy razonable a la pregunta formulada.
Casper Leon Nielsen
42

Utilice afirmaciones para verificar los supuestos del desarrollador y excepciones para verificar los supuestos ambientales.

Justin R.
fuente
31

Si yo fuera tú, haría:

Debug.Assert(val != null);
if ( val == null )
    throw new exception();

O para evitar la verificación repetida de la condición

if ( val == null )
{
    Debug.Assert(false,"breakpoint if val== null");
    throw new exception();
}
Mark Ingram
fuente
55
¿Cómo está resolviendo esto el problema? Con esto, debug.assert se vuelve inútil.
Simpático el
43
No, no lo hace: se rompe en código en el punto justo antes de que se lance la excepción. Si tiene un try / catch en otro lugar de su código, ¡es posible que ni siquiera note la excepción!
Mark Ingram
2
1 he tenido un montón de problemas en los que la gente simplemente try / excepciones de captura sin hacer nada de manera de seguimiento de errores era un problema
dance2die
55
Supongo que hay casos en los que querrás hacer esto, ¡pero nunca deberías atrapar una excepción general!
Casebash el
8
@MarkIngram -1 a su respuesta, y +1 a su comentario justificándolo. Este es un buen truco para ciertas circunstancias peculiares, pero parece una cosa extraña en general para toda validación.
Mark Amery
24

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.

Joe
fuente
1
+1 por señalar la distinción crucial entre Debug.Assert y Trace.Assert, ya que el OP preguntó específicamente sobre el código de producción.
Jon Coombs
21

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.

usuario19113
fuente
11

En breve

Asserts se utilizan para guardias y para verificar restricciones de Diseño por contrato, a saber:

  • Assertsdebe 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 sistema
  • Asserts NO son un mecanismo para la validación de primera línea de la entrada del usuario o las reglas comerciales
  • Assertsdebe 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.
  • Una Afirmación fallida debería ser fatal para su sistema, es decir, a diferencia de una excepción, no intente detectar o manejar una falla 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:

  • Para ayudar a encontrar la validación faltante de las entradas del usuario, o errores ascendentes en el código de nivel superior.
  • Las afirmaciones en la base del código transmiten claramente las suposiciones hechas en el código al lector
  • La afirmación se verificará en tiempo de ejecución en las Debugcompilaciones.
  • Una vez que el código se ha probado exhaustivamente, la reconstrucción del código como Release eliminará la sobrecarga de rendimiento de verificar la suposición (pero con el beneficio de que una compilación de depuración posterior siempre revertirá las comprobaciones, si es necesario).

... Mas detalle

Debug.Assertexpresa 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). - Las Assertcomprobaciones 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 prudente Assertque 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 contratadas
  • Contract.Ensures - Postcondiciones contratadas
  • Invariant - 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.
StuartLC
fuente
Desafortunadamente, los contratos de código están casi muertos ya que MS ha dejado de desarrollarlo.
Mike Lowery
10

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.

Molesto
fuente
44
Sin embargo, su respuesta merece algo de mérito, ya que destaca alguna preocupación a menudo planteada con respecto a ellos, el hecho de que interrumpen la sesión de depuración y la posibilidad de falsos positivos. Sin embargo, le faltan algunas sutilezas y está escribiendo "optimizar afirmaciones", lo que solo puede basarse en pensar que lanzar una excepción y hacer debug.assert es lo mismo. No lo es, tienen diferentes propósitos y características, como puede ver en algunas de las respuestas votadas. Dw
Casper Leon Nielsen
+1 para "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 el lanzamiento, ¿cómo tiene sentido eso?" En .NET, se System.Diagnostics.Trace.Assert()ejecuta en una versión de lanzamiento, así como una versión de depuración.
DavidRR
7

De acuerdo con el estándar IDesign , debe

Afirma cada suposición. En promedio, cada quinta línea es una afirmación.

using System.Diagnostics;

object GetObject()
{...}

object someObject = GetObject();
Debug.Assert(someObject != null);

Como descargo de responsabilidad, debo mencionar que no me ha resultado práctico implementar este IRL. Pero este es su estándar.

Devlord
fuente
Parece que a Juval Lowy le gusta citarse a sí mismo.
devlord
6

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.

Derek Park
fuente
En .NET, se puede usar System.Diagnostics.Trace.Assert()para ejecutar una aserción en una compilación de lanzamiento (producción).
DavidRR
Regla de análisis de código CA1062: Validar argumentos de métodos públicos requiere verificar un argumento para nullcuando: "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 arrojar ArgumentNullException.
DavidRR
6

Todas las afirmaciones deben ser código que pueda optimizarse para:

Debug.Assert(true);

Porque está comprobando algo que ya asumiste que es cierto. P.ej:

public static void ConsumeEnumeration<T>(this IEnumerable<T> source)
{
  if(source != null)
    using(var en = source.GetEnumerator())
      RunThroughEnumerator(en);
}
public static T GetFirstAndConsume<T>(this IEnumerable<T> source)
{
  if(source == null)
    throw new ArgumentNullException("source");
  using(var en = source.GetEnumerator())
  {
    if(!en.MoveNext())
      throw new InvalidOperationException("Empty sequence");
    T ret = en.Current;
    RunThroughEnumerator(en);
    return ret;
  }
}
private static void RunThroughEnumerator<T>(IEnumerator<T> en)
{
  Debug.Assert(en != null);
  while(en.MoveNext());
}

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 GetFirstAndConsumecon 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 != nullantes 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 optimizarse Debug.Assert(true), ¡sicne en != nullsiempre debería serlo true!

Jon Hanna
fuente
1
Entonces, en el tercer caso, ¿qué sucede si en == nullen producción? ¿Posiblemente dices que en == nulleso 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.
DavidRR
@DavidRR, de hecho estoy afirmando que nunca puede ser nulo, y también lo es la afirmación en el código, de ahí el nombre. Por supuesto, podría estar equivocado o equivocado por un cambio, y ese es el valor de la llamada de afirmación.
Jon Hanna
1
Las llamadas a 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.
DavidRR
@DavidRR, lo que lo hace apropiado solo cuando sostengo que no puede suceder, ya que nuevamente es una afirmación de hecho, no un control del estado. Por supuesto, tampoco tiene sentido si tiene la afirmación, tiene un error que detectaría y, sin embargo, nunca llega a ese caso en las pruebas.
Jon Hanna
4

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.Findoperación principal indica que devolverá un -1cuando 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 -1valor 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.

Shannon
fuente
4

Cita tomada del programador pragmático: de oficial a maestro

Deje las afirmaciones activadas

Hay un malentendido común sobre las afirmaciones, promulgado por las personas que escriben compiladores y entornos de lenguaje. Es algo parecido a esto:

Las aserciones agregan algo de sobrecarga al código. Debido a que verifican las cosas que nunca deberían suceder, solo se activarán por un error en el código. Una vez que el código ha sido probado y enviado, ya no son necesarios y deben apagarse para que el código se ejecute más rápido. Las afirmaciones son una instalación de depuración.

Hay dos supuestos evidentemente incorrectos aquí. Primero, suponen que las pruebas encuentran todos los errores. En realidad, para cualquier programa complejo es poco probable que pruebe incluso un porcentaje minúsculo de las permutaciones a las que se someterá su código (consulte Pruebas despiadadas).

En segundo lugar, los optimistas están olvidando que su programa se ejecuta en un mundo peligroso. Durante las pruebas, las ratas probablemente no roerán un cable de comunicaciones, alguien que juegue no agotará la memoria y los archivos de registro no llenarán el disco duro. Estas cosas pueden suceder cuando su programa se ejecuta en un entorno de producción. Su primera línea de defensa está verificando cualquier posible error, y su segunda está usando aserciones para tratar de detectar aquellas que ha perdido.

Desactivar las afirmaciones cuando entrega un programa a producción es como cruzar un cable alto sin una red porque una vez lo logró en la práctica . Hay un valor dramático, pero es difícil obtener un seguro de vida.

Incluso si tiene problemas de rendimiento, desactive solo aquellas afirmaciones que realmente lo afectan .

Teoman shipahi
fuente
2

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).

Thomas Danecker
fuente
1
No, al igual que algunas otras respuestas aquí: realmente no entiendes la diferencia, así que optas por una de las ofertas, estableciendo una falsa dicotomía entre ellas en el proceso. Dw
Casper Leon Nielsen
3
Esta es la única respuesta correcta en esta lista de la OMI. No lo descartes tan fácilmente Casper. Debug Assert es un antipatrón. Si es invariante en el tiempo de depuración, es invariable en tiempo de ejecución. Permitir que su aplicación continúe con una invariante rota lo deja en un estado no determinista y con problemas potencialmente peores que el bloqueo. En mi opinión, es mejor tener el mismo código en ambas compilaciones que fallan rápidamente con contratos incumplidos y luego implementan un manejo robusto de errores en el nivel superior. por ejemplo, aislar componentes e implementar la capacidad de reiniciarlos (como una pestaña que se bloquea en un navegador no bloquea todo el navegador).
justin
1
Puede ser útil incluir Trace.Assert en su discusión aquí, ya que no puede descartarse con el mismo argumento.
Jon Coombs
0

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.

orlando calresian
fuente
2
lo siento, pero las excepciones hacen lo mismo, por lo que esta respuesta no aborda la pregunta real.
Roman Starkov
0

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.Assertdurante 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.Asserttiene 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.

using System.Diagnostics;

public class ExceptionTraceListener : DefaultTraceListener
{
    [DebuggerStepThrough]
    public override void Fail(string message, string detailMessage)
    {
        throw new AssertException(message);
    }
}

public class AssertException : Exception
{
    public AssertException(string message) : base(message) { }
}

Y en el archivo de configuración de producción:

<system.diagnostics>
  <trace>
    <listeners>
      <remove name="Default"/>
      <add name="ExceptionListener" type="Namespace.ExceptionTraceListener,AssemblyName"/>
    </listeners>
  </trace>
 </system.diagnostics>
AlexDev
fuente
-1

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.

inexistente
fuente
-1

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.

mate
fuente