
IF-less programming - alisnic
http://alisnic.github.com/posts/ifless/
======
raverbashing
This is getting ridiculous

" ifs are a smelly thing in a Object Oriented language."

What's smelly is such a absurd assumption. Yes, let's replace every small
decision in our program with inheritance and another level of abstraction,
that will work out swell

Please go read an Assembly manual because you have obviously forgotten what a
computer is and does

And then we end up with unmaintainable systems with a class hierarchy higher
than the Empire State.

Of course, as mentioned, replacing an if with a "software structure" (like
pattern matching in an functional language) is beneficial, but to call it a
code smell is ridiculous

~~~
revscat
> And then we end up with unmaintainable systems with a class hierarchy higher
> than the Empire State.

Perhaps, but I would rather have testable code than untestable code that is
nothing more than a series of unnecessary, nested if blocks. We have all seen
code that has a structure like

    
    
        if (depObj1.isSomething()) {
            if (depOboj1.getAttr() == CONST1) {
                // Do something
            } else if (depObj1.getAttr() == CONST2) {
                // Do something different
            }
         } else if ... {
            // repeat with minor differences
         }
    

Most of the time the above structure can be abstracted. Abstracting It also
has the benefit of making this code more testable: you don't have to set up
many different objects, just one.

The OP is correct: in OO languages if statements are a code smell. Like all
smells, though, they are not hard and fast rules, but rather indicators that
things could probably be done better. No one, certainly not OP, is
recommending abandoning if statements. What he is recommending, and I agree
with, is that they can and should be avoided, and certainly not turned to as a
primary tool in the toolbelt.

~~~
raverbashing
It's irrelevant if you're using ifs or different objects, the number of
conditions/tests needed for what you are testing is the same.

Now, you could make depObj1 of type Obj1 and Obj2 to replace the if, which
_may_ simplify testing because the method is "simpler" in Obj1/Obj2/ObjBase
_but in the end it isn't_!

Why? Because your test of Obj1/2 has to take care of their dependency to
ObjBase. It's usually a dependency hell, needing lots of workarounds to test
properly.

~~~
aardvark179
I might agree if that type of if statement only appeared at one point in the
code, but there're normally several scattered around which all have to be
updated when obj3 is introduced. Also introducing a class hierarchy often
makes it easier to move responsibilities between objects as the code evolves.

For example I recently did some work on a graphics library which had a lot of
ifs to do with line and fill styles. By changing those into an inheritance
hierarchy I found a couple of places where clauses had been missed and so
fixed some bugs, but more importantly when we needed to add new fill styles
which were more complicated to draw it became very easy to turn the fill style
objects into fillers which had all the responsibility for filling paths, not
simply setting the graphics state.

~~~
raverbashing
Yes, there are situations where changing the structure of the code is better
than having an 'if' like the example you gave.

But this doesn't mean that it's better to completely eliminate ifs

------
barrkel
Functional pattern matching (more rigorous IFs) is open for functions but
closed for extension. You can write as many functions as you like, but you
need to modify all of them if you add a new data type.

OO polymorphism is closed for functions but open for extension. You have a
fixed number of functions (methods), but you don't need to update all the call
sites if you add a new data type - merely implement all the methods.

The requirement for what needs to be open and what can be left closed is what
determines which choice is better.

For example, a GUI framework is best left open for extension, because every
application GUI usually ends up with specific widgets custom-designed for that
app - sticking with standard widgets tends to make apps form-heavy and lacking
in polish. But for the widgets to work in the framework, they need a fixed set
of methods to be consistently manipulable.

A compiler AST is best left open for functions, because the majority of work
on an AST is in algorithms that transform the AST. The language changes at a
much slower pace than additional optimizations and analyses, and frequently
new language additions can be represented in terms of existing AST constructs
(where they are syntax sugar, effectively built-in macros). So having a more
fixed set of AST node types is less of an issue.

Choosing one or the other on the basis of the orthodox religion of OO or
functional programming, meanwhile, is just obtuse.

