English Русский 中文 Deutsch 日本語
Aserciones en los programas MQL5

Aserciones en los programas MQL5

MetaTrader 5Ejemplos | 23 febrero 2016, 13:28
766 0
Sergey Eremin
Sergey Eremin

Introducción

Una aserción es una construcción especial que permite comprobar suposiciones especiales en lugares arbitrarios del programa. Suelen embeberse en el código, generalmente en una función separada o en una macro. El código comprueba el valor de una expresión determinada. Si es falso, se muestra el mensaje correspondiente y el programa se detiene de acuerdo a la implementación proporcionada. De igual modo, si la expresión es cierta, es decir, si la suposición se cumple, quiere decir que todo funciona según lo previsto. En caso contrario el programará detectará errores y avisará de ello.

Por ejemplo, si se espera que un determinado valor X sea menor que cero, en todos los casos, entonces podemos formar este predicado: "confirmo que el valor de X es mayor o igual a cero". Si resulta que X es menor que cero, mostramos un mensaje relevante para que el programador pueda ajustar el programa.

Las aserciones son especialmente útiles en los proyectos grandes, donde hay muchos componentes que se pueden reutilizar o modificar con el tiempo.

Las aserciones contemplan aquellas situaciones que no deberían ocurrir durante la ejecución normal del programa. Como norma general solo se aplican en las fases de desarrollo y depuración del programa, esto es, no tienen que estar presentes en la versión final. Las aserciones tienen que eliminarse durante la compilación de la versión final. Esto se consigue con la compilación condicional.

Ejemplos de mecanismos de aserción den MQL5

Normalmente el mecanismo de aserción consiste en lo siguiente:

  1. Mostrar un mensaje que se tiene que comprobar.
  2. Mostrar el nombre del archivo y el código fuente donde se detecta el error.
  3. Mostrar el nombre y la signatura del método o función donde se detecta el error.
  4. Mostrar el número de línea del código fuente donde se revisa la expresión.
  5. Mostrar un mensaje arbitrario, especificado por el programador, en la fase de escritura del código.
  6. Terminación del programa después de encontrar los errores.
  7. Posibilidad de excluir todas las aserciones del programa compilado utilizando compilación condicional, o algún otro mecanismo parecido.

Casi todas las características se pueden implementar con las funciones estándar (a excepción del punto 6, como veremos más adelante), macro y compilación condicional del lenguaje MQL5. A continuación presentamos dos opciones posibles, a modo de ejemplo:

Opción N1 (versión suave, sin terminar el programa)

#define DEBUG

#ifdef DEBUG  
   #define assert(condition, message) \
      if(!(condition)) \
        { \
         string fullMessage= \
                            #condition+", " \
                            +__FILE__+", " \
                            +__FUNCSIG__+", " \
                            +"línea: "+(string)__LINE__ \
                            +(message=="" ? "" : ", "+message); \
         \
         Alert("La aserción ha fallado "+fullMessage); \
        }
#else
   #define assert(condition, message) ;
#endif 

Opción N2 (versión dura, termina el programa)

#define DEBUG

#ifdef DEBUG  
   #define assert(condition, message) \
      if(!(condition)) \
        { \
         string fullMessage= \
                            #condition+", " \
                            +__FILE__+", " \
                            +__FUNCSIG__+", " \
                            +"línea: "+(string)__LINE__ \
                            +(message=="" ? "" : ", "+message); \
         \
         Alert("La aserción ha fallado "+fullMessage); \
         double x[]; \
         ArrayResize(x, 0); \
         x[1] = 0.0; \
        }
#else 
   #define assert(condition, message) ;
#endif


La macro assert

Primero de todo declaramos el identificador DEBUG. Si permanece declarado, entonces la rama #ifdef de la expresión de compilación condicional es válida, y el programa incluye una macro assert completa. En caso contrario, (rama #else) el programa incluye la macro assert, que no ejecuta ninguna operación.

