Cover image by Talles Alves on Unsplash
Internet wisdom says that nested conditional operators are evil. And I firmly believed internet wisdom until today.
Today I found myself refactoring some old Javascript code and it had one of those functions that validate that a heckload of properties exist and have the right format in an object. Then I went crazy and wrote something similar to this.
const validateInput = ({ field1, field2, field3 }) =>
(!field1
? Promise.reject('Missing field1')
: !Array.isArray(field2)
? Promise.reject('field2 is not an array')
: !isValidType(field3)
? Promise.reject('field3 is invalid')
: Promise.resolve()
)
Don't mind the thing with promises, this function is used inside a promise chain.
The thing is, it makes me cringe a bit because, you know, nested conditional operators are evil, but I actually find it readable, and I'd say it might even flow better than the chain of ifs.
I would like to read your thoughts and opinions in the topic. Are nested conditional operators inherently evil and unreadable or are they alright, they have just been used in a messy manner for too long?
Latest comments (48)
WeirdScript:
Let me tell you the story of how once upon a time I refactored someone's switch cases to a functional expression with nice declarative map lookups:
Old code:
New code
๐ ๐ ๐ Too bad it was rejected ๐ ๐ ๐
I would format it like this:
To my disappointment, Prettier does exactly that when used to
format selectionbut flattens it when usingformat document. What gives?!Disclaimer: it originally didn't get rid of the parentheses, but after I removed them it didn't reinsert them.
However, you are messing around with Promises every single return here, so maybe the real solution here is the an async function?
Wow, this is amazing :O And shorter! And will produce less diffs on refactoring!
However, do we actually need to return promises? Surely not!
But do we ever? Nah.
If someone actually reads this long-azz comment, yes, you should usually wrap what you're rejecting with into a
new Error(str), for instance in theassertInputfunc, but this might be an acceptable exception.I don't know for js but in PHP, the evaluation is non trivial (left to right):
stackoverflow.com/a/6224398
I think that if I were to run across this while digging through code, I would be much happier to just see
If definitely doesn't win any code golf or cleverness competitions, but it imposes an absolute minimum of cognitive load on someone passing through the code.
In my opinion the main problem in this particular case is not the tenary operator - it's the ugly code that results from the violation of the DRY principle at the "error throwing".
Let's break this down a bit:
Why don't you just tell your program exactly this idea?
"I validate a field" becomes a function, that rejects a Promise on error, or resolves it if everything is fine:
Now we can set a condition and get a resolved / rejected Promise back - we just need to execute all of them (with Promise.all) and use the overall success as indicator, that all validation was successful:
I think that this increases readability by several levels and tells us on the first glance, what it's doing. You might even add other fancy stuff in the "validateField"-Function, which applies to all validations, or you can use the "validateField"-part without calling the validateForm-Function.
I think if I found your piece of code, I would've refactored it like that and I can imagine, other places where "multiple" tenarys would be needed can be refactored like that as well.
What do you think about this attempt?
Using
Promise.allis a very interesting approach, I like it.validateFieldseems reasonable, but I am not sure why it is asynchronous. All of the field information seems to be available as-is, so it looks as thoughvalidateFieldandvalidateInputcould be simplified:Also, I'm not sure why there are negations in
!!field1and!isValidType(field3).I just made it async, because I was too lazy to write new Promise bla (async always returns a Promise). Also I used Promise.all because I guessed that Avalander had some fancy async validation stuff going on.
!!field1 casts a value to a boolean (a bit hacky, I know ๐ Just wanted to point out, that someone should provide a boolean value).
!isValidType is taken from the original post - of course non-negated conditions should be preferred.
Yeah, let's not forget that the code I posted is a simplification of the real problem, but we can ignore the asynchronous part, I just wanted to discuss the chained conditional operators.
Just wondering, how come this code only checks the first invalid input, if they are all independent? Does that mean in this domain itโs not useful to find all of the invalid inputs at once?
Let's keep in mind that I was refactoring old code, I wouldn't design a validation system like this to start with. I kept it like this however because I didn't want to change any functionality during the refactor, and this code is in a request handler in a web service and the client can't handle a response with multiple errors anyway.
No worries. In that case I think Iโd prefer just (pseudocode):
I think itโs more straightforward.
Update:
The use of the ternary operator for this seems possible, but in my opinion it's leaning in the direction of being cute for cute's sake rather than really being useful. At the very least I would argue it's not idiomatic js, and if there is a benefit to it at all, I don't think it's significant enough. In general I like the idea of using the idiomatic approach for a given language unless there is really quite a strong reason not to.
All that being said, if you and your team are comfortable with this code, I don't think using it is the end of the world.
If you want to write this style of code, I'd prefer using cond from
lodashwhich reimplementscondfrom Lisp.Edit: didn't realise it was an object, not an array.
Could even do something like this:
This is a great suggestion, I actually ended up doing something similar with Ramda's cond
Indentation that follows the level of nesting would help me understand what is happening:
I'm not sure I got the evaluation order right, and I'd be suspicious about it in somebody else's code. Usually I'd use parentheses to make sure, but that might make it less readable with a lot of nesting.
In general, I will never use nested conditional operator, simply because it looks ugly (to me) and itโs hard to debug. Flatten out your code is the best way for readable and maintainable code.