Skip to content
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

Intent to stop using `null` in my JS code #7

Open
sindresorhus opened this Issue Apr 2, 2019 · 25 comments

Comments

Projects
None yet
@sindresorhus
Copy link
Owner

commented Apr 2, 2019

I intend to stop using null in my code, in favor of undefined. There are many reasons for doing so.

Feedback wanted

Tweet: https://twitter.com/sindresorhus/status/1114894287387258886

Background

http://2ality.com/2013/05/history-undefined.html

Reasons

  • Everyone uses null and undefined inconsistently and interchangeably.
  • Nobody really knows when to use which.
  • Supporting both null and undefined complicates input validation.
  • Newer JS features like default parameters only work with undefined.
  • typeof null === 'object'.
  • I strongly believe it was a mistake to have two have both null and undefined.
  • Using null makes TypeScript types more verbose: type A = {foo?: string | null} vs type A = {foo?: string}.

Problems

  • We cannot remove null from JavaScript

True, but we can pretend it doesn't exist (in most cases). And we can enforce no null usage with linters.

  • We cannot change browser and Node.js APIs that accept/return null.

That's fine. At least we can improve user-land code.

  • JSON doesn't support undefined

We just have to live with that. We could implement a custom JSON.parse/stringify that handles the conversion.

Resources

@niwox

This comment has been minimized.

Copy link

commented Apr 7, 2019

Other resource I found about this topic :

@jsynowiec

This comment has been minimized.

Copy link

commented Apr 7, 2019

Something can be defined, but allowed to be null (see it as a "no value" state). It’s different than just undefined. Especially with TypeScript and you’re even mentioning it a bit in the last point.

Using null makes TypeScript types more verbose: type A = {foo?: string | null} vs type A = {foo?: string}.

foo: string | null has different meaning and use than foo?: string

I’m interested in your thoughts about this difference. Don’t you encounter cases when you can benefit from it?

@atesgoral

This comment has been minimized.

Copy link

commented Apr 7, 2019

Since let got introduced, I started using null to give a dummy value to my variables, just because let something; reads like an unfinished sentence:

let x; // let x what?
let x = null; // oh so, we'll set it to something better later

The reason I don't use let x = undefined; is, well, it's functionally the same as just let x;, and only human-grammatically different. I find initialization to null to be an explicit promise to set the value later.

@eps1lon

This comment has been minimized.

Copy link

commented Apr 7, 2019

Feedback wanted

Don't know if you care about what other libraries do but React's ref objects for instance are declared as type Ref<T> = { current: null | T }. null is used here as the default value i.e. React.useRef().current === null.

type A = {foo?: string | null}

Using null as the bottom value might be interesting if you care about property existence. If you declare { foo?: string } you don't know if the value of foo might be undefined or if the the object doesn't have a property foo. If you want to explicitly communicate via types that property foo exists and might have a bottom value then null is kind of your only option

@joakimbeng

This comment has been minimized.

Copy link

commented Apr 7, 2019

How do you reason about nulls in a database when used together with JS? E.g. an SQL database. Do you propose that undefined is translated to and from null when writing to/reading from the database?

@paulmillr

This comment has been minimized.

Copy link

commented Apr 7, 2019

Screw null and undefined, we should just switch to Maybe monad. 🤗

@yannbertrand

This comment has been minimized.

Copy link

commented Apr 7, 2019

I tend to use undefined as an unknown state e.g. when waiting for an HTTP response.

I have been using null as an unset value e.g. a not-filled input but I never feel the need to use it as I could be using an empty string in most cases instead.

@felixfbecker

This comment has been minimized.

Copy link

commented Apr 7, 2019

A couple of thoughts:

JSON doesn't support undefined

It does for properties: JSON.parse(JSON.stringify({ foo: undefined })).foo === undefined.
It only doesn't for array elements, but in all my time writing JS/TS I've never encountered a use case for JSON arrays with holes.

Another point: Many of the modern JS APIs return undefined for "doesn't exist", e.g. array.find().

