2019/06/12

How to assert yourself more

Assertions in software are valuable. Coupled with automated testing and continuous integration (CI), assertions help to harden software, and in doing so, provide an early warning system for breaking changes.

But different assertions address different types of requirements. For example, some assertions should only be run while testing, because they are simply too expensive to be used in production. Other assertions are actually runtime checks, so they must always run, and must not be disabled. Lastly, assertions should be easy to write, even easier to read -- and when they fail, they should provide the necessary information to help a developer track down and understand the cause of the failure.

The first thing to know about Ecstasy assertions is that they are checked at runtime, "in production". For example, if it is illegal to proceed using a negative number, then this assertion would check and prevent that condition:

assert n.sign != Negative;

Conceptually, that assertion is similar to (but simpler than) writing code like:

if (n.sign == Negative)
    {
    throw new IllegalState(
        $"Assertion failed: n.sign != Negative, n={n}");
    }


Which would throw an exception with a text message like:

Assertion failed: n.sign != Negative, n=-1

An assertion handles all of that complexity automatically on behalf of the developer. Having detailed information -- the inputs to the assertion -- show up automatically when the assertion fails is invaluable for debugging a failure after the fact -- especially when a problem is not easily reproducible!

Assertions can also be fine-tuned to throw an appropriate exception:
  • assert   throws  IllegalState
  • assert:arg   throws  IllegalArgument
  • assert:bounds   throws  OutOfBounds
  • assert:TODO   throws  UnsupportedOperation
The syntax for an assertion condition is the same syntax used in an if or while statement,  so conditional declarations and assignments are naturally supported:

// the iterator must have at least one item left
assert String s := iterator.next();


A condition isn't even necessary if the assertion is being used to indicate that somehow execution reached some place that should have been impossible:

assert;  // just use "assert;" instead of "assert False;"

In each case, the syntax is intended to clearly convey the intent of the developer, with as little excess as possible for the reader of the code to ingest.

And what if the intent of the developer is to only have the assertion execute during testing, and not when the software is running "in production"? Fortunately, Ecstasy provides a simple way to enable an assertion only when testing:

assert:test checkReferentialIntegrity();

(Or only when debugging, by using assert:debug to trigger a breakpoint when the assertion fails.)

Similarly, if something is important to verify at runtime, but only needs to be checked the first time that the code is executed, then assert:once can be used:

assert:once configLoaded;

Lastly, if an assertion needs to be occasionally tested at runtime, but is too expensive to check every time -- such as when the assertion occurs inside a performance-critical loop -- then a sampling-based assertion may be appropriate:

assert:rnd(100) checkConsistency();  // on average, 1 in 100 times

But at the end of the day, the advanced options are just that: Advanced, and optional.

Just the way that they should be.

2 comments:

  1. The value printed when an assertion fails could be sensitive information. Did you consider a method to hide certain values?

    ReplyDelete
  2. There are two solutions for sensitive applications:
    1. An option to strip debug information when moving code to production
    2. The use of a custom output for the assertion

    For example, this would include an account id in the exception details:

    assert account.id > 12345;

    But this would not:

    assert account.id > 12345 as "invalid account id";

    ReplyDelete

All comments are subject to the Ecstasy code of conduct. To reduce spam, comments on old posts are queued for review before being published.