Joe Wright has an excellent blog post about Anti-If Patterns that I naturally believe everyone who write programs ought to read, but there's a detail that's missing that I think is absolutely crucial to getting anti-if programming correct.
Yeah, you can yawn and leave now, if you don't care. But I'm with Guido von Rossum on this, that once your software reaches a certain size the only way to stave off chaos is to demand more of the programmer up front, and that demand comes in the form of requiring programmers to understand the shape of the data, and to make sure that the pieces being passed between units of code fit perfectly.
Wright glances off the issue in his second pattern, "Use polymorphism instead of switch()." This is a common piece of advice, although it really only works when you have more than one switch statement; at that point, your switch statements are collections of methods that apply to different objects.
It's his first (Boolean Params) and fourth (Conditional Expressions) patterns where he falls down a little. The most critical issue in both of these is the shape of the data. "Boolean Params" is just "Conditional Expressions" written as a lookup table. If we play the classic programmer exercise of zero, one, or many, a lookup table is a conditional expression taken from the "one" state to the "many" state. It is therefore absolutely critical to put your foot down and state for the record, In any conditional expression, for all sub-expressions, all left-hand values must share the exact same type, and all right-hand values must share the same type.
If this isn't the case, you've created a way to sneak if back into the system, with separate code paths that must be unit tested. And down that road lies madness and unreliability.