I'm personally not super strict with not using null, when working with the DOM API for example I use null because that's what DOM APIs return, and when working with databases the distinction between a null value and an unknown value (i.e. wasn't SELECTed) can be useful. But in general, I prefer undefined. Especially with TypeScript it saves a lot of boilerplate code.

@igghera

This comment has been minimized.

Copy link

commented Apr 7, 2019

I quit using null more than an year ago and i never felt the need for it.

@ricardohbin

This comment has been minimized.

Copy link

commented Apr 7, 2019

About this custom JSON.stringify/JSON.parse:

The main problem with currently undefined fields disappears in actual scenario is when a JS service needs to provide/receive JSON code to other languages - and all of them supports "null", not "undefined".

For example, if the custom converter works like:

JSON.parse(JSON.stringify({a: undefined})) // {a: undefined}

It will only works in js world. This is a problem when working principally in backend between a lot of services with different languages.

To custom JSON.parse, you can do an override null to undefined like python does with None, but to JSON.stringify, we need to make sure undefined turns in null.

Ex:

JSON.stringify({a: undefined}) // '{"a":null}'
JSON.parse('{"a":null}') // {a: undefined}

Makes sense?

@mcmire

This comment has been minimized.

Copy link

commented Apr 7, 2019

I'm curious why you're choosing to use undefined over null? To me, null is a more meaningful value than undefined. If something is undefined, then it's not set yet. But why should that be possible? Everything should be initialized to something, even if it's nothing. And if I want to initialize something to nothing, then null seems like the more semantic option of the two.

Or is this less about semantics and more about pragmatism?

@medikoo

This comment has been minimized.

Copy link

commented Apr 7, 2019

To me it looks more as a TypeScript problem that put you in that weird corner.

In 99.999% of validation cases, treating both null and undefined same way as no value, is what what will work perfectly. If TypeScript makes that difficult, then it's a problem to be addressed there.

@ljharb

This comment has been minimized.

Copy link

commented Apr 7, 2019

I don’t think it matters what you use in your own code - what matters is that you treat null and undefined the same as input.

Also, the cost of needing custom JSON tooling seems pretty understated.

@PazzaVlad

This comment has been minimized.

Copy link

commented Apr 7, 2019

I switched to use only undefined when it's possible about half a year ago. Never regret it.

@MarkTiedemann

This comment has been minimized.

Copy link

commented Apr 7, 2019

I used undefined only for a while. But I'm back to using both now. I find that worrying about this kind of consistency was impacting my productivity more than any supposed gains. Sometimes, I think it's best to accept that the world is messy...

Or ditch both null and undefined, and use void this instead - it definitely sounds cooler.

@creepy-poet

This comment has been minimized.

Copy link

commented Apr 7, 2019

I use both. Null is explicit whilst undefined is implicit absence of a value. I care a lot about semantics because, well, code has two audiences, machines and developers; targeting one doesn't get you very far.

Having said that, I see no issues with just using undefined, even with JSON. If a property has a value of undefined, then don't send it.

@AaronFriel

This comment has been minimized.

Copy link

commented Apr 7, 2019

The only good use of null is in merge semantics over objects I think. Null means delete, whereas a stringified undefined key is absent meaning no change.

So I would say that maybe for your "is" and "ow" libraries it would be useful to keep null around, merge/PATCH semantics depend on it.

For other libraries if there is no distinction between "absent" and "deleted", use undefined.

For the rare case where "absent" and "deleted" are distinct values (do any of your libs do that?) I would keep null around instead of defining a symbol because it matches PATCH semantics and means your functions can be plugged into data transforming logic without change.

@BenoitRanque

This comment has been minimized.

Copy link

commented Apr 8, 2019

Regarding this being true:

JSON.parse(JSON.stringify({ foo: undefined })).foo === undefined

I would like to add that the following is also true:

JSON.parse(JSON.stringify({ foo: undefined })).bar === undefined

The JSON representation of { foo: null } is { "foo": null }
The JSON representation of { foo: undefined } is {}

Using null at least we know the property exists.

If anything do not use undefined, which is what you don't have in statistically typed languages as they won't let you access properties of objects that don't exists.

@fvilers

This comment has been minimized.

Copy link

commented Apr 8, 2019

A lot of people has already said it, but to me, null is different than undefined. I use undefined to specify that a variable is, hmm, undefined. But I use null to specify that a variable is defined, but, currently, it's value is not usable, aka null.

As function parameters, having a distinction between undefined (the parameter has not been provided) and null (the parameter has been provided but should not be used) could be useful.

@SpeedoPasanen

This comment has been minimized.

Copy link

commented Apr 8, 2019

Coming from a relational DB background, I agree with the other Null defenders. Null definately has it's use cases and a distinct meaning. I only use undefined to strip useless properties before JSON encoding eg. for an API call.

@caesarsol

This comment has been minimized.

Copy link

commented Apr 8, 2019

I probably will change my mind after following this thread, but I have always used null in my code as much as possible, and always treat undefined as an indication something was wrong.

I think JS should trigger an error instead of making undefined appear...
Always hoped for the strong mode to land in actual browsers.

And to tell the truth, undefined is such an ugly word to write! :)

