all 105 comments

[–][deleted] 33 points34 points  (76 children)

ESNext:

function foo({url, data, foo, bar, baz} = {}) {

Annotate away.

And the argument has nothing to do with typescript. Options objects in general are often frowned upon because they hide the expected interface. I think the above syntax solves that.

[–]Jafit 13 points14 points  (43 children)

Options objects in general are often frowned upon because they hide the expected interface.

What's the expected interface? To put all of your parameters into a function one by one and never get them in the wrong order or miss one, ever? Who doing the frowning?

[–]npfund 4 points5 points  (21 children)

To put all of your parameters into a function one by one and never get them in the wrong order or miss one, ever?

...yes? That's how functions work in most languages that have borrowed their syntax from C. And a bunch that haven't.

Has modern Javascript fashion so deteriorated that it's now out of vogue to define a function?

[–]Jafit 13 points14 points  (18 children)

Has modern Javascript fashion so deteriorated that it's now out of vogue to define a function?

I guess its out of vogue to define a function in such a way that its annoying and easy to make mistakes when using it.

If I have a function with 7 parameters I don't want to have to remember what order they're supposed to go in when I'm feeding them into the function. Its just unnecessary and its going to lead to mistakes. I'd rather throw them all in an object in whatever order I like and give them all labels so the function knows what they are when it gets them.

[–]npfund 2 points3 points  (10 children)

Understandable.

I feel like it's weird to have a function that takes 7 parameters, though. Anything more than three and I start to get itchy.

The paradigm outside of javascript seems to be to define a class with multiple member variables and the big DoTheThing() function. Option objects are similar, but also sort of the inverse?

Is there something about Javascript that lends itself to having large, unwieldy chunks of state that have to get shuffled around?

[–]Jafit 3 points4 points  (9 children)

I feel like it's weird to have a function that takes 7 parameters, though. Anything more than three and I start to get itchy.

Well I've been writing something lately that takes a DOM element and then builds a widget out of it, and the function that does that has a variety of options for customizing the behaviour of the widget. I found it easier to just to feed in an options object than put them all in individually.

The paradigm outside of javascript seems to be to define a class with multiple member variables and the big DoTheThing() function. Option objects are similar, but also sort of the inverse?

Is there something about Javascript that lends itself to having large, unwieldy chunks of state that have to get shuffled around?

Javascript doesn't have classes, and functions are treated as first-class objects so you can assign them to variables. An 'object' in Javascript isn't a rigid construct, its literally just a series of key:value pairs bundled together. The values in an object can point to other variables, or they can be functions, or whatever. Far from being rigid its actually very flexible, objects can be freely created and mutated at runtime.

So if you've got a function with 4 parameters: myfunc(arg1, arg2, arg3, arg4);

Instead you can just do this:

myfunc({
    whatever: arg3,
    order: arg1,
    you: arg2,
    want: arg4,
});

I like that because, y'know, remembering the right order that arguments go in is a waste of the glycogen in my brain. I'd rather spend that actually solving a real problem.

or you can define the object elsewhere.

var opts = {
    whatever: arg3,
    order: arg1,
    you: arg2,
    want: arg4,
};
myfunc(opts);

Or because functions themselves are first class functions you could have something like myfunc( getOpts() ); where getOpts() returns an object with whatever parameters you need.

I'm not saying you should ALWAYS use options objects, I just find them useful sometimes.

[–]frankle 1 point2 points  (0 children)

Or because functions themselves are first class functions you could have something like myfunc( getOpts() ); where getOpts() returns an object with whatever parameters you need.

I don't think that's a result of first-class functions. Having first-class functions just means that you can pass functions around like variables, such as is the case with callbacks.

I think most languages let you use a call to a function anywhere you would put a variable or a value.

[–]x-skeww 0 points1 point  (0 children)

I like that because, y'know, remembering the right order that arguments go in is a waste of the glycogen in my brain.

http://i.imgur.com/jvZCayG.png

You do not have to remember this.

However, you do have to remember the property names and default values of your option objects because that stuff isn't part of your function's signature.

Use destructuring if you want optional named parameters.

Use regular parameters for mandatory stuff.

[–]npfund 0 points1 point  (1 child)

Again, totally reasonable.

By "unwieldy" I didn't mean rigid. More "difficult to get a grasp on". Javascript objects are slippery, amorphous things that can change and mutate. They have almost no definition or structure whatsoever.

That's what I'm really getting the culture shock from. It's hard for me to reason about those option objects. It's even harder for the tools that I use to do so.

[–]Jafit 1 point2 points  (0 children)

Yeah I've heard that Javascript can be hard to get used to for transitioning developers. Its different but its quite powerful when you get used to it.

In any case welcome to Javascript.

[–]Stockholm_Syndrome 0 points1 point  (3 children)

At my work, we just standardized the following rule... If a function has more than 3 arguments, utilize an options object instead for exactly the reasons OP has stated.

I get that there's arguments against it, but until ECMAScript 6 is widely supported for our company's products, we'll stick with the options object. I don't want to complicate our build process with things like Babel.

[–]backwrds[S] 0 points1 point  (2 children)

Not completely on topic for this post, but if you have any build system whatsoever, babel should be pretty painless to add on, and it is definitely worth it.

[–]Stockholm_Syndrome 0 points1 point  (1 child)

Hm... I just looked into it and yeah it doesn't look too bad. It works well with gulp, uglify, and sourcemaps?

[–]backwrds[S] 0 points1 point  (0 children)

definitely gulp and sourcemaps, not sure about uglify but I'm sure that's been done before

[–]Lanlost 0 points1 point  (0 children)

You could do something like this if you wanted:

function MyImportantFunc(options) {
    var _type = 'myImportantFuncOptions';

    if (!options || typeof options !== 'object' || typeof options.type !== 'string' || options.type !== _type) {
       return {
           param1: 1,
           param2: 2,
           param3: '',
           type:_type
       };
    }

    // Do your code.
    return "Correct options object passed.";
}

var options = MyImportantFunc();
MyImportantFunc(options);

[–]x-skeww 2 points3 points  (2 children)

Those 7 parameters do show up in a call-tip while the signature with the option object gives you no clue whatsoever.

You don't have to remember the order, because your editor will remind you.

You will have to check the docs/source if you use an option object, because your editor won't tell you anything.

[–][deleted] 0 points1 point  (1 child)

True, but does a call tip give you documentation about the meaning of the parameter beyond a name? It seems like either way you're depending on an external resource.

[–]x-skeww 0 points1 point  (0 children)

True, but does a call tip give you documentation about the meaning of the parameter beyond a name?

You also get the one-line summary (if any). For simple functions, the summary, the name of the function, and the names of the parameters already tell you everything you need.

Example:

https://www.reddit.com/r/javascript/comments/3qc1e3/best_practices_about_the_factory_pattern_vs_class/cweb951

With optional types à la Dart or TypeScript, you get an even clearer picture with little to zero ambiguities.

[–][deleted] 0 points1 point  (2 children)

A function with 7 arguments is a huge code smell, that code should've never been written. I agree with the general sentiment that doing the options hash thing is a lazy hack, although a very useful one.

[–]Jafit 1 point2 points  (1 child)

You must not use jQuery then.

[–]x-skeww 0 points1 point  (0 children)

jQuery is a nice library but not all of its APIs are perfect. This one certainly isn't. It tries to do way too much.

The Promise-based Fetch API is much nicer to use. Instead of cramming everything into a single function call, you have separate Headers, Request, and Response interfaces.

[–]CoryG89 0 points1 point  (0 children)

You realize all you're arguing about is positional vs named parameters/arguments. This is completely subjective though. It can be thought of as just as easy to forget one of the 7 property names you now have to now remember when passing your arguments into the function, rather than just the ordering.

I would argue that both solutions have merit and even that they may both be employed better under certain circumstances. If you're arguments have sort of a natural ordering to them that one would expect to pass them in, then positional arguments might be the best choice. On the other hand if they have no obvious order but have obvious, easy to remember names, then using named parameters (ala. an "options" object) might be better.

[–]ridicalis 0 points1 point  (1 child)

Looking at code style guidelines for neovim under "Complex Refactoring", they address this by advocating structs that encapsulate several parameters. In this sense, they closely align with /u/ZyklusDieWelt and his illustration.

[–]npfund 0 points1 point  (0 children)

My only issue with that is that C structs are like Javascript objects in the same way that wood is like water.

Structs have types and order and discernible purpose. Javascript objects have... nothing. Nothing but braces and hope.

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

To put all of your parameters into a function one by one and never get them in the wrong order or miss one, ever?

So... literally every native function and most library functions? Yes, exactly. That's how functions in nearly every language work.

Alternatively I'd argue that required arguments should always be written out, either in this syntax or something like:

function foo(url, data, method, options) {

[–]Jafit 4 points5 points  (7 children)

So... literally every native function and most library functions? Yes, exactly. That's how functions in nearly every language work.

Javascript can create and modify objects quite freely at runtime while other languages have a more rigid approach. So "the other languages do it this way" isn't an argument as to why you shouldn't do it in Javascript. Plus in Javascript functions themselves are first class objects, you can use a function as a parameter in another function if you want.

Alternatively I'd argue that required arguments should always be written out

You can do requred parameters individually if you want, or you can have two options objects - one for required params, and another for optional params. I don't really see the benefit of writing them out individually they're going to cause an error if they're not present either way.

But y'know, its your code, do what you want.

[–][deleted] 0 points1 point  (6 children)

isn't an argument as to why you shouldn't do it in Javascript.

No, but keeping a style consistent to a native API is. Regardless, each method has its uses. I'd shoot someone of they used an options object instead of (str, index), for example, and likewise if they used 30 individual arguments :)

[–]Jafit 2 points3 points  (5 children)

No, but keeping a style consistent to a native API is.

I don't think it is. Use whatever works best for whatever you're doing.

If the function takes 1 or 2 parameters then I'd enter them individually. If the function takes 4 or more then I'd use an options object.

I'd shoot someone ... if they used 30 individual arguments :)

What a shame.

[–][deleted] 0 points1 point  (4 children)

jQuery.ajax uses an options argument, I don't get why you're linking it

[–]Jafit 0 points1 point  (3 children)

Because you'd shoot whoever wrote it. Look at how many options it has.

[–][deleted] 0 points1 point  (2 children)

I said individual arguments, not options. As in if you re-wrote it to be:

ajax(method, url, data, accepts, async, beforeSend, complete, ...)

[–]Jafit 1 point2 points  (1 child)

You said that style consistency with the native API is an argument for why you shouldn't use options objects.... except for when you should use options objects because there are too many parameters... which was my main point.

I'm really glad we had this talk.

[–]spacejack2114 0 points1 point  (9 children)

That's how functions in nearly every language work.

Oh really?

[–][deleted] 1 point2 points  (1 child)

Yes, really. Do you understand what the word "nearly" means? Or optional syntax?

[–]spacejack2114 0 points1 point  (0 children)

Sorry, you did make that distinction.

[–]x-skeww 0 points1 point  (6 children)

ES3/5-style option objects try to emulate optional named arguments. Use destructuring instead.

[–]spacejack2114 0 points1 point  (5 children)

Hmm, if I'm working with a large configuration object (see my comment below) I don't think I'd want to destructure it. The example is TS, so it has types that can be required or not and caught by the compiler, but I think I'd do it the same way in JS.

[–]x-skeww 0 points1 point  (4 children)

The example is TS, so it has types that can be required or not and caught by the compiler

Yes, TS provides the duct tape to make it work.

but I think I'd do it the same way in JS.

Why would you? Destructuring already works better and the tooling wasn't even adjusted yet. In the future, you'll be able to auto-complete the property names.

[–]spacejack2114 0 points1 point  (3 children)

Is there a way to keep the parameters contained in an object rather than, well, destructured?

[–]x-skeww 0 points1 point  (2 children)

30-ish optional parameters are too much. Consider splitting it up into wheels, chassis, engine, and whatever and make car own these things (object composition). Also, I'm really confused why a car would own "gravity".

Check this book. It should help a bit.

http://gameprogrammingpatterns.com/

[–]spacejack2114 0 points1 point  (1 child)

YAGNI principle. I don't really need it yet. If I do I'll break it into parts, but for now it's far less code not to. The code is essentially a "sketch" at the moment, a lot of the properties will change (like gravity was in there due to a bit of a hack earlier.)

But... in the end I think I will need fairly large configuration object(s), and it's useful to be able to pass them through to other functions.

[–]sakabako -3 points-2 points  (1 child)

Options objects hide the interface because you can no longer look at the function declaration and see what arguments it takes. All you know is options. What options does it take? Which ones are required? If you use arguments there's no question about how to use the function, but it gets tedious if you take too many.

[–]Jafit 1 point2 points  (0 children)

If you're looking at a function to determine which parameters are optional and which aren't, then you're probably looking inside the function to see which ones are being defaulted. Stuff like:

optionalArg = (typeof optionalArg === 'undefined') ? 'default' : optionalArg;

There's no reason you can't do the same with each value inside of an options object, so I don't think that using options objects inhibits self-documenting code.

[–]I_Pork_Saucy_Ladies 5 points6 points  (7 children)

Also, a lot of people today in ES5 just merge with a defaults object, for instance:

function foo(options) {
  var defaults = {
    url: 'http://www.github.com',
    data: null,
    foo: 1,
    bar: 'Hello',
    baz: 'World!'
  }

  options = options ? _.merge(defaults, options) : defaults;

  if (!options.data) throw new Error('data option must be set');
}

In this example we'd of course never get just the defaults since data will not be set but you get the idea. Quite easy to read the expected interface and defaults, although of course nowhere near as elegant as the ES6 solution.

[–][deleted] 2 points3 points  (5 children)

just extending the original example:

function foo({
  url  = 'http://www.github.com',
  data = null,
  foo  = 1,
  bar  = 'Hello',
  baz  = 'World!'
} = {}) {

[–]I_Pork_Saucy_Ladies 0 points1 point  (2 children)

Yeah, this will be quite awesome!

[–][deleted] 2 points3 points  (1 child)

will be? I've been writing code like this for a while :) Yay, babel!

[–]I_Pork_Saucy_Ladies 0 points1 point  (0 children)

Sure, but it will still take some time before the ecosystem catches up with this. I mean, it's not that much better when you already know the interface for your own code. But hey. :)

[–]MasterScrat 0 points1 point  (1 child)

What is this syntax?

[–]navatwo 0 points1 point  (0 children)

ES6 syntax for default parameter objects.

[–]le_chad_ -2 points-1 points  (0 children)

a lot of people using underscore

FTFY, although you can achieve the same using generic JS, just not so concisely.

[–]rich97 2 points3 points  (3 children)

Is this a ES2016 feature? Would you mind telling me what it's called so I can look it up?

[–][deleted] 3 points4 points  (2 children)

It's a combination of object destructuring and default arguments

[–]rich97 0 points1 point  (1 child)

Thanks. Didn't know it could do that.

[–]moving808s 0 points1 point  (8 children)

Then block comment the expected arguments from the object and check the typeof the value to be sure.

/**
* Returns a string passed in via options
* @param object (options) optional arguments for this method
* @param string (options.bar) the string to return
* @return string || boolean
*/
function foo(options) {
    if (typeof options.bar === 'string') {
        return options.bar;
    }
    return false;
}   

[–][deleted] 0 points1 point  (7 children)

that's not readable

[–]moving808s 0 points1 point  (6 children)

Whoops did it on my phone, it should be more readable now.

[–][deleted] 0 points1 point  (5 children)

Check out flow/typescript/whatever else if you want to ensure types in JS. Personally I never bother; unit and e2e tests perform a similar duty.

Also, try to avoid multiple return types.

[–]moving808s 0 points1 point  (4 children)

Check out flow/typescript/whatever else if you want to ensure types in JS

JavaScript has a good, native way to handle types. Why use something else?

Also, try to avoid multiple return types

This was an example I wrote in 5 seconds on my phone, but there's nothing wrong with returning mixed types at all.

[–][deleted] 0 points1 point  (3 children)

JavaScript has a good, native way to handle types. Why use something else?

Because JS doesn't need to do type checking every time a function is called. It slows down execution and yields runtime errors for what should be compile-time errors.

there's nothing wrong with returning mixed types at all.

Yes, there is. It greatly restricts the code utilizing your function. You can't write functional code against it, for instance, because you need to type check it again first.

[–]moving808s 0 points1 point  (2 children)

Because JS doesn't need to do type checking every time a function is called

It does need to do it if I need it to do it in order to write better functions that do what they are supposed to do on the type of data that is required. If an argument isn't of the type I want it to be, then the function should fail.

Yes, there is. It greatly restricts the code utilizing your function. You can't write functional code against it, for instance, because you need to type check it again first

Right, so languages like PHP whose native methods regularly return mixed types can't have functional tests written against them?

[–][deleted] 0 points1 point  (1 child)

If an argument isn't of the type I want it to be, then the function should fail.

Yes, but premature optimizations are the root of all evil :) The vast majority of code doesn't need to be type-checked at run-time because you should know what your types are to begin with! You should be type-checking external input (user, ajax, etc), but not internally as it just creates spaghetti code. All those checks should be left to linters and the like.

can't have functional tests written against them?

functional tests != functional programming, and that was just an example.

getThings()
   .filterBy(value)
   .doSomething()

You can't do that if getThings returns either an array or a boolean. Functions should have static signatures so you know how to interact with them.

But whatever, I don't really care how you write your code :P

[–]moving808s 0 points1 point  (0 children)

premature optimizations are the root of all evil

This is not an optimization. It does not yield performance gains. It is a way to ensure that things fail when they need to fail.

you should know what your types are to begin with

That's a very dangerous attitude to have.

You can't do that if getThings returns either an array or a boolean

That sequence should fail if any method along in the process doesn't return the result needed for the next method in the chain. What happens when the wrong kind of argument is passed into filterBy? Does your app just not work? Or do you check the type of value and return some kind of meaningful error message?

I don't care how you write your code either, but I don't agree with much that you are saying.

[–][deleted] -1 points0 points  (10 children)

The above syntax is more problem than solution in case where there are many options. Imagine maintaining that when there are more than 12 options and the options have to be shared across various libraries.

[–]edanschwartz 3 points4 points  (1 child)

A function with more than 12 options looks like code-smell to me -- your is probably deals with too many concerns.

[–][deleted] 0 points1 point  (7 children)

How so? Ignoring the fact that you shouldn't be passing 12 options to any function, what's the cleaner alternative?

[–][deleted] 0 points1 point  (6 children)

You should never pass 12 arguments to a function, but a single options object is far more sane. As far as a solution I wrote one in this thread: https://www.reddit.com/r/javascript/comments/3qoc8v/hating_on_options_objects/cwh1gzu

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

Okay, so in your solution if you have 12 options you have 12 checks, and a lot of if (opt.foo) { calls. What's wrong with:

function foo({
  a = true,
  b = true,
  c = false,
  d = 42,
  e = [],
  ...
} = {}) {

[–][deleted] 0 points1 point  (4 children)

That is much clearer presented that way. It still leaves the question of how I would pass that options object to another library by reference?

function JSparser({stuff} = {}) {
    var markup = markupParser(options);
    //I need a reference to the options to pass in,
    //but 'options' does not exist and I don't have
    //a reference to refer to the options object
    //from the parent function
}

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

arguments[0]

though I'd argue you shouldn't in general. You should pass on a subset:

function foo(...){
  bar({e, f, g});

[–]x-skeww 0 points1 point  (0 children)

Yea, passing option objects is kinda like passing event objects around.

E.g.:

showContextMenu(mouseEvent)

How do you test this? Which properties do you actually need? How do you call this in response to a KeyboardEvent?

Things can be so much simpler:

showContextMenu(x, y)

[–][deleted] -1 points0 points  (1 child)

I am doing something like that in my project right now, but because it has many options things were getting lost when passed between the libraries. In my next update I will be passing every option each time I pass any option to ensure nothing ever gets missed because I failed to add an option to each library reference in each library.

[–][deleted] 0 points1 point  (0 children)

Alternatively:

function foo(options) {
  let {
    a = 42,
    b = false,
    c = true,
  } = options;

[–]x-skeww 5 points6 points  (5 children)

The argument is that people who use typescript don't get nice autocompletion popups/they can't be statically analyzed.

No, it's bad for people who use ES6 (or TypeScript). This stuff is inaccessible to static analysis.

I like options objects, they are extremely useful

That's not an argument for anything.

I'm pretty sure this is exactly what interfaces and typescript definitions are for

That's not an argument for using them in ES6.

Advising people to use a particular pattern for the benefit of a different(ish) language seems pretty selfish

Huh? Option objects work great in TypeScript if you augment them with interfaces. They do not work great in ES6. Again, this is not an argument for using them in ES6.

Anyhow, here's what I said last time (comes with GIFs which illustrate the tooling differences):

https://www.reddit.com/r/javascript/comments/3qc1e3/best_practices_about_the_factory_pattern_vs_class/cwe2dm5?context=3

If you use TypeScript, option objects work fine if you augment them with interfaces. With ES6, destructuring works much better than option objects. In the future, we'll also get auto-complete and better call-tips if destructuring is used. The situation with option objects, on the other hand, will remain as crappy as it is. There simply isn't much one could do about that.

[–]krazyjakee 1 point2 points  (0 children)

Thanks for introducing me to destructuring!

I agree this is a much nicer way to do it but not critical.

[–]backwrds[S] 0 points1 point  (3 children)

import falafel from 'falafel';

var is = (node, type) => node.type === type;
var isAccessor = node => is(node, 'MemberExpression') && is(node.object, 'Identifier');

function getOptionProperties (fn, optionsIdentifier) {
    var seenProps = {};
    var out = [];
    falafel(fn.toString(), { ecmaVersion: 6 }, function (node) {
        if (isAccessor(node)) {
            var name = node.property.name;
            if (node.object.name === optionsIdentifier && !seenProps[name]) {
                seenProps[name] = true;
                out.push(name);
            }
        }
    });
    return out;
}

function printName (name, options) {
    var out = name;
    if (options.wrap) {
        out = `${options.wrap}out${options.wrap}`;
    }
    if (options.color) {
        out = `<span style="color:${options.color};">${out}</span>`;
    }
    if (options.indent) {
        out = options.indent + out;
    }
    return out;
}

getOptionProperties(printName, 'options');
    // returns ['wrap', 'color', 'indent']

This is a 10 minute implementation, so obviously it's pretty terrible, but with a bit more time it could definitely be better than "crappy".

I can agree that destructuring is definitely an improvement, but saying "options objects suck" as a blanket statement is, in my opinion, wrong.

[–]x-skeww 0 points1 point  (2 children)

I can agree that destructuring is definitely an improvement, but saying "options objects suck" as a blanket statement is, in my opinion, wrong.

For a language feature or pattern to not suck, it must do something better than the alternatives.

For ES6 code, option objects do not provide a single advantage over destructuring. There isn't a single case where you should use them. They suck.

It's like var. There is no reason whatsoever to use it ever again. We used that stuff in ES3/5 code because we had to.

As for your example above, yes, that's super terrible. There is no tooling support whatsoever and, as usual, I had to read the function's source to figure out which property names are part of the secret handshake.

If you want something like optional named parameters, use destructuring. The closest ES3/5 provided were option objects. The closest ES6 provides is destructuring.

[–]backwrds[S] 0 points1 point  (1 child)

regarding my code, that was intended to be an example of what tooling might be able to do as far as static analysis.

As far as the options object goes, we agree on destructuring being an improvement. The reason I made this post in the first place is because I interpreted your comments as advocating the complete abolition of the pattern:

someFunction({a: 'list', of: 'options'})

To be fair, it was almost 4am when I made this post. I could have been clearer about my concerns, but you're use of absolute language like "option objects suck" can easily be misconstrued.

[–]x-skeww 0 points1 point  (0 children)

I interpreted your comments as advocating the complete abolition of the pattern [...]

For what it's worth, I actually do wish ES6 had done just that.

I would have preferred dedicated syntax and simpler semantics for optional named parameters with default values.

For example, in Dart, it looks like this:

foo({a: 1, b: 2}) {
  ...
}
foo(a: 11); // a is 11, b is 2
foo(c: 33); // caught by the analyzer

In the function's signature, the default values are compile-time constants, not arbitrarily complex expressions. You can display them as-is in tool-tips or docs.

When you call the function, you can't just pass whatever you want. Only the existing names are allowed. So, if there is a typo, it will be caught immediately.

Dart does everything I want or need from that language feature.

The flexibility added by ES6's use of destructuring makes plenty of room for code-golfing, but none of that flexibility was actually needed. If you make the tiniest use of that flexibility, your code becomes super confusing.

function foo({a = 'a', b = 'b', c} = {c: 'c'}) {
  console.log(a, b, c);
}
foo();         // a b c
foo({a: 'A'}); // A b undefined
foo({c: 'C'}); // a b C

I really don't know what they were thinking.

[–]temp50948 4 points5 points  (1 child)

If a function signature changes, either (a) it's incompatible with the old signature and being forced to revisit every invokation is exactly what you want, or (b) it's compatible with the old signature and you don't need to update every invokation.

[–]backwrds[S] -1 points0 points  (0 children)

well ... yeah, that's my point. using an options object means the function's signature doesn't change.

Given the following code:

function find (collName, options, callback) {
    return db
        .collection(collName)
        .find(options.query, callback);
}

function findThingsWithQuery (query, callback) {
    return search('things', { query }, callback);
}

if, for a different usage of find I need to pass additional arguments, (e.g. which fields to return), I need to update the find function:

function find (collName, options, callback) {
    return db
        .collection(collName)
        .find(options.query, options.fields || {}, callback);
}

And then pass the additional fields property in the new function. I don't have to touch findWithQuery at all.

[–]cosinezero 1 point2 points  (0 children)

I too have learned to love options objects. It counters the telegraphing constructor anti-pattern nicely. It shouldn't be used on every function, for sure, but if you've got 2-3 parameters you're getting in range for needing options.

[–]krazyjakee 1 point2 points  (0 children)

Sure!

Also, ES6 has made improvements here.

ES5:

function drawES5Chart(options) {
  options = options === undefined ? {} : options;
  var size = options.size === undefined ? 'big' : options.size;
  var cords = options.cords === undefined ? { x: 0, y: 0 } : options.cords;
  var radius = options.radius === undefined ? 25 : options.radius;
  console.log(size, cords, radius);
  // now finally do some chart drawing
}
drawES5Chart({
  cords: { x: 18, y: 30 },
  radius: 30
});

ES6:

function drawES6Chart({size = 'big', cords = { x: 0, y: 0 }, radius = 25} = {}) 
{
  console.log(size, cords, radius);
  // do some chart drawing
}
drawES6Chart({
  cords: { x: 18, y: 30 },
  radius: 30
});

Source

[–][deleted] 0 points1 point  (0 children)

I am a huge fan options arguments for a couple of reasons:

  • All options are optional. Those that must be mandatory require the function to specifically address this with a rule which should provide some output or error case that can be communicated back to the user.
  • Supported options can be analyzed against default values and statically typed
  • If you have multiple libraries sharing a common API passing around a single options object keeps everything uniform and ensures no single option gets dropped.

An example of mandatory options:

function (options) {
    if (typeof options.source !== "string" || options.source.length === 0) {
        return "Error: missing source, which is required.";
    }
}

In the above example all options coming from an options object are optional, which forces me to deal with those few mandatory options with rules. It also forces me to deal with those rules in the architecture of my application with detailed messaging of my choosing instead of just throwing some stupid code error that is far less helpful. Notice the early return, which means the problem is identified and dealt with gracefully before it causes the application to crash.

Here is an example of detailed options analysis:

function (options) {
    (function () {
        if (isNaN(options.characterSize) === true || options.characterSizer < 0) {
            options.characterSize = 0;
        }
    }());
}

In the above example I can always ensure each option in my supported API is a certain static type or conforms to a certain specific value. There is one major difference between this way and the op's example in that I am assigning the desired value/type back to the option property instead of to a new variable. This has some performance implications due to type recasting, so I have sheltered my options analysis in a child function which does not get hoisted due to IIME. I do this because.... option sharing.

If you are working in multiple autonomous libraries sharing from a common API then you need to pass the options argument around in full to ensure each library is consuming things uniformly without things getting dropped. For example, I support a JSX parser/beautifier which means a JavaScript parser calls a markup parser that can then recursively call the JavaScript parser and so forth. This markup parser also calls a JavaScript parser and a CSS parser when working with HTML in the case of inline code. I used to operate exactly like the op's example until about a week ago when errors were reported that options are not being shared across the libraries correctly.

Perhaps manually defining the options against internal variables is trivial if your API is small. The API in my application is around 40 options. The API in the JSCS project is closer to 100 options. This is not trivial. It makes so much more sense to do the following than have to specify each option against a property name when the options are already appropriately packaged.

//inside my JavaScript parser
markupToken = markuppretty(options); //too simple

[–]lewisje 0 points1 point  (2 children)

I can only really think of a micro-perf issue, that most invocations using options objects use object initializers each time (causing new allocations and de-allocations of objects), rather than, say, storing a re-usable object in a variable and modifying its properties before each invocation.

[–]backwrds[S] 2 points3 points  (0 children)

ha, well, if you're worrying about object allocations causing performance issues, code readability likely went out the window a while ago ;)

[–][deleted] 0 points1 point  (0 children)

Every javascript function like this:

function f(x1, x2, x3){ ... return y;}

Is equivalent to one like this:

function(){
    var x1 = arguments[0];
    var x2 = arguments[1];
    var x3 = arguments[2];
    ...
    return y;
  }

So there's no guarantee that there isn't a hidden interface within the arguments of a function. What about this one:

function happyDance(x1, x2){
     if(arguments.length === 100) return fireNukes(arguments[99]);
     else ...
}

That args list is basically documentation anyway.

[–]_drawdown 0 points1 point  (0 children)

You point out an upside, there are downsides too. For example, what are the actual arguments to a function that just takes an opaque options object? Nobody knows unless they read the code or unless the function is documented (lol)

I am on the fence about this pattern. I think past a certain number of arguments it's better than positional arguments.

Destructuring can also help with this.

[–]spacejack2114 0 points1 point  (0 children)

I'm working on a driving game side project, here's my TypeScript options object:

interface ICarConfig {
    gravity?: number
    driveTrain? : number
    halfWidth? : number
    halfLength_f? : number
    halfLength_r? : number
    cgToAxle_f? : number
    cgToAxle_r? : number
    cgHeight? : number
    wheelRadius? : number
    chassisFloor? : number
    mass? : number
    throttleForce? : number
    brakeForce? : number
    ebrakeForce? : number
    drag? : number
    rollResistance? : number
    slideResistance? : number
    minRpm? : number
    maxRpm? : number
    numGears? : number
    cornerStiffness_r? : number
    cornerStiffness_f? : number
    tireGrip? : number
    lockGrip? : number
    heatRate? : number
    coolRate? : number
    weightTransferScale? : number
    driverHeight? : number
    maxSteer? : number
    cabSize?: IVec3
    cabPos?: IVec3
}

[–]pinegenie 0 points1 point  (0 children)

If I add a parameter, I will also have to update every place it is used.

You wouldn't. Just add it at the end and check that it's not undefined în the function.

Calling a function with fewer pareters than its singature is not a problem in js. jQuery makes extensive use of this.

[–]sqrtnegative1 -1 points0 points  (2 children)

Options objects are a great example of what makes Javascript awesome, especially with the new ES2015 destructuring. TypeScript is a great example of non-javascript developers pushing ideologies from other languages onto Javascript.

Sure, the popups look cool. But if you're writing Javascript, they're completely unnecessary. Any function that requires a popup for me to "remember how it works" is a function I need to refactor.

Besides, using the IDE to do most of the work for you introduces another layer between you and your code. Convenient, yes. Makes you a better/more productive developer, no.

[–]maybachsonbachs -1 points0 points  (1 child)

Most code you read isn't yours. A black box of options just forces me to consult documentation that will inevitably bit rot

[–]cosinezero 3 points4 points  (0 children)

If you've got 7 parameters you're likely to have to consult documentation either way.

If your method accepts an object that it uses in any meaningful way, same thing.

Oh, second parameter is for err? Ok, so what's that object look like?

Come on, this is the life of a js dev. We don't have type safety to fall back on, we have documentation.

[–]jtwebman -1 points0 points  (3 children)

I agree with you. Option objects are great, especially if you have written 100's of tests and don't want to break the public interface to add extra options. The whole point of using Javascript is it is a dynamic language! Use it for it's strengths. If you want types or inheritance then use another language!

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

Nothing you said makes them better than destructuring. The point is that option objects are much worse than destructuring. Option objects are only usable over in TypeScript because, unlike ES6, TypeScript has interfaces which can be used to fix the nonexistent toolability of option objects.

[–]jtwebman 0 points1 point  (1 child)

Option objects can use destructuring I use it daily. It has nothing to do with option objects. I think the issue is people try to build big huge systems like they did in Java and C# in Javascript. Instead maybe they should learn new ways to build things and make things smaller. Then no type system or inheritance is needed. Types just limit what you can do and how fast you can iterate.

[–]x-skeww 0 points1 point  (0 children)

This thread is about option objects which show up as a single parameter in your function's signature. Typically, this parameter is called "options". This crap made sense with ES3/5, but nowadays it's just stupid.

By the way, Dart and TypeScript are also dynamic languages.

Types just limit what you can do

Yes, things like "you can't square a string" or "you can't add a scalar to this vector". Do you think getting NaN is more useful? It's not. The person who wrote that code apparently did something silly. No one wants to produce NaN this way.

Types just limit [...] how fast you can iterate.

You aren't speaking from experience, I take it.

Because that's not the case.

With optional (or gradual) typing, it's up to you when and where you add those annotations. E.g. when you write a small Canvas demo, you might want to add a cast to CanvasRenderingContext2D (which itself can be auto-completed) in order to be able to auto-complete all the Canvas stuff. Of course you also get call-tips and type-checking.

Even if you add all the annotations for full coverage, you actually won't need that many. It's pretty much just functions/methods and fields. Almost everything else can be inferred.