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
}
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:
Then:
The first option: Completely independent functions:
…and of course, the odds are that you’d need to have those functions return something and then handle that, so for instance:
…if
doFooStuffneeds to updatev1. If it needs to updatev1andv2, you could return an array, or makev1andv2properties on an object you pass intodoFooStuff, etc. Here’s an example of using properties: Replace thev1andv2properties with an object (vdata) which hasv1andv2properties on it:…then your call to
doFooStuff:…which allows
doFooStuffto update bothv1andv2. But be careful, you don’t want to create a kitchen sink with all ofmyBigFunction‘s variables, that would be sacrificing the modularity.The second option: Using closures and working directly with the parent function’s data:
Note that here, the various subroutines are closures inside
myBigFunctionand so they have direct access to all ofmyBigFunction‘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.