Skip to content
AgileLogics
6 min read

Error Handling and Defensive Code

Prerequisites: Functions

Good functions check their inputs and fail with clear messages; good scripts handle the errors they can recover from.

Signalling problems: stop, warning, message

safe_sqrt <- function(x) {
  if (!is.numeric(x)) {
    stop("`x` must be numeric, not ", class(x)[1])
  }
  if (any(x < 0)) {
    warning("Negative values replaced with NA")
    x[x < 0] <- NA
  }
  sqrt(x)
}

safe_sqrt(c(4, -1, 9))
[1]  2 NA  3
Warning message:
In safe_sqrt(c(4, -1, 9)) : Negative values replaced with NA
  • stop() raises an error and stops.
  • warning() flags a problem but continues.
  • message() prints information (and can be silenced with suppressMessages()).

stopifnot() checks several conditions briefly: stopifnot(is.numeric(x), length(x) > 0).

Catching errors: tryCatch

tryCatch() runs code and decides what happens if an error or warning occurs:

parse_number <- function(text) {
  tryCatch(
    {
      value <- as.numeric(text)
      value
    },
    warning = function(w) {
      message("Could not parse '", text, "'")
      NA
    }
  )
}

parse_number("42")
parse_number("forty")
[1] 42
Could not parse 'forty'
[1] NA

as.numeric("forty") normally produces a warning; here the warning handler takes over, prints a message, and returns NA. Use error = function(e) ... to handle errors the same way, and conditionMessage(e) to get the message text. A finally = ... argument runs clean-up code no matter what.

try: continue past an error

result <- try(log(-1:1 * "a"), silent = TRUE)
class(result)
cat("The script continued\n")
[1] "try-error"
The script continued

Common mistakes

  • Wrapping large blocks in tryCatch that silently return NA — you lose information about what went wrong. Handle only the errors you expect, and log or report the rest.
  • Vague error messages. stop("invalid input") helps nobody; stop("age must be between 0 and 120, got ", age) saves time.