如何在R语言中优雅合并错误提示,避免逐次排查问题?
优化R函数参数校验的简洁方案
当函数本可以一次性告知所有参数错误时,却要让用户逐个修正,这种体验非常糟糕。比如下面的and1函数,每次仅能提示一个参数错误:
and1 <- function(a , b) { stopifnot(is.logical(a), is.logical(b)) a & b } and1(0, 1) # 只提示a的错误 #> Error in and1(1, 2): is.logical(a) is not TRUE and1(FALSE, 1) # 再提示b的错误 #> Error in and1(FALSE, 2): is.logical(b) is not TRUE
手动嵌套判断虽然能实现批量提示(如and2),但代码臃肿,参数越多维护成本越高:
and2 <- function(a , b) { if (!is.logical(a)) { if (!is.logical(b)) { stop("\n`a` must be logical\n`b` must be logical") } stop("`a` must be logical") } if (!is.logical(b)) { stop("`b` must be logical") } a & b } and2(1,2) #> Error in and2(1, 2): #> `a` must be logical #> `b` must be logical
以下是几种无需编写混乱代码的优化方案:
方案1:使用checkmate包(专业参数校验工具)
checkmate是R中专门用于参数校验的包,支持批量收集错误并一次性抛出,代码简洁易维护:
library(checkmate) and3 <- function(a, b) { # 创建断言集合 coll <- makeAssertCollection() # 逐个添加校验规则,错误存入集合 assertLogical(a, add = coll) assertLogical(b, add = coll) # 抛出所有收集到的错误 reportAssertions(coll) a & b } and3(0, 1) #> Error in reportAssertions(coll): Assertion failed: #> - Argument 'a' must be of type 'logical', not 'double'. #> - Argument 'b' must be of type 'logical', not 'double'.
方案2:自定义轻量校验函数(无第三方依赖)
如果不想引入额外包,可以自己封装一个简单的批量校验函数,扩展性强:
# 自定义逻辑型参数校验函数 check_logical_args <- function(...) { args <- list(...) error_msgs <- character(0) # 遍历所有传入的命名参数 for (arg_name in names(args)) { if (!is.logical(args[[arg_name]])) { error_msgs <- c(error_msgs, sprintf("`%s` must be logical", arg_name)) } } # 有错误则抛出 if (length(error_msgs) > 0) { stop(paste0("\n", paste(error_msgs, collapse = "\n"))) } } and4 <- function(a, b) { check_logical_args(a = a, b = b) a & b } and4(0, 1) #> Error in check_logical_args(a = a, b = b): #> `a` must be logical #> `b` must be logical
方案3:使用rlang包的错误处理(适配tidyverse生态)
如果你的项目已经在使用tidyverse工具链,可以用rlang的abort函数来优雅地抛出批量错误:
library(rlang) and5 <- function(a, b) { error_list <- list() # 收集错误信息 if (!is.logical(a)) error_list$a <- "must be logical" if (!is.logical(b)) error_list$b <- "must be logical" # 批量抛出错误 if (length(error_list) > 0) { error_msgs <- purrr::map_chr(names(error_list), ~sprintf("`%s` %s", .x, error_list[[.x]])) abort(paste0("\n", paste(error_msgs, collapse = "\n"))) } a & b } and5(0, 1) #> Error: #> `a` must be logical #> `b` must be logical
内容的提问来源于stack exchange,提问作者moodymudskipper
相关产品推荐
相关产品推荐

