Debug::Assert in X++


Update 4th of August 2014: Clarification of the AX runtime behavior when non-developers hit an assert statement.


  defect free software

“In computer programming, an assertion is a predicate (a true–false statement) placed in a program to indicate that the developer thinks that the predicate is always true at that place.”    

http://en.wikipedia.org/wiki/Assertion_(computing)

What would you rather have: A piece of source code with or without assertions? I’d definitely prefer source code with assertions – it makes the code easier to read, debug and troubleshoot.

My recommendation is to use assertions in X++ when all of the following are true:

  1. The condition can never be false if the code is correct, and
  2. The condition is not so trivial it obviously be always true, and
  3. The condition is in some sense internal to the component, and
  4. The condition can be verified without any method calls.

 

1. The condition can never be false if the code is correct

Assertions and error handling are two different things.

  • Assertions should be used to document logically impossible situations in a correctly implemented program. If the impossible occur, then something is wrong – a programming error has been discovered.
  • Error handling – as the name implies – is the implementation of what should happen when an error occurs. Error handling are valid code paths that determines how the software reacts to various erroneous conditions, like incorrect data entered by the user, to system error conditions like out-of-memory.

In a nutshell, it should be possible to write unit test for error handling – but not for assertions.

2. The condition is not so trivial it obviously be always true

Assert statements takes time to write and read – and if the condition they are asserting is obviously always true, then the assertion is pure clutter – and we are better off without it.

3. The condition is in some sense internal to the component

A failing assertion is an indication of a problem with the implementation. Something within the component – regardless of input from the outside world – is broken and needs fixing. Typically, I’d use assertions for input validation in private methods, and exceptions in public methods. Conversely, you don’t want consumers of your component to be hit by assertions – regardless of how they use your component.

4. The condition can be verified without any method calls.

Assert statements in X++ are a little special, as the X++ compiler always includes assert statements. In other languages (like C#) you can have multiple compiler targets – and typically the release build would not include the assert statements. In AX when a non-developer is hitting an assert statement, then the runtime will suppress eventual errors. I.e. in a production system assert statements have no functional impact.

Given assert statements in X++ are always evaluated, and thus degrades performance, they should be used with a bit of caution. If the condition can be verified with minimal overhead – for example that a variable has a certain value – then there is no problem. However; if the assertion requires execution of complex logic, RPC or SQL calls then it should be avoided, due to the performance impact. In cases where the performance impact is significant, but you don’t want to compromise on assertions, the assertions can be wrapped inside a call to Debug::debugMode().

“without any method calls” is just a guiding principles. Sometimes it makes sense to factor the condition into a Boolean method – for reuse or for clarity – here I would not object.

Examples

Here is an example of good use of assertion in X++:

private void markDetailRecordAsEdited(
RecId _journalControlDetailId,
RecId _draftConstraintTreeId) { Debug::assert(_journalControlDetailId != 0); Debug::assert(_draftConstraintTreeId != 0); if (! modifiedDetailRecords.exists(_journalControlDetailId)) { modifiedDetailRecords.insert(
_journalControlDetailId,
_draftConstraintTreeId); } }

 

Here is another example where Debug::debugMode() is used:

private void render()
{
    ...
    
    if (Debug::debugMode())
    {
        Debug::assert(this.hierarchyCount() > 0);
        Debug::assert(segments != null);
        Debug::assert(totalSegmentCount > 0);
    }

    ...
}

Closing remarks

I once saw a t-shirt with this print on the front: “If debugging is the process of removing bugs, then programming must be the process of putting them in”.  I wish the back had read: “Programming with assertions is one way to keep bugs out.”

Comments (3)

  1. msmfp says:

    @José: Assertions and debug code is not the same. I would consider it bad practice to leave debug instrumentation in production code. Assertions, on the other hand, is information from developer to developer, and I have no problem with assertions in production code. Consider assertions as a kind of well-structured comments.

    @skaue: An assertion does not releave you from dealing with cryptic error messages. If an assertion is false on a production system, it will be ignored, and whatever error that will lead to will surface to the user. Assertions should not be used where proper error handling logic is more appropriate.

  2. TommySkaue says:

    I see usage of assertions a lot in the Dimension*-classes (like DimensionConversionHelper). It tells me this code and logic is prone to errors given invalid state of data. This could be due to erroneous data conversion or bad cache. It also sort of tells me that perhaps these assertions needs to be there to prevent worse scenarios than having to struggle with a cryptic error message, like eventually creating invalid ledger transactions.

    You said it:

    "Assertions should be used to document logically impossible situations in a correctly implemented program."

    Are all the assertions sort of a reversed test on whether or not the current Dimensions logic is correctly implemented? 😉

  3. jaestevan says:

    Is it a BP to leave assertions or debug code in production application? I've always tried to avoid that.