~~~
Nitramp
That's the classic "Expression Problem" (aptly named wrt its common appearance
in ASTs).

Note that it's relatively straight forward to express the function-
extensibility through the visitor pattern in an OO language; in a functional
language, you could probably implement your own dynamic dispatch technique to
get the type-extensibility.

In both cases, you'll end up with some boilerplate, depending on your
language. E.g. for a complex language with lots of different AST node types,
you'll end up with lots of tiny classes, mostly just implementing stupid
constructors.

~~~
barrkel
Function-extensibility using the visitor pattern in OO sucks compared to
functional pattern matching. It is almost unusable except for the most trivial
tree traversals.

Frequently you want to be able to pass arguments down and return values up the
tree traversal. Using visitors, you need a whole separate class for each
unique function arity, or you have to use tedious little data carrier
instances. Sometimes you want to switch between them half-way through the
traversal, e.g. use your constant-folding visitor during an optimization
visit, and the visual overhead of constructing visitors and keeping track of
them, making sure they're all linked together properly, is really ugly. Don't
even think about matching the visitor method on more than one parameter type,
like you might do with e.g. overload resolution of binary operators. I've been
there working on a commercial compiler, and I don't want to go back there
again.

Haskell can implement the OO style using existential types (forall), a bit
like Go's interfaces, except once you put a value into an existential type
variable, you can't typecast it back out again.

~~~
pacala
> Function-extensibility using the visitor pattern in OO sucks compared to
> functional pattern matching.

Not only that, but using the visitor pattern tilts the extend types / extend
functions into extend functions realm, soundly defeating the supposed
advantage of being open to extend on types. As such, visitor pattern is
precisely the same as a switch statement.

The only difference left is language specific. In Java, adding a new type will
make your compiler whine if you have abstract methods in the visitor. That's
because javac can't tell when a switch over enums is exhaustive.

------
tallanvor
Some of this seems petty. For example, the author gives the following "bad"
example:

    
    
      def method (object)
        if object.property
          param = object.property
        else
          param = default_param
        end
      end
    

And suggests that this is better:

    
    
      def method (object)
        param = object.property || default_param
      end
    

To me, this is an example of an if statement by another name. Sure, you cut
out a few lines, but it's still an if-then construct. Both examples are most
likely going to be treated the same way by the compiler as well.

~~~
michaelfeathers
I disagree. Sure, the code is functionally equivalent today, but the next
person touching the 'or' version might be less likely to introduce side-
effecting computation. The 'if-else' version is completely open-ended.

We need to think about the effect our choices have on the next person who
touches the code. Often with a different baseline people make different
changes. It's a micro-form of 'No Broken Windows'.

~~~
delinka
"We need to think about the effect our choices have on the next person who
touches the code."

And that next person might be the newbie on the team that doesn't have the
business domain knowledge built up over years of working for This Company. So
I'll use the if statement to keep intent clear.

Or maybe it'll be someone whose primary programming language doesn't use the
|| operator. Or maybe, for some unknown reason, it'll be a PHB. So I'll use
the if statement to keep intent clear.

~~~
michaelfeathers
Setting the baseline gives you a higher probability of a better change than
you would have otherwise. This assumes that not everyone on the team is a newb
or a code-dabbling PHB. If they are, there's not much point in writing any
code.

------
d--b
No! This practice is absolutely terrible! Object-oriented programming _can_
allow you to make if-free programs, but it will cost you
readability/maintainability. Many people have been fighting against the
overuse of inheritance and oo patterns that emerged in the 90s. Do not fall in
the trap again!

~~~
nikic
I think this is (once again) just a question of finding the right balance.
Using polymorphism to avoid manual conditionals is a practice that can often
improve readability and maintainability. One just shouldn't drive it too far.

I do agree that people are massively overusing inheritance. In particular its
often used as a means for code reuse, not for polymorphism. In most cases
composition should be favored over inheritance.

~~~
michaelfeathers
I don't advocate a completely if-less style, but I do often grep and count ifs
and elses in a code base before digging in, just to get a sense of what sort
of developers have been there.

