调试是一门用来解决代码中意想不到的错误的艺术和科学。
- 致命错误由stop()引起
- 警告由warning()产生
- 消息由message()产生
9.1 调试技巧
确认那些你认为对的的确是对的,知道找到一个错的(本来你认为是对的)。
自动化测试套件。
从知道问题原因的大代码块开始,然后逐渐缩小范围。
错误产生的越快,找到漏洞的速度越快。
产生一个假设,设计实验来测试这个假设,记录你的结果。
9.2 调试工具
不要尝试一开始就编写一个具有所有功能的大函数,可以将所有功能分成很多小的部分分别实现。
9.2.1 确定调用顺序
使用调用栈 show traceback,或者traceback()函数
从下往上阅读调用栈。
traceback()只显示问题发生在哪里,不会告诉你为什么出错。
9.2.2 查看错误
打开交互式调试器的最简单方法是 Rerun with Debug。
下一个将被执行的语句会高亮显示。
- Next (n)执行下一步;
- Stop into(s)如果下一步是函数,那么单步执行这个函数,通过函数的每一行(进入到函数内);
- Finish 结束当前循环或函数的执行;
- Conrtinue 离开交互式调试并继续函数的正常执行;如果已经解决了问题,并检查函数能否正确运行,可以使用。
- Stop Q 停止调试,终止函数。当你找到了问题在哪,准备修复这个漏洞并重载代码时,可以使用。
- Enter 重复前一个命令
- where 输出调用的栈追踪
9.2.3 查看任意代码
使用browser()函数或Rstudio断电。
debug()在指定函数第一行插入一个语句,undebug()去除,使用debugonce()只浏览下一次运行。
9.2.5 其他故障类型
让你的R彻底崩溃,漏洞可能出现在C中
9.3 条件处理
意想不到的错误需要交互式调试来确定问题的根源,有些错误是可以预期到的,希望自动化处理这些错误。
- try()允许我们在错误发生时继续执行代码;
- tryCatch()可以让我们设置一个handler函数,控制在条件发生时应该做什么;
9.3.1 使用try来忽略错误
如果把可能出错的函数放在try()中,错误消息将被输出,但程序继续执行。
如果要在try()中放置长代码,需要用{}将它们括起来。
可以捕获try()的输出,如果执行成果,返回最后结果;如果失败,返回一个try-error类对象。
elements <- list(1:10,c(-1,10),c(T,F),letters)
results <- lapply(elements,log)
results <- lapply(elements,function(x) try(log(x)))
## Warning in log(x): 产生了NaNs
results
## [[1]]
## [1] 0.0000000 0.6931472 1.0986123 1.3862944 1.6094379 1.7917595 1.9459101
## [8] 2.0794415 2.1972246 2.3025851
##
## [[2]]
## [1] NaN 2.302585
##
## [[3]]
## [1] 0 -Inf
##
## [[4]]
## [1] "Error in log(x) : 数学函数中用了非数值参数\n"
## attr(,"class")
## [1] "try-error"
## attr(,"condition")
## <simpleError in log(x): 数学函数中用了非数值参数>
is.error <- function(x) inherits(x,"try-error")
succeeded <- !sapply(results,is.error)
str(results[succeeded])
## List of 3
## $ : num [1:10] 0 0.693 1.099 1.386 1.609 ...
## $ : num [1:2] NaN 2.3
## $ : num [1:2] 0 -Inf
str(elements[!succeeded])
## List of 1
## $ : chr [1:26] "a" "b" "c" "d" ...
try()另一种常用用法是在表达式失败时使用默认值,只需要在try代码块外进行简单的赋值。
9.4 防御性编程
防御性编程就是当有意想不到的错误发生时,程序仍然能够按照预定义的方式出错。
防御性编程的原则就是快速失败,如果出现问题就尽量快的发出错误信息。
3种实现方式:
- 更严格的输入控制;
- 避免使用非标准计算函数;
- 避免根据输入返回不同类型输出的函数。永远不要在函数内部使用sapply()。
7. 测试
测试是包开发的重要部分,确保你的代码做了你想做的事情。测试在包开发路程中增加了一个额外的步骤。
之所以开始使用自动化测试,是因为花了太多时间重新修改以前修改过的错误。问题不在于你没有测试代码,而在于没有自动化测试。
自动化测试的优势:
- 更少的错误;
- 更好的代码结构;编写测试迫使你把复杂的代码分解成单独的功能,各个功能可以独立工作;
- 容易重新启动;
- 可靠的代码;
7.1 测试工作流程
使用devtools::use_testthat(),这将:
- 创建一个tests/testthat目录;
- 在DESCRIPTION的Suggests域中增加testthat;
- 创建一个文件tests/testthat.R,在check是运行所有的测试。
一旦建好,工作流程很简单:
- 修改代码或测试;
- 使用C+S+T或devtools::test()来测试包;
- 重复,直到通过所有的测试。
输出测试,每一行代表一个测试文件,每个.表示一个通过的测试,每个数字都代表一个失败的测试。数字是失败测试列表的索引。