
Anti-If: The missing patterns (2016) - fagnerbrack
https://code.joejag.com/2016/anti-if-the-missing-patterns.html
======
jakewins
Another pattern I've found helpful is to have branches "exit early"; if you
have code like:

    
    
        if !cache.contains(key) {
          v = db.query("..")
          expiry = calcexpiry(v)
          cache.put(k, v, expiry)
          return v;
        } else {
          return cache.get(k)
        }
    

by the time you get to the "else", your mental stack is filled up with the
cache loading logic - you have to work to recall what this else branch is
triggered by.

instead, make the branch be the least complex option and exit early, and skip
the else. That way, it can be read as "if this condition, do this tiny logic
and exit", and that branch can then be mentally pruned.

    
    
        if cache.contains(key) {
          return cache.get(key)
        }
        
        v = db.query("..")
        expiry = calcexpiry(v)
        cache.put(k, v, expiry)
        return v;

~~~
adamtulinius
Your code should be written this way instead:

    
    
        if !cache.contains(key) {
          v = db.query("..")
          expiry = calcexpiry(v)
          cache.put(k, v, expiry)
        }
        
        return cache.get(k)
    

Only one return, and everything is still in the logical order.

~~~
recursive
I don't think there is anything virtuous about a single return in languages
that have catch, finally, and garbage collection.

~~~
acdha
I think it’s a judgement call: I like clearly indicating when there’s always a
single output path but wouldn’t use the single return style in that cache
example if it involved an extra lookup operation for a value which is already
known.

------
barrkel
Don't use boolean parameters to toggle behaviour - they're indecipherable from
the calling context. But don't then mindlessly create the cross-product of
methods from every combination of true and false for your boolean parameters:
If the caller would have used an expression rather than a literal to determine
which version of the method to call, use enums instead of booleans or multiple
methods. And don't turn setEnabled into enable/disable pairs, or setVisible to
show/hide pairs - these are understood as properties, not parameterized
methods.

Enums vs inheritance isn't straightforward. Enums make it easy to add new
kinds, while inheritance makes it easy to add new methods. They are more
extensible in different directions. If it's very rare for you to add new
values to your enum, and more frequent for you to switch on that enum, prefer
enums (and always throw in your default clause!). If you are adding enum
values all the time, and only have a couple of methods that switch on them,
then prefer inheritance. Although if you're using Java, consider methods on
your enum values instead.

