El problema: un IF con WHENEVER que no obedece
WHENEVER en Genero parece una instrucción más, y es tentador decidir con un IF qué hacer ante un error:
MAIN DEFINE l_seguir BOOLEAN DEFINE l_x INTEGER LET l_seguir = FALSE
IF arg_val(1) == "seguir" THEN
LET l_seguir = TRUE
END IFIF l_seguir THEN
WHENEVER ANY ERROR CONTINUE
ELSE
WHENEVER ANY ERROR STOP
END IF
DISPLAY "l_seguir = ", l_seguir
LET l_x = 1 / 0
DISPLAY "ha continuado tras el error"
END MAIN
Lanzado con seguir, se esperaría que continuara. No continúa, ni con TRUE ni con FALSE:
l_seguir = 1
Program stopped at 'p1_if.4gl', line number 16.
FORMS statement error number -1202.
An attempt was made to divide by zero.
La causa: lo aplica el compilador, no se ejecuta
WHENEVER no es una instrucción, es una directiva. El manual lo compara con una macro del preprocesador de C: su efecto es local al módulo y define el tratamiento de errores para el resto del módulo, hasta que el compilador encuentra otro WHENEVER de la misma clase o un bloque TRY/CATCH.
«El resto del módulo» se refiere a las líneas que quedan por debajo en el fichero, no a lo que se ejecute después. En el ejemplo, el último WHENEVER escrito antes del 1 / 0 es el STOP de la rama ELSE, y es el que el compilador asigna a esa línea. Qué rama se ejecute da igual: ninguna de las dos «ejecuta» nada.
Tres consecuencias que se pueden comprobar
Una función que nunca se llama cambia a las que tiene debajo
MAIN WHENEVER ANY ERROR STOP CALL f_despues() CALL f_antes() END MAINFUNCTION f_antes()
DEFINE l_x INTEGER
LET l_x = 1 / 0
DISPLAY "f_antes: ha continuado"
END FUNCTION FUNCTION nunca_se_llama()
WHENEVER ANY ERROR CONTINUE
END FUNCTIONFUNCTION f_despues()
DEFINE l_x INTEGER
LET l_x = 1 / 0
DISPLAY "f_despues: ha continuado"
END FUNCTION
f_despues: ha continuado
Program stopped at 'p2_lexico.4gl', line number 9.
FORMS statement error number -1202.
nunca_se_llama() no se ejecuta nunca, y aun así f_despues, que está escrita debajo, sigue tras el error. f_antes, escrita encima, se para. Lo que cuenta es la posición en el fichero, no el orden de las llamadas.
No pasa de un módulo a otro
El manual lo dice así: un WHENEVER solo define el tratamiento del módulo actual y no afecta a quien llama. Tampoco a quien es llamado. Un módulo sin ningún WHENEVER:
-- m_otro.gl
PUBLIC FUNCTION dividir()
DEFINE l_x INTEGER
LET l_x = 1 / 0
DISPLAY "m_otro: ha continuado"
END FUNCTION
Y un programa que define un manejador y lo llama:
-- p3_modulos.gl IMPORT FGL m_otro MAIN WHENEVER ANY ERROR CALL manejador CALL m_otro.dividir() DISPLAY "main: fin" END MAINFUNCTION manejador()
DISPLAY "manejador de MAIN llamado, status=", status
END FUNCTION
m_otro: ha continuado
main: fin
manejador no se ejecuta: el error ocurre en m_otro, y ahí no hay ningún WHENEVER que lo mande a él.
Sin WHENEVER, la división por cero pasa en silencio
La salida anterior enseña algo más: el 1 / 0 de m_otro no paró nada, y el programa siguió en la línea siguiente. Según el manual, la acción por defecto es STOP, pero no se aplica a los expression errors, como la división por cero o un error de conversión de tipos. Para eso hace falta WHENEVER ANY ERROR o la entrada de FGLPROFILE fglrun.mapAnyErrorToError = true. Con esa entrada activada, el mismo programa se para en la división de m_otro, y sigue sin llamar a manejador.
La solución
Para decidir en ejecución, usa TRY/CATCH. La condición se evalúa dentro del CATCH, que sí es código que se ejecuta:
TRY
LET l_x = 1 / 0
CATCH
IF NOT l_seguir THEN
DISPLAY "error ", status, ": se para"
EXIT PROGRAM 1
END IF
DISPLAY "error ", status, ": se sigue"
END TRY
Para la red de seguridad general, un WHENEVER al principio de cada módulo. Ask Reuben (#16) propone como norma que la primera función de cada .4gl sea una función privada con esa única línea. Así queda definida para todo el fichero, y no depende de que nadie la llame:
IMPORT FGL erroresPRIVATE FUNCTION excepciones()
WHENEVER ANY ERROR CALL errores.manejador
END FUNCTIONPUBLIC FUNCTION dividir()
...
END FUNCTION
En el módulo que tiene el MAIN no se puede: una función antes de MAIN da error de compilación (-6609, A grammatical error has been found at ‘MAIN’). Ahí la directiva va como una de las primeras líneas del propio MAIN.
Dos detalles del manejador:
- El programa sigue después. Tras ejecutar la función de
WHENEVER ... CALL, el programa continúa en la línea siguiente a la del error. Si quieres que pare, tiene que pararlo el manejador. - Ponle su propio
WHENEVER ANY ERROR STOPdentro. Si el código del manejador falla, así no se vuelve a llamar a sí mismo en bucle.
Conclusión
WHENEVER se lee de arriba abajo en el fichero, no en el orden de ejecución, y cada módulo tiene el suyo. Para decidir según el estado del programa, TRY/CATCH. Para cubrir todo un fichero, un WHENEVER en la parte de arriba.
Fuentes
- Manual Genero BDL 6.00: WHENEVER directive (alcance de módulo, comparación con el preprocesador de C, acción por defecto,
fglrun.mapAnyErrorToError). - Ask Reuben #16, When Things Go Wrong (part 3 of 3) – Exception Handling: https://4js.com/ask-reuben/ig-16/
Qué está comprobado y qué no
El alcance de módulo, que no afecta a quien llama, la acción por defecto y fglrun.mapAnyErrorToError están contrastados con el manual de Genero 6.00. El ejemplo del IF y la norma de la función privada vienen de Ask Reuben #16.
Compilado y ejecutado en Genero 5.01.04, en modo texto. Los listados se copiaron tal cual del texto de la entrada; los dos fragmentos (el TRY/CATCH y el módulo con ...) se completaron con un MAIN y un cuerpo mínimos sin cambiar sus líneas:
- El
IFconWHENEVER: se para en la línea 16 con la condición a TRUE y a FALSE. - La función que nunca se llama: cambia el comportamiento de la que tiene debajo.
- Entre módulos: el manejador de
MAINno se ejecuta para un error dem_otro. - La división por cero sin
WHENEVER: pasa en silencio, y se para confglrun.mapAnyErrorToError = true. - La decisión con
TRY/CATCH: sigue o para según la condición. - La función privada al principio del módulo: compila con el nombre calificado de otro módulo (
errores.manejador), el manejador se llama, y el programa continúa después. - Una función antes de
MAIN: no compila (-6609).