---
title: "Defensive Programming"
author: "Statistical Computing, 36-350"
date: "Monday October 24, 2016"
---

Error checking now avoid bugs later
===

Last week we learned "reactive" strategies, to fix bugs. Why not be "proactive": write a bit of extra code now, to check that inputs/relevant variables are in order, to prevent bugs later? This is the idea behind **defensive programming**

Main principle: "fail fast", i.e., to terminate whenever something appears to be wrong and may cause bugs downstream

(Editorial comment: not everyone is a proponent of defensive programming; there are some downsides, but it's a good thing to know about, and use in moderation. And it's very relevant when we're writing functions to read in/deal with data, our focus the rest of this week!)

Simple example
===

```{r, error=TRUE}
add.up.inv.powers = function(n) {
  return(sum((1:n)^(1/1:n)))
}

add.up.inv.powers(10) # Working well
add.up.inv.powers(-10) # Uh oh! Why NaN?
add.up.inv.powers("R") # Oh boy, error
```

`stop()`, `warning()`
===

The functions `stop()` and `warning()` provide the most basic mechanism for error checking in R. They can be used inside a function to throw an error and a warning, respectively. As in:

```
my.fun = function(arg1, arg2, arg3) {
  # Perhaps some initial code
  if (cond) { # cond is some condition we're checking
    stop("Oh no! The condition is not true!")
  }
  # Some further code here
}
```

Note: throwing an error terminates the function, but throwing a warning does not

Back to example
===

Let's add in some functionality to check whether the input is a positive integer, and if not, throw an error

```{r, error=TRUE}
add.up.inv.powers = function(n) {
  # Check whether n is a positive integer, otherwise throw an error
  # (Note the lazy OR, only if n==is.integer(n) does n <= 0 get evaluated)
  if (n != as.integer(n) || n <= 0) stop("n must be a positive integer")
  return(sum((1:n)^(1/1:n)))
}

add.up.inv.powers(10) # Working well
add.up.inv.powers(-10) # Yay! We caught this
add.up.inv.powers("R") # Still an error!
```

(Continued)
===

Error checking is not always trivial! The error we got in the last call was because we were were trying to compare `n` to `as.integer(n)` when `n` was a string ("R", in the last function call), and `as.integer(n)` was returning NA

The fix: use another lazy OR at the front, to check whether we have a numeric variable

```{r, error=TRUE}
add.up.inv.powers = function(n) {
  # Check whether n is a positive integer, otherwise throw an error
  # (Note the lazy ORs below)
  if (!is.numeric(n) || n != as.integer(n) || n <= 0) {
    stop("n must be a positive integer")
  }
  return(sum((1:n)^(1/1:n)))
}

add.up.inv.powers(10) # Working well
add.up.inv.powers(-10) # Yay! We caught this
add.up.inv.powers("R") # Yay! We caught this
```

(Continued)
===

Finally, depending on what we intend to use this function for, we might just throw a warning instead of an error, and return NA (or some default value) to signal that we couldn't compute the expression

```{r}
add.up.inv.powers = function(n) {
  # Check whether n is a positive integer, otherwise throw an error
  # (Note the lazy ORs below)
  if (!is.numeric(n) || n != as.integer(n) || n <= 0) {
    warning("n must be a positive integer; returning NA")
    return(NA)
  }
  return(sum((1:n)^(1/1:n)))
}

add.up.inv.powers(10) # Working well
add.up.inv.powers(-10) # Yay! We caught this
add.up.inv.powers("R") # Yay! We caught this
```

`tryCatch()`
===

The function `tryCatch()` allows us to do error handling in R. We can use it to try to run an expression, and if we encounter any error then we can *catch* it, i.e., we can handle the error in a custom way. As in:

```
tryCatch({
  # Some code goes here that could possibly throw errors
  }, error = function(e) {
  # Some code goes here for dealing with the error e
  return(NULL) # Customary; we could also return NA
  })
```

Error handling example
===

```{r}
summarize.some.cols = function(mat, col.inds) {
  out = tryCatch({
    apply(mat[,col.inds,drop=FALSE], MARGIN=2, FUN=summary)
  }, error = function(e) {
    cat("Encountered the following error:\n")
    cat("\t", e$message, "\n")
    cat("Returning NULL")
    return(NULL)
  })
  # If out is NULL, then we must have run the error function, so let's 
  # just return NULL 
  if (is.null(out)) return(NULL)
  
  # If we get down here, then the apply statement succeeded. Let's assign
  # some column names before returning
  colnames(out) = paste("Col", col.inds)
  return(out)
}

mat = matrix(rnorm(16), 4, 4)
summarize.some.cols(mat, 1:2) # Works
summarize.some.cols(mat, 3) # Works
summarize.some.cols(mat, 3:7) # Caught an error!
```