I'm trying to program defensively in my package development, using a lot input validation.
In particular, I'm relying on a lot of the ready-made assertions in checkmate, testthat, assertr, assertthat and the like, which makes life a lot easier (and code shorter).
Hadley Wickhams's tidyverse style guide for error messages suggests that error messages should point users to the exact source of the problem, like so:
#> Error: Can't find column 'b' in '.data'
(Columns are just an example, sometimes it might be rows, or some other index).
I'm now wondering how this can be implemented elegantly and consistently in a package, given that a lot of the existing assertions (from above packages, but also base r) don't give you any indices back in their errors.
Here's an example:
m <- matrix(data = c(0, 1, 5, -2), nrow = 2)
# arbitrary assertion
assert_positive <- function(x) {
if (any(x < 0)) {
stop(call. = FALSE,
"All numbers must be non-negative")
} else {
return(invisible(x))
}
}
# (there are *lots* of these in packages such as checkmate, testthat or assertr that should be reused)
assert_positive(m)
gives:
## Error: All numbers must be non-negative
So far so good, but this does not give the desired indices of the errors.
Yes, I know that I could just change the above assert_positive()
function to do that, but I would like to reuse a lot of the functions in checkmate and friends, so I can't touch them, and there's too many of them anyway.
So I should probably wrap something around these existing tests, such as a simple for loop:
# via for-loops
assert_positive2 <- function(x) {
for (r in 1:nrow(x)) {
res <- try(expr = assert_positive(x[r, ]), silent = TRUE)
if (inherits(x = res, what = "try-error")) {
stop(
call. = FALSE,
paste0(
"in row ",
r,
": ",
attr(x = res, which = "condition")$message,
"."
)
)
}
}
}
assert_positive2(m)
gives:
## Error: in row 2: All numbers must be non-negative.
That gets the job done, but it's a lot of clutter and the code is not very expressive.
I've also thought about Reduce()
with try()
, but that won't give indices, and neither would any apply()
action.
I guess, finally, a closure or function factory would be helpful to generalise this to many assertions.
This just feels like a problem that many other people (crafting better error messages) must have already run into, so:
What's an elegant/canonical way to do this?
I'm interested in a broader discussion of best practices in pkg development behind the scenes to get error messages of the quality demanded by the tidyverse style guide.
Ps.: This is crossposted from StackOverflow, where this obviously wasn't a good fit because it's too broad a question – hope it'll fit better here.