I think the if-less style is great kata material. You learn a lot when you
apply it on a toy project rather than a real one. Sometimes you need to overdo
things in order to understand what their limitations are.

------
kayoone
You know whats a smelly thing in OOP ? Overblown abstractions, design patterns
and stuff for the sake of flexibility which in the end mostly will never need
that kind of flexibility but in turn readability and clearness suffers
instantly.

I am all for Design Patterns and abstraction, but only when it makes sense.

~~~
meaty
In full agreement.

We have a rule about only using patterns if they become apparent rather than
selecting a pattern up front.

~~~
viscanti
I guess I don't see the harm in spending a little extra time up front to
design things properly rather than just jumping in and writing code, hoping to
stumble upon the correct design pattern. I've found it much easier to find and
solve problems up front rather than waiting for potential landmines, at least,
the time required to fix those problems is less if they're exposed earlier in
the process. It's certainly possible to over-engineer a solution if you select
a design pattern up front, but maybe you'd be better suited by having a rule
against over-engineering.

~~~
lmm
It's hard to define a rule against over-engineering. The only approach I've
seen that has any success is to ban any and all design up front, and only ever
write code to implement user-facing features.

------
laurent123456
In general, this kind of don't-use-this patterns are not very constructive. It
reminds of this obsession of not having global objects at all cost. And we end
up with singletons, static classes, etc. that are all just global objects in
disguise. If I need a global, I use a global and call it that, same if I need
an if/else statement.

It's an interesting article though, especially the conclusion: "Any language
construct must be used when it is the more reasonable thing to do. I like the
fact that the presence of if-s can indicate a bad OO design."

~~~
DigitalJack
I think your quote sums up the intention of this article and most of the
"don't do this" posts.

What they all are getting at I think is that constructs in a language can be
"abused" or used less than effectively either in terms of processing
efficiency or user/programmer interaction.

So when they say "don't do this" it's more to show cases where doing whatever
"this" is can be bad. Not that it's always bad and should never be used.

Also, the "don't do this" mentality can be a useful exercise to teach yourself
alternative ways of getting things done.

~~~
d--b
Fine, but the point is that the guy is only half-way through his reflexion. OO
programming is an effective way to organize the business logic. And that logic
should be coded in a sequential fashion to be readable and maintainable.
Finding the right balance would be a much more interesting subject than to say
'ifs suck'

------
rejschaap
Yeah, I've seen people go down this path. In the next step they realize they
need an if-statement to determine whether to instantiate a PDFPrinter or a
MSWordPrinter. Which they conveniently hide inside an AbstractPrinterFactory
or a ComplexPrinterBuilder. This is probably how the whole Enterprise Java
thing got started.

~~~
arethuza
My own pet theory is that 95% of the enterprise code carefully architected to
be extensible is never extended.

~~~
gutnor
Similarly, when there are no extension points, extension get hacked in (= "if"
everywhere !)

Here is my experience with architected extension points:

1\. Generally the first extension point used set the trend, other developers
will follow the "pattern" blindly. Hacking to make it fit rather than use
another more appropriate extension. That is both bad and common (everyone has
had to work with too little time)

2\. There is often a sharp refocus close before or after release 1.0. A lot of
the extensions disappear at that stage (demo, experimental feature, cross-
platform/framework support, performance targets are set, security
infrastructure is decided, server setup, integration test env. available
instead of simulated, ...). Structural change (like removing extension point)
become very difficult to justify after release 1.0.

3\. Technical debt is very often called "selling feature" at management level.

But yeah, real world code sucks.

------
jre
This is an interesting idea and I think it's interesting to have it somewhere
in mind while programming.

A few thought though :

\- If you really push this to the extreme, you'll end up with all the logic
hidden in the classes inheritance hierarchy. I'm not sure this is more
readable/extensible than if/else statements.

