html.json (8301B)
1 { 2 "hash": "c41d31acf029963308762ac94aeb9b3e", 3 "result": { 4 "engine": "knitr", 5 "markdown": "---\nengine: knitr\ntitle: Debugging\n---\n\n## Learning objectives:\n\n- General strategy for finding and fixing errors.\n\n- Explore the `traceback()` function to locate exactly where an error occurred\n\n- Explore how to pause the execution of a function and launch environment where we can interactively explore what’s happening\n\n- Explore debugging when you’re running code non-interactively\n\n- Explore non-error problems that occasionally also need debugging\n\n## Introduction {-}\n\n> Finding bug in code, is a process of confirming the many things that we believe are true — until we find one which is not true.\n\n**—Norm Matloff**\n\n> Debugging is like being the detective in a crime movie where you're also the murderer. \n\n**-Filipe Fortes**\n\n### Strategies for finding and fixing errors {-}\n\n#### Google! {-}\nWhenever you see an error message, start by googling it. We can automate this process with the [{errorist}](https://github.com/coatless-rpkg/errorist) and [{searcher}](https://github.com/coatless-rpkg/searcher) packages. \n\n#### Make it repeatable {-}\nTo find the root cause of an error, you’re going to need to execute the code many times as you consider and reject hypotheses. It’s worth some upfront investment to make the problem both easy and fast to reproduce.\n\n#### Figure out where it is {-}\nTo find the bug, adopt the scientific method: **generate hypotheses**, **design experiments to test them**, and **record your results**. This may seem like a lot of work, but a systematic approach will end up saving you time. \n\n#### Fix it and test it {-}\nOnce you’ve found the bug, you need to figure out how to fix it and to check that the fix actually worked. It’s very useful to have automated tests in place. \n\n## Locating errors {-}\nThe most important tool for finding errors is `traceback()`, which shows you the sequence of calls (also known as the **call stack**) that lead to the error.\n\n- Here’s a simple example where `f()` calls `g()` calls `h()` calls `i()`, which checks if its argument is numeric:\n\n\nWhen we run `f(\"a\")` code in RStudio we see:\n\n\n\n\nIf you click **“Show traceback”** you see:\n\n\n\nYou read the `traceback()` output from bottom to top: the initial call is `f()`, which calls `g()`, then `h()`, then `i()`, which triggers the error. \n\n## Lazy evaluation {-}\nOne drawback to `traceback()` is that it always **linearises** the call tree, which can be confusing if there is much lazy evaluation involved. For example, take the following example where the error happens when evaluating the first argument to `f()`:\n\n\n\n\n\nNote: `rlang::with_abort()` is no longer an exported object from 'namespace:rlang'. There is an [open issue](https://github.com/hadley/adv-r/issues/1740) about a fix for the chapter but no drop-in replacement.\n\n\n## Interactive debugger {-}\nEnter the interactive debugger is wwith RStudio’s **“Rerun with Debug”** tool. This reruns the command that created the error, pausing execution where the error occurred. Otherwise, you can insert a call to `browser()` where you want to pause, and re-run the function. \n\n\n\n`browser()` is just a regular function call which means that you can run it conditionally by wrapping it in an `if` statement:\n\n\n\n\n\n\n## `browser()` commands {-}\n`browser()` provides a few special commands. \n\n\n\n- Next, `n`: executes the next step in the function.\n\n- Step into, or `s`: works like next, but if the next step is a function, it will step into that function so you can explore it interactively.\n\n- Finish, or `f`: finishes execution of the current loop or function.\n\n- Continue, `c`: leaves interactive debugging and continues regular execution of the function. \n- Stop, `Q`: stops debugging, terminates the function, and returns to the global workspace. \n\n\n## Alternatives {-}\nThere are three alternatives to using `browser()`: setting breakpoints in RStudio, `options(error = recover)`, and `debug()` and other related functions.\n\n## Breakpoints {-}\nIn RStudio, you can set a breakpoint by clicking to the left of the line number, or pressing **Shift + F9.** There are two small downsides to breakpoints:\n\n- There are a few unusual situations in which breakpoints will not work. [Read breakpoint troubleshooting for more details](https://support.posit.co/hc/en-us/articles/200534337-Breakpoint-Troubleshooting)\n\n- RStudio currently does not support conditional breakpoints.\n\n## `recover()` {-}\nWhen you set `options(error = recover)`, when you get an error, you’ll get an interactive prompt that displays the traceback and gives you the ability to interactively debug inside any of the frames:\n\n\nYou can return to default error handling with `options(error = NULL)`.\n\n## `debug()` {-}\n\nAnother approach is to call a function that inserts the `browser()` call:\n\n- `debug()` inserts a browser statement in the first line of the specified function. undebug() removes it. \n\n- `utils::setBreakpoint()` works similarly, but instead of taking a function name, it takes a file name and line number and finds the appropriate function for you.\n\n\n## Call stack {-}\nThe call stacks printed by `traceback()`, `browser()` & `where`, and `recover()` are not consistent. \n\n\n\nRStudio displays calls in the same order as `traceback()`. rlang functions use the same ordering and numbering as `recover()`, but also use indenting to reinforce the hierarchy of calls.\n\n## Non-interactive debugging {-}\n\nWhen you can’t explore interactively...\n\n### `callr::r()` {-}\n\n`callr::r(f, list(1, 2))` calls `f(1, 2)` in a fresh session to help diagnose:\n\n- Is the global environment different? Have you loaded different packages? Are objects left from previous sessions causing differences?\n\n- Is the working directory different?\n\n- Is the `PATH` environment variable different?\n\n- Is the `R_LIBS` environment variable different?\n\n### `dump.frames()` {-}\n\n`dump.frames()` is the equivalent to `recover()` for non-interactive code.\n\n\n\n### Print debugging {-}\n\nInsert numerous print statements to precisely locate the problem, and see the values of important variables. Print debugging is particularly useful for compiled code.\n\n\n\n\n### RMarkdown {-}\n\n- If you’re knitting the file using RStudio, switch to calling `rmarkdown::render(\"path/to/file.Rmd\")` instead to run the code in the current session. \n\n- For interactive debugging, you’ll need to call `sink()` in the error handler. For example, to use `recover()` with RMarkdown, you’d put the following code in your setup block:\n\n{height=\"110\"}\n\n\n\n## Non-error failures {-}\nThere are other ways for a function to fail apart from throwing an error:\n\n- A function may generate an unexpected warning. Convert warnings into errors with `options(warn = 2)` and use the the call stack.\n\n- A function may generate an unexpected message. The removal of `with_abort()` from {rlang} breaks this solution.\n\n- A function might never return. \n\n- The worst scenario is that your code might crash R completely, leaving you with no way to interactively debug your code. This indicates a bug in compiled (C or C++) code.\n\n## Link to some useful resources on debugging {-}\n\n- Jenny Bryan's [\"Object of type closure is not subsettable\"](https://github.com/jennybc/debugging#readme) talk from rstudio::conf 2020\n\n- Jenny Bryan and Jim Hester's book: [\"What They Forgot to Teach You About R\"](https://rstats.wtf/debugging-r) Ch12\n\n- Hadley's video on a [minimal reprex for a shiny app](https://www.youtube.com/watch?v=9w8ANOAlWy4) \n", 6 "supporting": [ 7 "22_files" 8 ], 9 "filters": [ 10 "rmarkdown/pagebreak.lua" 11 ], 12 "includes": {}, 13 "engineDependencies": {}, 14 "preserve": {}, 15 "postProcess": true 16 } 17 }