【译文】使用Rstudio调试代码(debug)

作者 Jonathan McPherson

译者 钱亦欣

引言

在R语言中dubug是个广泛讨论的话题,本文将聚焦于Rstudio内集成的debug工具。如果你想了解更多这个领域的内容,请参考下面这篇Hadley Wickham的文章。
Debugging, condition handling, and defensive programming

大体上说,debug是被设计开帮助你发现代码中的问题,为了达成这个目的,你需要:

  1. 运行代码
  2. 在你怀疑有问题的地方停止运行
  3. 在问题点逐步检查代码

我们将详细讨论后面两个步骤

进入调试模式(stopping)

为进入调试模式,我们要告知R*何时*停止运行代码。R中没有暂停的功能 (并且大部分计算速度太快,就算有这么个特性也没啥用),所以你需要在启动计算之前先设置好暂定的点。

有好几种方法可以实现这样的功能,你需要根据情况选择最合适的方法。

1. 在某一行停止

编辑断点

最常用同时也是最简单的方法是在代码的某一行设置一个断点。在Rstudio的代码编辑窗里,在行号的左侧点击一下,或者按下Shift+F9就能在改行设置一个断点。

我们将其命名为”编辑器断点”。编辑器断点即点即用,并且不需要改动代码(和下文提及的浏览器断点不同)。它的作用机理是在R的函数对象内插入一些代码,这样含有这些代码的函数就会在环境窗显示一个红点,表名它们含有断点。

如果这个函数不存在(比如你忘记source了),或者函数与编辑器的内容不一致(比如在最后一次source后又改动了脚本),这个断点就会被延后。

上图的空心红点就表名了这一情况,大部分情况下,你再次source一下就能解决这个问题了。

浏览器断点

R中的函数 browser() 会中断代码的执行并调出环境浏览器。你可以在任意需要设置断点的地方放置 broswer() 来进行调试。下面的例子就是在一个函数要返回 TRUE 时终止代码。

和编辑器断点不同,broswer() 本身就是代码的一部分,所以你需要改动你的脚本来实现断点。它调出的环境浏览器可是试做最底层的调试工具,所有其他的调试方法都会用到它。broswer() 函数不需要其他特殊工具,可以在任何编辑器断点不起作用的时候使用。

broswer() 也常用来设置条件断点。举个例子,如果你要在上百次的循环后启动调试模式:

for (i in 1:1024) {
  start_work()
  if (i == 512)
    browser()
  finish_work()
}

在函数执行时停止

针对你想要调试的代码有相应的 .R 文件,使用编辑器断点或 broswer() 函数可以很方便地调试。但有时,你不需要源文件也能调试代码。

在这个情况下,你可以给那个函数立个flag,相当于在函数被声明前就设置好断点。它不会改变函数本身,却能在函数运行时立马启动调试状态。

使用R函数 debugonce() 可以给函数立调试的flag。如果你想调试tdevtools::install():

> debugonce(devtools::install)

debugonce() 会设置一个一次性断点,也就是说这个函数在下次运行时而不是在运行之后进入调试模式。如果你想在一个函数每次运行时都执行调试,就对那个函数调用 debug() 函数,当你不想每次都debug了就调用 undebug() 。当然,我并不推荐这个模式,它可能让你陷入无限调试的死循环。

在错误发生时停止

如果你在诊断一个特定的错误,你可以让Rstudio在这个错误发生的时候停下来。点击Debug -> On Error 并把这个值从 “Error Inspector” 改为 “Break in Code”。

为了让调试器在错误发生的任意时间地点都能启动,Rstudio 会在你的代码不在栈堆的时候不启动调试模式。如果你发现它漏掉了你想捕捉的错误,请到Tools -> Global Options把 “Use debug error handler only when my code contains errors” 前面的勾去掉。

如果你想一有错误就启动调试,请按下方代码修改配置:

> options(error = browser())

这个命令会修改 Rsutido 的默认配置,所有错误都会变得格外恼人,所以当你结束调试记得要修改回之前的设置 options(error = NULL)。

使用调试器

一旦你的代码停止运行了,IDE 会自动进入调试模式,提供给你多种工具来检测并调整你的程序,让我们来逐一介绍。

环境窗

通常你在用R写代码时都是在和全局环境交互,它包括了一系列的函数、数据和值等对象。当你进入调试模式,IDE会把工作环境转变到函数内部,你在环境窗看到的对象其实都是属于函数的内部环境,这是你在控制台输入的命令都是在函数内部执行的。

本案例中,灰色的值都是没有执行的函数参数。

局部环境对象列表的上方是你当前所处环境栈堆的下拉列表。它显示了你当前所属环境的继承关系。

大部分时间它只会包括当前函数,全局环境和一些包的命名空间。但如果你正在写一个包或者嵌套函数,它会包含有额外的入口。你可以点击下拉列表里的任何对象来查看对应环境的内容。

如果你看这段话有些糊里糊涂,没关系大部分时间你无须了解环境栈堆。如果你想学习这方面的知识看男生的Environments就可以。

追溯 (调用栈堆)

栈堆回溯与环境窗共用空间,它会展示代码是如何执行到当前节点的,从第一个被调用 (栈底)函数到最后一个 (栈顶)。

在大部分语言中这个过程叫调用栈堆,Rstudio 将其称作追溯,用 traceback() 就可以执行。