\- Most of the example given by the author to use "language features" are just
syntactic sugar. Using collection.select instead of collection.each or ||
instead of if else is really just a matter of notation. I doesn't reduce the
number of test cases required for your code and it might lead to "magical"
one-liners that you have to read 20 times to understand.

------
davidw
For those who have never used a functional programming language, those often
allow you to do "if-less" or at the very least "if-lite" programming via
pattern matching.

~~~
CJefferson
Is pattern matching really that different from switch statements, which are
really just fancy if statements?

Trying not to sound sarcastic, but if people are for if-free programming,
pattern matching does not seem to be the answer for me. When I add a new type
in haskell, I usually find myself having to look through all my pattern
matchings.

~~~
riffraff
I second the question, pattern matching is cool for destructuring or for
completeness checking (not sure those are fundamental properties of pattern
matching though), but it does not solve the problem of adding new behaviour
without changing existing code any more than an `if` does.

~~~
ufo
This is not a bug - its a feature! Pattern matching/ifs are good for creating
new functions, while OO style makes it hard to add new methods.

------
praptak
Those who use _if_ are obviously too flimsy to decide what their programs
should do. I would not trust such persons to write any code at all.

------
Zak
It's not just an OO thing. If you're using conditionals, you might actually
want something else - a dispatch table, for example. Thinking about
alternatives to conditionals will probably result in better code most of the
time, but actually trying to go if-less seems forced.

