1

You can change the global variable of undefined to a value and you can read a property from a string, but you can't read the property of undefined.

The first two (the ones you can do) seem completely useless and the third is something I think most developers would love to work. If you make an API call and you're trying to handle complex data, why not just evaluate undefined.undefined.undefined.undefined = undefined instead of literally crashing the program? I know about optional chaining but I'm curious why is this even an issue. It seems like an easy fix but I'm guessing there is something idk about.

Joshu
  • 460
  • 4
  • 14
  • Sorry, I don't understand what you're asking...? How is it completely useless to read a property on a string or define a global value? Pretty much every program does those. Why would you want to read a property on undefined, exactly? Crashing the program isn't a bad thing. It makes more sense to crash and produce a reasonable error that you tried to do something impossible than to carry on with corrupt state. If you want the feature you seem to be asking for, it exists, but it's (correctly) opt-in: `foo?.bar?.baz` – ggorlen Aug 10 '21 at 20:58
  • 1
    @ggorlen - I believe the OP is asking why `undefined.x` (replace `x` by any property) crashes the program rather than just giving `undefined`, as happens with for example `"a string".x`. It's an interesting question (why can't `undefined` be automatically "boxed" into some sort of object, presumably an empty one, like numbers/strings/booleans are) but whatever the rights or wrongs, it can't be changed now due to backwards-incompatibility I guess. – Robin Zigmond Aug 10 '21 at 21:00
  • @ggorlen why does console.log(undefined.hi) crash? Literally and philisophically. – Joshu Aug 10 '21 at 21:01
  • Same reason accessing properties/dereferencing `null`/`nil`/`None` crashes every programming language I'm familiar with. You can't do it so why go on pretending like you were able to do it? What behavior _should_ occur other than raising an error? JS is already permissive enough about rarely throwing as is, part of why it has the reputation it has. – ggorlen Aug 10 '21 at 21:01
  • I answered that in my question already. If you try to access a property on undefined, it should evaluate as undefined (and thus would continue downstream as well) – Joshu Aug 10 '21 at 21:04
  • I disagree -- better to throw than to return undefined, which is a legitimate value you might want to be able to differentiate from a failed lookup. Optional chaining lets you throw away the safety net in certain circumstances and say "I know what I'm doing -- if this lookup fails, don't tell me about it with an explicit error". That's useful, but only in limited cases. The rest of the time, the lookup is an invariant and the throw is basically a notification of an assertion/contract violation. Are you looking for language references or opinions in asking this question? – ggorlen Aug 10 '21 at 21:06
  • @ggorlen can you give me an example where your method benefits the user more than hurts? Keep in mind, that, hypothetically, optional chaining could be an "opt-out" and makes more sense (to me) – Joshu Aug 10 '21 at 21:08
  • 1
    Sure. Let's say I'm writing an algorithm and I expect the property to always exist. One day, something goes wrong and the property doesn't exist -- my program is essentially broken, but the program just continues on with corrupt state. Somewhere else in the million-line codebase, there's a crash, and the error message seems to have no relation to the missing property. Would you rather debug this scenario or would you rather get an error right away that the contract was breached at the exact point of failure? It's a common misconception that errors are somehow bad. On the contrary. – ggorlen Aug 10 '21 at 21:12
  • 2
    If you aren't convinced this benefits the user, you can find a lot of evidence for this "fail fast, fail hard, fail loudly" approach in real code, for example, React: https://reactjs.org/docs/error-boundaries.html#new-behavior-for-uncaught-errors: _"in our experience it is worse to leave corrupted UI in place than to completely remove it. For example, in a product like Messenger leaving the broken UI visible could lead to somebody sending a message to the wrong person. Similarly, it is worse for a payments app to display a wrong amount than to render nothing."_ – ggorlen Aug 10 '21 at 21:16
  • You make a good argument. However, I'm not convinced that the harms outweigh the benefits. If an algorithm were to expect a **property** to always be there, if the property ended up being undefined, you'd still have the same issue and you'd have to debug where undefined began. In short, if you can handle undefined at the end, you can handle is at each stage. In another universe: `const test = {};` `if(test!.data!.hasInfo()) doSomething()` could be used if you MUST have that property. There are many other ways to do that as well. – Joshu Aug 10 '21 at 21:30
  • See also https://medium.com/@vcarl/overly-defensive-programming-e7a1b3d234c2. I said property, but it's the same as any error or object definition (all variables are properties somewhere, either global or `this`). That's the whole point -- "Data errors are simpler to debug if an error is thrown close to the source of the bad data." If you programmed everything with clear throws whenever invariants occur, then you won't have long chains of mutations to follow to figure out where something went awry. If you write code that suppresses errors throughout your codebase, good luck. – ggorlen Aug 10 '21 at 21:31
  • See also: [Why does reading a property sometimes throw an error in javascript?](https://stackoverflow.com/q/23687233/1048572), [Why does trying to access a property of null cause an exception in some languages?](https://stackoverflow.com/q/6984241/1048572), [Why referencing non-existent property of an object in javascript doesn't return a reference error?](https://stackoverflow.com/q/19317943/1048572) – Bergi Aug 10 '21 at 22:28

1 Answers1

2

The point of errors in programming is that they inform you of a serious contract violation or illegal state. You expected something to be defined, but it wasn't. You expected something to be a certain type but it wasn't. Usually, the program shouldn't try to continue under a contract violation, and if it should, you'd want to make that explicit and at least log the violation rather than suppress it completely with head buried in the sand.

In many languages, these sort of invariant violations and illegal operations cause explicit errors, either at runtime or in compilation. The earlier you can catch the problem (thanks to a compiler error, warning or runtime crash/error during testing), the sooner and easier you can fix the bug.

JS has a reputation as stubborn to raise errors due to weak typing and other permissive design choices. You can concatenate and perform math on pretty much any types, or compare values with coercive equality operator == and wind up with anyone's-guess results. The reason is mostly historical: JS was designed for quick, simple scripts, not for massive single-page applications of today.

The reason technology like TypeScript exists is to essentially add errors to JavaScript to make programs safer and support the sort of large, industrial-grade applications that were never envisioned in 1995 when JS was designed. These errors are purely compile-time in the case of TS, but other languages like Python won't let you do many of the things JS does at runtime, like adding two different typed values "abc" + 42 which raises a TypeError (dictionary [a sort of Map] accesses also raise an error; you have to opt-in to the default value equivalent of undefined).

Simarly, JS' strict mode "eliminates some JavaScript silent errors by changing them to throw errors".

The contrary philosophy is to suppress errors, which is exactly what the optional chaining operator ?. offers, and it sounds like you're suggesting the default object property operator . should as well. This is syntactical sugar for code that returns undefined when an object doesn't exist, and it's nice to use occasionally to keep code terse and communicate intent, saying essentially "look, I realize this particular access might be on an undefined object, and my code is designed such that it expects that case and will continue on with the expression evaluating to undefined".

But it's a good thing this isn't the default behavior: it's essentially suppressing an error, pretty much the same rationale behind "abc" * 42 returning the default value NaN instead of throwing a type error in JS. Why would you ever want this? Probably never, hence JS's reputation as unsafe.

As I mentioned in the comments, React also made changes to ascribe to the "fail fast, fail loudly, fail hard" philosophy:

[...] in our experience it is worse to leave corrupted UI in place than to completely remove it. For example, in a product like Messenger leaving the broken UI visible could lead to somebody sending a message to the wrong person. Similarly, it is worse for a payments app to display a wrong amount than to render nothing.

[...] This change means that as you migrate to React 16, you will likely uncover existing crashes in your application that have been unnoticed before.

[...] We also encourage you to use JS error reporting services (or build your own) so that you can learn about unhandled exceptions as they happen in production, and fix them.

Here's advice from an article on javascript.info:

Don't overuse the optional chaining

We should use ?. only where it's ok that something doesn't exist.

For example, if according to our coding logic user object must exist, but address is optional, then we should write user.address?.street, but not user?.address?.street.

So, if user happens to be undefined due to a mistake, we’ll see a programming error about it and fix it. Otherwise, coding errors can be silenced where not appropriate, and become more difficult to debug.

And here's a nice blog post on the matter

ggorlen
  • 44,755
  • 7
  • 76
  • 106