

The benefits of programming with assertions (a.k.a. assert statements)

Programming with assertions (a.k.a. assert statements) is a great idea ...

  • because they provide run-time checks of assumptions that you would have otherwise put in code comments

    • the run-time execution environment knows nothing about comments
    • comments often get out-of-date and out-of-sync with the code base
    • assert statements (that actually end up executing) can't get out-of-date, because if they do, then they will fail for legitimate cases, and you will be forced to update them
  • An assert statement simply checks a boolean condition, and does nothing if it is true but immediately terminates the program if it is false

    • e.g., assert x >= 0 for making sure the input to a square root function is non-negative
    • e.g., assert isLegalSSN(joe.getSSN()) for making sure that Joe's social security number (SSN) is legal due to some criteria defined by that function
  • An assertion allows you to express in code what you assume to always be true about data at a particular point in execution

    • and terminates execution as soon as your assumption is violated
    • since there's often no point in continuing execution if your assumptions no longer hold
  • You probably already write comments throughout your code to document non-obvious assumptions about the values of data structures,

    • so why don't you instead convert those comments into assert statements?
    • whenever possible, instead of (or in addition to) writing a comment, write an assert statement that expresses the same details in code
  • When you are writing code filled with assert statements, you can be more confident that when execution reaches a certain point, certain conditions are guaranteed to be met.

    • This can simplify your code because after the assert statement, you can avoid other conditional checks. e.g.,:
assert (p != null);
// can now use p without first checking if it's null
  • When you are debugging code filled with assert statements, failures appear earlier and closer to the locations of the errors, which make them easier to diagnose and fix.

    • The longer errors propagate before causing visible trouble, the more their form can change, and the harder they are to debug.
    • Have your program (at least during development) fail fast and early
    • An Assertion Error is the best kind of error you can hope for because you have a better chance at pinpointing its source than when you receive some other random crash.
  • assert statements serve as test code integrated directly into your implementation.

    • Whenever you execute a program containing asserts, you are providing it with real-world test cases
    • assert statements in your implementation can complement your unit/integration testing code
  • Get in the habit of writing assert statements for conditions that you think would be obviously true.

    • Bugs often appear because you didn't understand all the conditions under which a piece of code can be executed, so if you think some condition must obviously be true, then it's still very possible that in some case you haven't considered, it's actually false!

    • If you think that at a certain point in execution, x should always contain less than 15 elements, then codify it in an assert

      • e.g., assert x.length < 15
      • Just because you don't see any possible way that this condition can fail to hold doesn't mean that it's truly impossible (many bugs are obvious only after someone points out their cause to you)
      • it's very difficult for humans to reason about all possible program executions except for trivial cases
      • Also, that assert statement serves as a concise replacement for a code comment like "photo list x should always contain less than 15 photos."
  • wah wah wah, but what about those assert statements slowing down my code?

    • in development, performance probably isn't your primary concern
      • development speed and code quality often trump time performance!
    • perhaps it doesn't even slow down your program by a noticeable amount!
      • most asserts are constant-time operations (e.g., simple boolean comparison or null check), with some more complicated ones being linear-time (e.g., membership tests for elements in list)
    • if you're really paranoid, surround your assert statements with an if guarded by some global const DEBUG variable that you can switch off
  • assert statements are great for helping you to refactor and optimize your code with greater confidence that you have preserved correctness

    • A refactoring or optimization is when you re-write an already-working block of code (say, a function) to make it more maintainable (for refactoring) or to improve performance (for optimization).
    • Say that you started with a function f(x) and refactored/optimized it to a new version f2(x). You want f(x) and f2(x) to behave identically on all inputs x (at least for those that satisfy pre-conditions).
    • You can add assert(f(x) == f2(x)) to your code in the appropriate places where f() was originally called (with the appropriate variables/values substituted for x, of course).
      • Now whenever your program executes and passes that assertion, you can have increased confidence that the two versions of the function behave identically.
      • It's often difficult to convince yourself that two versions of a function work in identical ways just by manually inspecting the source code; this strategy defers the hard work to the runtime system! All you need to do is to run your program in some realistic ways to try to exercise that assertion.
    • Of course, you won't actually get the performance benefits with the assertion in place since the original (presumably slow) f() is still being called; however, for testing purposes, you should care much more about correctness than performance.
    • Once you are confident that f() and f2() behave identically on many values of x during normal program executions, you can comment out the assertion and exclusively use f2(x) everywhere rather than f(x), thus completing your refactoring.

    • as a recap for refactoring/optimizing with assertions:

        // original code snippet:
          y = f(x);

        // code while refactoring and testing:
          assert(f(x) == f2(x));
          y = f2(x);

        // code after refactoring completed:
          y = f2(x);

Related articles

Created: 2008-06-20
Last modified: 2008-11-15