------
Strilanc
Replacing a switch statement or large if statement with a virtual call (enum
-> interface refactoring) can definitely be very beneficial. It can turn
[crimes against
humanity]([https://github.com/aidanf/Brilltag/blob/master/Tagger_Code/f...](https://github.com/aidanf/Brilltag/blob/master/Tagger_Code/final-
state-tagger.c#L145)) into perfectly respectable code.

But, obviously, don't take this too far. If you find yourself not using an if
statement (or ternary operator) when writing the absolute value (for non-
optimization reasons)... you've gone too far.

    
    
        int UnclearAbs(int value) {
            return value * (value >> 31);
        }

~~~
lmm
Abuse of bitwise operators is an abomination, but I'd instinctively write
"value.abs" (scala), which is clearer than an if.

Obviously many things are ultimately implemented using if, but it's too low-
level a construct to be using for day-to-day work.

------
jwilliams
This is an aside, but don't use the ruby "to_proc" approach that listed in the
article. i.e:

    
    
      result = collection.select(&:condition?)
    

The "&:proc" methods are (very, very likely) slower and they also "leak".

When I say "leak", the VM doesn't Garbage Collect the parameters of the proc
until it is used again. Most of the time this is fine, but when it's not,
you're wasting considerable amounts of memory. This is known and is considered
within the spec.

I know they are semantically equivalent, but the MRI is doing something weird
internally. (ps. Learnt this the hard way).

------
killahpriest
This is a terrible example of IF-less programming. The conditional is still
here, it is just implied.

IF:

    
    
            def method (object)
    	  if object.property
    	    param = object.property
    	  else
    	    param = default_param
    	  end
    	end
    

Claimed to be IF-less:

    
    
            def method (object)
    	  param = object.property || default_param
    	end
    

It may be easier to read, but in the end you are still writing an IF
statement.

~~~
dsego
It's not only easier to read, but it clearly shows the intent, that it is only
an assignment. It's faster to comprehend too, you don't even have to look at
the right hand side if you're not interested. The first example obfuscates the
intent a bit. Also, in other languages, you'd have to declare the variable
first and that's even more lines of code.

~~~
dragonwriter
It would be just as clear in expressing that the intent is an assignment (and
more clear what is being assigned, since it avoids the coalescing-OR) though
less concise to leverage the fact that Ruby's "if" is an expression not a
statement:

<code> param = if object.property then object.property else default_param end
</code>

Though, actually if you want to do what the description says (use the default
if the property is unassigned, represented conventionally in Ruby by the
accessor returning nil) rather than what either the good or bad code does, you
probably want:

<code> param = if not object.property.nil? then object.property else
default_param end </code>

(The difference between this and the other versions shows up if the property
is assigned, and its value is false.)

~~~
killahpriest
The code tag does nothing, do this instead:

 _Text after a blank line that is indented by two or more spaces is reproduced
verbatim. (This is intended for code.)_

<http://news.ycombinator.com/formatdoc>

------
mwilliamson
As an example of taking this to an extreme, I was at a coderetreat where we
had to implement Conway's Game of Life without any if statements (or similar,
such as switches) -- we had to use polymorphism instead. The result was that
my partner and I ended up reimplementing a subset of the natural numbers as
distinct classes.

[http://mike.zwobble.org/2012/12/polymorphism-and-
reimplement...](http://mike.zwobble.org/2012/12/polymorphism-and-
reimplementing-integers/)

I'm definitely not advocating this as good programming practice, but the point
is that if you're used to always using if statements, then it's hard to learn
alternatives. By forcing yourself to use the unfamiliar, you might find some
situations where polymorphism is better suited to the problem, whereas you
would have previously defaulted to using ifs.

(barrkel has already left an excellent comment on when the two styles are
useful, so I won't repeat it:

<http://news.ycombinator.com/item?id=4977487>)

------
lclarkmichalek
The first example is an actual example of how removing ifs can reduce
complexity, but the last few seem misguided. It is no easier to test
`collection.each {|item| result << item if item.condition? }` than
`collections.select(&:condition)`; they are all but equivalent. The exception
handling example doesn't actually show the benefit of not using ifs, it shows
the benefits of using exceptions over return values. Setting up default values
via || is also a nice trick, but it hardly makes a macro difference.

Also, "# I slept during functional classes"? I don't know ruby, but the `each`
method seems to be just a variant of map, which is a pretty fundamental
functional construct.

~~~
ajanuary
each is basically an entirely non-functional variant of map if functional is
defined as no side affects.

------
primitur
One reason why "ifs are smelly" has become a maxim in some circles is because
they represent an under-tested code path. In such areas as safety-
critical/life systems, where a different codepath can be taken on the basis of
a single var, this can be a very, very dangerous practice. Certainly in
safety-critical, a reduction of "if"-based codepaths represents _higher
quality software_ in the end.

I have seen cases of radiation-derived bit-rot which don't manifest in any way
until a certain "if"-path is evaluated by the computer - this seriously does
happen and can still happen in modern computers today.

Having an abundance of such code switch points in a particularly large
codebase can be a degree of complexity that nobody really wants to manage - or
in the case of disaster, be responsible for .. so this maxim has been pretty
solidly presented in industrial computing for a while. Make the decision-
making as minimal as possible to get the job done, and don't over-rely on the
ability of the computer to evaluate the expression in order to build robust
software.

Now, its sort of amusing that this has propagated into the higher-order realms
of general application development by which most Class/Object-oriented
developers are employed .. but it is still an equally valid position to take.
State changes in an application can be implemented in a number of different
ways, "if" being one of the more banal mechanisms - there are of course other
mechanisms as well (duffs devices, etc.) which are equally testable, yet more
robust - simply because they break sooner, and can thus be tested better.

I take the position, however, that a well-designed class hierarchy won't need
much navel-gazing decision-making, which is what the ol' "if (something ==
SOMETYPE)" statement really is: a kind of internal house-keeping mechanism
being done by the computer at runtime, instead of at compile-time.

So there is a balance to this maxim, and the key to it is this: how complex
does it need to be, versus how complex can the codebase be before it becomes
unmanageable. If you're not doing full code-coverage testing with 100% testing
of potential codepaths, then every single if statement represents a potential
bug you didn't catch yet.

------
alter8
A nice blog series on if-less programming (in Portuguese):
<http://alquerubim.blogspot.com/search/label/ifless>

------
riffraff
Not endorsing, but much more expanded by the Anti-IF campaign
<http://www.antiifcampaign.com/> (which focuses on "bad IFs")

------
bane
I'm a little surprised nobody is lamenting the performance hit this kind of
technique will incur vs just using an if statement.

(reaching into my way back machine, ifs essentially compile down to a few
comparison instructions (which are often just subtractions) and a jmp
instruction (depending on the platform), it's literally built into the
processor! For a simple if statement we might be talking a handful of cycles
to eval the if vs an extended call stack pumping and dumping exercise)

~~~
neumann_alfred
For inner loops etc., having less branch prediction misses or none at all can
actually outweigh having to do slightly more complex calculations.

<http://stackoverflow.com/a/11227902>

~~~
bane
I'm actually curious about the internals of how a modern OOP system works
internally once it's boiled down to the CPU level. I'd imagine there's still
lots of branch prediction issues in complex OOP systems.

~~~
neumann_alfred
Well, that you don't use OOP for those inner loops is kinda taken for granted
I think. That is, you certainly don't program for code beauty first and
foremost -- OOP may not hurt in a particular case, but if it does, code beauty
may have to go... example: <http://www.particleincell.com/2012/memory-code-
optimization/> (which is not about OOP per se, and not about branching, but
illustrating that knowing what the CPU actually does (not just what it did
decades ago) is really important when talking about performance)

------
jayvanguard
2001 called, it wants its debate back. For all the new kids on the internet:
OO doesn't enable re-use, inheritance generally sucks, and bloating your code
with new types just to solve something three lines of if/then/else could solve
isn't worth it.

------
polskibus
If you're doing "ifs" on the same condition sets in various functions then you
should consider encapsulating the condition in class hierarchy. If there is
just one if for a condition set, introducing a class hierarchy is just bloat.

------
andrewcooke
how does "try to use less X because Y" become "don't use X"? and why is this
considered good?

to clarify: my question "why is this considered good?" isn't about "if-less
programming", but about taking ideas to dumb extremes.

~~~
alisnic
You did not read the article, by no means I suggest to get rid of the IFs
completely.

------
lucian1900
Replacing static decisions with polymorphism is indeed often a good idea, but
there's nothing wrong with using if when it's appropriate.

------
PaulHoule
The problem isn't if, it's "else if".

If-then-else ladders tend to evolve to be very difficult to understand,
maintain and debug

------
gbog
An often better alternative to inheritance for conditionals is configuration
with functions as values.

------
wildranter
Don't. Just don't. Do you think Da Vinci would've painted just with oil
because people think real painters work just with that? No he didn't, and so
you shouldn't too.

Don't limit yourself just to blindly comply to some silly idea. Use everything
you know to get the job done, and once you get it working, make it beautiful.

If statements are an incredible tool. Just ask any Erlanger and they will
either tell how much they miss it, or just lie to your face. ;)

