Take the following Go code as an example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

type CodeType int

const (
    CodeA CodeType = iota + 10
    CodeB
)

func calculateCodes(codeType CodeType) int {
    var value int
    if codeType == CodeA {
        value = 10
    } else {
        value = 20
    }
    
    return value
}

func main() {
    fmt.Println(calculateCodes(CodeA))
}

Why is else evil?

There are a number of things that can cause an issue as code gets maintained over its lifecycle which can be dangerous:

  • We’ve already allocated memory by declaring the variable, therefore, what should its default be if no condition is met?

    • value has been declared without an underlying value - generally this could lead to introducing undefined/null in the system especially when working with pointers.
    • Nulls can lead to undefined or unexpected behaviour depending on the following conditionals or worse… panics can occur due to nil pointer dereferencing where we’ve expected a value.
  • If we decide at some point to change the value of CodeA we may now have unintentional behaviour happening in the system where we fall into the else case when we shouldn’t.

    • This could easily be missed in PR as the else case may be a long way away therefore not visible in the review window.
  • else leads to developer memory overhead

    • Generally humans can only hold 5±2 items in memory
    • An else adds +1 things to remember, so if we can remove it, we can hold more important things 😉

How do we fix this?

  • Be explicit about our expected default values
  • Use conditions as explicit conditions and/or guard statements - “if something, do this” not “If something do this, but uhh sometimes this”
  • Use shortcut returns which will help lead to flat and sparsely indented code.

What would we change to fix it?

Original Code

Just for a quick recap…

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
func calculateCodes(codeType CodeType) int {
    var value int
    if codeType == CodeA {
        value = 10
    } else {
        value = 20
    }
    
    return value
}

Use explicit defaults and conditions

1
2
3
4
5
6
7
8
func calculateCodes(codeType CodeType) int {
    value := 20
    if codeType == CodeA {
        value = 10
    }
    
    return value
}

Use explicit defaults, conditions and guarding/shortcut returns

1
2
3
4
5
6
7
func calculateCodes(codeType CodeType) int {
    if codeType == CodeA {
        return 10
    }
    
    return 20
}

Caveats

There are definitely places where we can’t get away without using an else statement for example calling expensive functions and that’s okay. In 99% of cases it should be possible to remove an else statement.

1
2
3
4
5
6
7
func calculateLateness(code CodeType, isLate bool) int64 {
	if isLate == True {
        expensiveFunc(code)
    } else {
        expensiveOtherFunc(code)
    }
}

If this path is deep within a function maybe we could return early at this point, or potentially this path could be a good candidate for being refactored out into its own function:

1
2
3
4
5
6
7
func calculateLateness(code CodeType, isLate bool) int64 {
    if isLate {
        return expensiveFunc(code)
    }
    
    return expensiveOtherFunc(code)
}

Final Thoughts

If we consider else to be mid we can save ourselves from else ladders and potential undefined/unexpected outcomes in future.

Take it easy out their developers 👌

Addendum

  • Time to blog: 2h