Working on Imperative Programming Code Ain't Fun

Posted on December 20, 2014 by Marko Dimjašević

Have you ever read someone else’s code that has more than 20 lines? If so, I’m sure you came to appreciate a few things about software engineering. For example, the code being documented comes in very handy. Another crucial thing is: in what ways and if at all does this and this method/function change the state of its input parameter and possibly a hidden state somewhere at lower layers?

Courtesy of Mark Skipper

For the last few months I’ve been reading and extending a few projects that form a new project for runtime verification of an air traffic control system being developed here in the NASA Ames Research Center. A strong opinion that I have developed in the process — feel free to call it frustration instead — is that imperative programming should be banned and punished for, unless there was no other way to achieve the goal the piece of code is written for. It is extremely energy and effort consuming to build a mental map of what developers and researchers that came before you had in mind when they were writing that code in the imperative style. What changes, when, and if at all are questions that needlessly exhaust you most of the time. With a few projects, together having more than 60 thousand lines of Java/AspectJ code, these things mater a lot.

To make a case, let me give you an example. Here is a simple Java class with a single Integer field:

package imperative;

public class Container {
    public Integer x;

    public Container(Integer i) {
        x = i;
    }

    @Override
    public String toString() {
        return x.toString();
    }
}

Then add a class to be used in a static way which adds a Container instance to a map, but with a side effect of modifying the instance:

package imperative;

import java.util.HashMap;
import java.util.Map;

public class StaticClass {
    public static Map<String, Container> map =
        new HashMap<String, Container>();

    public static void addContainerToMap(Container c) {
        c.x--;
        map.put(c.toString(), c);
    }
}

In the end lets add a main method that uses the Container class:

package imperative;

import java.util.ArrayList;
import java.util.List;

public class Main {

    private final List<Container> containerList;

    public Main() {
        containerList = new ArrayList<Container>();
    }

    public void addContainer(Integer i) {
        Container c = new Container(i);
        containerList.add(c);
    }

    public List<Container> getContainerList() {
        return containerList;
    }

    public static void main(String[] args) {
        Main m = new Main();
        m.addContainer(42);
        StaticClass.addContainerToMap(
            m.getContainerList().get(0));
        System.out.println("x = " +
            m.getContainerList().get(0));
        System.out.println("Container with x = 42: "
                + StaticClass.map.get("42"));
    }
}

The main method, as you can imagine, does not print out 42 as the value of the x parameter of the only Container instance in the array list. Nor it gets the expected container from the map. The output of the main method is:

x = 41
Container with x = 42: null

This is because imperative programming permits changing the state, i.e. the value of x. What if you needed that container and its original value x for later? Imagine furthermore that the static map is later used to identify your containers. Now scale this to 60+ KLOC. Sounds like a lot of fun, right? Not. It gets next to impossible to reason about such programs.

I’m far from being a good software engineer, but I can say that the code above is not well written and one should avoid writing such code. Maybe it was convenient to write it in the first place, but to read it and maintain it becomes a nightmare.

This is way I am hoping to write in a functional way in the future. For that, I’ll need to use persistent data structures. Also, I would like to do it in a JVM language that emphasizes functional programming; Scala immediately comes to mind. Such a switch asks for a steep learning curve, but I am willing to go for it because it will definitely pay out in the long run.