Nitpicky Coding Advice: Use Dataflow and Types for Preconditions

July 2024

Back home

One suggestion for mitigating sequential coupling.

I'm becoming allergic to code like this:

start_service()
run_tests()

or this:

check_validity(inputs)
do_actions(inputs)

These are examples of sequential coupling, a famous anti-pattern. Sequential coupling means that two procedures have to be called in a particular order or they don't work.

While you can find a lot of text about why sequential coupling is bad, there isn't a lot of advice on how to fix it. A lot of "fixes" boil down to having procedures check their own preconditions and do their own setup. But that just kicks the can down the road, moving the sequential coupling from one place to another.

Ultimately, sequential coupling is inevitable. Some procedures simply have to be called before other ones!

A good way to mitigate sequential coupling is to use dataflow to capture the precondition in code rather than just in documentation. Dataflow refers to definitions and uses of variables in your program.

For instance, we could improve the initial examples by writing:

service = start_service()
run_tests(service)

or:

checked_inputs = check_validity(inputs)
do_actions(checked_inputs)

In both cases the program dataflow makes it obvious that the first procedure does something the second procedure needs.

Advanced extension: Using types to capture preconditions

In typed languages we can do even better:

class CheckedString {

    static void check(String input) {
        // ...
    }

    String value;

    CheckedString(String v) {
        check(v);
        this.value = v;
    }

    String toString() {
        return this.value;
    }

}

CheckedString is a class with an object invariant: its value is always one that passed the check procedure. There is no way to construct a CheckedString that has an unchecked value.

Using dataflow and types, we can force callers to do checks before calling certain methods:

void act(CheckedString input) {
    // ...
}

Making the precondition part of the dataflow and type of the procedure makes the whole program clearer and safer. It does not remove the sequential coupling, but it makes the preconditions obvious and easy to verify.

If you like this idea, you'll love dependent types.

Back home