-
Notifications
You must be signed in to change notification settings - Fork 21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Do not use else after stop() #229
Conversation
Codecov Report
@@ Coverage Diff @@
## develop #229 +/- ##
========================================
Coverage 97.31% 97.31%
========================================
Files 14 14
Lines 1527 1527
========================================
Hits 1486 1486
Misses 41 41
Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Haha, nice! Standard comment about a news item and dev version increment otherwise looks good to me.
disagree with this notion as a rule of thumb (and apparently, the cyclomatic complexity metric, if it triggers on this sort of thing). in this particular case, i think the stopifnot(
"error1text" = checkcondition1,
"error2text" = checkcondition2,
...
) |
I double checked and indeed, it doesn't affect cyclomatic complexity but I still believe this is a good change that makes the code easier to read. cyclocomp::cyclocomp(function() {
if (a) {
stop("error")
} else {
message("good")
}
})
#> [1] 2
cyclocomp::cyclocomp(function() {
if (a) {
stop("error")
}
message("good")
})
#> [1] 2 Created on 2023-04-12 with reprex v2.0.2.9000 |
For error-checking / messages, i think this generally reads more clearly (comments for emphasis - they're unnecessary imo, especially if this idiom is repeated through a library) stopifnot( # ... we're doing some error checking
"checking this feature" = ..., # against some logical expression
... # repeat for all the desired checks
) |
Aside: I am on-board with the don't-if-else-your-errors mentality, just not necessarily the
I have a slight preference for the first, but its not always practical, nor is it always more readable. As such, disagree with making it a general principle / practice - that's for strong preferences, particularly where the lazy alternatives pose distinct downsides. |
looking at the linked JB example, agree the LHS is code smell, but again: the However, sometimes there's actually some branching logic (e.g. when embracing some polymorphism in function arguments, though yes that too can go overboard), and you can't properly check exit conditions until you've made some progress on evaluating arguments. |
I agree that |
Everyone agrees it appears the original is not ideal. The choice is between using individual if and stop checks or doing a single Alternatively, we can merge this PR and then look at moving to a |
Have y'all stood up style guidelines or some such? I think the practical solution is roughly:
|
There are (in the contributing guide) but they are quite vague. |
Maintaining a thorough style guide is so much work that I usually try to stick with existing ones, even if I disagree with some of their choices (as long as we still agree on most points obviously). |
Unsolicited, 😬, but hopefully useful comments:
How much the last two points matter depends on the use case but worth bearing in mind. |
Doesn't have to be complete (e.g. "the specified lintr config passes" covers a lot), and this is more of a "style" guide anyway. I think it's roughly at the moment:
Others? I do think the contributing guidelines currently are too focused on "process" - that's useful, but it's distinctly worthwhile to establish what are the "vocabulary" or "idioms" for enw. That's has some elements of "style" guidelines, but is generally a bit more meta (i.e. human-enforceable only) than concrete (i.e. static analysis works fine). |
These are some good points @pearsonca. Shall we move this discussion to an issue ahead of updating the contributing guide? @TimTaylor !!! (all good points) I agree with @Bisaloo when it comes to trying to stick with existing guides due to the amount of work (which is what our current contributing guide says). I think we have diverged a bit here so perhaps we need to update on some specific points and then point out to existing guides for everything else that doesn't contradict? |
In terms of actual code, for with(obs, stopifnot(
"Both `reference_date` and `report_date` must be columns." =
!(is.null(reference_date) && is.null(report_date)),
"`reference_date` must be a column." = !is.null(reference_date),
"`report_date` must be a column." = !is.null(report_date),
local = ... # not quite sure here; seems like this is an internal function used for checks - parent.frame?
)) probably before the internal copy steps as well? |
For a .stopf <- function(fmt, ..., .use_call = TRUE, .call = sys.call(-1L), .class = NULL) {
.call <- if (isTRUE(.use_call)) .call[1L] else NULL
msg <- sprintf(fmt, ...)
err <- errorCondition(msg, class = .class, call = .call)
stop(err)
}
.assert_not_missing <- function(x, arg, call) {
if (missing(x))
.stopf("argument `%s` is missing, with no default.", arg, .call = call)
}
.positive_int <- function(x, arg = deparse(substitute(x)), call = sys.call(-1L)) {
.assert_not_missing(x, arg = arg, call = call)
if (!is.integer(x) || x <= 0L)
.stopf("`%s` must be a positive integer.", arg, .call = call)
}
external <- function(bob) {
.positive_int(bob)
}
# will work
external(1L)
# will error
external(1)
#> Error in external(): `bob` must be a positive integer.
external(0L)
#> Error in external(): `bob` must be a positive integer.
external()
#> Error in external(): argument `bob` is missing, with no default. Created on 2023-04-12 with reprex v2.0.2 The first two functions are the boiler plate stuff with the Created on 2023-04-12 with reprex v2.0.2 |
Right so I think where we are with this PR is to take forward @Bisaloo suggests as is then open an issue to take forward the general argument checking approach. I like the move to Would anyone be happy to write up an issue for this and port over some of the discussion? Ah we also have a suggested extension to the style guide. @pearsonca could you take point on porting that part of the discussion to its own issue? We can then discuss more on both and add to a version roadmap + unstick this PR! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM to me. I have added a news item and bumped the dev version. Porting the outstanding conversation here to two new issues. Thanks again for this PR @Bisaloo and the great discussion @pearsonca, @TimTaylor, and @Bisaloo!
#233 captures the discussion here on approaches for improving input checking. |
#234 captures the contributing guide discussion. |
In the words of Jenny Bryan: "there is no else, there is only if."
https://speakerdeck.com/jennybc/code-smells-and-feels?slide=45
Having
else
afterreturn()
ofstop()
increases the number of branches in the code, which makes it harder to read. It also translates into a higher cyclomatic complexity.