19.5.3 跟踪调试
R和RStudio提供了很好的跟踪运行程序的能力。
R的browser()命令可以用在程序中,
命令进入跟踪调试;
RStudio的源文件显示界面可以用鼠标点击定义跟踪调试位置。
函数定义一般都包含多行,所以一般不在命令行定义函数,
而是把函数定义以及较长的程序写在源程序文件中,
用source命令运行。
用source命令调入运行的程序与在命令行运行的效果基本相同,
这样定义的变量也是全局变量。
考虑如下函数定义:
f
for(i in 1:n){
s
}
}
这个函数定义有许多问题。
用一个测试输入调用f,发现有错误:
print(f(1:5))
## Error in 1:n : NA/NaN参数
简单的函数可以直接仔细检查发现错误,
用cat, print等输出中间结果查找错误。
R提供了一个browser()函数,
在程序中插入对browser()函数的调用,
可以进入跟踪调试状态,
可以实时地查看甚至修改运行时变量的值。
在RStudio的编辑窗口中打开.R源程序文件,
在某一程序行行号左端的空白处用鼠标单击,
就可以设定某一行为端点,
在用source命令运行到该处时就可以进入跟踪调试状态。
加入browser()命令后的程序如:
f
browser()
for(i in 1:n){
s
}
}
程序运行遇到browser()函数或设定的断点时程序进入跟踪调试状态,
命令行的提示符变成“Browse[1]>”。
这个命令行的环境一般不再是全局环境,
而是断点所在的函数的运行环境,
可以看到函数的局部变量。
可以在调试环境中用命令去查看当前定义的变量值、逐个命令地运行,
但是用RStudio则可以更方便地执行这些操作。
在调试命令行,可以使用如下命令:
输入变量名查看变量值;
用n命令或者换行键逐句运行;
用s命令跟踪进调用的函数内部逐句运行;
用f命令快速执行到循环末尾或者函数末尾;
用c命令恢复正常运行,不再跟踪;
用Q命令强行终止程序运行。
进入调试状态后,
RStudio界面提供了相应的支持。
这时RStudio的命令行窗格(Console)将会显示用于控制运行的图标,
包括执行下一语句(Next)、跟踪进入要调用的函数运行(Step into)、执行到函数末尾或者循环末尾(Finish)、不再跟踪继续正常运行(Continue)、终止运行(Stop)。
同时,
在RStudio的Environment窗格中会显示当前执行的命令所在的运行环境的内容,
包括函数内的局部变量;
如果点击其中的下拉菜单还可以显示函数的各层父环境。
在同一窗格中还会显示向后追踪(Traceback),
即函数是如何被调用的。
为调试如上函数f的程序,
在定义中插入对browser()的调用如:
f
browser()
for(i in 1:n){
s
}
}
当在RStudio中调试运行时,
程序编辑窗口将显示当前要运行的程序行,
用命令行窗口(Console)的Next快捷图标可以运行到下一行。
命令行的显示如:
> print(f(1:5))
Called from: eval(expr, p)
Browse[1]> n
debug在D:/disk/projects/Rbookweb/tmp2.R#2: for (i in 1:n) {
s
}
Browse[2]>
继续用“Next”图标运行,命令行结果如:
Browse[2]> n
Error in 1:n : NA/NaN参数
发现是在for(i in 1:n)行遇到未定义的变量n。
在源文件中把出错行改为for(i in 1:length(x)),
再次运行,
发现在运行s
遇到“错误: 找不到对象’s’”。
这是忘记初始化引起的。
在for语句前添加s
f
browser()
s
for(i in 1:length(x)){
s
}
}
再次运行,
在跟踪到循环时,
为了避免繁琐的跟踪过程,
可以用“执行到函数末尾或者循环末尾”快捷图标或命令行的f命令,
或者“Continue”快捷图标或命令行的c命令。
程序不显示错误但是也没有显示结果为NULL而不是我们期望得输入元素之和。
检查可以看出错误是忘记把函数返回值写在函数定义最后。
在函数定义最后添加s一行,
再次运行,程序结果与手工验算结果一致。
函数变成
f
browser()
n
s
for(i in 1:n){
s
}
s
}
自定义函数应该用各种不同输入测试其正确性和稳定性。
比如,上面的函数当自变量x为零长度向量时应该返回0才合适,
但是上面的写法会返回一个numeric(0)结果,
这个结果表示长度为零的向量:
f(numeric(0))
## Called from: f(numeric(0))
## Browse[1]> c
## numeric(0)
程序输入了零长度自变量,
我们期望其输出为零而不是numeric(0)。
在自变量x为零长度时,
函数中for(i in 1:length(x)应该一次都不进入循环,
跟踪运行可以发现实际对i=1和i=0共运行了两轮循环。
把这里的1:length(x)改成seq_along(x)解决了问题,
seq_along(x)生成x的下标序列,
如果x是零长度的则下标序列为零长度向量。
函数不需要修改后,
可以把对browser()的调用删除或注释掉,
在RStudio中关闭断点。
函数最终修改为:
f
s
for(i in seq_along(x)){
s
}
s
}
这里只是用这个简单函数演示如何调试程序,
求向量元素和这个问题本身是不需要我们去定义新函数的,
sum函数本来就是完成这样的功能。
实际上,许多我们认为需要自己编写程序做的事情,
在R网站都能找到别人已经完成的扩展包。