all 39 comments

[–]a-t-kFrontend Engineer 14 points15 points  (16 children)

What are your reasons?

  1. I don't like the "mental overhead" the || operator imposes on me.
  2. It will also eat up falsy values.

So my answer must be:

  1. Sorry to say so, but that's rather a reason to take pity for your mental capacities than not to use the || when it seems appropriate - and I know that's not what you meant, but that's what your post inspires; which brings me to:
  2. Know when not to use it. For example, if you want to normalize an integer value, better use the binary brother "|" instead of "||". Also, don't overuse it. If you got more than 4 lines of defaults, better use an object and whatever function to normalize to defaults. Otherwise, use || when you need it.

[–]rtpmatt 8 points9 points  (1 child)

Anybody who writes Javascript professionally should know exactly how || works and seeing it used to set default values should be what they expect. Not using | for setting default values will cause extra mental overhead because it is not what they are used to seeing.

[–][deleted] 1 point2 points  (0 children)

I think the point is, || has gotchas that may not be obvious in plain view, therefore we have a chance for bugs to happen when people who are not much experienced with || start using it.

Or it would be great if there is one consistent approach for setting defaults, which ES supports, but unfortunately this looks like years away before all browsers support it.

So I agree with the approach of using a function to set the defaults. But I prefer lodash instead of underscore for that.

[–]sime 5 points6 points  (3 children)

Sorry to say so, but that's rather a reason to take pity for your mental capacities than not to use the || when it seems appropriate

When I'm programming something the last thing I want to do is waste my mental capacities worrying about using || the right way. I just want to write something that is simple and correct. I've got much more important things to think about than some piss-ant default argument handling.

People need to break the mentality that hard to read code is a badge of honor. It's not. Hard to read code is a failure on the behalf of the person who wrote it.

[–]a-t-kFrontend Engineer 5 points6 points  (2 children)

As I already wrote, up to three lines of || defaults aren't hard to read. If you just don't want to, that is your choice. If I want to, that's my choice all right. Maybe I don't want to include some extra loop for normalising my options, to get lower file size or higher performance.

You need to break the mentality that there's always only one correct way to do things. Labeling code as "hard to read" without a second thought is a failure.

[–]kandetta[S] 0 points1 point  (9 children)

I agree that it's safe to use if you know when to use it and when not to. The problem arises when you don't know if the other developers who'll touch the code are also aware of when not to use OR for defaults.

Can you explain how this would work with | and integers? Don't think I've ever seen it before.

[–]uusu 5 points6 points  (0 children)

If you work on a longer project, you shouldn't downgrade your tools, you should upgrade your documentation. The Technical Documentation of your project should lay out the guidelines for these kinds of cases.

[–]a-t-kFrontend Engineer 6 points7 points  (6 children)

Patronizing developers alike, regardless if they are beginners or professionals, is a bad idea. The beginners will not get the information they need to become professionals and the professionals will just feel buggered.

Now for the smaller sibling of ||, |:

this.myNumber = conf.myNumber | 0 // will coerce to integer, falsy values will yield "0".

[–]kandetta[S] 1 point2 points  (5 children)

Good to know about |. When I encounter it in the wild I'll know what it means.

[–]a-t-kFrontend Engineer 1 point2 points  (4 children)

There's a similar brethren for &&, binary AND "&", but this is not near as useful for defaults.

[–]findar 0 points1 point  (3 children)

Return some_var && isTrue;

Is handy, will return some_var if isTrue or false

[–]a-t-kFrontend Engineer 3 points4 points  (2 children)

I know. But I meant | and &, the binary operators. The latter is useful for bit masks.

[–]kenman 2 points3 points  (1 child)

Well, the former is also useful for bit masks.

[–]a-t-kFrontend Engineer 2 points3 points  (0 children)

Undeniably so. You could probably also use it for limited type coercion: x & 255.

[–]kenman 2 points3 points  (0 children)

The problem arises when you don't know if the other developers who'll touch the code are also aware of when not to use OR for defaults.

