-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Don't flag if_not_else when else block is longer than then block #5654
Conversation
Consider the following if/else block: if !failure_condition { proceed_as_normal(); } else { handle_error(); clean_up(); notify(); etc(); } Currently, clippy will flag this block with if_not_else and suggest that it be rewritten as if failure_condition { handle_error(); clean_up(); notify(); etc(); } else { proceed_as_normal(); } But top-heavy blocks are hard to read, and the suggested rewrite is worse than the original. So if_not_else should only be raised when the else block is no longer than the then block.
Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @flip1995 (or someone else) soon. If any changes to this PR are deemed necessary, please add them as extra commits. This ensures that the reviewer can see what has changed since they last reviewed the code. Due to the way GitHub handles out-of-date commits, this should also make it reasonably obvious what issues have or haven't been addressed. Large or tricky changes may require several passes of review and changes. Please see the contribution instructions for more information. |
Previous discussion: #3363 |
The outstanding discussion issues are:
Note that this change does not add a new lint. Instead, it tempers an existing one. Some users might prefer the strict approach of the lint as it currently exists, but I think that kind of aggressive behavior should be opt-in, not opt-out. Readability is what matters here, not consistency. That said, I don't care all that much about configuration options, just so long as one way or another I can get reasonable behavior out of this lint. |
I think this is reasonable. Can you add a configuration for this? Since there wasn't more discussion on the reopened PR, I think we can merge a configurable version. |
@nickdrozd, @flip1995: I don't understand why this is a new PR now. The previous discussion is relevant and is sidestepped by this new PR. I don't recognize the issue of 'top heaviness' (some branch having a higher number of lines). It is not an issue for readability at all for me. Whether the heaviness is at the top of the bottom or in the middle, it's there anyway. If you have too much source code in your branches, refactor it to redistribute it (e.g., across helper functions). Moreover, if it is an issue for @nickdrozd, then you should know and acknowledge that IDEs can (un)fold blocks and even (un)fold them by default or in bulk, based on your selection. The tampering with an existing lint is exactly what makes the proposal less desirable over adding a new one. I do not like the arbitrariness introduced by making top-heaviness a factor at all. I wonder how this interacts with old macros. @nickdrozd: is there previous work in linters for other languages that favors your change? |
Github seems to prevent the author of a closed PR to reopen it, when it was closed by a maintainer. I don't think adding a configuration option is a big deal. Making a new lint for this does not seem worth to me. Maybe we can make the configuration default so that the top-heaviness check is disabled. So that the config is opt-in. |
@flip1995: on reopening the PR. Will you then change your standard message upon closing the PR, to only offer the opener to ask a maintainer to reopen the PR? Your current resolution broke the discussion thread. |
@sanmai-NL Yes, I didn't know, that the author can't reopen closed PRs. I will word it differently in the future. |
@flip1995: I believe you only accept contributions that add value (cost vs. benefit). I'm sorry for @nickdrozd but the context hasn't changed since he stopped working on the previous PR. So far, there have been critical comments, and no counterarguments from the PR opener, community support or examples from previous work. |
I don't think a configuration option for this lint is too costly. It's 3 LoC testing the top-heaviness and 2 LoC for the config option. This won't have any measurable influence on performance. I don't think the cost will be too high for the benefit, that there is a configuration option. User-facing cost would just be to have a few more lines in the documentation to read, when looking up this lint. Benefit is that everyone can decide for themselfs, if they want the top-heaviness feature enabled. So from a cost-benefit perspective, I don't see why we shouldn't allow this contribution. |
I generally agree on the cost, although I wouldn't add source code so easily if I had to maintain this. For me the important cost is in complexity in understanding other people's code bases with this knob enabled, and understanding Clippy itself, as you noted. The benefit is zero for me. To me this is addressing a non-issue. |
There seems to be some confusion here. It makes no sense to suggest adding a new lint, because no new suggestions are being made. It is exactly the same suggestion as ever, only applied less aggressively. In particular, the current suggestion will not be made if the resulting code would be top-heavy and therefore ugly. No new warnings would be emitted. It would be exactly the same warning as currently, but applied in fewer cases. Let's be clear. There is no issue of correctness here. All the code involved is equivalent. The compiler does not care. The only consideration, as the documentation says, is readability. There is no reason to believe that readability is always, no matter what, every single time, going to be improved by switching blocks so that there is no negation in the conditional. Getting rid of those negations is a good idea generally, but obviously there are exceptions. A foolish consistency is the hobgoblin of little minds. One problem with linters is that most users hate them. People don't like being told to rewrite their code. It is in the interest of the linter not to emit false positives, because false positives cause users to become skeptical of the linter's value. This is particularly true when it comes to functionally irrelevant opinions about style. Currently Clippy suggests replacing certain perfectly good conditional blocks with equivalent but uglier conditional blocks for no reason at all. This is a false positive, and it makes the tool less pleasant to use.
I don't know about linters, but an aversion to top-heavy conditional blocks is built into Emacs Lisp at the sytactical level. An (if (not failure-condition)
(proceed-as-normal)
(handle-error)
(clean-up)
(notify)
(etc)) If this lint were run against that block, the output would look like this: (if (not failure-condition)
(progn
(handle-error)
(clean-up)
(notify)
(etc))
(proceed-as-normal)) That second block is obviously much uglier than the first one. Maybe some people like that kind of thing, but it is strange and nonstandard. I don't think Clippy should be so opinionated as to suggest changing code to look like this. |
Wait, I just saw, that The pedantic lint group is designed to include more opinionated lints, that can be enabled for specific code bases, if desired. It is not recommended to use the complete Clippy has lint groups which are more or less opinionated.
But that is exactly the point of a linter: The machine tells you to rewrite your code, to adhere to idiomatic/company/code base standards, and that no human has to do this in a code review. If a user hates a linter, than the linter is either bad or misused. I personally don't think that Clippy is a bad linter (obviously 😄) Since this is a This lint is not enabled by default, and nowhere in the documentation is it recommended to enable it. I will leave this PR open a bit longer, to allow more discussion about this. |
if let ExprKind::If(ref cond, ref thn, Some(ref els)) = item.kind { | ||
// Don't suggest rewriting if the result would be top-heavy. | ||
if let ExprKind::Block(ref els_block, ..) = els.kind { | ||
if thn.stmts.len() < els_block.stmts.len() { |
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.
Because we already talked about cost-benefit:
The cost for this configuration option would be way higher, since this:
if !a {
println!("xyz");
} else {
let _ = if {
// ...100 lines
};
}
would still lint with this implementation. So we would need to implement a line counter. Or something that calculates complexity. But detecting complexity is a different story. Long story short: we can't do this (yet?, probably?, ...? #3793 (comment))
So what @sanmai-NL said, the cost for a working implementation for this would be too high for the benefit that it would give for a pedantic lint IMO.
So, I've been following the discussion and would like to share my thoughts. @flip1995 I think @nickdrozd stated that they dislike the lint when it results in a top-heavy if-else, not that they dislike it in all cases, so I interpret this PR as a possible enhancement to the existing lint. I think the default behavior should not change though, and "top-heaviness" evaluation should be opt-in, for deference to the current users that like the lint the way it is, like @sanmai-NL. About the cost, couldn't we use the span of the if/else blocks to determine the number of lines? If it's not that simple, then it's probably not worth it as already stated. |
Yeah, but should comments count? What if the if branch is shorter, but way harder to read (more indentations, ...)? Which is the happy path and should it be first? ...? Number of lines is IMHO not a good heuristic for ordering blocks. Neither is the number of statements (especially how statements are defined in the rust compiler). Here comes the maintenance overhead in play, @sanmai-NL correctly mentioned. If we start now with the simple statement comparison, the next contribution to this could be to change this to number of lines, but then someone else thinks comments shouldn't count and changes it again. And on it goes. I don't think, that we should add an opinionated config option to an opinionated lint (which is marked as opinionated as it is in the
Then this lint should just be |
Hmm, I think you're right, this has many more dimensions of opinionated that what has been discussed and could be a never-ending battle.
Fair point. I thought clippy could accomodate more opinions, but failed to see the maintenance cost. |
I deal with enough of this kind of bullshit at work, so I'm closing this. For future reference, here is a summary of the discussion thread:
|
Please stay civil and lower the aggressive tone! @nickdrozd I think every point you made was addressed:
That being said, I don't like this lint myself for the same reasons as you (and some other, e.g. happy path). The solution for me is to just not enable this lint. There's a reason, why |
Consider the following if/else block:
Currently, clippy will flag this block with if_not_else and suggest
that it be rewritten as
But top-heavy blocks are hard to read, and the suggested rewrite is
worse than the original. So if_not_else should only be raised when the
else block is no longer than the then block.
changelog: Don't flag if_not_else when else block is longer than then block