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.