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

}
1
  • 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. Commented Sep 22, 2010 at 9:04

2 Answers 2

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.

Sign up to request clarification or add additional context in comments.

2 Comments

this was great advice, thank you! I went for the closures option for now and it's already much easier to follow.
@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).
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.

1 Comment

Thanks for the pointer, JS.State seems to be a nice implementation: jsclass.jcoglan.com/state.html

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.