La macro completa assert se construye como sigue. Primero se ejecuta la condición entrante. Si se evalúa a false, se muestra el mensaje fullMessage. El mensaje fullMessage está formado por los siguientes elementos:

  1. Texto de la expresión a comprobar (#condition).
  2. Nombre del archivo con el código fuente desde donde se llamó a la macro (__FILE__).
  3. Signatura del método o función desde donde se llamó a la macro (__FUNCSIG__).
  4. Número de línea del archivo fuente donde se coloca la llamada a la macro (__LINE__).
  5. Mensaje transferido a la macro, si no está vacío (message).

Después de mostrar el mensaje (Alert), la segunda macro intenta asignar un valor al elemento de un array que no existe, lo que provoca un error en tiempo de ejecución, y el programa termina inmediatamente.

Esta forma de parar el programa tiene unos efectos secundarios en los indicadores que operan en subventanas; como estos se quedan en el terminal, hay que cerrarlos manualmente. Además puede haber artefactos en forma de objetos gráficos no eliminados, variables globales del terminal o archivos, por poner algunos ejemplos, que se crearon mientras el programa estaba operando, hasta que se colgó. Si el comportamiento es inaceptable se tiene que utilizar la primera macro.

Explicación. En el momento de escribir este artículo, MQL5 no tenía ningún mecanismo para que el programa pudiera ejecutar una parada de emergencia. Como alternativa se disparaba un error en tiempo de ejecución que hacía que el programa se colgara.

Esta macro se puede añadir en el archivo separado assert.mqh, que puede guardarse, por ejemplo, en <data folder>/MQL5/Include. El archivo (opción N2) se adjunta en este artículo.

El código que mostramos a continuación contiene un ejemplo de aserción, y muestra el resultado de las operaciones.

Ejemplo de utilización de la macro assert en el código del Asesor Experto

#include <assert.mqh>

int OnInit()
  {
   assert(0 > 1, "mi mensaje")   

   return(INIT_SUCCEEDED);
  }

void OnDeinit(const int reason)
  {  
  }

void OnTick()
  {
  }

Aquí se observa una aserción que significa literalmente "Confirmo que 0 es mayor que 1". La aserción es obviamente falsa, lo que provoca que se muestre un mensaje de error:

Fig. 1. Ejemplo de aserción

Fig. 1. Ejemplo de aserción


Principios generales de las aserciones

Las aserciones sirven para identificar las situaciones inesperadas que suceden en el programa, así como para documentar y controlar la ejecución de las suposiciones aceptadas. Por ejemplo, podemos utilizar aserciones para comprobar estas condiciones:

  • Valores de los parámetros de entrada y salida, junto con los resultados de los métodos o funciones, cuyos valores están comprendidos en el rango esperado.

    Ejemplo de aserción que comprueba los valores de entrada y salida del método
    double CMyClass::SomeMethod(const double a)
      {
    //--- comprobamos el valor del parámetro de entrada
       assert(a>=10,"")
       assert(a<=100,"")
    
    //--- calculamos el valor resultante
       double result=...;
    
    //--- comprobamos el valor resultante
       assert(result>=0,"")
      
       return result;
      } 
    
    
    Este ejemplo supone que el parámetro de entrada a no puede ser menor que 10 y mayor que 100. Además se espera que el valor resultante no sea menor que cero.

  • Los límites del array están dentro del rango esperado.

    Ejemplo de aserción que comprueba que los límites del array están comprendidos en el rango esperado
    void CMyClass::SomeMethod(const string &incomingArray[])
      {
    //--- comprobamos los límites del array
       assert(ArraySize(incomingArray)>0,"")
       assert(ArraySize(incomingArray)<=10,"")
    
       ...
      }
    
    
    Este ejemplo espera que el array incomingArray contenga por lo menos un elemento, pero no puede tener más de diez.

  • El descriptor del objeto creado es válido.

    Ejemplo de aserción que comprueba que el descriptor del objeto creado es válido
    void OnTick()
      {
    //--- creamos el objeto a
       CMyClass *a=new CMyClass();
    
    //--- ejecutamos algunas acciones
       ...
       ...
       ...
    
    //--- comprobamos que el objeto a todavía existe
       assert(CheckPointer(a),"")
    
    //--- borramos el objeto a
       delete a;
      } 
    
    
    Este ejemplo supone que al final de la ejecución OnTick todavía existe el objeto a.

  • ¿El divisor vale cero en una operación de división?

    Ejemplo de aserción que comprueba si el divisor es igual a cero
    void CMyClass::SomeMethod(const double a, const double b)
      {
    //--- comprobamos que b no es igual a cero
       assert(b!=0,"")
    
    //--- dividiendo a por b
       double c=a/b;
      
       ...  
       ...
       ...
      } 
    
    
    Este ejemplo asume que el parámetro de entrada b, esto es el divisor de a, no es igual a cero.

Ciertamente hay más condiciones que vale la pena comprobar utilizando aserciones, y en cada caso son completamente únicas. Algunas de ellas se muestran arriba.

Aserciones para comprobar precondiciones y postcondiciones. En este sentido hay un enfoque de diseño de software y desarrollo llamado "Diseño por contrato". Según este paradigma, las funciones, los métodos y las clases firman un contrato con una parte del programa utilizando precondiciones y postcondiciones.

Las precondiciones son acuerdos realizados en el código cliente; en concreto, el método o clase que hace una llamada implementa las precondiciones antes de llamar al otro método o crear una instancia. Por ejemplo, supongamos que hay un método que tiene un cierto parámetro cuyos valores deben ser mayores a 10; entonces, es responsabilidad del programador que el código llama a dicho método garantice que, en ninguna circunstancia, se pase a ese parámetro un valor menor o igual a 10.

Las postcondiciones son acuerdos que los métodos o clases se comprometen a hacer justo antes de finalizar su trabajo. De este modo, si un método determinado no tiene que devolver valores por debajo de 100, el programador se responsabiliza de que el valor devuelto no supere o sea igual a 100.

Se recomienda documentar las precondiciones y las postcondiciones, así como monitorizar su correcto funcionamiento en las fases de desarrollo y depuración del programa. A diferencia de los comentarios convencionales, las aserciones no son meras declaraciones de las expectativas del programa, sino que monitorizan su cumplimiento de forma constante. Arriba encontrará ejemplos de aserciones que comprueban y documentan las precondiciones y las postcondiciones, por favor eche un vistazo al "Ejemplo de aserción que comprueba los valores de entrada y salida del método".

Intente comprobar los errores de programación con aserciones cuando tenga la oportunidad de hacerlo; es decir, aquellos errores que no dependen de factores externos. Es mejor no utilizar aserciones para comprobar que la apertura de órdenes es correcta, o la disponibilidad del historial de cotizaciones en el período de tiempo solicitado. Es más adecuado manejar estos errores y registrarlos en el archivo de log.

Evite colocar código ejecutable en las aserciones. Las aserciones no afectan al comportamiento del programa porque pueden eliminarse en la fase de compilación de la versión final del programa. Por ejemplo, este problema suele estar relacionado con la llamada a una función o método dentro de la aserción.

Aserción que puede afectar al comportamiento del programa tras deshabilitar todas las aserciones

void OnTick()

  {
   CMyClass someObject;

//--- comprobamos el correcto funcionamiento de los cálculos
   assert(someObject.IsSomeCalculationsAreCorrect(),"")
  
   ...
   ...
   ...
  }

En este caso, coloque la llamada a la función antes de la aserción, guarde el resultado en una variable de estado y, finalmente, compruébelo en la aserción:

Aserción que no puede afectar al comportamiento del programa tras deshabilitar todas las aserciones

void OnTick()
  {
   CMyClass someObject;

//--- comprobamos el correcto funcionamiento de los cálculos
   bool isSomeCalculationsAreCorrect = someObject.IsSomeCalculationsAreCorrect();
   assert(isSomeCalculationsAreCorrect,"")
  
   ...
   ...
   ...
  }

No hay que confundir las aserciones con la gestión de los errores esperados. Las aserciones sirven para buscar errores en las fases de desarrollo y depuración de los programas. Idealmente no tendría que haber errores de programación; sin embargo, por otro lado, el procesamiento de los errores esperados hace que la versión final del programa funcione correctamente, de forma robusta. Las aserciones no gestionan los errores, sino que simplemente dicen: "¡Oye!, aquí hay un error".

Por ejemplo, si tenemos un método con un parámetro que necesita un valor mayor que 10 y el programador intenta asignarle el valor 8, entonces se producirá un error y el programa generará el mensaje correspondiente:

Ejemplo de llamada a un método con un valor no permitido como parámetro de entrada (las aserciones comprueban los valores de los parámetros)

void CMyClass::SomeMethod(const double a)

  {
//--- comprobamos si a es mayor que 10
   assert(a>10,"")
  
   ...
   ...
   ...
  }

void OnTick()
  {
   CMyClass someObject;

   someObject.SomeMethod(8);
  
   ...
   ...
   ...
  }

Ahora bien, si el programador ejecuta el código anterior pasando al método el valor 8, el programa informará: "Confirmo que este método no acepta valores menores o iguales a 10".

Figura 2. Resultado de la operación al llamar al método pasando un valor no permitido en el parámetro de entrada (las aserciones se utilizan para comprobar los valores de los parámetros)

Fig. 2. Resultado de la operación al llamar al método pasando un valor no permitido en el parámetro de entrada (las aserciones se utilizan para comprobar los valores de los parámetros)

Después de recibir el mensaje de error, el programador puede corregirlo rápidamente.

Por otro lado, si un programa requiere que el historial de un activo determinado tenga más de 1000 barras, esta situación no se considera como un error del programador porque el volumen del historial disponible no depende de él. Lo más lógico es contemplar este escenario donde hay menos de 1000 barras disponibles como un error esperado:

Ejemplo de gestión de la situación donde el historial disponible de un instrumento es menor que el necesario (en este caso procesamos el error)

void OnTick()
  {
   if(Bars(Symbol(),Period())<1000)
     {
      Comment("El historial es insuficiente para que el programa pueda operar correctamente");
      return;
     }
  }

Para garantizar la estabilidad de la versión final del programa compruebe sus expectativas utilizando aserciones y maneje los errores a continuación:

Ejemplo que combina aserciones y gestión de errores

double CMyClass::SomeMethod(const double a)
  {
//--- comprobamos el valor del parámetro de entrada con una aserción
   assert(a>=10,"")
   assert(a<=100,"")
  
//--- comprobamos el valor del parámetro de entrada y si hace falta lo corregimos
   double aValue = a;

   if(aValue<10)
     {
      aValue = 10;
     }
   else if(aValue>100)
     {
      aValue = 100;
     }

//--- calculamos el valor resultante
   double result=...;

//--- comprobamos el valor resultante con una aserción
   assert(result>=0,"")

//--- comprobamos el valor resultante y si hace falta lo corregimos
   if(result<0)
     {
      result = 0;
     }

   return result;
  } 


Así pues, las aserciones ayudan a hacer el seguimiento de los errores del programa, antes de lanzar la versión final. Si gestionamos los errores en la versión final el programa funcionará correctamente incluso con los errores que no se encontraron en las fases de depuración y desarrollo.

Los errores se pueden manejar de varias maneras distintas; por ejemplo, corrigiendo los errores no permitidos, como hemos ilustrado en el ejemplo anterior, pero también podemos parar por completo una operación determinada. Sin embargo, examinar estos métodos está fuera del alcance de este artículo.

En los casos donde los programas reaccionan a los errores de los programadores finalizando adecuadamente e indicando el motivo del problema, las aserciones se consideran redundantes. Por ejemplo, cuando en una división por cero el programa MQL5 termina su ejecución y muestra el mensaje correspondiente en el archivo de log. En principio esta aproximación ayuda a encontrar problemas mejor que las aserciones. No obstante, las aserciones proporcionan información importante acerca de las suposiciones que se llevan a cabo en el código. Son más sensibles y más adecuadas que los clásicos comentarios que se hacen en el código, y ayudan enormemente a mantener el programa y a desarrollar el código.


Conclusión

En este artículo hemos estudiado cómo funciona el mecanismo de las aserciones, hemos proporcionado un ejemplo de implementación en MQL5, y hemos ofrecido algunas recomendaciones generales sobre su aplicación. Si se aplican correctamente, las aserciones simplifican en gran medida el desarrollo de software y la fase de depuración.

Recuerde que principalmente utilizamos aserciones para buscar errores en los programas, pero errores provocados por los programadores, no aquellos que no dependen del programador. Las aserciones no están incluidas en la versión final del programa. En los errores que no dependen del programador es mejor aplicar la gestión de errores.

Las pruebas de software suelen implementarse con aserciones. Finalmente, tanto la gestión de los errores como las pruebas de software son dos temas extensos que tendríamos que exponer en artículos aparte.

Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/1977

Archivos adjuntos |
assert.mqh (1.01 KB)

Otros artículos del autor

Utilización de layouts y contenedores en los controles GUI: la clase CGrid Utilización de layouts y contenedores en los controles GUI: la clase CGrid
Este artículo explica un método alternativo de creación de GUIs basado en layouts y contenedores por medio de un gestor de layouts: la clase CGrid. La clase CGrid es un control auxiliar que actúa como contenedor de contenedores y controles, utilizando un diseño de rejilla o cuadrícula (grid layout).
Interfaces gráficas I: Probamos la librería en los programas de diferentes tipos y en el terminal MetaTrader 4 (Capítulo 5) Interfaces gráficas I: Probamos la librería en los programas de diferentes tipos y en el terminal MetaTrader 4 (Capítulo 5)
En el capítulo anterior de la primera parte de la serie sobre las interfaces gráficas, en la clase del formulario han sido añadidos los métodos que permiten manejar el formulario con los clics en los controles. En el presente artículo vamos a testear el trabajo realizado en diferentes tipos de programas MQL, como indicadores y scripts. En vista de que se ha planteado la tarea de diseñar una librería multiplataforma (en marco de las plataformas comerciales MetaTrader), también realizaremos las pruebas en MetaTrader 4.
El indicador Cuerda de Erik Nayman El indicador Cuerda de Erik Nayman
En el presente artículo explicamos cómo funciona el indicador "Cuerda" (Rope), nos basamos en la obra de Erik L. Nayman "The Small Encyclopedia of Trader" (La pequeña enciclopedia del trader). Este indicador muestra la dirección de la tendencia mediante los valores alcistas (toros) y bajistas (osos) calculados en un periodo de tiempo determinado. Explicamos los principios de creación y cálculo de indicadores, ofreciendo algunos ejemplos de código. También cubrimos la construcción de un Asesor Experto basado en el indicador "Cuerda", así como la optimización de los parámetros externos.
Experto comercial universal: El comercio en grupo y la gestión de la cartera de estrategias (Parte 4) Experto comercial universal: El comercio en grupo y la gestión de la cartera de estrategias (Parte 4)
En la parte definitiva de esta serie de artículos sobre el motor comercial CStrategy, estudiaremos el funcionamiento simultáneo de múltiples algoritmos comerciales, la descarga de estrategias desde archivos XML, así como la presentación de un sencillo panel para la selección de expertos, que se encuentra dentro de un módulo ejecutable único, y veremos la gestión de los modos comerciales de los mismos.