你可以在调用栈堆的下拉框里选择任何函数来检查环境和函数代码的执行位点。请注意此时的选择不会改编控制台的现有环境,如果你需要这么做,使用下方介绍的 recover() 函数。

大部分时间你只对自己的代码的栈堆感兴趣,所以 Rstudio 默认隐藏了内部函数 (就是那些没有 .R 文件的函数) 是的调用栈堆的过程更干净。勾选上View Internals 可以显示内部函数,比如在 tryCatch 代码块内部,你可以展开来看 R 的异常处理函数 (灰色部分) 。

代码窗

代码窗显示了你当前正在执行的函数,将被执行的代码行会用黄色高亮。

如果 Rstudio 无法找到和函数对应的 .R 文件,它会将代买呈现在 Source Viewer 里。这种情况会在 .R 文件不存在或者函数对象和 .R 文件中的定义不一致时出现。

控制台

在调试模式下,你会发现 R 的控制台有两大变化,第一是提示符变了:

Browse[1]> 

这个提示符代表你在 R 的环境浏览器中。

调试模式的控制台支持普通模式的所有命令,只有一点点不同:

  1. 命令是在当前环境被执行的,也就是说如果你的函数有个变量 x ,那么在提示符后输入 x 就会显示对应的值 (使用 ls() 可以查看所有变量)。
  2. 在控制台按下回车键可以执行当前代码行并跑到下一行,对于逐行运行而言很方便。
  3. 提供了一系列调试命令 (详见下文)

如果你想在控控制台中和不同的函数交互,使用 recover() 来列出运行中的函数,然后从中选择就可以。

第二个变化是顶部的工具栏变了:

工具栏提供的很多按钮让你方便地发出各类调试指令 (详见下表) ,这和直接在控制台输入那些命令并无不同,所以有了它你能省下不少时间。

调试命令

命令 快捷键 说明
n or Enter F10 Execute next statement
s Shift+F4 Step into function
f Shift+F6 Finish function/loop
c Shift+F5 Continue running
Q Shift+F8 Stop debugging

所有命令的说明都在 browser() 的帮助文档里,直接查看就行。

特殊情况

一般来说你都会直接调试独立的函数和脚本,但如果你调试的代码是一个大项目的一部分,这里有4种情况值得注意:

调试外部函数

你或许已经注意到 Rstudio (还有R本身,通过 setBreakpoint()) 在设置断点时会改动函数,那么在函数 外部 设置断点会怎样呢?

最常见的用例是在执行 source() 时停下,以检查 R 脚本的执行状况。R 内建的 source() 并没有这个功能,好在 Rstudio 有名为 debugSource() 的命令来实现这一功能。如果一个文件在函数外有断点,你需要用 debugSource() 来代替 source() 。Rstudio中的 Source命令会自动帮你完成这个替换。

值得注意的是,debugSource 函数是非递归的。如果你在 file2.R 中 调用了 source(“file1.R”),然后执行了 debugSource(“file2.R”),你会在 file2.R 中开启调试模式,file1.R 则不会。这能是的你的代码在调试时保持独立性。

在包内调试

断点也可以直接设立在包内的代码中,主要的差别在于你需要把包更新到最新版本,否则 Rstudio 会发出 warning。

为了高效地在包内调试,你要确定那个包开启了 –with-keep.source 选项。这个选项对于新的包而言默认开启,不然就在 Tools -> Project Options -> Build Tools 内设置。

当一个断点被设置在脚本中的一个包内,如果这个包没加载,Rstudio 会自动忽略这个断点。

在 Shiny 应用上调试

Shiny 应用调试起来有些麻烦,因为无法事先设置断点,哪些需要被调试的函数只有在应用被执行后才存在。因此 Shiny 应用中只有在 shinyServer() 函数内的断点才会生效,ui.R, global.R 或 Shiny 中使用的其他 .R 文件中都无法设置断点。

最后,请记住 Shiny 的栈堆中有许多基础组件,在调试到你的代码之前,确实有很多组件要执行。

在 RMarkdown 文档中调试

断点在 RMarkdown 文档的代码块中不会起作用,所以你需要用 broswer() 函数来终止代码块。

默认地,Rstudio 会启用一个独立的 R 进程来渲染你的 RMarkdown 文档。这么做好处不少:将文档和现有的 R 进程独立使得它可以重复生产,并且让 UI 和控制台在文档渲染时保持响应。然而,调式模式只能在 R 的主进程中启动,所以你需要用直接对你的文件调用 rmarkdown::render() 函数。

> rmarkdown::render("~/mydocs/doc.Rmd") 

最后,由于RMarkdown 的代码块不包含 source references ,大部分调试模式的可视化工具都无法使用,例如在编辑器里你看不到高亮的当前行,大部分的调试工作就只能在控制台里完成。

更多资料

Introduction to Debugging in R (video, about 11 minutes)

R function documentation: browser(), debug(), debugonce(), options(error = …), recover(), setBreakpoint(), traceback().

Debugging, condition handling, and defensive programming by Hadley Wickham

Source References (R Journal article), by Duncan Murdoch

R语言-调试代码

原文链接:https://support.rstudio.com/hc/en-us/articles/205612627-Debugging-with-RStudio

你可能感兴趣的:(译文)