3

I inherited a largish JavaScript (MooTools) application for browsing and configuring products.

In it there's a function that tracks the products' configured state. Other functions are called depending on what state the product is in.

I extended it to add some new features, but have ended up with a mess of nested conditionals inside a big switch statement.

My question is, how do you refactor conditional logic? Is there a general (or JS specific) pattern / technique for tracking state?

Update: here's a pseudo-version of the function to illustrate the problem:

switchToVariation: function(link) {

  // set the variables      
  // before switch, set state    

  switch ( nextType ) {   
    case 'type1':
      if ( prevType == 'type1' ) {        
        if ( nextIsSameVariation ) {            
          // do stuff                                           
        } else {                                                                              
          if ( nextIsSomethingElse ) {              
            // do stuff
          }          
          // more stuff                      
        }
      } else {        
        // different stuff        
      }
      break;

    case 'type2':        
      if ( prevWasType1 ) {
        // stuff
      } else {
        if (nextIsSameStyle) {
          // stuff
        }
        // more stuff
      }    
      break;    

    default:                       
      // default stuff        
  }          

  // after switch, set state

}
meleyal
  • 32,252
  • 24
  • 73
  • 79
  • 1
    There awe many design patterns that can be used to solve the problem of too many/too deeply nested conditionals, however, some code or mockup examples will be helpful first. – Anurag Sep 22 '10 at 09:04

2 Answers2

3

The primary answer, of course, is: Break it up into smaller pieces, where each piece does one thing well.

It's hard to be more specific without seeing an example, but you said you have a "...mess of conditionals inside a big switch statement..." which definitely offers an opportunity for improvement: You can move the content of each case into its own function.

If you do that, you have two choices: Either make the functions work purely with arguments you pass into them, which tends to keep things more modular, or make the functions closures that manipulate the data in the outer function. I'd say the latter is less ideal — for these purposes — but it may be quicker to do and so be a "quick win."

Say we originally had:

function myBigFunction() {
    var v1, v2, v3;

    /* ...set v1, v2, and v3 to various things...*/

    switch (condition) {
        case foo:
            /* ...do lots of stuff involving v1 and v2...*/
            break;

        case bar:
            /* ...do lots of stuff involving v1 only...*/
            break;

        case charlie:
            /* ...do lots of stuff involving v2 and v3...*/
            break;
    }
}

Then:

The first option: Completely independent functions:

function myBigFunction() {
    var v1, v2, v3;

    /* ...set v1, v2, and v3 to various things...*/

    switch (condition) {
        case foo:
            doFooStuff(v1, v2);
            break;

        case bar:
            doBarStuff(v1);
            break;

        case charlie:
            doCharlieStuff(v2, v3);
            break;
    }
}

function doFooStuff(v1, v2) {
}

function doBarStuff(v1) {
}

function doCharlieStuff(v2, v3) {
}

...and of course, the odds are that you'd need to have those functions return something and then handle that, so for instance:

    case foo:
        v1 = doFooStuff(v1, v2);
        break;

...if doFooStuff needs to update v1. If it needs to update v1 and v2, you could return an array, or make v1 and v2 properties on an object you pass into doFooStuff, etc. Here's an example of using properties: Replace the v1 and v2 properties with an object (vdata) which has v1 and v2 properties on it:

var vdata = {
    v1: "someInitialValue",
    v2: "someInitialValue"
};

...then your call to doFooStuff:

    case foo:
        doFooStuff(vdata);
        break;

...which allows doFooStuff to update both v1 and v2. But be careful, you don't want to create a kitchen sink with all of myBigFunction's variables, that would be sacrificing the modularity.

The second option: Using closures and working directly with the parent function's data:

function myBigFunction() {
    var v1, v2, v3;

    /* ...set v1, v2, and v3 to various things...*/

    switch (condition) {
        case foo:
            doFooStuff();
            break;

        case bar:
            doBarStuff();
            break;

        case charlie:
            doCharlieStuff();
            break;
    }

    function doFooStuff() {
        // work with v1 and v2 directly
    }

    function doBarStuff() {
        // work with v1 directly
    }

    function doCharlieStuff() {
        // work with v2 and v3 directly
    }
}

Note that here, the various subroutines are closures inside myBigFunction and so they have direct access to all of myBigFunction's local variables. This is more modular, but not quite the full modular approach of the first option. It's also like a local version of the issue of functions working with global variables: Side-effects are easy to introduce and can cause trouble.

The first option is preferred, but if it's impractical, the second option can at least help you make your mainline logic clearer. And of course, a combination of both may work.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • this was great advice, thank you! I went for the closures option for now and it's already much easier to follow. – meleyal Sep 22 '10 at 17:12
  • @melayal: Excellent, glad that helped! You can gradually keep modularizing on your own schedule (one of the great things about how easy closures are in JS). – T.J. Crowder Sep 22 '10 at 18:48
2

Check out the State pattern.

I wrote an answer explaining it with an example in Java a while back, but it can be easily implemented in JavaScript. It is essentially modeling the system as a finite state machine.

Community
  • 1
  • 1
Anurag
  • 140,337
  • 36
  • 221
  • 257
  • Thanks for the pointer, JS.State seems to be a nice implementation: http://jsclass.jcoglan.com/state.html – meleyal Sep 22 '10 at 15:31