Dealing with the Flexibility of JavaScript

By on October 14, 2007 3:56 pm

This is a continuation of my previous post A Fine Line Between Abstraction and Obfuscation and, of course, deals with the same material.

JavaScript is flexible in almost every way, and many people end up either abusing the flexibility, or creating strategies of overcoming the flexibility that only create confusion and messy code. I’d like to go over one of my favorite topics today, JavaScript’s function signature, although lack of a function signature is probably a better way to say it. You’ll see that can employ some strategies that can solve these problems without increasing the amount of code you have, while at the same time, providing context for those using your code in the future. Even if you don’t use everything I’m setting out below, I hope it will reveal what to be cautious of, and help you create meaningful solutions of your own.

Dealing with Multiple Function Signatures

Before we begin, I’d like to outline the situation we are trying to avoid by showing you snippets of real source code from an old version of the Dojo Toolkit (there’s no need to analyze all of this, just give it a quick scan):

connect = function(/*...*/){
  if(arguments.length == 1){
    var ao = arguments[0];
  }else{
    var ao = interpolateArgs(arguments, true);
  }
  if(isString(ao.srcFunc) && (ao.srcFunc.toLowerCase() == "onkey")){
    // ...
  }
  if(isArray(ao.srcObj) && ao.srcObj!=""){
    // ...
  }
}

interpolateArgs(args, searchForNames){
  // ...
  switch(args.length){
    case 0: return;
    case 1: return;
    case 2:
      // ..
      break;
    case 3:
      if((isObject(args[0]))&&(isString(args[1]))&&(isString(args[2]))){
        // ...
      }else if((isString(args[1]))&&(isString(args[2]))){
        // ...
      }else if((isObject(args[0]))&&(isString(args[1]))&&(isFunction(args[2]))){
        // ...
      }else if((isFunction(args[0]))&&(isObject(args[1]))&&(isString(args[2]))){
        // ...
      }
      break;
    // We will stop here, you get the idea. 
  }
}

All of this code exists to allow our function (connect) to be called in all sorts of different ways with all sorts of different parameters. This, of course, adds a ton of power to our function, since it fulfills a wide range of uses. But I only ever called this function with a standard set of parameters, so all of this extra power was lost on me.

Allowing the same function to be called with a variety of arguments is built into many modern languages. Most of these make this type of magic pretty fluid and safe. Java forces you to declare them by function signature (basically the quantity and types of the parameters), Python allows you to pass arguments by name and provides errors if done incorrectly, but JavaScript provides no assurance that the parameters passed are the parameters that you wanted.

