El problema: un TRY que no salta
Un TRY/CATCH alrededor de una llamada parece una red de seguridad completa: si algo falla dentro de la función, el CATCH se encargará. En Genero no funciona así:
MAIN TRY CALL divide() CATCH DISPLAY "divide: capturado ", status END TRY TRY CALL abre_formulario() CATCH DISPLAY "abre_formulario: capturado ", status END TRY DISPLAY "fin" END MAINFUNCTION divide()
DEFINE l_x INTEGER
LET l_x = 1 / 0
DISPLAY "divide: ha seguido sin avisar"
END FUNCTIONFUNCTION abre_formulario()
OPEN FORM f FROM "no_existe"
END FUNCTION
divide: ha seguido sin avisar
Program stopped at 'no_ve_llamadas.4gl', line number 22.
FORMS statement error number -1110.
Form file (no_existe) not found.
Ninguno de los dos CATCH se ejecuta. La división por cero pasa en silencio y el formulario que no existe para el programa, todo en el mismo módulo y dentro de un TRY.
La causa: TRY cubre líneas, no llamadas
El manual describe TRY/CATCH como una pseudoinstrucción: el compilador no genera código para esas líneas, y el bloque se puede comparar con un WHENEVER ANY ERROR GOTO. El TRY toma el control de los errores en las líneas escritas entre TRY y CATCH.
Las líneas de divide() y de abre_formulario() no están ahí, están más abajo en el fichero. Para ellas manda el tratamiento de errores que tengan por su posición, como con WHENEVER, que es de compilación. Aquí no hay ningún WHENEVER, así que se aplica la acción por defecto:
- El
-1110para el programa. - La división por cero se ignora. La acción por defecto no cubre los expression errors.
En ningún caso el error vuelve al TRY del llamante.
Lo que sí captura
Dentro de sus propias líneas, TRY captura los errores de cualquier clase sin necesitar ningún WHENEVER previo, también los de expresión:
MAIN
DEFINE l_x INTEGER
TRY
LET l_x = 1 / 0
CATCH
DISPLAY "capturado: ", status, " ", err_get(status)
END TRY
END MAIN
capturado: -1202 An attempt was made to divide by zero.
Para saber qué ha fallado, dentro del CATCH:
statustiene el código del error.err_get(status)devuelve el texto del error.sqlca.sqlcodees el código que da la base de datos, en los errores SQL.
La solución: propagar con WHENEVER ANY ERROR RAISE
Para que el error de una función llegue al TRY de quien la llama, la función tiene que propagarlo: WHENEVER ERROR RAISE reenvía la excepción al llamante. Con ANY incluye también los errores de expresión. Como WHENEVER se aplica a las líneas que tiene debajo, basta con ponerlo como primera línea de MAIN para cubrir las funciones escritas después:
MAIN WHENEVER ANY ERROR RAISE TRY CALL divide() CATCH DISPLAY "divide: capturado ", status END TRY TRY CALL abre_formulario() CATCH DISPLAY "abre_formulario: capturado ", status END TRY DISPLAY "fin" END MAINFUNCTION divide()
DEFINE l_x INTEGER
LET l_x = 1 / 0
DISPLAY "divide: ha seguido sin avisar"
END FUNCTIONFUNCTION abre_formulario()
OPEN FORM f FROM "no_existe"
END FUNCTION
divide: capturado -1202
abre_formulario: capturado -1110
fin
Si las funciones están en otro módulo, la directiva tiene que estar en ese módulo, porque cada módulo tiene la suya. Una forma cómoda es que la primera función del fichero sea una función privada que solo contiene WHENEVER ANY ERROR RAISE: así cubre todas las que tiene debajo, aunque nadie la llame nunca.
Lo que no captura nunca
Hay errores que no se pueden capturar ni con TRY/CATCH ni con WHENEVER. El manual los llama non-trappable errors y tiene una lista. Uno típico es -6206, el módulo .42m que no se encuentra: el programa se para siempre. Está explicado en la entrada de FGLLDPATH.
Dos detalles del manual
- Un error dentro del propio
CATCHno lo captura eseTRY. EntreCATCHyEND TRYvuelve a mandar elWHENEVERque hubiera antes. TRYdetiene la evaluación de una expresión en el primer error.WHENEVER ERROR CONTINUEno la detiene. En(Pi() / 0) + Pi(), conTRYla función se llama una vez y conWHENEVER ... CONTINUE, dos.
Conclusión
TRY/CATCH cubre las líneas que tiene escritas dentro, no las funciones a las que llaman. Para que el error de una función llegue al CATCH, esa función tiene que estar bajo un WHENEVER ANY ERROR RAISE. Sin él, según el tipo de error, el programa se para o el error pasa en silencio.
Fuentes
- Manual Genero BDL 6.00: TRY – CATCH block; WHENEVER directive (
RAISE, acción por defecto); Non-trappable errors;ERR_GET; Example 3: Typical TRY / CATCH block (sqlca.sqlcode).
Qué está comprobado y qué no
El carácter de pseudoinstrucción, la comparación con WHENEVER ANY ERROR GOTO, el RAISE, los errores no capturables y los dos detalles del final están contrastados con el manual de Genero 6.00.
Compilado y ejecutado en Genero 5.01.04, en modo texto. Los listados se copiaron tal cual del texto de la entrada:
- Un
TRYalrededor de una llamada del mismo módulo: la división por cero pasa en silencio y el-1110para el programa en la línea 22. - Una función propia usada en una expresión (
LET l_x = f_div() + 1): el error de dentro tampoco llega alCATCH. En cambio, un método del sistema que falla en una línea delTRY(base.Channel.openFilecon un fichero que no existe) sí se captura. statusyerr_get(status)dentro delCATCH.- Con
WHENEVER ANY ERROR RAISEcomo primera línea deMAIN: los dos errores llegan alCATCH.