@mrchief

This comment has been minimized.

Copy link

commented Apr 8, 2019

I strongly believe it was a mistake to have two have both null and undefined.

Agree 100%.

Newer JS features like default parameters only work with undefined.

This and the extra verbosity are good reasons to stop using it.

  • Everyone uses null and undefined inconsistently and interchangeably.
  • Nobody really knows when to use which.
  • Supporting both null and undefined complicates input validation.

Cost of fixing or ensuring consistency for these points is also highly understated. :)

I've stopped using it for quite a while now. Doing so in newer projects is much easier than enforcing them in older projects.

JSON doesn't support undefined

True but one can achieve such effect in some cases by omitting the property from the serialized JSON string. E.g, in C#, if you deserialize a JSON object into a class that has nullable properties then .Net serializer will correctly parse the JSON (missing props will not throw an error) and reversely, serialize the class's instance correctly in JSON (again, the nullable prop will be missing from a serialized string which is not an issue in JS land). This may not be true universally but at least it's not a universal blocker.

@JustinLivi

This comment has been minimized.

Copy link

commented Apr 10, 2019

I think the number of comments here and the wide range of responses indicate that there's probably no right or wrong answer here, similar to most coding style decisions. I think more important here than the final decision is coming up with a general guideline or rational.

That could take the form of an FAQ:

  • What does the presence of undefined mean in your code? Does it imply that the value is optional, a placeholder, was removed, etc.?
  • If null values will stick around, what do those represent? Is there any overlap in representation with undefined?
  • How should the presence of null or undefined values in third party integrations be handled? Should they be handled via the conventions of the third party? Should some interface be maintained? Should a best-effort be made on a case-by-case basis to translate third party conventions to whatever convention you decide on?
@GeoffreyBooth

This comment has been minimized.

Copy link

commented Apr 11, 2019

So CoffeeScript has something it calls the existential operator:

CoffeeScript: JavaScript:
obj.prop? obj.prop != null

It takes advantage of the loose comparison operator != or == to compare against null, because this way null and undefined are interchangeable. In the code above, obj.prop? will evaluate as false if prop is either null or undefined.

I’ve copied this technique in JavaScript to basically treat null and undefined as interchangeable when evaluating input:

if (input.prop != null) {
  // input.prop can’t be null or undefined, but can be false or 0 or an empty string
  doSomethingWith(input.prop);
}

This requires a flexible linter and a willingness to using one Bad Part (the loose equality operator) to hide another Bad Part (the existence of both null and undefined). I haven’t tested much, but it seems that input.prop != undefined should work just as well if your goal is to pretend that null doesn’t exist.

@ljharb

This comment has been minimized.

Copy link

commented Apr 11, 2019

The only difference there is that null is a keyword and undefined an identifier, which is why == null is generally preferred.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.