Tuesday, June 5, 2012

Maintainable JavaScript: Part II: Common Function Stumbling Blocks


Series Synopsis

I see often JavaScript in the wild lacks a focus on maintainability. I want to cover some of the basics that have served me well in improving maintainability as I try to tame whatever project I am working on. These crop up with such a usual frequency I want to save my fellow programmers some reoccuring pain.

The topics I covered so far in this Maintainable JavaScript series:
Part I: Fundamentals of an Object Oriented Approach
Part II: Common Function Stumbling Blocks (you are here)

Patterns


I believe starting the discussion of patterns early is useful for learning any programming language. The patterns do not change from language to language only implementation details. Throughout this series I will cover patterns as they come up. Last time we covered the beginnings of implementation constructors in JavaScript. This is a natural time to bring up Factory methods and other helpers. Deciding on common methods for implementing features is key to code reuse and thus consistent architecture.

Limit Function Arguments

Anytime I start breaking up code into functions the first question that comes up is, "What should I pass in?" Experience has shown more than three arguments on a function brings about issues in almost any language. With JavaScript, the dynamic nature of the language just increases the number of issues. The biggest issue is JavaScript does not produce any errors if you provide less or more arguments than the signature defined in the function declaration.

Sample 2.1: Parameters Problem

var jrf jrf || {};
jrf.trash jrf.trash || {};
jrf.trash.passMeStuff function (firstsecondthird){
        console.log("Run:");
        console.log(first);
        console.log(second);
        console.log(third)
        console.log(arguments[0]);    
        console.log(arguments[1]);
        console.log(arguments[2]);                    
}       

jrf.trash.passMeStuff(1)//second argument is undefined
jrf.trash.passMeStuff(1,2);
        

Output to console:
Run:
1
undefined
undefined

1
undefined
undefined

Run:
1
2
undefined

1
2
undefined

Sample 2.1 does not produce any JavaScript errors. JavaScript is happy to return undefined for second, third arguments as necessary. Therefore it is imperative for the JavaScript developer to code defensively against possible missing parameters.

One parameter approach

The jQuery library exposed a plug-in model as a way of extending jQuery while limiting what features jQuery was responsible for internally. If jQuery did not create the one parameter approach they certainly made it more popular for  JavaScript developers. The basic premise is for any component that exposes number optional parameters for a given function get callers to create a JavaScript composed of the options necessary.

Sample 2.2: One parameter

var jrf jrf || {};
jrf.trash jrf.trash || {};
jrf.trash.passMeStuff function (options){
    console.log("Run:");
    if(typeof options === 'undefined'){
        console.log("Nothing");                
    }
    else {
        if(typeof options.first !== 'undefined'){
            console.log(options.first);
        }
        if(typeof options.second !== 'undefined'){
            console.log(options.second);
        }
        if(typeof options.third !== 'undefined'){
            console.log(options["third"]);
        }
    }        
}       

jrf.trash.passMeStuff();
jrf.trash.passMeStuff(first:})//second argument is undefined
jrf.trash.passMeStuff({first1second2});


Output to console:

Run:
Nothing
Run:
1
Run:
1
2

Another great side effect of the one parameter approach is you can create a function that serves as a factory for  reusable defaults for certain function calls that use identical arguments or for workhorses.

Sample 2.3: Default Factory

var jrf jrf || {};
jrf.trash jrf.trash || {};
jrf.trash.passMeStuff function (options){
    console.log("Run:");
    if(typeof options === 'undefined'){
        console.log("Nothing");                
    }
    else {
        if(typeof options.first !== 'undefined'){
            console.log(options.first);
        }
        if(typeof options.second !== 'undefined'){
            console.log(options.second);
        }
        if(typeof options.third !== 'undefined'){
            console.log(options["third"]);
        }
    }        
}  

jrf.trash.optionsDefaultFactory function(){
    this.first 1;
}   

var defaults new jrf.trash.optionsDefaultFactory ();
jrf.trash.passMeStuff();
jrf.trash.passMeStuff(defaults)//second argument is undefined
defaults.second 2;
jrf.trash.passMeStuff(defaults);

Output to console:
Run:
Nothing
Run:
1
Run:
1
2

One slight difference in Sample 2.3 from previous examples in post 1 is my constructor member initialization syntax for OptionsDefaultFactory. Sample 1.5, used a different syntax for lost member:


jRF.grid.gridMetadata function (){
    lostMember51 
};

I eliminated the bad habits from 1.5 for brevity. The key difference is in Sample 2.3 syntax we can place arbitrary code in the constructor not just define members. We are making the assumption that we own the function name. Not too dicey of an assumption.

Setting Defaults

Finally I wanted to cover a simple way defining defaults in a constructor. Regardless of whether a consumer of API wants to define a value for option member it is often useful to move on with a convenient default. I find great value in shooting for  'It just works' type scenarios when it comes to defaults. That is I try to limit the responsibilities of an object to a single responsibility with the option of composition of other objects or extension of the current object under development. More on inheritance and composition patterns later.

Sample 2.4: Setting a default

var jrf jrf || {};
jrf.trash jrf.trash || {};
jrf.trash.passMeStuff function (options){
    console.log("Run:");
    if(typeof options === 'undefined'){
        console.log("Nothing");                
    }
    else {
        console.log(options.first || 0);
        console.log(options.second || 0);
        console.log(options.third || 0);
    }        
}   

jrf.trash.optionsDefaultFactory function(){
    this.first 1;
}   

var defaults new jrf.trash.optionsDefaultFactory();
jrf.trash.passMeStuff();
jrf.trash.passMeStuff(defaults)//second argument is undefined
defaults.second 2;
jrf.trash.passMeStuff(defaults);

The only difference between this code and our last sample is we are using a default when we try to use option members' first, second, and third. I could even use 'Nothing' instead of zero since JavaScript is not statically typed. I am in favor of using default values sharing a type with expected values but honestly it depends on usage. The secrete sauce here is consistentancy whatever convention you happen to pick with defaults.




        



2 comments:

  1. Good stuff, I'm using the same approach .

    This article tells the same about arguments:
    http://msdn.microsoft.com/en-us/magazine//gg602402.aspx

    Regards,
    Rui

    ReplyDelete
  2. Interesting link. I adopted the one function parameter pattern from some of the generally accepted jQuery plug-ins philosophies I saw passed around the net. It definitely gets you out of the business of caring about parameter order and into thinking about good defaults.

    ReplyDelete