~~~
revscat
If statements are rife for abuse and can be an indicator of poorly thought out
structure. This article mimics my own experiences, namely that overuse of if
statements is a smell and can usually be avoided to the benefit of the code.

I long ago abandoned else clauses. It was a short time thereafter that I
realized that if statements themselves weren't all that necessary, most of the
time.

~~~
colomon
There is no programming construct that exists that is not rife for abuse.

------
sublimit
Oh great. What will you people come up with next? Variableless programming?

~~~
gordonguthrie
Erlang is mostly IF-less (destructuring pattern matching in function heads)
and doesn't have variables.

A = 1,

A = 2, % fatal error because 1 != 2

So, yeah, variabless programming FTW!

~~~
dragonwriter
Erlang has mutable variables (the process dictionary), it just makes you do
more work to get at them instead of immutable ones and prevents them from
being directly shared and causing synchronization problems.

~~~
gordonguthrie
No, the process dictionary is not a mutable variable - there is no natural
idiom to use values stored in the process dictionary as variables in code, you
have to get them out and put them in via immutable variables.

Any given Erlang process has meta-information about itself, how many
reductions it has, how big its heap is, which flags are set. These are the
global state of the process.

The process dictionary allows you to store and manipulate your own global
state of the process - and the people (hands up, that includes me) get smart
and use it as local state of the programme and then get their bum bitten badly
and swear never to dance with the dark side again... :(

Not bitter :)

