TRY/CATCH en Genero: qué captura y qué no

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 FUNCTION
FUNCTION 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 -1110 para 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:

  • status tiene el código del error.
  • err_get(status) devuelve el texto del error.
  • sqlca.sqlcode es 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 FUNCTION
FUNCTION 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 CATCH no lo captura ese TRY. Entre CATCH y END TRY vuelve a mandar el WHENEVER que hubiera antes.
  • TRY detiene la evaluación de una expresión en el primer error. WHENEVER ERROR CONTINUE no la detiene. En (Pi() / 0) + Pi(), con TRY la función se llama una vez y con WHENEVER ... 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 TRY alrededor de una llamada del mismo módulo: la división por cero pasa en silencio y el -1110 para 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 al CATCH. En cambio, un método del sistema que falla en una línea del TRY (base.Channel.openFile con un fichero que no existe) sí se captura.
  • status y err_get(status) dentro del CATCH.
  • Con WHENEVER ANY ERROR RAISE como primera línea de MAIN: los dos errores llegan al CATCH.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *


Scroll al inicio