Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: function argument validation #773

Merged
merged 6 commits into from Feb 10, 2017
Merged

feat: function argument validation #773

merged 6 commits into from Feb 10, 2017

Conversation

bcoe
Copy link
Member

@bcoe bcoe commented Jan 30, 2017

Too help folks consuming the yargs API to better understand when they've provided illegal arguments to functions, this pull request introduces an approach to function argument validation.

I think this is a pretty clean API, and would potentially like to release it post-hoc as a library.

yargs.js Outdated
@@ -173,41 +174,49 @@ function Yargs (processArgs, cwd, parentRequire) {
}

self.boolean = function (keys) {
argsert('<array|string>', [].slice.call(arguments))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the [].slice.call(arguments)? This seems horrifically ugly. =D (Uhm, in the nicest way possible.)

Is argsert trying to forEach the args part? Why not change that to a C-style for loop and save yourself this weirdness?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, no, I see that you're doing the same thing in argsert itself, so this is thankfully unnecessary.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

definitely a bit ugly, but in our discussion in coding it sounded like there's a pretty big performance hit with passing in arguments -- I opted for ugly and fast.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That doesn't improve the performance. Passing it to slice still requires the object be created.

@bcoe bcoe changed the title [WIP] feat: function argument validation feat: function argument validation Feb 6, 2017
@bcoe
Copy link
Member Author

bcoe commented Feb 6, 2017

@JaKXz @nexdrew, etc., I believe I've added argument validation for the whole public API surface; making this pull request ready for prime-time ...

regarding the ugly [].slice.call(arguments) syntax being discussed by @iarna and I, this comes from a conversation with @isaacs and @chrisdickinson ... tldr; passing arguments into another function is a performance hit for the compiler.

@iarna
Copy link
Contributor

iarna commented Feb 6, 2017

Using arguments that way is exactly the same performance hit for the compiler.

@iarna
Copy link
Contributor

iarna commented Feb 6, 2017

I recommend you read Bluebird's Optimization Killers docs, it goes into great detail.

(The only bit that's a little outdated is reassigning, which is only an issue if you don't 'use strict')

@bcoe
Copy link
Member Author

bcoe commented Feb 7, 2017

@iarna hmmm, I'd rather avoid adding a build step/macro ... is your thinking "might as well just pass arguments, since slice is slow too"?

@isaacs
Copy link
Contributor

isaacs commented Feb 8, 2017

Look, your api is not requiring that you pass the actual arguments object. As long as you're ok with taking an array as well, it's fine. Just have a caveat in your docs. Most programs will call this once on startup, not in a fast loop. It's probably fine.

If you want to avoid passing arguments at all, you could still test for unwanted args by passing arguments.length.

function argcert (spec, len) {
  var args = new Array(Math.max(0, arguments.length - 2))
  for (var i = 2; i < arguments.length; i++) {
    args[i - 2] = arguments[i]
  }
  if (len < args.length) {
    // missing arguments!
  } else if (len > args.length) {
    // extra arguments!
  }
}

function myFunction (a, b, c, d) {
  argcert(stringDefinition, arguments.length, a, b, c, d)
}

You could do the same thing without the loop using a splat:

function argcert (spec, len, ...args)

But really, that's a probably just less convenient API, and for a dubious performance benefit, so why bother?

I'm really academically curious about the optimization differences between [].slice.call(arguments), Array.prototype.concat.apply([], arguments) and Array.apply(Object.create(Array.prototype), arguments), and the newer Array.from(arguments).

@iarna
Copy link
Contributor

iarna commented Feb 8, 2017

Yeah, basically what @isaacs said. I would write the code such that the args argument can be an array or a arguments object. That is, just do c-style iteration over it. And then document that folks call it like:

function (a, b, c) {
  argcert(spec, arguments) // when perf doesn't matter
  argcert(spec, [a, b, c]) // when it does
}

And beyond that, unless you're calling a function (tens/hundreds of) thousands of times it really doesn't matter if it's deopted. Unoptimized v8 is still fast enough, most of the time.

@bcoe
Copy link
Member Author

bcoe commented Feb 8, 2017

@iarna @isaacs, having discussed this topic with @JaKXz too; and having read the bluebird docs @iarna shared, I think @isaacs' example of:

argcert(stringDefinition, arguments.length, a, b, c, d)

Is what I'd like to move to; gives me everything I want, isn't too much more messy, and doesn't do something slow just to have a slightly more magic API signature.

This 🚲 🏠 taught me something about Node.js performance, which rocks.

@bcoe
Copy link
Member Author

bcoe commented Feb 10, 2017

declaring 🚲 🏠 painted.

@bcoe bcoe merged commit b3195bf into 7.x Feb 10, 2017
@bcoe bcoe deleted the validate-arguments branch February 10, 2017 05:59
bcoe added a commit that referenced this pull request Feb 18, 2017
BREAKING CHANGE: there's a good chance this will throw exceptions for a few folks who are using the API in an unexpected way.
bcoe added a commit that referenced this pull request Feb 26, 2017
BREAKING CHANGE: there's a good chance this will throw exceptions for a few folks who are using the API in an unexpected way.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants