Herramientas de depuración en R

R dispone de un conjunto de herramientas para depurar (debug) programas. Yo suelo usar la función debug de manera casi exclusiva y sistemática, pero leyendo The Art of R Programming he dado con una discusión sistemática sobre el proceso de depuración así como algunas herramientas adicionales.

Una de las primeras que menciona el libro es la función stopifnot, que puede ser intercalada en el código para verificar condiciones necesarias (y lanzar un error en caso de que no se cumplan):

mi.error <- function( x ){
  res <- 1 / x
  stopifnot( ! is.infinite( res ) )
  2 * res
}
 
mi.error( 2 )
mi.error( 0 )

Puede ser usado para anticiparse activamente a los errores.

Son, creo yo, conocidas de todos las funciones debug y undebug, que permiten ejecutar código línea a línea. Una adición interesante a la familia es debugonce, que llama a debug una única vez y evita tener que eliminar explícitamente a la función undebug en situaciones similares a

f <- function( n, x ){
  for( i in 1:n)
    g(x)
}

La función browser permite inspeccionar el estado de la función sin tener que llamar a debug sobre toda ella. Se le puede añadir, además, una condición para que sólo interrumpa la ejecución del programa bajo ciertas condiciones.

mi.error <- function( x ){
  res <- 1 / x
  browser( expr = x == 0 )
  2 * res
}
 
mi.error( 2 )
mi.error( 0 )

Este resultado también puede obtenerse usando las funciones setBreakpoint o trace.

Finalmente, existe la posibilidad de saber qué ha pasado después del fallo de una función de R usando

options( error = recover )

Con esa opción, después de un fallo, R te deja elegir el contexto que se quiere analizar. Por ejemplo:

> options( error = recover )
> myFit <- lm(y ~ x, data = xy, weights = w)
Error in inherits(x, "data.frame") : object 'xy' not found

Enter a frame number, or 0 to exit   

1: lm(y ~ x, data = xy, weights = w)
2: eval(mf, parent.frame())
3: eval(expr, envir, enclos)
4: model.frame(formula = y ~ x, data = xy, weights = w, drop.unused.levels = TRUE)
5: model.frame.default(formula = y ~ x, data = xy, weights = w, drop.unused.levels = TRU
6: is.data.frame(data)
7: inherits(x, "data.frame")

Selection: 2
Called from: model.frame.default(formula = y ~ x, data = xy, weights = w, 
    drop.unused.levels = TRUE)
Browse[1]> ls()
[1] "enclos" "envir"  "expr"  
Browse[1]> Q
>

No son este tipo de herramientas aquellas a las que los programadores están más acomodaticiamente acostumbrados. Aparentemente, existen herramientas de depuración análogas a las que dispone Eclipse (para Java) o Eric (para Python) en desarrollo para RStudio. ¿Llegarán pronto?