Jakub Jarosz

Security, Systems & Network Automation

Don't Overload Your Brain: Write Simple Go

2025-04-08 Go

We wrote a simple, initial Go package in the Organizing Your Go Code. We also emphasized the need to

Keep things as simple as possible but no simpler.

This time, we will look at the cars package and refactor it. We will see how small changes affect code readability and reduce cognitive load.

The cars package provides functions for:

  • checking if a driver needs a licence to drive a vehicle legally
  • comparing which car is better
  • calculating vehicle prices

Let’s dive in.

Evaluating expressions

First, we will answer whether a driver must have a driving licence to drive the vehicle.

The function takes a string representing a vehicle type and returns a boolean value indicating whether a driving licence is required. If we get true, it means drivers need a licence. If false, they don’t.

func NeedsLicense(kind string) bool {
    if kind == "car" || kind == "truck" {
        return true
 	}
    return false
}

Listing 1

The first boolean (logical) expression checks if the kind value is equal to car. The expression evaluates to true or false.

kind == "car"

Next, we have the same expression, but this time, we check if the kind’s value is truck.

kind == "truck"

After both expressions evaluate to true or false we check if the kind value is either car OR (||) truck.

The final expression on line 2 is evaluated as true or false.

kind == "car" || kind == "truck"

What’s the takeaway? The function can directly return the expression value as it evaluates to true or false.

This means we can simplify the entire function to one line.

func NeedsLicense(kind string) bool {
    return kind == "car" || kind == "truck"
}

Listing 2

Let’s run tests.

go test
PASS
ok  	github.com/qba73/cars	0.287s

Great. We finished the first refactoring. We simplified the function, and all tests are green.

Which car is better?

We answer the question by calling the following function.

func ChooseCar(car1, car2 string) string {
    if strings.Compare(car1, car2) < 0 {
        return car1
 	}
    return car2
}

Listing 3

Is there something wrong with it that we consider changing the function? Nothing really. And we need to remember that it’s rarely about being wrong or right. It’s always about engineering tradeoffs and knowing the costs and consequences of the decisions we make.

So, what can we do with the function? As we are curious Gophers we jump straight to the Go source code and standard library examples and read the function documentation.

// Compare returns an integer comparing two strings lexicographically.
// The result will be 0 if a == b, -1 if a < b, and +1 if a > b.
//
// Use Compare when you need to perform a three-way comparison (with
// [slices.SortFunc], for example). It is usually clearer and always faster
// to use the built-in string comparison operators ==, <, >, and so on.
func Compare(a, b string) int {
    return bytealg.CompareString(a, b)
}

Notice the sentence:

It is usually clearer and always faster to use the built-in string comparison operators ==, <, >, and so on.

As we strive to write clear and, as a consequence, fast Go code, we follow the advice and update our function. After the change, we don’t really need to think about what it’s the meaning of the something < 0 expression.

strings.Compare(car1, car2) < 0

What we read now is simple and familiar.

func ChooseCar(car1, car2 string) string {
    if car1 < car2 {
        return car1
 	}
    return car2
}

Listing 4

Was the change right? Was it necessary? As long as it works and you find it easier to read and understand it’s a good choice. Just don’t forget to run tests after each refactoring. By the way, we are good to go.

go test
PASS
ok  	github.com/qba73/cars	0.281s

Mixing ranges

What do we mean by mixing ranges? Back in a day, in one of our mathematics classes in elementary school, teachers introduced numbers, ranges and the concept of the X-Y axis.

Next, they introduced a concept of sets and various operations on them. We counted fruits: apples, oranges, pears, and vegetables: carrots, cabbage, broccoli. Next, we started doing more abstract operations on sets of numbers.

Teachers often presented sets and numbers and correlations between them on the X-axis.

So, after years of using this method we see the numbers in a linear order. We read from the left to right, and from the top to bottom. This means our brains grasp linear concepts faster as they appear to be natural.

Let’s read the function below.

func CalculateResellPrice(originalPrice, age float64) float64 {
    if age < 3 {
        return originalPrice * 80 / 100
 	} else if age >= 10 {
        return originalPrice * 50 / 100
 	} else {
        return originalPrice * 70 / 100
 	}
}

Listing 6

First, we trim unnecessary else if statements.

Notice that the statement appears after the return on line 3. This means the function exits if the age is less than 3. If it’s not less than 3 the function enters the else if branch with the condition age >= 10 anyway.

So, let’s trim the condition and leave only the necessary code.

func CalculateResellPrice(originalPrice, age float64) float64 {
    if age < 3 {
        return originalPrice * 80 / 100
 	}
    if age >= 10 {
        return originalPrice * 50 / 100
 	}
    return originalPrice * 70 / 100
}

Listing 7

We removed else, and the positive flow aligns more to the left side. It’s easier to read as our eyes don’t need to travel further to the right.

Great. We can’t forget to run our tests and see if all looks good.

go test
PASS
ok  	github.com/qba73/cars	0.297s

Now let’s focus on the ranges and if branches.

First, we check numbers that are less than 3 (A). Next, we jump to the numbers higher than 10 (B), and at the end, we leave the middle range numbers between 3 and 9 to the very end (C).

Let’s re-arrange the branches (if statements) and see what the code looks like.

func CalculateResellPrice(originalPrice, age float64) float64 {
    if age < 3 {
        return originalPrice * 80 / 100
 	}
    if age < 10 {
        return originalPrice * 70 / 100
 	}
    return originalPrice * 50 / 100
}

Listing 8

Once again it’s the right time to check whether tests are green.

go test
PASS
ok  	github.com/qba73/cars	0.317s

The number of exit points (return) stays the same, but following the logic and thinking about how data flows through the function is clearer.

We read from the top to bottom and see numbers (conditions) in the familiar, linear order.


What are the takeaways?

  • if possible, keep ranges in linear order
  • align positive flow to the left side as much as possible

There is one more important refactoring left in this package. We will change how the float64 type numbers are compared in our tests.

But this work, including an introduction to the cmp package from Google, we will do next week.


Useful links: