1

What is the best was to handle errors in R? I want to be able to both abstract away stack traces from the end user of my script, as well as clean up any temporary variables and state i may be working with.

So i suppose my question is two fold:

  1. Most importantly, how do i properly handle cleaning up state when handling errors?
  2. How do i extract, useful, nice information from R's error messages, without internal stuff being spit out?

At the moment, I have something which looks like this, but (1) it seems horribly inelegant, and (2) still gives me horrible error messages.

# Create some temporary working state and variables to clean up
file <- "somefile"
working.dir <- getwd()
setwd("../")  # Go somewhere else
saf.default <- getOption("stringsAsFactors")
options(stringsAsFactors = FALSE)

# Now lets try to open the file, but it doesn't work (for whatever reason)
# I want to clean up the state, stop, and wrap the error string with a nicer
# message
tryCatch({
  # Need to embed the tryCatch because we still need some of the state variables
  # for the error message
  tryCatch({
    f <- read.table(file)
  }, error = function(err.msg) {
    # Can't clean up here, we still need the `file variable!
    stop("Could not open file '", file, "' with error message:\n", print(err.msg), 
         call.=FALSE)
  })
}, error = function(err.msg) {
  # Now we can clean up state
  setwd(working.dir)
  options(stringsAsFactors = saf.default)
  rm(file, working.dir, saf.default, 
     envir=globalenv())  # This also seems awful?
  stop(print(err.msg), call.=FALSE)
})

# Do more stuff, get more state, handle errors, then clean up.
# I.e can't use `finally` in previous tryCatch!

The error message from this comes out as, still lots of ugly internals:

# <simpleError in file(file, "rt"): cannot open the connection>
# <simpleError: Could not open file 'somefile' with error message:
# Error in file(file, "rt"): cannot open the connection
>
# Error: Could not open file 'somefile' with error message:
# Error in file(file, "rt"): cannot open the connection
# In addition: Warning messages:
# 1: In file(file, "rt") :
#   cannot open file 'somefile': No such file or directory
# 2: In stop(print(err.msg), call. = FALSE) :
#   additional arguments ignored in stop()
>
Scott Ritchie
  • 10,293
  • 3
  • 28
  • 64

1 Answers1

2

I would isolate any state-changing code into its own function, and use on.exit. This guarantees that cleanup will happen, no matter if an error occurs.

readFile <- function(.....)
{
    on.exit({
        setwd(cur.dir)
        options(stringsAsFactors=saf)
    })
    cur.dir <- getwd()
    saf <- getOption("stringsAsFactors")
    setwd("new/dir")
    options(stringsAsFactors=FALSE)
    ....
}
Hong Ooi
  • 56,353
  • 13
  • 134
  • 187
  • Is it possible to grow the list of things that need to happen on exit as my script progresses? – Scott Ritchie Jun 17 '13 at 06:31
  • 1
    Yes, but it's complicated. I'd just put all the necessary statements in at the start. If your code involves a huge number of state changes, it's probably worthwhile asking if it could be rewritten. In your use case, for example, none of the filename, current directory or `stringsAsFactors` option need to be treated as state. – Hong Ooi Jun 17 '13 at 08:14
  • Normally I'd agree, but unfortunately what I'm writing is one big long analysis script, which don't seem to lend themselves well to modularisation. I still haven't found a good solution for dealing with them in a reproducible script. For now the solution I'm working with involves wrapping the whole script in a function, and calling it in the same source file. That way I'm not left with temporary variables on exit, and I can minimise the state that needs to be changed back (currently `stringsAsFactors` and the current directory, because it makes subsequent calls for opening files much shorter). – Scott Ritchie Jun 18 '13 at 00:51
  • 1
    You might want to look at `devtools::with_something` for one strategy. And see https://github.com/hadley/devtools/wiki/Special-environments for more details. – hadley Jun 18 '13 at 12:30