Debugging

Reading: Matloff Chapter 13

Today: Debugging

Principle of confirmation

You wrote a function, and it does something you don’t think it should. Debugging is figuring out why this is.

Aside from syntax errors, bugs are assumptions you made when writing the code that aren’t actually true.

This is Matloff’s principle of confirmation:

Fixing a buggy program is a process of confirming, one by one, that the many things you believe to be true about the code actually are true. When you find that one of your assumptions is not true, you have found a clue about the location (if not the exact nature) of a bug.

Some common causes of bugs

Syntax problems:

data(diamonds)
mean(subset(diamonds$carat), cut == "Ideal")
## Error in subset.default(diamonds$carat): argument "subset" is missing, with no default
mean(subset(diamonds$carat, cut = "Ideal"))
## Error in subset.default(diamonds$carat, cut = "Ideal"): argument "subset" is missing, with no default

Inputs to functions are of a type you didn’t expect:

Scope issues/global variables:

Bug processing

Once you realize you have a bug, there are three steps:

Characterizing the bug:

Localizing the bug

Main debugging operations

Stepping through the source code

Inspecting variables

Setting up a function for debugging

The debug function:

f = function(y, z) {
    x = y^2 - 3 * z^2
    w = 28
    if (x > 0 && a > 0) {
        u = 1 + x
    } else {
        u = 10
    }
    return(u)
}
f(0, 1)
## [1] 10
f(1, 0)
## Error in f(1, 0): object 'a' not found
## try:
## debug(f)
## f(1, 0)

The browser function:

f = function(y, z) {
    x = y^2 - 3 * z^2
    w = 28
    browser(expr = x > 0)
    if (x > 0 && a > 0) {
        u = 1 + x
    } else {
        u = 10
    }
    return(u)
}
f(0, 1)
f(1, 0)

The setBreakpoint function

Commands once you’re in the browser

g = function(a) {
    y = a^2 + 3
    if(y - 10 > 2) {
        return(y)
    } else {
        return(z)
    }
}
f = function(y, z) {
    x = y^2 - 3 * g(z)
    w = 28
    if (x > 0 && a > 0) {
        u = 1 + x
    } else {
        u = 10
    }
    return(u)
}
f(0,1)
debug(f)
f(0, 1)

Debugging after an error

The traceback function:

f = function(y, z) {
    x = y^2 - 3 * z^2
    w = 28
    if (x > 0 && a > 0) {
        u = 1 + x
    } else {
        u = 10
    }
    return(u)
}
f(1, 0)
traceback()
g = function(a) {
    y = a^2 + 3
    if(y - 10 > 2) {
        return(y)
    } else {
        return(z)
    }
}
f = function(y, z) {
    x = y^2 - 3 * g(z)
    w = 28
    if (x > 0 && a > 0) {
        u = 1 + x
    } else {
        u = 10
    }
    return(u)
}
f(1, 0)
traceback()
f(10, 3.1)
traceback()

The debugger function:

options(error = dump.frames)
f(1,0)
debugger()

Example 1

findruns is supposed to find the starting positions of all the runs of 1’s of length k in x:

findruns = function(x, k) {
    n = length(x)
    runs = NULL
    for(i in 1:(n-k)) {
        if(all(x[i:i+k-1] == 1)) {
            runs = c(runs, i)
        }
        
    }
    return(runs)
}
findruns(c(1,0,0,1,1,0,1,1,1),2)
## [1] 3 4 6 7

Example 2

## returns the minimum value of d[i,j], i != j, and
## the row/col attaining that minimum, for square
## symmetric matrix d; no special policy on ties;
## motivated by distance matrices
mind = function(d) {
    n = nrow(d)
    ## add a column to identify row number for apply()
    dd = cbind(d, 1:n)
    wmins = apply(dd[-n, ], 1, imin)
    ## wmins will be 2xn, 1st row being indices and 2nd being values
    i = which.min(wmins[1, ])
    j = wmins[2, i]
    return(c(d[i, j], i, j))
}

## finds the location, value of the minimum in a row x
imin = function(x) {
    n = length(x)
    i = x[n]
    j = which.min(x[(i + 1):(n - 1)])
    return(c(j, x[j]))
}

m = rbind(c(0, 12, 5), c(12, 0, 8), c(5, 8, 0))

Antibugging or defensive coding