To understand the enum / inheritance tradeoff better, examine the Expression
problem:
[https://en.wikipedia.org/wiki/Expression_problem](https://en.wikipedia.org/wiki/Expression_problem)

~~~
TorKlingberg
> Don't use boolean parameters to toggle behaviour - they're indecipherable
> from the calling context

This is mostly solved in languages with named parameter calling (if you use
it). Example: createFile(name, contents, temporary=True)

Sometimes it is already obvious enough. In C I have no problem with
set_visible(item, true);

~~~
sanderjd
You can mimic this in most languages: createFile(name, contents, /* temporary=
*/ true). Of course that doesn't guarantee that that's what that parameter is
actually named, but you can write a lint for that (with quite a bit of
effort).

~~~
lower
I'm not sure about this, because it's easy to mix up two arguments of the same
type.

For example, you could mean

    
    
        createFile(name, contents, /* temporary= */ true, /* overwrite= */ true)
    

but write

    
    
        createFile(name, contents, /* overwrite= */ true, /* temporary= */ true)
    

by accident.

~~~
SomeCallMeTim
Now I want to see something like Prettier (a code formatter that enforces
_exact_ whitespace and specific optional syntax) that will parse C code and
fill in the correct parameter names, in comments, based on the headers, for
any boolean parameters (maybe excluding single boolean parameters?).

------
mikhailfranco
Erlang provides an 'if' expression as syntactic sugar for 'case' (switch),
which must always have a true (else) clause. The 'if' form is strongly
deprecated.

Even the 'case' expression is mildly deprecated in favor of pattern matching
and guards in function heads (definitions).

There are no null values, no classes and no inheritance, but the presence of
atoms (arbitrary tokens that can be used like null, true, false or
enumerations), and pattern matching (data polymorphism and destructuring),
means that conditional execution is elegantly implemented by _functions_.

This is the second, often forgotten, meaning of _functional programming_.
Providing functions as first-class types, supporting anonymous lambdas
(closures), and applying functions over collections are given all the glory,
but deprecating conditionals in favor of pattern matching over many fine-
grained functions is just as significant.

~~~
macintux
Now that I'm comfortable with Erlang, writing in any language that doesn't
support branching at the function heads is so depressing.

~~~
philsnow
also not being able to write multiple versions of a function with different
arity is annoying.

~~~
cbcoutinho
I recently started learning Clojure and using this pattern is immensely
satisfying as well.

------
d--b
I disagree with the comments here. This is mostly not good advice.

Ifs exist because most real world problems require them. “If the box is
checked, do this, otherwise do that”.

Masking if statements by syntactic sugar doesn’t serve any purpose in my
opinion and if anything it makes the code more opaque, or worse, may force you
to duplicate code...

~~~
meheleventyone
I was wondering which of the examples you think particular fall into this
category of making the code more opaque?

I actually went into this with similar misgivings. A lot of "get rid of ifs"
advice, particularly from OOP people ends up making things more complex IMO.
However most of the advice other than the "Switch to Polymorphism" seem very
down to earth and a simplification. Less about getting rid of ifs as if their
mere presence was a stain on humanity and more how to rewrite what they are
doing more clearly.

~~~
phaedrus
I still "believe in" OOP, but there's something that is increasingly troubling
me lately: I keep thinking about the difference between the unbounded set of
possible derived classes which _could_ be substituted for a base class, versus
the small (probably 1-3) number of different actual implementations are ever
used in a real program. It seems like this disparity indicates the mechanism
we're using is overly general. Maybe this is an argument for Sum Types as
opposed to runtime polymorphism.

It seems the biggest thing OOP polymorphism has going for it is that it is
"open", that is, the possible derived classes need not be known in advance.
But, the fact that our toolchains use a separate link step, and that linkers
remain primitive and link-time code generation is not widespread, is merely an
historical accident. There's no a priori reason things have to be this way.

For example, an alternative would be if all of the source for an executable
were in a single image file a la Smalltalk, and compilation consisted of
converting this "database of code" into a single executable (no shared
libraries!) in one big-bang compilation step (no object files, no static
libraries!) You could even have something which syntactically resembled open
polymorphism, maybe even using existing languages almost as-is, but instead of
compiling down to type erasure and dynamic pointer casts, would compile down
to Sum Types (variants). (The compiler could scan the image to find all of the
derived types it _could_ be and could construct a variant out of that.)

I'm not saying this is better, just putting it forth as an example of how
things could be radically different than they are now (even for statically
typed, compiled languages), had we made different choices. Consider the
alternative, the situation we have now: we use a technique which could support
anywhere from 1 to infinity polymorphic derived classes to implement
interfaces which probably have approximately 2 different implementations in a
typical finished program.

Perhaps an analogy would help: If we approached hardware the way we approach
software, we would be using connectors which support between 1 and infinity
pins everywhere when connectors which support N pins would suffice.

~~~
veebat
Sum types are a huge benefit to branchy code. Philosophically, OOP
polymorphism can do a similar thing, but it doesn't really cover it as
succinctly: if your operation is of the "similar data, mostly the same
algorithm but with variations at points depending on the data's type", going
down the polymorphic route requires a lot more naming of things to cover each
variation, and then when you go to inspect it, the code is twisty and jumps
all over the place. I've had a lot of bugs introduced by method boundaries
obscuring the execution flow.

What polymorphism is good at is building the black box, the soft boundaries,
and that isn't a good thing to do for fine-grained details. I do occasionally
find a use for extension, but it's at a scale closer to "call this entire
subprogram as a kind of state machine", vs "write this algorithm as a
collaboration of objects abstracted away from each other".

The "alternate hard and soft layers"[0] pattern comes to mind: if what you
really need is modularity, going towards full dynamism and reflection seems
like a better choice than to try to extend everything statically, which
creates a very large latency issue(the default response to every form of
change in a static system is "recompile from the beginning, precompute all
answers"). At the same time, it's not a good fit to have a vast codebase do
everything dynamically since the comprehensibility and throughput suffer.

[0]
[http://wiki.c2.com/?AlternateHardAndSoftLayers](http://wiki.c2.com/?AlternateHardAndSoftLayers)

------
dahart
> Problem: Any time you see this you actually have two methods bundled into
> one. That boolean represents an opportunity to name a concept in your code.

I like this statement. After 30 years of coding, I've found that trying to do
something quick/clever without naming it explicitly, for fear of letting the
code get bigger, is what often leads me to write confusing code and harder to
understand ifs.

I think maybe it's that naming in new code is easy, but adding new concept
names to existing code as it grows gets a lot harder. I'm automatically naming
when I create a new class, but when I fix bugs or touch existing files, I'm
actively trying to avoid naming things and I'm trying to touch the least
possible code, so over time it trends toward having unnamed concepts.

I usually try to apply naming to Pattern 4. So maybe instead of:

    
    
      return foo && bar || baz;
    

I might:

    
    
      bool bad = foo && bar;
      bool worse = baz;
      bool isHorrible = bad || worse;
      return isHorrible;

~~~
Zardoz84
Just today, I was talking with a team mate about this. We had some pice of
code that it's like this :

    
    
        public boolean foo() {
          
    
          if (!this.doesSomethingWithA()) {
            return false;
          }
          
          if (!this.doesSomethingWithB()) {
            return false;
          }
          
          if (!this.fooBar2000.isEmpty()) {
            return false;
          }
          
          if (!this.anotherLongAttribute) {
            return false;
          }
          
          if (!this.anotherMethod()) {
            return false;
          }
          
          return true;
       }
    

We try to replace to a "one liner" return !A && !B && !C ... Well, the
expression was very long and we noted that was more hard to read that the
multiple if's. I suggested split the one liner expression on multiples lines
doing something like this :

    
    
        return !this.doesSomethingWithA()
            && !this.doesSomethingWithB()
            && !this.fooBar2000.isEmpty()
            && !this.anotherLongAttribute
            && !this.anotherMethod();
    

However, the code formatter (eclipse), changes every time it to the hard to
read one liner expression. So we ended using the multiple if's.

~~~
veebat
There's a semantic thing going on with this expression that is rather
interesting.

First, consider this reworking:

    
    
        boolean a = this.doesSomethingWithA();
        if (a) {a = a && !this.doesSomethingWithB();}
        if (a) {a = a && !this.fooBar2000.isEmpty();}
        if (a) {a = a && !this.anotherLongAttribute;}
        if (a) {a = a && !this.anotherMethod();}
        return a;
    

And then this one:

    
    
        boolean a = this.doesSomethingWithA();
        a = a && !this.doesSomethingWithB();
        a = a && !this.fooBar2000.isEmpty();
        a = a && !this.anotherLongAttribute;
        a = a && !this.anotherMethod();
        return a;
    

In the first version and your original, it's clear that there's short
circuiting and it can return early, but automatic formatting will bloat up the
vertical size of the code. In the one-liner there's also short circuiting, but
it doesn't format well. In my second version you get the formatting, but it
will always evaluate every possibility.

In general, when I'm favoring code formatting, the variable declarations come
out. Quickly aliasing something into a name when it could exist purely as an
expression adds a degree of conceptual flexibility. It keeps the code local(no
new function name and jumping over into it). But it does also result in this
kind of unnecessary computation.

------
jstimpfle
Overall, very good advice. I disagree on polymorphism through virtual methods
being a solution to the maintainability problem switches pose, though.

With such polymorphism it's difficult to know the things that can happen.
Implementations of the virtual method are not closed - they could come from
anywhere in the codebase.

Another problem is that it's hard to cleanly separate the common code from the
specialized code in each implementation class. That's why one ends up with
ugly helper functions that receive a lot of state, or alternatively with
unmaintainable class hierarchies.

IME the only solution is to minimize usage of datastructures that have choices
in them ("ADTs") and minimize the coupling of the code to the choices. The
latter is done by separating code paths early, and by only switching if
absolutely necessary.

~~~
tabtab
Polymorphism and inheritance often assume that differences are hierarchical.
The real world is often NOT hierarchical, or at least may easily change to not
be hierarchical in the future. Noun "classification" of most domain things is
messy. It's one of the reasons for the backlash in the 2000's against OOP as a
domain modelling system.

IF's often handle unpredictable variations better. Features are often better
viewed as a buffet restaurant instead of an animal kingdom classification tree
as found in biology. The biology classification-like approach popular in the
90's usually fails outside of biology. And, "lots of little methods" can make
code rather hard to read & follow in my opinion. Your eyes may differ.

~~~
dragonwriter
> The real world is often NOT hierarchical, or at least may easily change to
> not be hierarchical in the future.

The real world is an undifferentiated unified whole, but you can't actually
work with it without dividing it up and labelling/modelling it, and most
useful models have a heirarchical structure. OTOH, they usually aren't a
single-inheritance heirarchies, and most OOP languages either handle multiple
inheritance poorly or not at all.

~~~
your-nanny
OOP is lousy as an ontologicsal model of the stuff making up the real world.
But that doesn't make it not useful for building designed systems.

~~~
tabtab
True, but that doesn't imply one should turn most IF statements into
polymorphism, or other OOP constructs. OOP is one of multiple tools in our
toolbox.

------
noelwelsh
It's important to take this kind of advice in it's context, which I would
hand-wavingly describe as "imperative old-fashioned OO".

For example, the advice "Pattern 2: Switch to Polymorphism" definitely does
not hold in languages with algebraic data types, where the compiler will check
that your "switch" expressions check all possible cases. Given that at least
Rust, Swift, Scala, and Typescript can all do this, this kind of advice is
becoming increasingly outdated.

~~~
bjpbakker
> Pattern 2: Switch to Polymorphism

As a matter of fact it does. Polymorphisms come in many forms (pun intended).
The OO-pattern of class inheritance is simply just one of them - not quite so
good too if you ask me :)

The author seems to believe that polymorphism is OO-specific. But it isn't. As
a matter of fact, if/switch statements are _also_ polymorphic, often used in
more procedural code.

The more functional way to match on different type constructors is a fine
example of a polymorphic function.

~~~
ben509
> As a matter of fact, if/switch statements are _also_ polymorphic, often used
> in more procedural code.

Polymorphism has a straightforward meaning, that the same code is operating on
different data types. Switch statements (if's are just a special case) are
running different code on different types, so they're monomorphic.

> The more functional way to match on different type constructors is a fine
> example of a polymorphic function.

I think I see where you're coming from. Here's an example of what you're
talking about.

    
    
        data Foo = Foo Int | Bar String | Qux
    
        what :: Foo -> Foo
        what (Foo num) = Bar $ show num
        what (Bar str) = Qux
        what Qux = Foo 0
    

That's not polymorphic; there's a single type there, and any given code is
strictly acting on a single type of data. It's just a nice way of writing a
case.

And, in fact, you find out it's not magic because it gets just as messy and
confusing once you have to nest logic as with imperative programming, for
example, if Foo contained another ADT, I'd often have to use a case statement
or equivalent to manage it.

------
ariehkovler
Fascinating. I love constrained writing and constrained poetry.

No If-statements in code reminds me a bit of the E-Prime movement
([https://en.wikipedia.org/wiki/E-Prime](https://en.wikipedia.org/wiki/E-Prime))
which was about removing the verb 'to be' and all its variants like 'is',
'was' etc from English language. Supposedly it made English easier to
understand and encouraged clarity and precision.

------
wst_
Although I generally agree with the concept the truth is it's not easy to stop
using `if` or `switch` completely. By applying rules described in the post you
are just pushing the problem around. At some point, somehow, a decision must
be made which branch to follow. Want to create two methods instead of `if`
fine, do it - how are you going to decide which of them to call? You want to
use polymorphism, fine, do it - how are you going to decide which class to
instantiate? What to use enums with methods on them, fine, how are you going
to decide which enum to use? You may say "Oh, I am just deserializing user's
input to enum. I don't have to decide." Fine, so you've just pushed decision
onto users of your application. Sure, you may use some other methods,
especially when you must choose from multiple options (map instead of switch,
etc.) but you should always remember - just by avoiding decision making in one
place, doesn't mean you've completely eliminated it from business process.

~~~
JustSomeNobody
>... By applying rules described in the post you are just pushing the problem
around. At some point, somehow, a decision must be made which branch to
follow.

Right, and you should try and make that decision as early and as few times as
possible. For example, let's say you work with older systems that have grown.
You went from one type of config to two types, so now you have If (configOne)
... else ... scattered all over your code. You can't really get rid of the
decision, it has to be made, but you can refactor the code to make the
decision only _once_ when the app starts up.

------
MattBearman
Interesting article and definitely some good advice, but I completely disagree
with example 4.

Surely I can't be the only person who finds

    
    
        if (foo) {
            if (bar) {
                return true;
            }
        }
        
        if (baz) {
            return true;
        } else {
            return false;
        }
    

Much easier to parse than

    
    
        return foo && bar || baz;
    

It's a bit better to my eye with parentheses:

    
    
        return (foo && bar) || baz;
    

Of course the first version is far from perfect (no need for the nested if,
etc.) but I've always found too many comparisons on one line just looks messy.

~~~
diggan
Also, it's not really removing the `if`, it's just making it into `&&` and
`||` instead, which are shorthand conditionals anyways. So a bit misleading to
call this a anti-if strategy.m

~~~
pythonaut_16
If you have a function that's using if statements to decide which boolean to
return, it's almost always better to return the expression directly.

`if (foo) { return true } else { return false }`

is the same as `return boolean(foo)`

------
d--b
Pattern 1 is impracticable: most reasons why you would add a Boolean parameter
to a function is to avoid code duplication:

void blah(int x, bool logInfo) {

    
    
       ...
    
       if (logInfo) ...
    
       ...
    

}

Pattern 2 can be horrible when applied to the wrong problem. As in: you can
use polymorphism to implement fact(int x) without if statements, but it’s not
going to be pretty...

Pattern 3 is not a pattern, it just says “if the if is useless remove it”...

Pattern 4 is particularly confusing, especially when you start to have nots in
there:

var x = !(a || !b) // ugh

Pattern 5: doesn’t remove the if...

~~~
hitsthings
Pattern 1 you can pass in a logger there or a noop logger and not have any
duplication.

~~~
d--b
Well, I could do operations for logging that are otherwise not required...

    
    
       if(log) { logger.write(computestuff()); }

~~~
meheleventyone
Depending on language if you have first class functions you could also do:

    
    
      logger.write(computestuff);
    

Or for parameterized functions:

    
    
      logger.write(() => computestuff(a, b, c));
    

Then all that logging policy stuff can live with the logging and doesn't need
to be checked explicitly throughout the codebase.

------
r3n
I would suggest one use these as a sign of "there is another way to write
these code", rather than follow it without knowing why you need it.

------
amarant
am I reading this right? it seems number 3 is suggesting I accept Optional as
function/method parameters? That is an anti-pattern if ever there was one, and
it also adds another layer of if's instead of removing it. an empty collection
is fine, but if I accept an optional, I need to first check that the optional
isn't null (because it can be!) and if its not, check if the optional has a
value(basically another nullcheck, jay!). optionals should only ever be used
as a return-type. I hear a lot of people say that they should be used as
parameters to indicate that one parameter is not necessary, but that's what
overloads are for!

------
KirinDave
It's very weird to see people genuinely suggest, "Conditional logic is
complicated, so try burying it in inheritance."

~~~
Jtsummers
But it is complicated when that (I'm assuming you mean Pattern 2) switch-case
expression is in a large number of locations. Every time you add or remove (in
that example) species from your enumeration, you have to address that switch-
case in every location (or verify that the default is fine).

By addressing it via (well, I'd use an interface or similar) inheritance the
conditional logic isn't removed, but it is isolated to a few decision points.
All you need to know is that you have a Foo, and my implementation whether
it's a Bar or a Baz will provide the needed functions.

~~~
KirinDave
Semi-serious: switch cases are only complicated when you're using compilers
that can't check totality for you.

~~~
Jtsummers
I'll mostly agree with that. If you have a compiler that can check totality
(I've been playing with Idris lately at home, that feature is fantastic), then
the work of adjusting your switch-cases becomes much easier. But there's still
a lot of work relative to, for instance, creating a type class (or in Idris
terms an interface) and implementing the functions of that type
class/interface for your specific data type.

Similarly, in OO-languages with interfaces, you can apply this same approach.
It doesn't eliminate the conditional, it still exists in the code. But it
moves where you have to think about it and reduces the redundancy across your
code base.

With these things you don't even have to modify _every_ function when you add
a new value to your equivalent to an enum, you just add the implementations
for that new type/class.

Though it does shift the difficulty when you want to add a new function/method
to the interface. Now you _will_ have to go to each implementation and add
this new capability. But the nice thing, here, is that most languages with
static typing (whether fancy dependently typed languages like Idris, or
relatively simpler ones like Java) will make it _immediately_ clear which
implementations are incomplete.

------
jmartrican
IF statements increase cost of unit tests, is what I have found. By enforcing
tight code coverage standards you start seeing IF statements disappear from
code as developers do not want to write a test case for each of those IF
statements.

With tools like JaCoCo we track number of branches and cyclomatic-complexity
in our code (which is a measure of IF statements in most cases, try/catch
being the other big source of branches). We try to keep that number down to 1
as much as possible. Once was able to take a method with over 160 branches
down to 1 by eliminating every IF statement. The unit test was then very
trivial and only had one test.

~~~
cle
This doesn’t make any sense to me. You should still be testing the different
behaviors, what difference does it make to testing if they are in a branch or
in another class?

------
macintux
Garrett Smith tackled similar themes. While his emphasis is on Erlang with its
function heads (a feature nearly every other language could really use) he
also shows examples in Python.

His ideas changed the way I think about clean code.

* [http://www.gar1t.com/blog/solving-embarrassingly-obvious-pro...](http://www.gar1t.com/blog/solving-embarrassingly-obvious-problems-in-erlang.html)

* [http://www.gar1t.com/blog/more-embarrassingly-obvious-proble...](http://www.gar1t.com/blog/more-embarrassingly-obvious-problems.html)

------
hellofunk
> Moderation in all things, especially moderation

Brilliant!

> being defensive inside your codebase probably means the code that you are
> writing is offensive. Don’t write offensive code.

There are some catchy insights here.

------
btilly
Looking at the code in the examples, the first style thing that I would
suggest fixing is that the name of a boolean variable should be a yes/no
question which the value is an answer to. So don't name the boolean
"temporary". Name it "isTemporary" instead.

Moving on to the actual point, for a balanced view just look at cyclotomic
complexity. In a function, count the decision points. Every if and loop
counts. Any function with 10+ decision points is too complex and needs to be
refactored to make the control flow more obvious.

Following this rule will let you use if quite happily, while also avoiding the
problems with it.

(A side tip. If your language has some variant of "unless", don't use it.
Ever. Our brains do not do de Morgan's laws naturally. So you can write code
that is clearer upon writing, but is much harder to keep straight when you're
debugging. The time that you need to understand your code best is while
debugging, so ease of debugging is more important.)

------
dep_b
It could be renamed "Things that suck about Java" and you wouldn't need to
remove much. It's true that if-statements are something you might want to
avoid in any language but Java doesn't give you as many ways to do it as I
would like to see. The first part about the boolean is pretty universal for
most languages.

For example the switch on type: wouldn't it be nice you could simply have a
compiler error whenever you forgot to either add default or added all values?
I use enums a ton in other languages that do and it's like a to-do list of
code to implement if I would add for example another specie. Java just doesn't
so the only compile-time safe way to have it is to force subclassing.

Equally the argument about not checking nulls inside your own application.
Nullable pointers are a language problem. The argument to that function should
not be nullable to begin with. It doesn't make sense to call it with null.
Ever.

I find foo && bar || baz is pretty hard to parse as well. It might be easier
to throw all three arguments in a switch / case statement and give all
relevant states their own case. But that doesn't work in Java.

The hidden "null means something" example could easily be rewritten to a
response that simply has a Error or Result state that either contain the error
or the result we wanted to have. Java only allows you to return an object that
you need to cast to use (Error extends BaseResult, Result extends BaseResult)
or declare it might throw a particular type of error.

I think all if not most of these problems are solved in Kotlin _. Java is just
not a great language to express problems in a short and succinct way without
introducing side effects or massive amounts of boilerplate (sub sub sub sub
classing).

_ more experienced in Swift but Kotlin devs can follow the patterns I use

------
sverhagen
Let's eradicate nulls first, then do the ifs in your own time ;)

~~~
hellofunk
I agree that null/nil/None is one of the worst programming constructs ever
invented [0] and deserves more attention than if statements.

[0] [https://www.infoq.com/presentations/Null-References-The-
Bill...](https://www.infoq.com/presentations/Null-References-The-Billion-
Dollar-Mistake-Tony-Hoare)

------
jillesvangurp
I'd argue against using abstract classes in general (just use interfaces) and
stuffing logic in domain classes. Domain classes should be simple, not contain
any algorithms or logic, and preferably be immutable. If you are putting ifs
in domain classes you are doing it wrong. If you are moving logic from service
classes to domain classes, you are no longer separating concerns.

If you have sixteen polymorphic methods in one service class (which is of
course possible), maybe using a switch statement would actually be a bit
shorter and easier on the eyes. It depends. I think about this in terms of
unit tests and code paths. More ifs means more paths. Units with lots of code
paths are complex and hard to test. On the other hand having code paths cross
many classes and methods just moves the problem. It's a tradeoff. A few other
tricks you can apply here is using enums with e.g. lambdas and other feature
flags. That's a nice way to tie functionality to types or associate some
default functionality with domain classes without littering them with logic.

Not using abstract classes is somewhat controversial for some but I have
disliked them for quite long and find I don't need them and can trivially
refactor them away whenever I encounter them in an codebase. Preferring
delegation over inheritance is a good thing IMHO and using inheritance
generally is something I end up regretting. I find framework code (Spring,
cough) that uses abstract classes to be mostly unreadable and convoluted. You
have a Thingy that is also an AbstractThingy that inherits an
EvenMoreAbstractThingy that implements a gazillion interfaces that are also
implemented by other thingies and yet more AbstractClasses, etc. If you have
to control click through six classes to actually get to a place that actually
has logic, something is deeply wrong. Deep inheritance hierarchies are a good
sign your type system is struggling with your abstractions and sets you up for
maintenance problems when the cohesiveness of your classes drops and coupling
increases as you are forced to add stuff in places where it really shouldn't
be for API compatibility reasons.

------
sampl
> Pattern 5: Give a coping strategy

> Context: You are calling some other code, but you aren’t sure if the happy
> path will succeed.

I’d love to see some good examples of this if-less code in React.

I find myself writing conditionals in data provider component render methods
for things like “isLoading”, “hasError”, and “items.length == 0”.

------
pq0ak2nnd
Testing if(null) inside my code always makes me feel like I've been punched in
the face. Why should my code have to be on the lookout for bad parameters all
of the time? But on the other hand, my code should be on the lookout for bad
parameters all of the time. Advice?

~~~
projektfu
If it's not an interface, then it is probably correct to crash when called
incorrectly. Problems should be found early on. If it is an interface, you may
need special processing. A clear way of doing this is to do the sanity
checking in the interface method and then pass to a module-private method that
does the work. Internal logic that calls the same method can bypass the sanity
check.

------
csomar
>> Solution: Use a NullObject or Optional type instead of ever passing a null.
An empty collection is a great alternative.

That doesn't work if you are building a library, or working with other
software developers. The alternative is to have a strictly typed language that
forces the user to pick the type of the variable.

It doesn't also fix the problem of whether you function is returning a
functioning result or an error.

That's one of the thing that I like about idiomatic Rust. The Result/Option
types are implemented as standard language features. You get to use them from
the get-go and they reduce lots of if/then/null friction.

------
tbronchain
While I agree on reducing the amount (especially either too long or nested)
ifs in the code, I disagree with some of the proposed patterns, especially the
#3.

Sometimes we might want to use "null" rather than an empty collection to
signal something different i.e null -> error, empty collection -> no result
(avoiding the debate on whether we should use some exceptions for all error
management).But generally speaking, creating a new object everytime instead of
passing/checking null looks like a real potential loss of performances in some
(not so rare) cases.

~~~
jerrre
Instead of passing/returning null, there are much clearer things for all your
examples, depending on the situation: Optionals, Empty Containers, Errors.
Just using null communicates very little.

~~~
heavenlyblue
Said so many times. Do not ever use nulls as flags.

It’s incredibly like Python exception handling: it works well until you try to
work with a somewhat complex codebase: eg paramiko. The IOErrors can be raised
by any layer of the stack. So how do I handle this in the code?

Well, turns out I can’t definitely tell one from another.

Same is with the null values: never use nulls as a flag unless you’re
absolutely sure that null value will not be boxed in another “Optional”.

~~~
tbronchain
So your understanding of the pattern #3 is that, null was being used instead
of an empty list, and in this case it's better to use an empty container. In
that case, it makes sense, that's not how I understood it at first.

Oh and, I agree about the not using null thing at multiple levels.

------
AndrewSChapman
Pattern 1: Reasonable

Pattern 2: Yes absolutely

Pattern 3: Seems a bit inefficient, allocating memory when a simple if
statement would avoid calling the method at all.

Pattern 4: Would prefer

if (foo && bar) { return true; }

return baz;

Than the proposed solution which is harder to read.

Pattern 5: Just no! The repository getRecord method should have no care about
coping strategies, which may change depending on the calling context. It
should instead throw an RecordNotFoundException. You could also use success
and error callbacks in the method signature.

~~~
RodgerTheGreat
Regarding pattern 3, I think the most correct way to do what the author
proposes is to use Collections.EmptyList(). You get an immutable empty list,
which by virtue of being immutable need only be allocated once.

[1]
[https://docs.oracle.com/javase/7/docs/api/java/util/Collecti...](https://docs.oracle.com/javase/7/docs/api/java/util/Collections.html#emptyList\(\))

------
amelius
Surprised it didn't mention passing closures around ...

------
wruza
[https://web.archive.org/web/20180627100453/https://code.joej...](https://web.archive.org/web/20180627100453/https://code.joejag.com/2016/anti-
if-the-missing-patterns.html)

If link doesn’t open.

------
troels
It probably makes me rather pedantic, but if you write about programming
style, I expect you to format your code consistently. It's either `if(record
== null) {` or `if (result != null) {`. Make up your mind (I know which one I
prefer, but at least be consistent)

~~~
mort96
I'm not sure if it's that simple. Sometimes, you want to guard your code with
an early return, which means `if (x == NULL) return errorvalue;`. However, if
the function should handle both cases, I prefer to start with the "happy
path", which usually means `if (x != NULL) { happy; } else { handle_sad_case;
}`.

~~~
troels
Yeah, I was addressing the inconsistent space between "if" and opening
parentheses.

------
sshine
In "Pattern 2: Switch to Polymorphism", he recommends not writing huge switch-
statements, but instead recommends inheritance. Polymorphism is entirely
doable without inheritance using interfaces and parametric polymorphism.
Besides that, great advice.

------
skybrian
Mostly good advice, except that being able to execute the code in your head is
a good thing. You know you have it bad when you can't do this reliably and
have to break out the debugger.

(Of course, debuggers are useful too, but code that requires them is not
good.)

------
jaunkst
Dynamic combinators can compose logic without if statements or switches.

Imagine a combinator that takes an array of curried functions that are
composed from a filter or reducer. No if statement needed if you can dynamicly
compose a functional pipe

------
adrianmsmith
The trouble with solution 1, removing a boolean method parameter and instead
creating two methods like:

    
    
        public class FileUtils {
            public static void createFile(...) { .. }
            public static void createTemporaryFile(...) { .. }
        }
    

is that there is now no way to create a single wrapper function.

Let's say you want to create a function "createFileAndLog" which takes the
boolean parameter and passes it to the underlying "createFile" function, now
you have to create two "createFileAndLog" functions rather than just one, even
though you didn't have the if statement in your code.

I haven't provided a great example, I know, but I've definitely split up such
methods myself, felt good about it for increasing readability, then come to
regret it later.

~~~
QasimK
Pass the "create file" function itself as a parameter to the "create file and
log" function?

------
CodeCleanly
There is actually a current book out that has 70 of similar advice. Java by
Comparison: [https://java.by-comparison.com/](https://java.by-comparison.com/)

~~~
codetrotter
Seems like an interesting book, but for _some_ reason (your username and the
fact that the only two comments you have ever written were about this same
book) I am getting a feeling that you might be one of its authors.

IMO, if you are you should disclose that when you write a comment. I am all
for people promoting their stuff — as long as they do so in a way that make it
clear that they stand to gain from sales.

------
andreygrehov
All of this is not new. For anyone interested in similar patterns, check out
Refactoring: Improving the Design of Existing Code by Martin Fowler.

~~~
tabtab
I've disagreed with many of Martin Fowler's opinions. He assumes future
changes will mirror current variations. That's often false. It's over-
extrapolation of what's known as a pattern for handling a future that may not
fit the current pattern.

------
tzahola
Mostly good advice, but without multiple dispatch, polymorphism can only work
in the simplest cases, and even then it's often not worth the boilerplate.

