-
Notifications
You must be signed in to change notification settings - Fork 12
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
Javascript: reconsider guidance on ==
WRT comparisons to null
#10
Comments
Thanks for reposting. Curious what others think. |
I strongly disagree. There are many APIs and language features that treat these values differently, and I much prefer explicit code that self-documents the expectations of the data it handles.
My personal experience with sloppy null checks is that it makes the code harder to understand because it's unclear if the author is deliberately handling both values or if they were just reaching for the tool they always use when comparing |
point 1: In general the intrinsic value of the concept of
Both of the above will print 'baz', which I would find surprising.. and it potentially covers up a bug. Undefined being a value in the runtime makes it essentially nothing more than "another kind of null", where the meaning of that value is purely based on it's use, rather than a clearly understandable, well defined meaning at the language level. I find making there be a difference between null and undefined is, in the majority of cases, confusing to consumers. And recent feature additions to the language would also appear to support this stance, as the I understand that the concerns may be different when using typescript, as the type system means that checking for "undefined" can happen during static analysis. I want to make sure it's clear, my concern here is with preventing bugs rather than with a stylistic preference. My experience has been that, at least for javascript development, devs attempting to follow the current lint rule in earnest have written bugs. I believe this is because the difference between null and undefined is subtle and unintuitive, especially for those coming from a static-typed background. |
🤷♂️ I've seen plenty of bugs caused by sloppy null checks as well. It's a crappy corner of the language, and I'm not convinced reversing our guideline would improve things. @jacobcarpenter, @ddunkin would either of care to offer a preference? |
We consider boolean coercion acceptable, so this rule has seemed somewhat contradictory to me. My preference would be to allow |
I ❤️ I can see you've thought about this a fair bit, and I'm sorry if those replies seem glib. I really haven't spent much time thinking about it because I haven't actually found this to be a pain point in code I write/maintain. In fact, I'm pretty surprised by @EthanRutherford and @ddunkin 's statements to the contrary. I'm a pretty big fan of only ever reaching for one equality operator, and dealing with potential undefined/null situations as needed. Do some of the newer operators like nullish coalescing ( |
To clarify, I think I generally use boolean coercion (typically as guard clauses) in these situations. I don't tend to write Smart Media Editor has only 15 occurrences of |
@jacobcarpenter optional chaining is short-circuiting, so if the lhs is null it returns null, if undefined it returns undefined, and otherwise continues the chain. So it doesn't really resolve the difference, but rather ignores it and passes it along. As I mentioned earlier, though, I've been burned enough times by falsiness checks being too loose/sloppy that I personally avoid them whenever possible, and would love to see a lint rule forbidding them if it were possible (but alas, that requires a type system to enforce). |
That's a good point... I do, too. I also tend to test for truthy cases more often. Most of my Do you guys have any real world examples of strict null comparisons causing bugs? |
I agree that this example looks odd, but it also seems pretty contrived. Why would you ever explicitly coalesce a null/undefined to false? I don't disagree that there are situations (more so with numbers or strings, I'd think) where valid incoming values are "false". But in those situations, it does seem like default parameter values and/or nullish coalescing can actually make the code more maintainable. |
That's what I'm getting at, if you want an if check (or other expression) to return Examples where strict checks have resulted in bugs: Examples where falsiness results in bugs:
So, I always write these as It is both avoiding the bug, as well as making it more clear what exactly I'm checking for. |
I'm sorry, but I'm finding it challenging to map I totally agree that boolean coercion isn't an end-all solution, and there are almost surely some real world cases that are awkward. I'd love to engage on a discussion around a specifically difficult case to express elegantly with our currently enforced guidelines... but the above example doesn't give me any nouns I can work with and suggests (in the comment) that the value can be zero or empty string or undefined or null... |
Do you have real world examples? I'm aware of the boolean coercion rules, and these contrived examples don't seem helpful. |
My point is not "unknown types" at all, but that for a known type, either numeric or string, 0 for numbers and "" for strings, is not the same as null/undefined, more often than not. I'll give a more specific example.
If you've got some behavior or display intended for when the number/string is not set, you typically do not want that same behavior for e.g. |
In this example when would you ever need to handle |
Yes, that code is explicitly declaring |
Sure, but we don't always control the values we're working with. Lets instead say instead that This was very surprising, and took a non-trivial amount of time to discover the cause, because nothing threw, the code just didn't work right, but only sometimes (when the value passed in was null instead of undefined). |
Here's a commit where we fixed several checks where falsiness bit us: |
Example of a personal project where I was bitten by a falsiness check (I did a force-push here to correct it, but the point is I originally was lazy and wrote this as a boolean coercion, and it caused it to be impossible to set the value of the top-left cell in the sudoku board, essentially making the game unplayable. And I had shipped it.) |
I'm sorry if I'm being obtuse. I really don't understand any of these examples. Most of the changes in that commit are replacing boolean coercions; I don't see any examples of strict comparisons causing problems. How is the moment issue relevant? It sounds like a case where a strict null check would avoid the problem. |
The moment issue is relevant because moment's api only handled undefined, but had different handling (or no handling as it were, in that it silently returned an invalid object) for null, and that it caused confusion and a non-trivial amount of wasted dev time. In addition, the bug was live for customers for months without us realizing anything was wrong. It's relevant in that it illustrates my point that any API which treats null differently than undefined is a potential point of confusion for consumers. |
Sorry for any confusion, but unless I misunderstood, I believe that real world examples of both boolean coercions as well as strict null checks causing bugs was asked for (the former by Jacob, and the latter by you). |
On the flip side of things, what are some examples of "loose" null checks causing bugs? Anecdotally, I've never run into problems using |
My objections to The benefits of the consistency of only ever using one equality operator ( |
I agree that I believe we should enforce that we use |
I'm really not trying to be snarky, but isn't not knowing which version of "null" the cause of the aforementioned problem with moment? I don't see gaining the ability to not be aware of the possible values of a variable as a win. |
Yes, and that's exactly my point. I believe that this is not "gaining the ability to not be aware", but rather "addressing the reality that we are not always aware". Without typescript, I do not believe that we can ever be 100% whether our API is going to be passed a null or an undefined, and this is especially the case when we're writing anything that we expect someone other than ourselves to be using. |
As currently configured, the
eqeqeq
rule forbids using==
for null checks. In my experience, however, forbidding this check often leads to authoring of more error-prone code.First, let's establish the fact that
null
andundefined
are two different values which in virtually all cases mean the same thing for the author, i.e. the value is not set, and attempting to access any properties of the value would throw. And further, any API which behaves differently when given null vs undefined is surprising, and typically frustrating. A function which has handling forundefined
, but throws for null, or worse, does something subtly weird like coercing null to the string "null" is surprising. And in the latter case, is difficult to debug.Given the above, it's reasonable to conclude that any null/undefined check should check for both values, or else be a potential point for bugs, and thereby pain for users and future devs alike.
Javascript has a built-in operator for "is null or undefined", spelled
== null
. But what are our alternatives, given that it's currently disallowed?One alternative is a falsy check. Falsiness, however, is quite a bug-prone way to check for null.
0
,''
,NaN
,false
, anddocument.all
(bizarrely, due to legacy backward compat reasons) are all also falsy values. I have personally been bitten numerous times in the past by falsy checks triggering behaviors not intended for values of 0 etc, and make it a point of personal policy to avoid using falsiness checks in place of null checks.Further, the purpose of
eqeqeq
is to use strict checks, and so using the even-looser falsiness check seems to go against the spirit of the rule.The other alternative is to always triple-equals check for both null and undefined. Aside from being more verbose (especially when checking properties nested within objects), this also introduces a new surface area for errors.
Firstly, an author may only check null, and not check both. There are several reasons they may do this: perhaps laziness, or forgetfulness, or simply not knowing they need to (which will often be the case for developers new to javascript). Additionally, they may even just opt for the falsiness check to avoid the verbosity of the full check. I have seen both of these approaches taken, and lead to bugs.
Additionally, combining the two checks is a surface area for bugs. DeMorgan's laws must be followed; the
isNull
check needs to use||
, but thenotIsNull
check needs to use&&
. Any combination of tiredness, forgetfullness, rustiness, or green-ness could lead to accidentally writing the wrong check, or inverting the logic and not remembering to change the boolean operator.Now, I'm not necessarily arguing that that last error is common, but it is an error surface that simply doesn't exist when using
== null
, and we all make mistakes from time to time.So, with the above established, my argument boils mainly down to this: enforcing that null-checks use
== null
(and that=== null
is disallowed) helps developers fall into the pit of success. Unfortunately, there's no eslint rule for "prevent using falsiness checks for null checks" (as that would require type information), but we can at least make checking for both null and undefined easy to do, and less bug-prone.originally posted here
The text was updated successfully, but these errors were encountered: