RSS Amplifier

Arsen's Blog Posts · Nov 18, 2022

How to assert for fun and profit

0
Sign in to vote or save

aarsen.me

A hastily put together, hasty start guide to asserting
Published on Fri November 18, 2022 with tags: general-programming, basics, edurant.
Updated on Fri November 18, 2022.

Recently, I had the (dis-)pleasure of using Java. In a function, as a responsible1 developer, I made sure to verify pre and post conditions via the assert keyword. Later, a caller violated one of the preconditions and, instead of raising a clear assertion failure, the program failed with a weird error further down the function.

This was puzzling to me. The bit of code that raised an error was the exact bit of code written with the assumption documented in the assertion I wrote, so something else must’ve been wrong, since the assertion passed.

Then it hit me, a realization from long ago: The entire Java ecosystem failed to understand assert and has been avoiding it using it, so much so, that neither IntelliJ IDEA, nor better tools, pass -ea to the JVM by default during development.

I wish to clear up some of the confusion surrounding assertions, and help more people use them effectively.

Note

This post won’t have code written in any particular language, more so pseudocode written in a syntax with nice highlighting support from Pygments. There is, however, some discussion of languages, since it inevitably plays a role in how you assert.

What is an assert?

So, if the Java folk managed to get it so wrong, what is the right approach? When do you use an assert and what meaning should it portray? What are the semantics of assert !this.getChildList().isEmpty();?

Fundamentally, to assert is to specify that, at the point in code where the assert is invoked, a condition MUST hold true, no matter what, and if it doesn’t, the code is broken in some way.

Each assertion, however, must satisfy a few properties, without which, it’s most likely assert abuse:

  • Assertions must be pure; that is, assertions must not impact the state of the running program in any way. Assertions serve only to verify the state of code at a given point in execution. Failure to adhere to this rule will lead to code that behaves different depending on whether assertions are enabled, even in the happy path.

    The following program, for instance, is problematic:

    int i = 5;
    assert((i = 6) == 6);

    as the value of i will change if and only if assertions are enabled. While this particular case is fairly obvious, in the general case, there’s no solution to the problem of detecting whether an expression is pure, at least in imperative languages.

  • Assertions should be self-explanatory, in essence, if someone reading your code sees an assert, its purpose should be clear. If it’s even slightly ambiguous, add a message to your assertion (the exact way you do that depends on your language of choice).

  • Assertions may be expensive. Since assertions can be turned on or off, it’s not problematic if they incur a noticeable performance cost, though, more often than not, it’s a good idea to implement multiple types of assertions for cases where not every developer would benefit from them all being on (for an example, see --enable-checking in GCC),

  • Assertions MUST NOT be the only thing between misbehavior and input data, including data returned from other APIs (e.g. fopen calls, i.e. auto x = fopen ("foo"); assert (foo != NULL); is invalid). Anything dealing with foreign data must not assume anything about the state or contents of the data, and as such, cannot ever assert, as it doesn’t have an assertion to make. If you take away only one thing from this article, let it be this: assertions do not verify arbitrary inputs.

The pronunciation of assert

In math, it is common for an operation to not be pronounced the way it is spelled; for instance log2n is pronounced “base two logarithm of n”, or some variation of that, depending on who you ask. In computer science, a similar thing applies.

So, how is assert f(x); pronounced? Something along the lines of “The property f(x) must hold true, or else the program is malformed,” though, the pronunciation of “assert that f(x)” is more concise, if the person you’re talking to knows what it means to assert.

This pronunciation is how I want you, dear reader, to think about asserts, asserts are more effective comments, they are a checked and functional construct to state what your function is meant to do and what it expects to be able to do it, something much more powerful than a mere /* comment */.

Anticipated questions

What assertions should I write?

All the ones you feel necessary, really. Personally, I feel like it’s good practice to write down an assertion when I:

  • Enter a function, to verify arguments not verified by the type system,
  • Think that something must hold true, or
  • Implement an algorithm that does something unintuitive or ambiguous.

Picking when to assert is much like picking when to comment, except more explicit and more effective.

Why write a check that can be disabled?

Because your testsuite should be thorough enough to benefit from assertions being enabled, and end users not to (i.e, if you’re a library author, your assertions should still benefit the developers depending on your code, but not the end user that depends on their code).

Assertions can be expensive, non-asserting contract validation that is always on cannot: the former grants a very large freedom to do very rigorous verification.

I wouldn’t blame you for invoking Objects.requireNotNull on your arguments, but think about whether that’s better than an assertion for the end user. My position is that it’s not, since the user would be seeing precisely the same exception, manifesting in exactly the same way. Note that it’s not wrong to use both, as assertions can be far more powerful.

This kind of checking is, of course, more useful for languages which handle null more fatally, but I’d argue that most memory errors that are not result of all-out failure or corruption can be avoided through good use of C++, or other languages with highly expressive type systems2, in a much more effective manner than Java.

Are assertions not a tool for testing?

Yes and no, they are most useful during development and testing stages, but are not exclusive to testing, and especially not unit testing. A highly assert-dense codebase should grant you much greater confidence in your coverage, while making your code more expressive through the mere fact that you’re explicitly stating what must be the case at a given moment.


  1. Citation needed.↩︎

  2. As an aside, if you really dislike asserts, picking a language with a type system which can constrain your code to not be able to express invalid state in the first place will reduce the number of assertions you need.↩︎

Read the original on aarsen.me

Comments

Nothing yet. Say the first thing.

    Sign in to join the conversation.