knitr::opts_chunk$set(eval = TRUE, error = TRUE)

Why raise conditions?

Conditions in the wild

library(readr)
log(-1)
this_object_DNE
readr::read_csv("inst/extdata/baxter.metadata.csv")

How to raise conditions

library(rlang)
message("This is a benign message.")
warning("Something could be wrong, but we can continue.")
abort("The program can't go on, burn it all down!")

4th type of condition: Interrupt: when you press "Stop" or ctrl-c.

Example

my_log <- function(x, base = exp(1)) {
  if (!is.numeric(x)) {
    abort(paste0(
      "`x` must be numeric; not ", typeof(x), "."
    ))
  }
  if (!is.numeric(base)) {
    abort(paste0(
      "`base` must be numeric; not ", typeof(base), "."
    ))
  }

  base::log(x, base = base)
}
my_log('abc')

How to handle conditions: tryCatch

tryCatch(
  error = function(cnd) {
     # do this if you catch an error
    code_to_run_when_error_is_thrown
  },
  {
  # try this
  code_to_run_while_handlers_are_active
  }
)

Why? When you want to modify the default behavior.


tryCatch doesn't return to the try after Catching an error

tryCatch(
  error = function(cnd) { # catch an error
      message("Oh no, there's an error!")     
    },
  { # try this
    bad_thing_happened = TRUE
    if( bad_thing_happened) {
        abort("We've gotta kill the program")
    }
    message("This code is never run!")
  }
)
# the code outside the tryCatch block continues on afterward

You can access the condition message in the Catch

tryCatch(
  error = function(cnd) { # catch an error
      message("Oh no, there's an error!")      
      message(conditionMessage(cnd))
    },
  { # try this
    bad_thing_happened = TRUE
    if( bad_thing_happened) {
        abort("We've gotta kill the program")
    }
    message("This code is never run!")
  }
)
# the code outside the tryCatch block continues on afterward

How to handle conditions: withCallingHandlers

withCallingHandlers(
  warning = function(cnd) {
    code_to_run_when_warning_is_signalled
  },
  message = function(cnd) {
    code_to_run_when_message_is_signalled
  },
  code_to_run_while_handlers_are_active
)

withCallingHandlers returns to the try after a Catch

withCallingHandlers(
  warning = function(cnd) {
      message("Caught a warning!")
      # do something special
    },
  {
    warning("Something might be wrong.\n")
    message("But let's keep going anyway.")
  }
)

How to write custom conditions

library(glue)  # Write a function that wraps `abort`
abort_bad_argument <- function(arg, must, not = NULL) {
  # create a custom message
  msg <- glue::glue("`{arg}` must {must}")
  # append to the message if `not` was specified
  if (!is.null(not)) {
    not <- typeof(not)
    msg <- glue::glue("{msg}; not {not}.")
  }
  # pass custom metadata to abort
  abort("error_bad_argument", 
    message = msg, 
    arg = arg, 
    must = must, 
    not = not
  )
}

Using our custom condition

my_log <- function(x, base = exp(1)) {
  if (!is.numeric(x)) {
    abort_bad_argument("x", must = "be numeric", not = x)
  }
  if (!is.numeric(base)) {
    abort_bad_argument("base", must = "be numeric", not = base)
  }

  base::log(x, base = base)
}
my_log(3, 'abc')

Best practices

"The best error messages tell you what is wrong and point you in the right direction to fix the problem."

"The goal is to make it as easy as possible for the user to find and fix the problem."

Worst Practices

Activity

Let's write conditions for the code from the Riffomonas minimalR tutorial and work on a few exercises from AdvancedR.

Activity

  1. Clone this repo {bash, eval=FALSE} git clone https://github.com/SchlossLab/exception-handling or if you previously cloned it, pull new commits: {bash, eval=FALSE} cd path/to/repo/ ; git pull
  2. Checkout a new branch {bash, eval=FALSE} git checkout -b descriptive-branch-name
  3. After modifying your part, commit your changes {bash, eval=FALSE} git add . ; git commit -m "descriptive commit message"

Activity Wrap-up

  1. Push your changes {bash, eval=FALSE} git push -u origin descriptive-branch-name
  2. Open a pull request on GitHub, mention your issue number(s), and merge it if there aren't conflicts.

    new P{height=275px} example PR{height=275px}

Additional Reading

Relevant XKCDs:

library(rmarkdown)
rmarkdown::render('exception-handling.Rmd', output_file = 'docs/exception-handling.html')


SchlossLab/exception-handling documentation built on Oct. 30, 2019, 11:50 p.m.