Write Short Units of Code

Limit the length of code units to 15 lines of code. Do this by not writing units that are longer than 15 lines of code in the first place, or by splitting long units into multiple smaller units.

Units are the smallest groups of code that can be maintained and executed independently. In Java, units are methods or constructors. A unit is always executed as a whole. It is not possible to invoke just a few lines of a unit. Therefore, the smallest piece of code that can be reused and tested is a unit.

Motivation

The advantages of short units are that they are easy to test, easy to analyze, and easy to reuse.

How to Apply the Guideline

Following this guideline is not difficult when you know the right techniques, but it requires discipline. When writing a new unit, never let it grow beyond 15 lines of code. That means that well before you reach 15 lines of code, you need to start thinking about how to add further functionality. Does it really belong in the unit you are writing, or should it go into its own unit? When a unit grows beyond 15 lines of code despite your efforts, you need to shorten it.

Example:

When Writing a New Unit

The most basic version of the start method checks whether the game is already in progress. If so, it silently returns; otherwise, it updates inProgress, a private member to keep track of its state:

public void start() {
    if (inProgress) {
        return;
    }
    inProgress = true;
}

When Extending a Unit with New Functionality

if we add code that tells all observers the level has been lost if the player has died, and that tells all observers that the level is won if any pellets are left:

public void start() {
    if (inProgress) {
        return;
    }
    inProgress = true;
    // Update observers if player died:
    if (!isAnyPlayerAlive()) {
        for (LevelObserver o : observers) {
            o.levelLost();
        }
    }
    // Update observers if all pellets eaten:
    if (remainingPellets() == 0) {
        for (LevelObserver o : observers) {
            o.levelWon();
        }
    }
}

Adding the code to update observers made our unit grow to 16 lines of code (and 18 lines in total, including the 2 lines that contain comments). After testing the behavior of this new code, you are probably already thinking about the next functionality to implement. However, you need to refactor?

Using Refactoring Techniques to Apply the Guideline

Refactoring technique: Extract Method

public void start() {
    if (inProgress) {
        return;
    }
    inProgress = true;
    updateObservers();
}

private void updateObservers() {
    // Update observers if player died:
    if (!isAnyPlayerAlive()) {
        for (LevelObserver o : observers) {
            o.levelLost();
        }
    }
    // Update observers if all pellets eaten:
    if (remainingPellets() == 0) {
        for (LevelObserver o : observers) {
            o.levelWon();
        }
    }
}

As you can see, the unit (the method called start) that had grown to 16 lines of code is now back to 7 lines of code, well below the limit of 15 lines. A new unit (method), called updateObservers, has been added. It has 12 lines of code, which is also under the limit of 15 lines.

The new method still has two responsibilities, as indicated by the comments. We can refactor the code further, applying Extract Method two more times:

private void updateObservers() {
     updateObserversPlayerDied();
     updateObserversPelletsEaten();
}

private void updateObserversPlayerDied() {
    if (!isAnyPlayerAlive()) {
        for (LevelObserver o : observers) {
            o.levelLost();
        }
    }
}

private void updateObserversPelletsEaten() {
    if (remainingPellets() == 0) {
        for (LevelObserver o : observers) {
            o.levelWon();
        }
    }
}

There is no need for the comments anymore: they have been replaced by the names of the new methods. Using short units makes source code self-explanatory, as the names of the methods take over the role of comments.

There is a price, however: the total number of code lines has increased, from 16 to 25.

Writing maintainable code is always a trade-off between different guidelines. When splitting a unit into multiple units, you might increase the total number of code lines. However, you have decreased the length and complexity of units that need to be tested and understood. Therefore, maintainability has improved.

Refactoring technique: Replace Method with Method Object

Sometimes, you want to extract a method that does access local variables. It is always possible to pass local variables as parameters to the extracted method. However, this may lead to long parameter lists. Return values can be even more troublesome, as in Java you can return only a single value from a method. In these cases, you can use a second refactoring technique, called Replace Method with Method Object.

Example:

public Board createBoard(Square[][] grid) {
    assert grid != null;

    Board board = new Board(grid);

    int width = board.getWidth();
    int height = board.getHeight();
    for (int x = 0; x < width; x++) {
        for (int y = 0; y < height; y++) {
            Square square = grid[x][y];
            for (Direction dir : Direction.values()) {
                int dirX = (width + x + dir.getDeltaX()) % width;
                int dirY = (height + y + dir.getDeltaY()) % height;
                Square neighbour = grid[dirX][dirY];
                square.link(neighbour, dir);
            }
        }
    }

    return board;
}

If you apply the Extract Method technique, you will have to pass seven parameters to the extracted method:

