-
Notifications
You must be signed in to change notification settings - Fork 170
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
proposal: avoid_non_null_checks
#3008
Comments
/fyi @duttaoindril Feedback welcome. /fyi @jakemac53 @natebosch @munificent @lrhn @eernstg @goderbauer @jefflim-google @davidmorgan |
I think its fine to have a lint like this - but I probably wouldn't want it in the core/recommended sets. The null assertion operator can be safely used and it is a lot less verbose than some of the alternatives. It is also potentially more efficient than explicit null checks (in particular for dart2js, where I think they are mostly free, at least in the case of |
Hey @pq thanks for creating this proposal, I think this is a great idea. While I agree with @jakemac53 that it's much less verbose to use a |
While it's certainly true it can fail, so can any cast. And actually you can consider I do think this is a lint that some teams would choose to enable, I just don't think it should be a part of pub scoring (or even the recommended lints). For instance this code is perfectly safe:
And also sometimes you really do just want something to fail if something isn't null that you expected to be null. Maybe you are handling that exception in a different way, or are in an application where simply crashing is fine for that use case. |
I like the cleaner code this produces in the examples. I was slightly confused by the proposed name of the lint, |
We shouldn't put it in the recommended or scoring lints, but I think it would be fine to have as an option for projects that want to try to avoid |
Elsewhere we refer to the |
avoid_non_null_assertions
avoid_null_check_operators
Works for me. Updated. |
I do too. I mean they're a little contrived but I think compelling enough. For an example of code in the wild that would benefit, check out this bit from List<double> _impliedSnapSizes() {
for (int index = 0; index < (widget.snapSizes?.length ?? 0); index += 1) {
final double snapSize = widget.snapSizes![index];
assert(snapSize >= widget.minChildSize && snapSize <= widget.maxChildSize,
'${_snapSizeErrorMessage(index)}\nSnap sizes must be between `minChildSize` and `maxChildSize`. ');
assert(index == 0 || snapSize > widget.snapSizes![index - 1],
'${_snapSizeErrorMessage(index)}\nSnap sizes must be in ascending order. ');
}
widget.snapSizes?.asMap().forEach((int index, double snapSize) {
});
// Ensure the snap sizes start and end with the min and max child sizes.
if (widget.snapSizes == null || widget.snapSizes!.isEmpty) {
return <double>[
widget.minChildSize,
widget.maxChildSize,
];
}
return <double>[
if (widget.snapSizes!.first != widget.minChildSize) widget.minChildSize,
...widget.snapSizes!,
if (widget.snapSizes!.last != widget.maxChildSize) widget.maxChildSize,
];
} IMO extracting a local for |
I tend to agree personally, but worry this might be too strict for the rule to see wide adoption -- it also contradicts the explicit advice in the FAQ. I'd be curious to get @munificent's 2 cents though. |
Code like if (map.contains('foo')) map['foo']!; is perfectly safe because of the invariant promised by the There is nothing wrong with using If we introduce a lint, I'd actually prefer it to apply to all casts, not just non- (I do prefer using tests instead of casts. For map lookups, I'd actually prefer using null checks: P.S.: Oh dear, |
Exactly! The presence of a
I'd like to explore this more. (I think @srawlins has done some thinking here and I wonder how this corresponds to the various Thanks for the thoughtful responses and feedback! |
I don't know if I'm convinced that |
Well, we did have |
Well, then: Why did According to #1401 it seems there were just too many cases where you actually needed the cast after we removed implicit downcasts. (Possibly even before, if people had |
This is the rub for me. With null-aware operators and an idiom of local assignment (which is often more readable anyway IMO), I think the judicious uses of For a point of reference, it's interesting to see comparable advice in the Google Swift Style Guide: https://google.github.io/swift/#force-unwrapping-and-force-casts (which I generally agree with). |
I don't think I agree, or at least not "few" in relation to the amount of false positives I'm comfortable with. I don't think I have strong feelings against the lint existing if someone really wants it. I'm in favor of helping educate authors who are not as wary of Mostly, though, I'm going to be sad if this lint results in someone rewriting var foo = this.foo;
if (foo == null) {
throw StateError('foo should not be null');
}
foo.bar(); For full transparency, there was a case in the |
Oof, yeah. I'd hate to see a bunch of gratuitous That example from
💯 I guess the wrinkle is how to do that. Banning |
I just think that teams should be able to have the option to easily highlight all the |
Big -1 from me. People actually have to learn this, the lint doesn't help. I've seen overly-strong guidance against using |
Throwing a better error message is good when the value can actually be When the code has invariants which ensure that the value is guaranteed to be non-null, using Different use-cases have different code. In some cases using |
As I pointed out in dart-lang/sdk#47185, I think that that terminology is confusing and is particularly bad for less experienced Dart users. Calling it the "null-check operator" is ambiguous: there are many types of null-checks, some of which involve special operators (e.g. |
Just stumbled back into this thread to say that "null assertion operator" is bad because it's not an assertion (enabled by Maybe "non-null-check-operator" or "non-null-enforcing-operator"? |
@lrhn Yeah, I agree that "null assertion operator" also has problems. Also see dart-lang/sdk#47185 (comment). |
I'd suggest |
avoid_null_check_operators
avoid_non_null_checks
My view on code like this: var foo = this.foo;
if (foo == null) {
throw StateError('foo should not be null');
}
foo.bar(); ...or its original in form foo!.bar(); ...is to question "how do we know that Something like this - don't force types, but satisfy them. |
Just want to add my two cents: It would be great if we wouldn't have to make local copies, for the checks to work properly - just refactored a bunch of widgets out of my build routine and now "everything" is red because I don't have the local copies anymore ... might be hard to solve, but as it is right now its making me think that the benefits of avoiding (!) is not greater than adding lots of local assignments. |
Bumping for @scheglov @srawlins @keertip @bwilkerson @jcollins-g as it relates to our conversation this morning. |
I'm coming back to this issue and have a few questions after reading through some the discussion. Operator name (a bit off topic for this repo)Long...discussion on operator name (Collapsed since moving elsewhere)I collapsed this discussion, since I'm moving it elsewhere. Feel free to respond still though :) I had a bit of trouble finding this issue, because on the Dart site, we always referred to the operator with assert/assertion in its name. In Bob's original understanding null safety doc, it's called "The postfix null assertion "bang" operator ( I did a quick scan through various sources including linter rules, diagnostics, dart.dev, the language repo, and the SDK and found quite a mix of terms: What is the consistent name we should be presenting to developers? So they can find and understand documentation, lints, issues, and more discussing it, and discuss it with each other consistently. It seems each name has problems:
While it'd be nice to have the perfect name, choosing one and standardizing it across our documentation and shared vernacular is more important. To that end, I would suggest I'd like to get the docs standardized sooner rather than later, as names do tend stick, so I'd love some thoughts or just a final decision from someone :) Lint nameThis depends on name we standardize on for the operator, but I will say if (value != null) {
} Lint statusI would love to see this lint implemented. Especially since this issue was raised, the situation has got a lot better. Pattern matching and to a lesser extent, field promotion, make safer options to the operator much easier to implement. It might not be something everyone wants to enable all the time, but even for those cases, it could be nice for occasional code-base auditing or cleanup. |
just adding on to ideas since nobody mentioned and this is Bad in that case.
|
While this is trending for those sorting by recently updated, the "good" example can now use patterns (and class Buffer {
String? contents;
int get length {
if (contents case var c?) {
return c.length;
}
return -1;
}
} I'd probably do |
I hope it isn't too spammy to add another comment on the naming. Otherwise please skip this comment. ;-) As I mentioned here, the Dart specification documents use 'test' to describe behaviors like The motivation for this terminology has not been made explicit anywhere that I know of, but you could say that when we 'check' something it is assumed to be true ("let me check that the brakes on this car are actually working"), and we need to have some kind of recovery if it isn't; and if we 'test' something then it may or may not be true, we just want to know which one it is (a bit like Abraham being tested to see if he would make the sacrifice or not). So I suggested that we could use a name of the form However, as @parlough and others have argued, this interpretation is not obvious at all to everybody. The words 'assert' or 'assertion' have been mentioned as less confusing ways to designate the behavior where 'false' causes a run-time failure, and the standard argument against this is that However, as I understand the semi-recent developments in the naming of lints, So maybe a really simple name like could be used? The lint flags an expression of the form You could argue that many other constructs will match the same description ( As I see it, the really big question is whether there will be too many false positives. It's basically outlawing an entire language feature. (Oh, should I even mention this? Of course not! So here we go: We could introduce some kind of support for marking certain categories of expressions as "safe for non-null checking", e.g., all invocations of a specific getter |
Name: avoid_non_null_checks
Description: Avoid using non-null-check operators.
Details: Code that uses non-null-check operators (
!
) is harder to understand and can lead to runtime exceptions. In general, you should avoid non-null-check operators by using null-aware operators or making local copies of potentiallynull
elements.Good:
Bad:
An exception to this general rule is made for map lookups (which return a nullable type by default). Idiomatic Dart allows expressions like this:
Good:
Alternatively, you might consider making a local copy and an explicit check:
Good:
See discussion in #2562
The text was updated successfully, but these errors were encountered: