1. Introduction

In this quick tutorial, we’ll learn what is successive refinement in coding and why it has so much significance.

As the name suggests, successive refinement is a technique for continuously improving the code while maintaining core functionality. To put it another way, successive refinement makes sure that the code is cleaner, maintainable, and flexible.

2. Need for Successive Refinement

In “Successive Refinement” of the book “Clean Code”, Uncle Bob presents a case study of a command-line argument parser. In the beginning, the program started well which supports the boolean arguments only. Then, with the addition of two more types first String and then integer, the whole code becomes a festering pile with a sheer number of instance variables and methods.

2.1. Initial Draft

Let’s look at a code snippet from the book which only parses Boolean and String arguments right now. However, with these two types alone, the number of variables and methods that need to be created increases to 9+ and 26+, respectively.

Here’s the code snippet:

public class Args {
    private String schema;
    private String[] args;
    private boolean valid = true;
    private Set<Character> unexpectedArguments = new TreeSet<Character>();
    private Map<Character, Boolean> booleanArgs = new HashMap<Character, Boolean>();
    private Map<Character, String> stringArgs = new HashMap<Character, String>();
    private Set<Character> argsFound = new HashSet<Character>();
    private int currentArgument;
    private char errorArgumentId = '\0';
    private String errorParameter = "TILT";

    private boolean parseSchema() { ... }
    private void parseSchemaElement(String element) { ... }

    // parse the arguments
    private void parseStringSchemaElement(char elementId) { ... }
    private void parseBooleanSchemaElement(char elementId) { ... }
    
    // validate the arguments
    private boolean isStringSchemaElement(String elementTail) { ... }
    private boolean isBooleanSchemaElement(String elementTail) { ... }
    
    // get methods to return to caller
    public String getString(char arg) { ... }
    public boolean getBoolean(char arg) { ... }

    ...
}

2.2. Refactoring

People often destroy the program by making massive structural changes in the name of improvement. It’s very difficult to make the code functional again as before, after such improvements.

To avoid such a situation, our improvement process should follow incrementalism and also be backed with Test-Driven development(TDD). This will ensure the core functionality of the system during the process of improvement.

When we closely look at the above code, we can see that the process of parsing for each type consists of three phases.

In the first place, the program parses the schema element of each argument to select the relevant HashMap. Then, we parse each argument type into its true type. Lastly, each argument type needs a getXXX() method so that it could be returned as its true type.

Because of these phases, we can wrap them into their respective classes.

public class ArgumentMarshaller {
    private boolean booleanValue = false;
    public void setBoolean(boolean value) {
        booleanValue = value;
    }

    public boolean getBoolean() {
        return booleanValue;
    }
    
    private class StringArgumentMarshaller extends ArgumentMarshaller { ... }
    private class BooleanArgumentMarshaller extends ArgumentMarshaller { ... }
}

3.3. Optimized Code

Clean code involves a two-step process of initially writing code that may not be optimal, followed by refining it to improve its quality. During the coding process, it is important to focus on elements such as appropriate naming conventions, function size, and code formatting to ensure that the code is clean and maintainable.

public class Args {
    private String schema;
    private Map<Character, ArgumentMarshaller> marshallers = new HashMap<Character, ArgumentMarshaller>();
    private Set<Character> argsFound = new HashSet<Character>();
    private Iterator<String> currentArgument;
    private List<String> argsList;

    ...
}

If we compare this code snippet to the initial draft code, there’s a huge difference between them.

Writing code is more of a craft than a science. One common issue among novice programmers is that they tend to move on to the next task as soon as their code is in a working state. However, writing good code is not a one-time event and requires continuous effort and refinement.

3. Conclusion

In this article, we learned that a good piece of code does not appear by chance. Writing code that is easy to understand, maintain, and extend requires time, effort, and discipline. A strong strategy that can help us gradually raise the standard of our code is successive refinement.