Debugging and defensive programming

MACS 30500 University of Chicago

Bugs

Not the kind of bugs we’re looking for

Bugs

  • “An error, flaw, failure or fault in a computer program or system that causes it to produce an incorrect or unexpected result, or to behave in unintended ways.”
  • Computers are powerful tools that are incredibly stupid
  • Debugging has two goals:
    • Fix bugs once they occur
    • Prevent bugs from occurring in the first place

Defensive programming

  • Style guide
  • Failing fast

Writing code

Programming Language
Scripts Essays
Sections Paragraphs
Lines Breaks Sentences
Parentheses Punctuation
Functions Verbs
Variables Nouns

weve grown used to wonders in this century its hard to dazzle us but for 25 years the united states space program has been doing just that weve grown used to the idea of space and perhaps we forget that weve only just begun were still pioneers they the members of the Challenger crew were pioneers and i want to say something to the school children of America who were watching the live coverage of the shuttles takeoff i know it is hard to understand but sometimes painful things like this happen its all part of the process of exploration and discovery its all part of taking a chance and expanding mans horizons the future doesnt belong to the fainthearted it belongs to the brave the challenger crew was pulling us into the future and well continue to follow them the crew of the space shuttle challenger honored us by the manner in which they lived their lives we will never forget them nor the last time we saw them this morning as they prepared for the journey and waved goodbye and slipped the surly bonds of earth to touch the face of god

We’ve grown used to wonders in this century. It’s hard to dazzle us. But for 25 years the United States space program has been doing just that. We’ve grown used to the idea of space, and perhaps we forget that we’ve only just begun. We’re still pioneers. They, the members of the Challenger crew, were pioneers.

And I want to say something to the school children of America who were watching the live coverage of the shuttle’s takeoff. I know it is hard to understand, but sometimes painful things like this happen. It’s all part of the process of exploration and discovery. It’s all part of taking a chance and expanding man’s horizons. The future doesn’t belong to the fainthearted; it belongs to the brave. The Challenger crew was pulling us into the future, and we’ll continue to follow them….

The crew of the space shuttle Challenger honoured us by the manner in which they lived their lives. We will never forget them, nor the last time we saw them, this morning, as they prepared for the journey and waved goodbye and ‘slipped the surly bonds of earth’ to ‘touch the face of God.’

File names

# Good
fit-models.R
utility-functions.R
gun-deaths.Rmd

# Bad
foo.r
stuff.r
gun deaths.rmd

Object names

# Good
day_one
day_1

# Bad
first_day_of_the_month
DayOne
dayone
djm1

Overwriting objects

# Bad
T <- FALSE
c <- 10
x <- 1:10
mean(x)
## [1] 5.5
# create new mean function
mean <- function(x) sum(x)
mean(x)
[1] 55

Spacing

# Good
average <- mean(feet / 12 + inches, na.rm = TRUE)

# Bad
average<-mean(feet/12+inches,na.rm=TRUE)

Curly braces

# Good

if (y < 0 && debug) {
  message("Y is negative")
}

if (y == 0) {
  log(x)
} else {
  y ^ x
}

# Bad

if (y < 0 && debug)
message("Y is negative")

if (y == 0) {
  log(x)
} 
else {
  y ^ x
}

It’s ok to leave very short statements on the same line:

if (y < 0 && debug) message("Y is negative")

Line length

# Good
scdbv <- scdbv %>%
  mutate(chief = factor(chief,
                        levels = c("Jay", "Rutledge", "Ellsworth",
                                   "Marshall", "Taney", "Chase",
                                   "Waite", "Fuller", "White",
                                   "Taft", "Hughes", "Stone",
                                   "Vinson", "Warren", "Burger",
                                   "Rehnquist", "Roberts")))

# Bad
scdbv <- mutate(scdbv, chief = factor(chief, levels = c("Jay", "Rutledge", "Ellsworth", "Marshall", "Taney", "Chase", "Waite", "Fuller", "White", "Taft", "Hughes", "Stone", "Vinson", "Warren", "Burger", "Rehnquist", "Roberts")))

Indentation

# pure function
long_function_name <- function(a = "a long argument", 
                               b = "another argument",
                               c = "another long argument") {
  # As usual code is indented by two spaces.
}

# in a mutate() function
scdbv <- scdbv %>%
  mutate(majority = majority - 1,
         chief = factor(chief,
                        levels = c("Jay", "Rutledge", "Ellsworth",
                                   "Marshall", "Taney", "Chase",
                                   "Waite", "Fuller", "White",
                                   "Taft", "Hughes", "Stone",
                                   "Vinson", "Warren", "Burger",
                                   "Rehnquist", "Roberts")))

Assignment

# Good
x <- 5
# Bad
x = 5

Comments

# Section One ---------------------------------
 
# Section Two =================================
 
### Section Three #############################

Auto-formatting in RStudio

  • Code > Reformat Code (Shift + Cmd/Ctrl + A)
  • Code > Reindent Lines (Cmd/Ctrl + I)

Exercise: style this code

