DEV Community

Cover image for Explain like I'm your junior: why is ?. good?
Matt Ellen-Tsivintzeli
Matt Ellen-Tsivintzeli

Posted on

Explain like I'm your junior: why is ?. good?

I've been writing JS almost two decades at this point, but I still can't see why ?. is not the beginning of an anti-pattern, especially for method calls.

I saw the post

And one that caught my eye was number 2 "Optional Chaining with Function Calls":

const user = {
getName: () => 'Alice',
};
console.log(user.getName?.());   // Alice
console.log(user.getAge?.());    // undefined
Enter fullscreen mode Exit fullscreen mode

Which screams "I don't know the interface to my objects, and I don't care."

You definitely should care about the interface to your objects.

I can almost see an argument for robustness if a library API changes, but no, if you're using a library, you know the interface to the objects, fix your code.

It just seems like a massive bug attractor.

You never want something to be undefined. Or have I missed a memo about that?

And that goes double for methods. If two objects don't have the same behaviours, they need to be treated as different types, not just shrugging and saying "it'll be alright".

Is testing for null illegal these days? I know null has been called a billion dollar mistake, but that is solved by an option type (like Maybe in Haskell), not what looks like the inverse.

An option type lets you know that this is something that could be null, so proceed with caution. It doesn't hide the fact, like ?. does.

With all that said, I'm happy to be disabused of the notion that ?. is just kicking the can down the road when it comes to checking for null, or is just bad design when it comes to undefined.

What is a good pattern, i.e. one that reduces bugs, that you can get using ?.?

Top comments (2)

Collapse
 
oculus42 profile image
Samuel Rouse

Optional chaining is still testing for null(ish). Just a different way to do it.

I find optional chaining most useful when you do not have a safe/reliable data/object contract. There are lots of reasons this can be the case.

  • An API you don't control which has several possible return objects
  • Versioned data contracts (old records may have different/missing structures)
  • Imperfect abstractions where extended classes or specific implementations may need different designs.

If you have an API where success cases have a status object and error cases have a cause object, you might write response.status?.code ?? response.cause?.code to simplify your handling.

Versioned objects are a major consideration for optional chaining. If you add an audit log to your transaction record at some point, you can check for transaction.audit != null but you could save yourself an extra layer with transaction.audit?.length which will tell you not only that the audit log exists, but that there are entries in it to display. It effectively replaces if (transaction.audit && transaction.audit.length). Sure, you can implement this a dozen different ways, but optional chaining can be a pretty reasonable one.

I generally dislike OOP, so with imperfect abstractions my mind doesn't go to extended classes, but to positioned content on the DOM. If you have a custom dropdown the list might have to be applied to the body and positioned to ensure it isn't cut off by a container. As a result, you may or may not have related elements or methods depending on where in the DOM your content is injected. You can choose between a bunch of guards, or you can use optional chaining to achieve the same result with less noise.

Collapse
 
mellen profile image
Matt Ellen-Tsivintzeli
  • An API you don't control which has several possible return objects

Does this mean one API URL (e.g. example.com/getARecord) can return different objects independent of your input? I'll admit that's a good case for ?.. I would also council moving away from such a haphazardly implemented API where possible.

  • Versioned data contracts (old records may have different/missing structures)

Versioned data contracts mean having different calls for each version. So long as the contracts are valid you have different methods for interacting with them.

I will admit if(transaction.audit?.length) is pretty neat. I like that. Data structures that can have varying properties due to old data is a use case I can understand.

Thanks for your reply. It helped me understand what ?. is good for.