DEV Community

Cover image for Code Smell 149 - Optional Chaining
Maxi Contieri
Maxi Contieri

Posted on • Originally published at maximilianocontieri.com

Code Smell 149 - Optional Chaining

Our code is more robust and legible. But we hide NULL under the rug

TL;DR: Avoid Nulls and undefined. If you avoid them you will never need Optionals.

Problems

Solutions

  1. Remove nulls

  2. Deal with undefined

Context

Optional Chaining, Optionals, Coalescence, and many other solutions help us deal with the infamous nulls.

There's no need to use them once our code is mature, robust, and without nulls.

Sample Code

Wrong

const user = {
  name: 'Hacker'
};

if (user?.credentials?.notExpired) {
  user.login();
}

user.functionDefinedOrNot?.();

// Seems compact but it is hacky and has lots
// of potential NULLs and Undefined
Enter fullscreen mode Exit fullscreen mode

Right

function login() {}

const user = {
  name: 'Hacker',
  credentials: { expired: false }
};

if (!user.credentials.expired) {
  login();
}

// Also compact 
// User is a real user or a polymorphic NullUser
// Credentials are always defined.
// Can be an instance of InvalidCredentials
// Assuming we eliminated nulls from our code

if (user.functionDefinedOrNot !== undefined) {  
    functionDefinedOrNot();
}

// This is also wrong.
// Explicit undefined checks are yet another code smell
Enter fullscreen mode Exit fullscreen mode

Detection

[X] Automatic

This is a Language Feature.

We can detect it and remove it.

Tags

  • Null

Conclusion

Many developers feel safe polluting the code with null dealing.

In fact, this is safes than not treating NULLs at all.

Nullish Values, Truthy and Falsy are also code smells.

We need to aim higher and make cleaner code.

The good: remove all nulls from your code

The bad: use optional chaining

The ugly: not treating nulls at all

Relations

More Info

WAT?

Credits

Photo by engin akyurt on Unsplash


He who fights with monsters might take care lest he thereby become a monster. And if you gaze for long into an abyss, the abyss gazes also into you.

Nietzsche


This article is part of the CodeSmell Series.

Top comments (29)

Collapse
 
loucyx profile image
Lou Cyx •

This is your opinion, and is ok ... my problem is mainly that is presented as fact, when it isn't.

// I agree that we should avoid "falsy" values, so for that you can just combine
// optional chaining with nullish coalescing and default to `false` if the value
// is `undefined`, making the value always an actual boolean.
if (user?.credentials?.notExpired ?? false) {
    user.login();
}

// This is also ok, if the function exists you call it, if it doesn't, you don't.
user.functionDefinedOrNot?.();

// If the function returned a value, then ideally you should use nullish
// coalescing again.
const value = user.functionDefinedOrNot?.() ?? "default";
Enter fullscreen mode Exit fullscreen mode

You say that it "seems compact", but it IS compact. You also say is "hacky", but...

// This (the old way of "solving" the problem):
if (user && user.credentials && user.credentials.notExpired) {
    user.login();
}

// If way more "hackier" than this:
if (user?.credentials?.notExpired ?? false) {
    user.login();
}

// And doing this:
if (user.functionDefinedOrNot !== undefined) {
    user.functionDefinedOrNot();
}

// Is EXACTLY the same as doing this:
user.functionDefinedOrNot?.();
Enter fullscreen mode Exit fullscreen mode

Your proposed "solutions" are to either take more memory with default values (in the case of the object), or do an unnecessary checks that are already done for you with optional chaining (in the case of the function). You can see how TypeScript handles optional chaining when compiled as ES5 here, but the long story short is that is pretty much the same as what you did with the if, but with less boilerplate.

Now don't get me wrong, I agree with your point about how null should be avoided (I even wrote about that on a post of mine), but undefined is ok, and both optional chaining and nullish coalescing are great tools to deal with possible missing values. Writing your own "abstraction" of nullish values serves no purpose other than over-engineering and over-complicating a simple language feature.

One thing that we need to keep in mind as developers, is that stuff that looks like a "good practice" in a language, doesn't necessarily translate to a "good practice" in another language (and it could even be consider bad). Case in point is that Java is an OOP language with strong use of classes, which I already discussed several times here in DEV are terrible in JavaScript (and I would even say are terrible in general), because they provide a really poor form of encapsulation and reuse, compared with just using functions, modules and composition. So maybe from a class mindset it makes sense to avoid language features such as optional chaining, but it definitely doesn't make sense from a JavaScript point of view, when it actually helps you write less boilerplate to achieve the same goal.

Is almost like when those devs that need to put everything inside classes to the point where if they need to do an addition, they...

// do something like this:
class Operations {
    static add(a, b) {
        return a + b;
    }
}

// Instead of just:
const add = (a, b) => a + b;
Enter fullscreen mode Exit fullscreen mode

But long story short, I would recommend to change the wording of this kind of articles a little to make them look more like "opinion/suggestions", and less like "facts/rules", so we avoid confusing juniors that might think this are like "universal rules" when they aren't.

Cheers!

Collapse
 
mcsee profile image
Maxi Contieri •

1 I have no strong evidence it takes more memory.
Memory is expendable. Readability and maintainability aren't
It seems we are dealing with 'Premature Optimization'.

I also think 'undefined' are yet another code smell.
I will also very happy if juniors stop using nulls since we are no longer in the 50s.
But I wil DO take your advice and add a disclaimer these articles are opinions and write it down

I am also reading your articles!

Collapse
 
mrcaidev profile image
Yuwang Cai •

I'm a beginner and am using null and undefined in my current project, and I'd love to hear why they are bad. 🤔
For example we have an Order object, and it has a property called finishedAt with type Date. What should I return if the order is not finished? I'm using null in this case.

Thread Thread
 
mcsee profile image
Maxi Contieri •

if the order is not finished you shouldnt ask for the date. it makes no sense in real world

An exception should follow fail fast principle

Thread Thread
 
yoursunny profile image
Junxiao Shi • • Edited

An unfinished order is signified with .finishedAt = undefined. If you make this property throw an exception, you are violating:
dev.to/mcsee/code-smell-73-excepti...

Thread Thread
 
loucyx profile image
Lou Cyx • • Edited

Hi @mrcaidev! You can just omit it. From TypeScript, that Order type would look something like this:

type Order = {
  id: number;
  finishedAt?: Date;
}
Enter fullscreen mode Exit fullscreen mode

So we say finishedAt is optional (is not always there), that means it can be either Date or undefined. Then when you get that from the front-end, you can check if finishedAt is present, instead of checking if is null.

The best thing is that the response will also be smaller:

// You go from this:
{
  "id": 1,
  "finishedAt": null
}

// To this:
{
  "id": 1
}
Enter fullscreen mode Exit fullscreen mode

The property is only sent if it is set, if not, then its omitted :D

Cheers!