Helper Methods and Code Readability

Using Helper Methods

Helper methods, or helper functions, are smaller pieces of code that perform part of an overall task. Often, when we write helper methods, it’s because we want to factor out a piece of code that’s common across multiple higher-level methods. But, we shouldn’t be afraid to write helper methods just for the sake of improved readability in a single algorithm as well.

Why We Write Methods

Students often ask, what code should go into a method? Should it be all of the code that accomplishes a single task — for some notion of what a single task is? Should it be any piece of code that can be reused, that we can call from other modules? Many computer science textbooks start with one of these explanations of methods.

Rather than try to come up with an explanation specific to computer science, I prefer to think of methods as analogous to steps in a food recipe. A high-level method could be analogous to cooking a lasagna. One step in that process could be to make the tomato sauce. Making the sauce could be further broken down into smaller steps such as preparing the tomatoes, then adding the spices.

Breaking down a food recipe into its component steps is analogous to breaking down a large method into component helper methods. If making a lasagna is analogous to a high-level method, then preparing the tomato sauce is analogous to a helper method. Preparing the tomatoes is analogous to yet a smaller helper method used by the larger helper method.

The decomposition of methods into smaller methods is what yields helper methods.

Methods or Functions?

Because the examples in this blog post are written in Java, we’re using the term “methods” to refer to pieces of code that can be called or invoked. In other languages such as C, these pieces of code would be called “functions”. We can swap those two words, and the discussion remains the same.

A Bubble Sort Implementation

Bubble sort is an algorithm for sorting arrays, often taught in first-year programming classes. The algorithm repeatedly traverses an array, considering pairs of adjacent elements. If the two elements are in the wrong order, they’re swapped. As such, larger elements slowly bubble to the top of the array. This process is repeated until no more swapping occurs.

A classic implementation in Java looks like the following:

public void bubbleSort(int[] values)
{
    boolean swapPerformed;
    do {
        swapPerformed = false;
        for (int i = 0; i < values.length-1; i++) {
            if (values[i] > values[i+1]) {
                int temp = values[i];
                values[i] = values[i+1];
                values[i+1] = temp;
                swapPerformed = true;
            }
        }
    } while (swapPerformed);
}

The for statement iterates over the array, swapping adjacent elements that are out of order. The enclosing do/while statement repeats the iteration until the swapping stops. But, the most complex part of the code is inside the if statement, where the swap of the two elements occurs.

With so much of the code contained inside the if statement, the overall algorithm becomes difficult to read. Returning to our analogy of making a lasagna, it’s as though the steps for making the tomato sauce are just lumped into the middle of the recipe, instead of being put under a separate heading (the way we’d find the steps for making the sauce separated out in most cookbooks).

Bubble Sort With a Helper Method

If we think of methods as performing something akin to steps in a recipe, it makes sense to call swapping two elements in an array a single component step. That component step could be placed into a helper method.

But, before we worry about writing a helper method that swaps two elements in an array, let’s start by redesigning the bubbleSort(int[]) method to look the way we’d like it to look.

When writing code with helper methods, it’s best practice to start by considering how the higher-level method should read; then, to write the corresponding helpers. This approach of thinking about and designing (though not necessarily writing) the high-level method first is called “top-down” programming, and it generally produces more readable code.

A far more readable bubble sort algorithm is one that merely states we want to swap two elements, at the point in the algorithm where the swap needs to happen:

public void bubbleSort(int[] values)
{
    boolean swapPerformed;
    do {
        swapPerformed = false;
        for (int i = 0; i < values.length-1; i++) {
            if (values[i] > values[i+1]) {
                swap(values, i, i+1);
                swapPerformed = true;
            }
        }
    } while (swapPerformed);
}

This version of bubble sort is more readable, with the one component step of swapping two elements in the array reduced to one line of code. That one line of code is like one step in a lasagna recipe reading, “Next, prepare the sauce”, with the instructions for the sauce under another heading in the cookbook.

To complete this more readable implementation of bubble sort, we only have to write the swap(int[], int, int) method that swaps two elements of an array:

private void swap(int[] values, int indexA, int indexB)
{
    int temp = values[indexA];
    values[indexA] = values[indexB];
    values[indexB] = temp;
}

Revisiting the Purpose of Helper Methods

In this bubble sort example, we split the swap(int[], int, int) method off from the rest of the code in order to make the code more readable. Personally, I’d rather write, read, and maintain a bubble sort algorithm designed like this, instead of one written as a single, larger method.

Could we reuse the swap(int[], int, int) method to perform some other task? Absolutely; we could use it as a step in a selection sort, for example. But, we don’t need to reuse the method for swapping elements anywhere else — splitting off that step of the algorithm to improve readability is useful in its own right.

Summary

Often, methods (or functions) are considered to be pieces of code that perform some single useful task. But we should be careful not to make our definition of a “single useful task” so large, such as sorting an array, that our code becomes long and convoluted. Helper methods let us break down larger, more complex tasks into smaller, more readable steps.

More readable code is easier to write, read, and maintain, whether you’re a student or working as a programmer. Don’t be afraid to split off part of an algorithm into a separate method to make your code easier to understand.

For more tips, and to arrange for personalized tutoring for yourself or your study group, check out Vancouver Computer Science Tutoring.