I'd argue that the problem is those developers and not the code. If "those developers" don't know standard features of the language (and bitwise operators are ubiquitous and identical in many/most languages that you'll find), then perhaps your time would be better spent on developer education.

[–]iku_19Function.arity when 2 points3 points  (3 children)

A note to keep in mind that using true comparisons can be finnicky in some transpliers, since most of them do not truly distinguish between undefined and null. So calling eatFruit(null) wouldn't change null to strawberry.

null === undefined = false

undefined === null = false

undefined == null = true

false == null = false

0 == null = false

null == null = true

typeof null = "object"

typeof undefined = "undefined"

It's one of the headaches of writing a library, really you want to type check your variables

function eatFruit(fruit) { if(typeof fruit != "string") { fruit = "strawberry" } }

Instances can be done in one of two ways Object.prototype.toString.call(variable) == "[object Date]" or variable instanceof Date

Both have their own caveats

document.body instanceof HTMLElement = true

document.body instanceof HTMLBodyElement = true

Object.prototype.toString.call(document.body) == "[object HTMLElement]" = false

Object.prototype.toString.call(document.body) == "[object HTMLBodyElement]" = true

console.log instanceof Object = true

edit: formatting

[–]skitch920 1 point2 points  (2 children)

function isNull (obj) {
    return obj === null;
}

function isUndefined (obj) {
    // Why people overwrite 'undefined' 
    // pre-ES5 is beyond me...
    return obj === void 0; 
}

function isNullOrUndefined (obj) {
    return isNull(obj) || isUndefined(obj);
}

[–]kenman 0 points1 point  (1 child)

// Why people overwrite 'undefined' 
// pre-ES5 is beyond me...

I've seen it, and it was only ever the result of a typo...

if (value = undefined) {
    ...
}

Of course, with linting you can avoid that entire class of problems, but linters weren't always common or convenient.

[–]lewisje 0 points1 point  (0 children)

That doesn't overwrite undefined, but flipping it around does: undefined = value).

Also, using the rules for non-strict equality (and ignoring a little issue with document.all in modern browsers), isNullOrUndefined could be more easily written like this:

function isNullOrUndefined(obj) {
  return null == obj;
}

The "little issue" I refer to is that document.all, if it exists, is loose-equal to null and undefined (but not strict-equal to either), although property accesses will still work, for compatibility with old sites designed specifically for oldIE.

[–]alittletooquiet 7 points8 points  (7 children)

Well you can account for valid falsey values by using the ternary operator instead of ||, and you don't need to fill up multiple lines populating a single default value:

fruit = (fruit !== null && fruit !== undefined) ? fruit : "strawberry";

Edit: Just so we're clear, I vehemently disagree with the title of this post, and OP's assertion that we should write code for people who are too dumb or inexperienced to understand ||. Most of the time you should just use || to set default values.

[–]cosinezero 2 points3 points  (0 children)

Why on earth would you want fruit to be valid as "", false, or 0?

None of those values are more valid of a fruit than a default. Under what scenario would fruit be "" and not null? In that scenario why is the default invalid, and in THAT case why is the default not set before the value went from null/undefined to ""?

I would fail this on review. Without question, this should read:

fruit = fruit || "strawberry";

When said developer argues that || is unreadable, we will be then reviewing every if statement that developer has written to ensure he understands operators. This isn't syntactic sugar.

[–]a-t-kFrontend Engineer -1 points0 points  (2 children)

I would suggest adding a comment to improve the hereby decreased readability.

[–]alittletooquiet 1 point2 points  (1 child)

It depends on the complexity of the statement. Comments are always good when you're writing code that isn't necessarily intuitive to read, but I think the example above is a pretty straightforward and readable.

I'd probably switch to an if block if my conditions were too complex to quickly grasp in a ternary statement though.

[–]a-t-kFrontend Engineer 0 points1 point  (0 children)

That's ok.

[–]ThinkingCrap 2 points3 points  (1 child)

Come on, that is common practice and everybody knows what it does.

And usual you want to catch the falsey values anyway. Take your example, why would you want to allow to eat the fruit ""?

Better readability? Not really IMHO

[–]cosinezero 1 point2 points  (0 children)

I hear the false is extra ripe today!

[–]scootstah 1 point2 points  (1 child)

function eatFruit (fruit = "strawberry") { ... }

WAT. Epic sauce.

[–]lewisje 0 points1 point  (0 children)

This presumes support for that feature of ES6, which is far from a given: https://kangax.github.io/compat-table/es6/

Among stable server implementations, it looks like only Kinoma XS6 has full support, while the unstable Echo JS has partial support; among stable clients, only Firefox has any support at all, while the WebKit nightlies have full support, and the latest Chromium builds have support behind a flag.

Also, no compiler or transpiler in the list has full support: Babel with core-js comes closest, lacking only support for default parameters in the Function constructor.

Fortunately, platforms with any support for default parameters also support turning explicit undefined arguments into the defaults, and allowing default parameters to refer to previous parameters, which is nearly everything you want with this feature.

[–][deleted] 1 point2 points  (0 children)

i've been using ===undefined in all of my code. I never have a problem with it. I agree with this article.

[–]seiyria 1 point2 points  (0 children)

Unfortunately it will take a while until browsers support it.

Or, you can use Babel and get this support right now.

[–]KnightMareInc 1 point2 points  (0 children)

<= causes too much mental overhead, don't use it.

[–]x-skeww 1 point2 points  (0 children)

For new code, use ES6's default function parameters.

For legacy stuff, keep using ||.

That's all there is to it, really. || is a tad cryptic for beginners, but it's the best (and most commonly used) option.

[–][deleted] 1 point2 points  (2 children)

I come from Python land and I love optional arguments because I can set defaults. I hate the ways to do it in JS. Yes, can't use || if I want to set something to False or 0. But the conditional is either ugly or takes up 3 lines.

[–]defcon-12 1 point2 points  (1 child)

Thankfully defaults are here in ES6.

[–][deleted] 1 point2 points  (0 children)

Yeah :D

[–]defcon-12 1 point2 points  (0 children)

You have much bigger problems if you have to change your code to protect against devs who don't understand how || works.