Fatal errors

addition <- function(x, y){
  if(!is_numeric(c(x, y))) stop("One of your inputs is not a number.")
  
  x + y
}

addition(3, "abc")
## Error in addition(3, "abc"): One of your inputs is not a number.

Warnings

logit <- function(x){
  log(x / (1 - x))
}

logit(-1)
## Warning in log(x/(1 - x)): NaNs produced
## [1] NaN

Warnings

logit <- function(x){
 if(x < 0 | x > 1) stop('x not between 0 and 1')
 log(x / (1 - x))
}

logit(-1)
## Error in logit(-1): x not between 0 and 1

Warnings

logit <- function(x){
  x <- ifelse(x < 0 | x > 1, NA, x)
  if(is.na(x)) warning('x not between 0 and 1')
  log(x / (1 - x))
}

logit(-1)
## Warning in logit(-1): x not between 0 and 1
## [1] NA

Messages

ggplot(diamonds, aes(carat, price)) +
  geom_point() +
  geom_smooth()
## `geom_smooth()` using method = 'gam'

Suppressing messages

demo_message <- function() message("This is a message")
demo_message()
## This is a message
suppressMessages(demo_message())  # no output

demo_print <- function() print("This is a message")
demo_print()
## [1] "This is a message"
suppressMessages(demo_print())  # still output
## [1] "This is a message"

Exercise: build a function with conditions

The call stack

f <- function(a) g(a)
g <- function(b) h(b)
h <- function(c) i(c)
i <- function(d) "a" + d
f(10)
## Error in "a" + d: non-numeric argument to binary operator
traceback()
# 4: i(c) at exceptions-example.R#3
# 3: h(b) at exceptions-example.R#2
# 2: g(a) at exceptions-example.R#1
# 1: f(10)

Condition handling

  • Unexpected errors
  • Expected errors
  • try()
  • safely()

Ignore errors with try()

f1 <- function(x) {
  log(x)
  10
}
f1("x")
## Error in log(x): non-numeric argument to mathematical function

Ignore errors with try()

f2 <- function(x) {
  try(log(x))
  10
}
f2("a")
## [1] 10

Ignore errors with try()

try({
  a <- 1
  b <- "x"
  a + b
})

Dealing with failure using safely()

  • Adverb
  • Always returns a list with two elements
    1. result
    2. error
  • More predictable than try()

Dealing with failure using safely()

safe_sqrt <- safely(sqrt)
str(safe_sqrt(9))
## List of 2
##  $ result: num 3
##  $ error : NULL
str(safe_sqrt("a"))
## List of 2
##  $ result: NULL
##  $ error :List of 2
##   ..$ message: chr "non-numeric argument to mathematical function"
##   ..$ call   : language sqrt(x = x)
##   ..- attr(*, "class")= chr [1:3] "simpleError" "error" "condition"

safely() and map()

x <- list("a", 4, 5)

# unsafely square root
y <- x
  map(sqrt)
## Error in as_mapper(.f, ...): argument ".f" is missing, with no default
# safely log
y <- x %>%
  map(safely(sqrt))
str(y)
## List of 3
##  $ :List of 2
##   ..$ result: NULL
##   ..$ error :List of 2
##   .. ..$ message: chr "non-numeric argument to mathematical function"
##   .. ..$ call   : language sqrt(x = x)
##   .. ..- attr(*, "class")= chr [1:3] "simpleError" "error" "condition"
##  $ :List of 2
##   ..$ result: num 2
##   ..$ error : NULL
##  $ :List of 2
##   ..$ result: num 2.24
##   ..$ error : NULL

transpose()

y <- y %>%
  transpose()
str(y)
## List of 2
##  $ result:List of 3
##   ..$ : NULL
##   ..$ : num 2
##   ..$ : num 2.24
##  $ error :List of 3
##   ..$ :List of 2
##   .. ..$ message: chr "non-numeric argument to mathematical function"
##   .. ..$ call   : language sqrt(x = x)
##   .. ..- attr(*, "class")= chr [1:3] "simpleError" "error" "condition"
##   ..$ : NULL
##   ..$ : NULL

Extract the values of x where y is an error

is_ok <- y$error %>%
  map_lgl(is_null)
x[!is_ok]
## [[1]]
## [1] "a"

Extract the values of y that are ok

y$result[is_ok] %>%
  flatten_dbl()
## [1] 2.000000 2.236068

possibly()

x %>%
  map_dbl(possibly(sqrt, NA))
## [1]       NA 2.000000 2.236068

quietly()

x <- list(1, -1)
x %>%
  map(quietly(sqrt)) %>%
  str()
## List of 2
##  $ :List of 4
##   ..$ result  : num 1
##   ..$ output  : chr ""
##   ..$ warnings: chr(0) 
##   ..$ messages: chr(0) 
##  $ :List of 4
##   ..$ result  : num NaN
##   ..$ output  : chr ""
##   ..$ warnings: chr "NaNs produced"
##   ..$ messages: chr(0)

Exercise: handle conditions using safely()