使用 stop() 和 warning() 编写稳健函数
在自定义函数中发出自定义条件信号
使用 stop() 和 warning() 编写稳健函数 是 CoddyKit 上的免费 R Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 R Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 R Academy 课程共包含 4 节课。
为什么输入验证很重要
防御式函数会检查输入,并通过提供信息丰富的错误来明确失败,而不是悄悄地产生错误结果。在边界处捕获错误输入的函数,比让无效数据沿着处理流程传播的函数更容易调试。
# Without validation: wrong result, no error
bad_mean <- function(x) sum(x) / length(x)
bad_mean('hello') # no error, just NA
# With validation: clear error
good_mean <- function(x) {
if (!is.numeric(x)) stop('x must be numeric')
sum(x) / length(x)
}
tryCatch(good_mean('hello'), error = function(e) cat(e$message, '\n'))信息丰富的错误消息
像 stop('bad input') 这样的通用错误会让调试变得困难。请在 stop() 中使用 paste0() 或 sprintf(),将实际值和预期类型包含在消息中。这样可以节省在处理流程中追踪错误的时间。
check_numeric <- function(x, arg_name = 'x') {
if (!is.numeric(x)) {
stop(sprintf(
"'%s' must be numeric, but got class '%s'",
arg_name, class(x)
))
}
}
tryCatch(check_numeric('abc', 'score'),
error = function(e) cat(e$message, '\n'))验证长度和范围
常见的验证包括:检查向量是否为空(length(x) == 0)、标量是否在指定范围内,以及数据框是否包含预期列。每项检查都应有针对性的错误消息。
validate_prob <- function(p) {
if (!is.numeric(p)) stop(paste0('p must be numeric, got: ', class(p)))
if (length(p) != 1) stop(paste0('p must be length 1, got: ', length(p)))
if (p < 0 || p > 1) stop(paste0('p must be in [0,1], got: ', p))
p
}
cat(validate_prob(0.7), '\n')
tryCatch(validate_prob(1.5), error = function(e) cat(e$message, '\n'))使用 warning() 处理非致命问题
当函数仍能返回合理结果,但调用者应当知道发生了异常情况时,请使用 warning()——例如强制转换值、填入默认值或检测到处于边界的输入。
clamp <- function(x, lo, hi) {
if (any(x < lo)) warning(paste0(sum(x < lo), ' value(s) below lo, clamped'))
if (any(x > hi)) warning(paste0(sum(x > hi), ' value(s) above hi, clamped'))
pmax(lo, pmin(hi, x))
}
result <- clamp(c(2, -5, 7, 100, 4), 0, 10)
cat('Result:', result, '\n')stop() 中的 call. = FALSE
默认情况下,stop() 会在消息前加上 Error in funcname(...):。当调用上下文已经很明确,或该函数是面向用户的工具函数时,可以传入 call. = FALSE 来抑制此前缀。
# With call. = TRUE (default): shows calling function
f1 <- function(x) stop('bad input')
tryCatch(f1(1), error = function(e) cat(conditionMessage(e), '\n'))
# With call. = FALSE: cleaner message
f2 <- function(x) stop('bad input', call. = FALSE)
tryCatch(f2(1), error = function(e) cat(conditionMessage(e), '\n'))用于 stop() 的自定义条件类
使用 structure() 创建自定义错误类,使调用者可以只捕获您函数产生的错误,而不捕获所有错误。这是编写软件包级错误的专业方式。
value_error <- function(msg, call = sys.call(-1)) {
structure(
class = c('value_error', 'error', 'condition'),
list(message = msg, call = call)
)
}
check_positive <- function(x) {
if (x <= 0) stop(value_error(paste0('Expected positive, got: ', x)))
x
}
tryCatch(check_positive(-3),
value_error = function(e) cat('ValueError:', e$message, '\n'),
error = function(e) cat('Other error:', e$message, '\n'))检查数据框列
当函数需要数据框时,请在使用必需列之前验证这些列是否存在。使用 %in% 检查列名,使用 setdiff() 报告缺少哪些列。
require_cols <- function(df, cols) {
missing <- setdiff(cols, names(df))
if (length(missing) > 0) {
stop(paste0('Missing columns: ', paste(missing, collapse = ', ')))
}
invisible(df)
}
df <- data.frame(x = 1:3, y = 4:6)
tryCatch(
require_cols(df, c('x', 'z', 'w')),
error = function(e) cat(e$message, '\n')
)使用 stopifnot() 进行简洁断言
如果任一条件为 FALSE,stopifnot(condition1, condition2, ...) 就会抛出错误。在函数开头使用它断言前置条件,无需分别编写多个 if (!...) stop(...) 代码块,十分简洁。
compute_area <- function(width, height) {
stopifnot(
is.numeric(width),
is.numeric(height),
width > 0,
height > 0
)
width * height
}
cat(compute_area(5, 3), '\n')
tryCatch(compute_area(-1, 3), error = function(e) cat(e$message, '\n'))使用命名的 stopifnot() 获得更好的消息
在 R 4.0 及更高版本中,stopifnot() 中的命名表达式会用您的自定义描述替换自动生成的消息。将表达式文本用作名称,即可生成信息丰富的失败提示。
validate_age <- function(age) {
stopifnot(
'age must be numeric' = is.numeric(age),
'age must be positive' = age > 0,
'age must be under 150' = age < 150
)
invisible(age)
}
tryCatch(validate_age(-5), error = function(e) cat(e$message, '\n'))
tryCatch(validate_age('x'), error = function(e) cat(e$message, '\n'))结合验证与业务逻辑
结构良好的函数会将验证与逻辑分开。在开头进行验证,然后执行操作。这样可以提高函数的可读性,并确保在任何计算开始前检测到错误。
discount_price <- function(price, pct) {
if (!is.numeric(price) || price <= 0)
stop(paste0('price must be positive numeric, got: ', price))
if (!is.numeric(pct) || pct < 0 || pct > 100)
stop(paste0('pct must be in [0,100], got: ', pct))
if (pct > 50) warning('discount > 50% is unusual')
price * (1 - pct / 100)
}
cat(discount_price(100, 20), '\n')
cat(suppressWarnings(discount_price(100, 60)), '\n')在工具函数中使用 tryCatch
有时,工具函数应在内部处理错误并返回备用值,而不是将错误继续向上传递。将核心逻辑包装在 tryCatch() 中,并在失败时返回约定的哨兵值,例如 NA 或 NULL。
safe_log <- function(x) {
tryCatch({
if (!is.numeric(x)) stop('not numeric')
if (x <= 0) stop('must be positive')
log(x)
}, error = function(e) {
warning(paste0('safe_log failed for x=', x, ': ', e$message))
NA_real_
})
}
results <- sapply(list(10, -1, 'a', 100), safe_log)
cat(results, '\n')快速检查
将 call. = FALSE 传给 stop() 时,它会执行什么操作?
稳健函数:要点总结
编写稳健函数的要点:
- 在开头验证输入;使用信息丰富的
stop()消息明确报告失败 - 在错误消息中包含实际值:
paste0('Expected numeric, got: ', class(x)) - 对于可以继续计算的非致命问题,使用
warning() call. = FALSE会抑制错误消息中的调用函数前缀- 使用
stopifnot()进行简洁断言;使用命名形式提供自定义消息(R 4.0 及更高版本) - 自定义条件类支持调用者有选择地捕获错误
robust_divide <- function(x, y) {
stopifnot('x must be numeric' = is.numeric(x),
'y must be numeric' = is.numeric(y))
if (y == 0) stop('Division by zero', call. = FALSE)
if (abs(y) < 1e-10) warning('y is very small; result may be inaccurate')
x / y
}
cat(robust_divide(10, 2), '\n')
tryCatch(robust_divide(10, 0), error = function(e) cat(e$message, '\n'))用 AI 导师学习 R — 免费
在浏览器中编写并运行真实代码,获得全天候 AI 导师的即时帮助,并在网页或应用中继续学习。
- 课程
- 43
- 课程
- 159
常见问题解答
「使用 stop() 和 warning() 编写稳健函数」课时是免费的吗?
是的 — 「使用 stop() 和 warning() 编写稳健函数」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 R Academy 课程的其余内容,请升级到 CoddyKit PRO。 R Academy 课程共包含 4 节课。
「使用 stop() 和 warning() 编写稳健函数」这节课中我会学到什么?
在自定义函数中发出自定义条件信号 你通过在浏览器中直接运行的动手代码来练习 R Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 R Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 R Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。
「使用 stop() 和 warning() 编写稳健函数」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 R Academy 课中编写并运行代码吗?
能。每节 R Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- R 中的错误、警告与消息
- 使用 tryCatch() 恢复错误
- withCallingHandlers() 与重启机制
- 使用 stop() 和 warning() 编写稳健函数