I will use Programming With Assertions since that’s what I’m used
to right now and since OP asked if it’s a “good practice”.
Java is not C++ and in particular does not have compile-time metaprogramming
support, a feature which would make implementing different shades of assertions
something that we end-users could do. But it does have assert which achieves
the same thing.
You talk about assertion method. It does seem like the Java document is more
about simple, inlined assert statements. But the document does call out exactly
the practice that you are using: “class invariants”.
The assertion mechanism does not enforce any particular style for checking invariants. It is sometimes convenient, though, to combine the expressions that check required constraints into a single internal method that can be called by assertions. Continuing the balanced tree example, it might be appropriate to implement a private method that checked that the tree was indeed balanced as per the dictates of the data structure:
// Returns true if this tree is properly balanced
private boolean balanced() {
...
}
Here we cannot spread inlined assertion statements around unless they are very
simple. We naturally want one or more method to run every time we want to make
sure that the class invariants have not been violated. So using an “assertion
method” is is completely natural.
I have also found that I tend to gravitate towards assertion methods when I want
nice error reporting. Because I don’t want something like:
assert a.equals(b);
To fail with “false, not equal”. I want to see exactly how they are equal. And
sure I can use this syntax:
assert a.equals(b) : a.printDiff(b);
But now it feels like I have to do a two-step process each time: assert, then
recover and visualize whatever might have caused the assertion to fail.
Instead I can lean on a third-party library which can print an error about
exactly what is not equal, which leads me to use a private method:
private boolean assertSaveGetContract(...) {
assertThat(<equals check>).isTrue();
return true;
}
Here I am relying on the side effect of the third-party error, so not really
using assert per se. But I could always make a wrapper in order to make sure
that AssertionError is thrown here if assertThat fails.
Now this last thing is not really an assertion method. It’s a helper method
which is conditionally called based on whether assertions are turned on or not.
And that demonstrates how you can build up more involved state conditionally
(i.e. without paying the cost when assertions are off) in order to do more
complex or convenient checks.
Say you have to store some state for a later post-condition check.
Naturally you have to do that at the start of the method.
public void save(...) {
Long preCount = expensiveCall();
<do work>
assert assertPostCount(preCount, ...);
}
But you don’t want to have to do this needless call if assertions are not on.
The Java document has a solution for that:
public void save(...) {
Long preCount = null;
assert ((preCount = expensiveCall()) != null);
<do work>
assert assertPostCount(preCount, ...);
}
Here you rely on the side-effect of the assignment and the outer != null check
which will always succeed.
modifying_member_methodsuch thatx.modifying_member_method();changes the internal state. If that method is complex, you might want to assert that the internal state is good.