public Board createBoard(Square[][] grid) {
    assert grid != null;

    Board board = new Board(grid);

    int width = board.getWidth();
    int height = board.getHeight();
    for (int x = 0; x < width; x++) {
        for (int y = 0; y < height; y++) {
            Square square = grid[x][y];
            for (Direction dir : Direction.values()) {
                setLink(square, dir, x, y, width, height, grid);
            }
        }
    }

    return board;
}

private void setLink(Square square, Direction dir, int x, int y, int width,
    int height, Square[][] grid) {
    int dirX = (width + x + dir.getDeltaX()) % width;
    int dirY = (height + y + dir.getDeltaY()) % height;
    Square neighbour = grid[dirX][dirY];
    square.link(neighbour, dir);
}

Let us try the Replace Method with Method Object technique instead.

public class BoardCreator {
    private Square[][] grid;
    private Board board;
    private int width;
    private int height;

    BoardCreator(Square[][] grid) {
        assert grid != null;
        this.grid = grid;
        this.board = new Board(grid);
        this.width = board.getWidth();
        this.height = board.getHeight();
    }

    public Board create() {
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                Square square = grid[x][y];
                for (Direction dir : Direction.values()) {
                    setLink(square, dir, x, y);
                }
            }
        }
        return this.board;
    }

    private void setLink(Square square, Direction dir, int x, int y) {
        int dirX = (width + x + dir.getDeltaX()) % width;
        int dirY = (height + y + dir.getDeltaY()) % height;
        Square neighbour = grid[dirX][dirY];
        square.link(neighbour, dir);
    }
}

Not only have we ended up only with methods shorter than 15 lines of code and avoided creating methods with long parameter lists, but the code is actually easier to read, test, and reuse.

Common Objections to Writing Short Units

Objection: Having More Units Is Bad for Performance

“Writing short units means having more units, and therefore more method calls. That will never perform.”

Indeed, theoretically, there is a performance penalty for having more units. There will be more method invocations (compared to having fewer, longer units). For each invocation, a bit of work needs to be done by the Java Virtual Machine (JVM). In practice, this is almost never a problem. In the worst case, we are talking about microseconds. Unless a unit is executed hundreds of thousands of times in a loop, the performance penalty of a method invocation is not noticeable. Also, the JVM is very good at optimizing the overhead of method invocations.

Objection: Code Is Harder to Read When Spread Out

“Code becomes harder to read when spread out over multiple units.”

Well, psychology says that is not the case. People have a working memory of about seven items, so someone who is reading a unit that is significantly longer than seven lines of code cannot process all of it. The exception is probably the original author of a piece of source code while he or she is working on it (but not a week later).

This Unit Is Impossible to Split Up

“My unit really cannot be split up.”

Sometimes, splitting a method is indeed difficult. Take, for instance, a properly formatted switch statement in Java. For each case of the switch statement, there is a line for the case itself, at least one line to do anything useful, and a line for the break statement. So, anything beyond four cases becomes very hard to fit into 15 lines of code, and a case statement cannot be split.

However, it is true that sometimes a source code statement simply cannot be split. A typical example in enterprise software is SQL query construction.

public static void printDepartmentEmployees(String department) {
    Query q = new Query();
    for (Employee e : q.addColumn("FamilyName")
        .addColumn("Initials")
        .addColumn("GivenName")
        .addColumn("AddressLine1")
        .addColumn("ZIPcode")
        .addColumn("City")
        .addTable("EMPLOYEES")
        .addWhere("EmployeeDep='" + department + "'")
        .execute()) {
        System.out.println("<div name='addressDiv'" + e.getFamilyName()
            + ", " + e.getInitials() + "<br />" + e.getAddressLine1()
            + "<br />" + e.getZipCode() + e.getCity() + "</div>");
    }
}

This example has 16 lines of code. However, there are just three statements. The second statement contains an expression that spans nine lines. Indeed, you cannot extract just this statement; neither the Extract Method nor the Replace Method with Method Object technique is applicable, at least not directly.

If you are faced with a unit that seems impossible to refactor, do not ignore it and move on to another programming task, but indeed raise the issue with your team members and team lead.

When a refactoring seems possible but doesn’t make sense, rethink the architecture of your system.

There Is No Visible Advantage in Splitting Units

“Putting code in doSomethingOne, doSomethingTwo, doSomethingThree has no benefit over putting the same code all together in one long doSomething.”

Actually, it does, provided you choose better names than doSomethingOne, doSomethingTwo, and so on. Each of the shorter units is, on its own, easier to understand than the long doSomething. More importantly, you may not even need to consider all the parts, especially since each of the method names, when chosen carefully, serves as documentation indicating what the unit of code is supposed to do. Moreover, the long doSomething typically will combine multiple tasks. That means that you can only reuse doSomething if you need the exact same combination. Most likely, you can reuse each of doSomethingOne, doSomethingTwo, and so on much more easily.

Put code in short units (at most 15 lines of code) that have carefully chosen names that describe their function.

Last updated