Despite our best efforts to keep things simple, sometimes getting the business logic correct necessitates deep, nested branching. It's painful when you get painted into a corner like that, dozens of different paths, each conditionally dependent on the last, but you gotta do what you gotta do. Trying to keep these things readable and debuggable is one of the JavaScript Common Pitfalls
We need to get down to the very serious business of choosing the right emoticon to use depending on the occasion. We need to choose based on what type of emotion are we trying to convey, should it be intensified, and/or are we exaggerating?
One way to handle this is with if/else statements.
var type = 'sympathetic' var acute = true var exaggerated = true var face = '' if (type === 'joking') { if (acute) { if (exaggerated) { face = ';p' } else { face = ':p' } } else { face = ';)' } } else if (type === 'sympathetic') { if (acute) { if (exaggerated) { face = ':( :( :( :(' } else { face = ':(' } } else { if (exaggerated) { face = ':*(' } else { face = ':/' } } } else { face = ':)' } console.log(face) //:( :( :( :(
But even with a small number of cases this gets obviously painful. You have to write the assignment again and again, you have to make sure each if has an else or you risk not assigning an emoticon at all. And you can imagine, as this gets more cluttered and the conditions become more complex it gets really hard to tell where you end up given a set of parameters.
var type = 'sympathetic' var acute = false var exaggerated = true var face = type === 'joking' ? acute ? exaggerated ? ';P' : ':p' : ';)' : type === 'sympathetic' ? acute ? exaggerated ? ':( :( :( :(' : ':(' : exaggerated ? ':*(' : ':/' : ':)' console.log(face) // :*(
Using nested ternaries we can get rid of the redundant assignments, which is nice. This is very terse compared to the if statements, and it is nice that it's an expression with a value, instead of a bunch of statements with side effects. However it's even more confusing. The only reason I can read it at all is the use of white space. Cram it all on one or two lines, and I'm totally lost.
I like to wrap this kind of problem up in a function, and use if/else statements and ternary expressions in whatever combination makes it easiest for me to understand.
var face = getFace('joking', true, true) console.log(face) // ;P function getFace (type, acute, exaggerated) { if (type === 'joking') { if (acute) return exaggerated ? ';P' : ':p' return ';)' } if (type === 'sympathetic') { if (acute) return exaggerated ? ':( :( :( :(' : ':(' return exaggerated ? ':*(' : ':/' } return ':)' }
This approach also makes the state that goes into making the decision more explicit, as well as the result of the decision (choosing a string). This also makes it extremely plain what the catchall default is, and hard to screw up in a way that we don't end up choosing an emoticon, which would be tragic.
Fun
Playing with other ways to do this, I looked at what you could do using mostly object property access. This implements the same logic based on the same parameters, but in an extra interesting way. This is too obtuse for most use cases, but it was fun to write :)
var type = 'sympathetic' var acute = false var exaggerated = false // this is the crazy answer // not the good answer. var smileys = { joking: [ [ ';p', ':p' ] , [ ';)', ';)' ] ] , sympathetic: [ [ ':( :( :( :(', ':(' ] , [ ':*(', ':/' ] ] } var face = type in smileys ? smileys[type][+!acute][+!exaggerated] : ':)' console.log(face) // :/
`[ a, b][+!condition]` is approximately the same as a ternary `condition ? a : b`, but if you start littering that all over your code base, you didn't hear that here ;p