Skip to content

Debugging

Some general notes and tips on debuggers are here.

The Upshot

I almost never use a debugger (put tastes vary).

My main debugging tool is Infiltrator.jl.

It works like Matlab's keyboard command (though it's a bit less powerful).

Example: examples/infiltrator.jl.

Another good option is Exfiltrator.jl (which is all of 37 lines of code!).

Stacktraces

A stacktrace shows the stack of functions that were called to get to a particular line of code.

You mostly see them where there is an error. Here is a contrived example:

julia> foo(x) = sin(UInt8(x))
foo (generic function with 2 methods)

julia> foo(1234567)
ERROR: InexactError: trunc(UInt8, 1234567)
Stacktrace:
 [1] throw_inexacterror(f::Symbol, #unused#::Type{UInt8}, val::Int64)
   @ Core ./boot.jl:602
 [2] checked_trunc_uint
   @ ./boot.jl:632 [inlined]
 [3] toUInt8
   @ ./boot.jl:694 [inlined]
 [4] UInt8
   @ ./boot.jl:754 [inlined]
 [5] foo(x::Int64)
   @ Main ./REPL[16]:1
 [6] top-level scope
   @ REPL[17]:1

Stacktraces are often very long and look confusing. There are 2 key lines:

  1. ERROR: Inexacterror: ...: this tells you what went wrong.
  2. [5] foo(x::Int64): this is the line of your code that caused the error.

Entries [1] to [4] are Base code that you did not write. This will generally be correct and is often irrelevant.

The key is to patiently parse the stacktrace until you understand what actually went wrong.

  • InexactError tells you that something was truncated.
  • It even tells you the instruction that went wrong: trunc(Uint8, 1234567).
  • In words this says: You told me to convert a big Int64 to a UInt8. This caused an overflow.

Common mistakes

Passing arguments in the wrong order.

Often functions have lots of input arguments. This is generally a sign of poor design, but sometimes hard to avoid in economic applications.

It is easy to confuse the order and write myfun(b,a) instead of myfun(a,b).

To avoid this: check that inputs have admissible values.

Reusing variable names.

It is easy to use a variable name twice without noticing. This can produce tricky bugs.

One way of avoiding this: keep your functions short. Some experts advocate that functions should be 4 lines of code or less. This is probably not realistic, but you get the idea.

Indexing problems.

It is easy to make mistakes when extracting elements from matrices. This is especially true for code that wraps a loop into a single line of code.

For example, this is easy to read:

for ix = 1 : nx
  zV[ix] = xV[ix+2] + yV[nx + 2 - ix];
end

This is the same thing, more compact but harder to read:

zV = xV[3 : nx+2] .+ yV[nx+1 : -1 : 2];

Tip: Write out code explicitly. Once it works, one can still make it faster (if that is even worthwhile). You can keep the slow version in a unit test.

Note that vectorizing code does not improve speed in Julia (unless you exploit the parallel execution capabilities of modern CPUs).

Another common indexing mistake is to use too few arguments. For example:

    x = rand([3,4]); 
    y = x(3);

This should produce a syntax error, but it does not. Instead, it flattens x into a vector and then takes the 3rd element.