/ KOTLIN, PROGRAMMING BY CONTRACT

Programming by contract on the JVM

This week, I’d like to tackle an interesting approach that I’ve rarely seen used, but is quite useful.

Design by contract, also known as contract programming, programming by contract and design-by-contract programming, is an approach for designing software. It prescribes that software designers should define formal, precise and verifiable interface specifications for software components, which extend the ordinary definition of abstract data types with preconditions, postconditions and invariants. These specifications are referred to as "contracts", in accordance with a conceptual metaphor with the conditions and obligations of business contracts.
— Wikipedia
https://en.wikipedia.org/wiki/Design_by_contract

In essence, conditions allow to fail fast. It’s no use to run code if at the end, a computation will fail because of a wrong assumption.

Let’s use the example of a transfer operation between two bank accounts. Here are some conditions:

Pre-conditions
  • The transferred amount must be positive
Invariants
  • The source bank account must have a positive balance
After the transfer
  • The source bank account balance must be equal to the initial balance minus the transfered amount
  • The target bank account balance must be equal to the initial balance plus the transfered amount

Naive implementation

One can easily implement pre- and post-conditions "manually":

public void transfer(Account source, Account target, BigDecimal amount) {
    if (amount.compareTo(BigDecimal.ZERO) <= 0) {
        throw new IllegalArgument("Amount transferred must be higher than zero (" + amount + ")";
    }
    if (source.getBalance().compareTo(BigDecimal.ZERO) <= 0) {
        throw new IllegalArgument("Source account balance must be higher than zero (" + source.getBalance() + ")";
    }
    source.transfer(target, amount);
    if (source.getBalance().compareTo(BigDecimal.ZERO) <= 0) {
        throw new IllegalState("Source account balance must be higher than zero (" + source.getBalance() + ")";
    }
    // Other post-conditions...
}

It’s quite cumbersome to write and quite hard to read.

Checking for invariants translates as checking both as a pre-condition and a post-condition

Java implementation

You might already be familiar with pre- and post-conditions via the assert keyword:

public void transfer(Account source, Account target, BigDecimal amount) {
    assert (amount.compareTo(BigDecimal.ZERO) <= 0);
    assert (source.getBalance().compareTo(BigDecimal.ZERO) <= 0);
    source.transfer(target, amount);
    assert (source.getBalance().compareTo(BigDecimal.ZERO) <= 0);
    // Other post-conditions...
}

There are several problems with the Java approach:

  1. It makes no difference between pre- and post-conditions
  2. It has to be activated through the -ea launch flag

The Oracle documentation states it explicitly:

While the assert construct is not a full-blown design-by-contract facility, it can help support an informal design-by-contract style of programming.

Alternative Java implementation

Since Java 8, the Objects class offers three methods that provide some very limited programming by contract approach:

  1. public static <T> T requireNonNull(T obj)
  2. public static <T> T requireNonNull(T obj, String message)
  3. public static <T> T requireNonNull(T obj, Supplier<String> messageSupplier)
    The Supplier argument in the last method returns the error message

All 3 methods throw NullPointerException if obj is null. More interestingly, they all return obj if it’s not. This leads to this kind of code:

public void transfer(Account source, Account target, BigDecimal amount) {
    if (requireNonNull(amount).compareTo(BigDecimal.ZERO) <= 0) {
        throw new IllegalArgument("Amount transferred must be higher than zero (" + amount + ")";
    }
    if (requireNonNull(source).getBalance().compareTo(BigDecimal.ZERO) <= 0) {
        throw new IllegalArgument("Source account balance must be higher than zero (" + source.getBalance() + ")";
    }
    source.transfer(target, amount);
    if (requireNonNull(source).getBalance().compareTo(BigDecimal.ZERO) <= 0) {
        throw new IllegalState("Source account balance must be higher than zero (" + source.getBalance() + ")";
    }
    // Other post-conditions...
}

Not only this is pretty limited, it doesn’t really improve readability, especially if you add the error message argument.

Framework-specific implementations

The Spring framework provides the Assert class that offers a lot of condition checking methods.

spring assert
Figure 1. Spring Assert

As per our own naive implementations, pre-condition checks throw an IllegalArgumentException if the condition is not met, while post-condition checks throw an IllegalStateException.

The Wikipedia page above also lists several frameworks dedicated to programming by contract:

Most of the above frameworks are based on annotations.

Pro and cons of annotations

Let’s start with the pro: annotations make conditions obvious.

On the other hand, they suffer from some drawbacks:

  • They require bytecode manipulation, either at compile time or at runtime
  • They either:
    • Are pretty limited in their scope (e.g. @Email)
    • Or delegate to an external language, which is configured as an annotation string attribute and the opposite of typesafe

Kotlin’s approach

Kotlin’s programming by contract is based on simple method calls, grouped in the Preconditions.kt file:

preconditions
Figure 2. Preconditions.kt class structure
  • require type methods implement pre-conditions and throw an IllegalArgumentException if not met
  • check type methods implement post-conditions and throw an IllegalStateException if not met

Rewriting the above snippet with Kotlin is quite straightforward:

fun transfer(source: Account, target: Account, amount: BigDecimal) {
    require(amount <= BigDecimal.ZERO)
    require(source.getBalance() <= BigDecimal.ZERO)
    source.transfer(target, amount);
    check(source.getBalance() <= BigDecimal.ZERO)
    // Other post-conditions...
}
The valid4J project listed has the same approach.

Conclusion

As it is often (always?) the case, simpler is better. By just wrapping the check and the exception throwing instructions into a method, one can readily use programming by contract concepts. While no such wrappers are available out-of-the-box in Java, valid4j and Kotlin offer them.

Nicolas Fränkel

Nicolas Fränkel

Developer Advocate with 15+ years experience consulting for many different customers, in a wide range of contexts (such as telecoms, banking, insurances, large retail and public sector). Usually working on Java/Java EE and Spring technologies, but with focused interests like Rich Internet Applications, Testing, CI/CD and DevOps. Also double as a trainer and triples as a book author.

Read More
Programming by contract on the JVM
Share this