function clicked(event){
  var id = event.target.id

This is a common event handler, probably something you would write at the very beginning of a project to handle an onclick event. In this example, you are analyzing the node in the DOM that was clicked, and finding the ID that it provided. About a month into the project, though, you realize that you want to be able to call this function using a plain old ID. At first you decide to just to some quick type checking.

function clicked(event){
  if(event.target){
    var id = event.target.id;
  }else{
    var id = event;
  }

Of course, you have to document this. So you probably add some comments to your function signature.

function clicked(/*Event|Integer*/ event){

But has this really improved your code? First of all, your function is still named clicked, even though it probably does an operation that could be described properly in a function name. On top of that, your argument is still named event, even though it could be an event or an ID. My solution would be to move the body of this function into a new function, properly named, and use the clicked function to merely extract the ID and call this new function. You will end up with something like this:

function clicked(event){
  processById(event.target.id);
}

function processById(id){

You just made your code a lot less confusing, you have created two meaningful variable names, and the developer using your code can now effectively ignore the clicked function, since it’s only called by the browser.

This is a simple case, and you will probably start itching at some point to try out your JavaScript voodoo, start doing arguments.length checks, typeof checks, making sure that your arguments are unique and varied enough so that you don’t have any conflicts. Resist this if you can.

I think a good rule of thumb is that each parameter should always do the same thing. That is, you should use what many of us like to refer to as duck-typing to determine your function signature. What this means is that if you have a parameter named url, go ahead and allow it to be a variety of object types, as long as they all indicate URLs. But once you start letting a parameter be a URL or a date, and you figure out which it should be based on variable analysis, you should start worrying. More than anything, the next person to use your function is going to be awfully curious about how they were intended to call it. This is where people argue, “I will just comment it”. But why bother? Instead, it is easy to provide an abstraction to your original function that merely moves your parameters around as appropriate.

When you have created a lot of abstractions of the same function, you might consider one of the heavyweights of the JavaScript function-calling world: the simple object. Instead of passing arguments in a certain order, just have an abstracted function that accepts one parameter with a variety of keywords. Your function can then figure out how to best call your base function.

The problem here is knowing what keywords to use. I prefer creating at least one file in your project to hold dummy objects. To reference the example I laid out at the start of this chapter, your dummy object might look like this:

function dummyArgs(args){
  // id: Integer
  // The ID to process
  this.id = args.id;
}

Of course, you would say which keys are optional, which keys should have other keys, anything you think the person reading your code would find useful. Although be careful if you find yourself writing if…then comments, because it’s a good indication that you have gotten yourself into the parameter soup you were trying to avoid. If one variable truly relies on another, do not force the developer to sift through comments, just throw an error or otherwise get the developer’s attention.

To tell the person using your code to look at this object, add a little comment to your new function:

function processByKwArgs(/*dummyArgs*/ args){

As a footnote, let us look at the issue of optional parameters. Some functions are bound to accept a basic set of parameters, with the possibility of some extras. In this case, your basic set of parameters should be at the start of the function and the optional ones at the end. The position of the optional arguments is usually sorted by the likelihood they will be used, sorted from most to least likely. A good way of denoting this is by adding a question mark to the end of the object type in a comment:

function process(/*Object*/ item, /*Boolean?*/ flag1, /*Boolean?*/ flag2){

Also, you might create a function with a recurring final argument. Even though it is possible, I would discourage against putting a recurring argument anywhere but at the end of the argument. If you want to put it in the middle, simply require that the user pass an array of items. A good way of denoting this is by adding an ellipsis to the end of the object type in a comment:

function processAll(/*Object...*/ item){

If you are using the keyword args approach I just outlined, simply make a note that the key is optional using the same syntax.

Comments Don’t Fix Bad Code

I’ve used a fair amount of comments in the previous section, but I want to make a point of discouraging JavaScript developers from using them to solve just any old problem. When I used them above, I was describing a type, something that is simply not possible in the language.

That said, comments do not fix bad code. Unfortunately, I have seen the problems caused by various abstraction schemes attempted to be solved through commenting. A good rule of thumb is to clean up your code as much as possible and, only then, add comments. Comments should describe what is happening, not what might happen.

My own experience has shown that adding a comment like /*do not use the number 4 here*/ fails miserably. There are lots of posts online about why commenting does not fix bad code, some style guides, and countless veteran programmers that will further confirm this truth, so I will leave it to them.

In Summary

It’s always tempting to be clever for the sake of being clever. And a lot of that attitude is why we find some JavaScript written in our workplace, or even in the open source toolkits, that have functions that can be called a million different ways. It’s one of those things that at first glance seems powerful, or flexible, but it is the truly bad kind of abstraction. Simply noticing when you’re using comments as a crutch, when someone is passing an argument that doesn’t coincide with the named parameter, when you’re calling a function that incorrectly identifies its purpose, or just realizing that properly abstracting what you’ve written doesn’t really add any extra code, will allow you to write truly great code.

Comments

  • Isabelle

    Utter nonsense !

    function clicked(event){ processById(event.target.id); }
    function processById(id){ }

    This IS THE correct way to go, it’s called a “bridge”.

  • One of the things I find interesting about jQuery is that it makes extensive use of function signature overloading (even to the extent of behaving differently depending on the first character of a string passed to the jQuery() function) but manages to do so in a way that I think actually improves the usability of the jQuery APIs. I think the reason it works is that its done consistently – for example, many jQuery methods have a kind of symmetry to them – call with one argument to read a value, call with two arguments to set a value. Before looking at jQuery I’d always considered signature overloading to be bad practise, but it seems that if you use it intelligently you can create some pretty intuitive APIs.

  • Simon, you’ll see some of that same API use in dojo.query as well… for example, dojo.query(“.foo”).styles(“width) returns an array of widths, whereas dojo.query(“.foo”.styles(“width”, “100px”) will set the width of each item.

    In many ways its a better way of doing getters and setters when it is obvious from the context of the parameters. I’m not 100% sold on it myself, but it does reduce the amount of typing and the number of methods.

    It would be especially useful for the DOM Level 2 NS methods. For example, they have getElementsByTagName and getElementsByTagNameNS. I think it would have been a lot cleaner if the namespace was just an optional last parameter for the normal method.

  • sil

    Completely agreed on getElementsByTagName[NS]. That’s really hysterically annoying. JQuery is a bit different, though, in that there’s only really one function, named JQuery, and you call it for *everything*. Having lots of differently-named functions, all of which you can call in multiple overloaded ways, is confusing. JQuery’s swapped one set of complexities (work out which function name to call, and the parameters are obvious) for an equivalent set (the function name is obvious, but I must work out which parameters to pass it). It’s where those two are combined (I have to work out which function name to call, and then I also have to think hard about the parameters I should pass to it) that it’s a problem. Duck typing is, as noted, the answer here; if I have a function which, say, hides an element, I ought to be able to pass it an element ID (as a string) or an actual element (or a CSS selector or an XPath or a list of element objects), because they’re all just a way of referring to “the element” (or “the elements”).

  • Simon, I actually really like the getter/setter paradigm of the optional final argument. I think the key there is that it’s super easy to figure out (and document), and once you do, it’s very easy to apply the pattern everywhere. Also, the function names and argument names still have clear meanings.

    sil, I think the element ID/CSS/XPath/node argument is a great example of duck-typing where a little argument analysis is just fine.

    These comments have a great pattern to them: that the flexibility of JavaScript can produce a function signature that is both clear and powerful.

  • I think signature overloading does have a place, but it should be used sparingly. A typical example is perhaps a function that changes an element’s className – it could accept either a single node or an array/collection of nodes.

    It really only becomes problematical if you take it too far, e.g. it accepts a string which could be a node ID, CSS declaration, XPath or whatever. The large parsing and error-checking overhead should be provided by alternative methods/functions.

  • Recently it occurred to me that no one has borrowed the ruby on rails style of method overloading and defaulting. Granted JavaScript is not as elegant a solution but the why not the following:

    function doSomething(args){
    ….
    }

    doSomething ({duration: 14, message:”Click Next to contintue or wait 14 seconds”});
    doSomething ({ message:”Click Next when you are ready to contintue”});

    A little utility function to merge two hashes together (I’d kill for native set functions like this) and you can go happily on you way.

    Never the less, if every every function does only one thing, you’ll go a long way to solving your problem.

  • For those just arriving who are skipping straight to the comments, Isabelle is quoting one of my solutions and saying that it’s the only “right” one. I disagree with there being a “right” way to do anything, only ways that are either cleaner or faster.

    Craig proves that point: if a user can pass either a single value, or an array of values, it will probably be inefficient to make a function call for every item in the array and should probably be moved to the main function. In this case, you have to weigh the clarity/performance tradeoffs of both approaches.

    Adam van den hoven, I’m not sure of what you’re suggesting. It sounds like you’re proposing the kwArgs suggesting I made above, but using defaults? dojo.mixin is one of the utility functions that Dojo provides to merge two hashes together.

  • What I’m suggesting is that, instead of

    function process(/*Object*/ item, /*Boolean?*/ flag1, /*Boolean?*/ flag2){

    }

    you use something like

    function process(args}{
    var defaults = {flag1:false, flag2:0XF00};
    args = merge(args, defaults);

    ….

    }

    Mostly, I’m surprised that this pattern is rarely used. In Ruby (notably Ruby on Rails) this is a common thing to do since the language provides some nice syntax sugar that lets you skip using the {}.

    The value of this is that it gives you named arguments, which can be good for documentation. With a bit of creativity it could make for some interesting currying.

    As an aside, I’m glad to see you left referencing a library to the comments. Personally, I think that no one who wants to be a JavaScript developer should ever use a library like Dojo or Prototype or YUI or what have you until you could conceivably write one yourself. They’re great for casual JavaScript folk but you loose a lot of opportunity to learn about JavaScript (especially with something like Prototype) that is very valuable.

  • ttrenka

    To expand on Neil’s last statement @Adam, this is a technique I use all the time: expect a keyword object as a single parameter, and the first thing you do in the body of the function is mix it into a default keyword object:

    function doSomething(arg){
    var keywords={ /* defaults here */ };
    dojo.mixin(keywords, arg);

    // do stuff
    }

    This helps to define the parameter set *and* it allows you to see, right at the top of the function, what the function might expect in keywords. And there are a number of variations on this, all of which work pretty well.

  • ttrenka

    I should also say you can do this as well, if you don’t know if someone will actually pass you an args object or not:

    dojo.mixin(keywords, args || { });

    That way if *everything* is optional, you’ll be fine.

  • One thing we noticed while developing ASP.NET Ajax is that the arguments array performs very poorly. For that reason, as well as those you mention, and also because they are difficult to tool, we never use overloads in JavaScript and use the arguments array as little as possible. We do optional parameters instead, or name the overload differently (the bridging technique mentioned above). We’ve stayed clear of dictionary parameters to simulate named parameters because again they are difficult to tool. Optional parameters are a lot less confusing to users of your API and not as limited as it may seem.

  • Bertrand: The dictionary parameters are for when you need them, of course simpler is better. But one of my least favorite function calls looks like this:

    myFunc(1, “elephant”, true, null, null, “apple”, null, null, null, null, new Date());

    When you see something like this happening throughout code (with no clear pattern), it’s time to think about passing a dictionary. Many projects might never encounter this situation, but it’s a good way of dealing with this type of complexity.

  • @Adam, ttrenka:

    jQuery’s extend is used in that manner for effects and plugin options, and is suggested in the plugin-writing docs (http://docs.jquery.com/Plugins/Authoring#Options). It took me a bit to catch on, but now I find it extremely useful — I also think it’s a great way to add functionality to a method without changing its original function.

  • Pingback: Javascript News » Blog Archive » Dealing with the Flexibility of JavaScript()

  • Charles, extend as it’s used there is exactly the same as Dojo’s mixin. To contrast the two:

    settings = jQuery.extend({ name: “defaultName”, size: 5, global: true }, settings);

    settings = dojo.mixin({ name: “defaultName”, size: 5, global: true}, settings);

    But it’s not that you’re adding functionality to a method, you’re just providing defaults for any keywords that weren’t passed.

  • An overloaded function signature tends to mean the function is too complex and needs to be broken down. The less parameters, the better. Two is average, one is good, none is excellent. :)

    I often find one of the easiest ways to do this is to have my functions return the objects so that they can be strung together. So, instead of this:

    myFunct(myObj, param1, param2) {
      var tempObj = myFunct2(myObject, param1);
      return myFunt3(tempObj, param2);
    }

    …you’d have something like this:

    myObj.myFunct2(param1).myFunct3(param2);

    This is more the “Prototype” way of doing things, and it feels clean.

  • Matt, function chaining very easily lends itself to obfuscation, which is exactly what we’re trying to prevent here. It’s hard to keep track after a few calls exactly what object you’re working with..

    All things being the same, the argument here isn’t about what sort of syntax you like to use. If you can use call chaining, and the programmer that sees your code six months from now can tell what you were trying to do without having to either read a document or look through your source code, you’re doing fine.