The following is an opinion piece; it reflects my personal preferences; it demonstrates a coding construct that works well for me.
This is the 15th post in my series: Coding Without the Jargon, where I show how I use condition ladders.
Condition Ladders
The construct is not something I invented, but I like to think I came up with the name.
Check it out for yourself and try Google-ing
"condition ladders" coding
and there’s a good chance you’ll bump into one of my blog posts as the top ‘hit’.
The construct is nothing more than a non-looping loop. In C/C++ it uses do { } while(false)
;
I call this loop ‘the ladder’.
Inside the loop, you can use a break;
statement to ‘fall of the ladder’ and skip ahead to the while (false)
;.
Something like:
do {
if (! canPerformTheTask()) { // Non-error reason to fall off ladder
break;
}
if (! precondition(data)) { // Problematic reason to fall off ladder
LOG_ERROR("precondition not met");
break;
}
// Made it down the ladder without falling
retVal = doSomething(data) + doSomethingElse(data);
}
while (false);
The two if
statements are the ‘rungs’ of the ladder. If something is not right, you fall off the ladder.
By the time we reach the ‘meat’ of the code, we’re somewhat confident all is well.
I first came across the construct when I started working with the Adobe InDesign C++ SDK. The whole SDK and all code samples use this construct.
My initial reaction was ‘yikes!’.
But then I did a lot of work with the InDesign SDK, and I started to see some advantages of the construct, and now it’s part of my ‘regular’ toolkit. I like it because it works in many languages and environments.
I’ve since refined my approach a little bit more; I’ll get to that further down.
Advantages
For me, the advantages of the construct are:
Avoid Line Wrapping
I can ‘flatten’ nested if
statements.
If I have a deeply nested if
I will rewrite it to use the condition ladder, which tends to shift back the code back to the left.
Since I’ve started using the condition ladder, I can write all my code within lines of no more than 80 or 132 characters.
It takes a bit of refactoring, and ‘flipping’ of conditions, but once I am done, my code will generally be an almost linear sequence of rungs down a condition ladder.
Simple example.
if (something) {
if (somethingElse) {
doSomething();
if (somethingMore) {
doSomethingMore();
}
}
}
vs.
do {
if (! something) {
break;
}
if (! somethingElse) {
break;
}
doSomething();
if (! somethingMore) {
break;
}
doSomethingMore();
}
while (false);
In the above example, the condition ladder is only nested two levels, whereas the original is nested 3 levels.
Furthermore, I have more fine control of where I want to break into the debugger.
In the first bit of code, it would be a bit more cumbersome to set a break when ! something
.
With the condition ladder, I can put a simple breakpoint on the break;
Single Return
I prefer that my methods and functions only have a single return
at the end of the function.
That way I can confidently set a breakpoint on that line, and know that the execution will ‘hit’ that line and I can inspect the return value.
To ascertain that, I tend to combine the condition ladder with a nested try/catch
to make sure the code cannot escape my debug session when something throws.
There are arguments for and against that; my approach is that exceptions should be exceptional, and if my code throws it’s because of something I did not anticipate, and things are really bad. See Exceptional!
A method might look similar to this:
int someFunction(const someType& someParam) {
int retVal = 0;
LOG_ENTRY();
do {
try {
... condition ladder rungs ...
... calculate sumethin' ...
retVal = theResult;
}
catch (std::exception& e) {
LOG_ERROR_WITH_DATA("throws %s", e.what());
retVal = BAD_RETURN_VALUE;
}
}
while (false);
LOG_EXIT();
return retVal;
}
That return retVal
is the only place where the method exits, so I can confidently put a breakpoint there and know I’ll hit it.
Inspect Intermediate Results
I also like condition ladders because I find it easier to help debugging and readability by naming intermediate results.
So instead of
if (something() && (somethingElse() * A_CONSTANT) > SOME_LIMIT) {
...
}
I’ll tend to write
auto someThingResult = something();
auto someValue = somethingElse() * A_CONSTANT;
if (someThingResult && someValue > SOME_LIMIT) {
...
}
In a debug session, I can now easily inspect the ingredients before performing the if
.
I find that introducing such intermediates is easier when there is a condition ladder.
And in practice, a good compiler or interpreter will generate the same or nearly the same production code, and this is often ‘speed-penalty-free’.
Alternatives
There are alternatives to condition ladders.
One of them is ‘early return’. The code is not wrapped in an unsightly do { } while(false)
; and instead of using break
; to fall off the ladder, you use return
; to return early.
I am not a fan, but that does not mean it’s a bad approach.
I don’t like it because it makes my debug sessions more frustrating.
Also, I occasionally want to tweak the return signature of my methods, and if there are multiple returns, they might all need tweaking.
Another alternative is nesting your if
statements, but I often get frustrated when I accidentally get my { and } in a twist.
With a condition ladder, I can avoid deeply nested {}.
Tricklets
As time goes, I often try out little improvements. They’re not real, full blown ‘tricks’, so I jokingly refer to them as ‘tricklets’ (which actually a real word, but with a different meaning).
CONDITION_LADDER_EXIT
One tricklet is to define a constant whose value is false
. This constant has a name like CONDITION_LADDER_EXIT
.
That way I can write
do {
...
}
while (CONDITION_LADDER_EXIT);
and I can then add documentation to a comment near the definition of CONDITION_LADDER_EXIT
.
That way, I don’t have to add any comments explaining what a condition ladder is.
Whoever reads the code will spot CONDITION_LADDER_EXIT
, go ‘huh?’, right-click it to view the definition of the constant, and bingo! there’s the explanation.
Macros
In C++, I make extensive use of macros to visually simplify condition ladders.
These macros condense recurring patterns in my code and make them one-liners rather than ‘fluff up’ the code with boring, repetitive stuff.
I love macros, because I can easily switch between multiple ‘versions’ of macro-definitions: in debug versions, there is extensive checking and logging, in production versions, pure debug code can be stripped.
A made-up non-functional code extract (I added some fake lines to serve as examples).
IOMNodePtr EvalScriptTask::call(
const OMStoredPtr& wrapped_this,
const OMScopePtr& scope,
const std::string& scriptFilePath,
HandleReturnValue handleReturnValue)
{
IOMNodePtr retVal;
BEGIN_FUNCTION;
PRE_CONDITION(scriptFilePath.length() > 0, FUNCTION_BREAK);
TaskPtr task =
factory(
wrapped_this,
scope,
scriptFilePath,
handleReturnValue);
SANITY_CHECK(task, FUNCTION_BREAK);
EvalScriptTask* evalScript = dynamic_cast<EvalScriptTask*>(task.get());
SANITY_CHECK(evalScript, FUNCTION_BREAK);
SANITY_CHECK(
evalScript->runToCompletion(),
FUNCTION_BREAK);
retVal = evalScript->fRetValNode;
END_FUNCTION;
return retVal;
}
The BEGIN_FUNCTION/END_FUNCTION
macros combine logging of entry and exit into method, and also contain an implicit condition ladder.
Then, macros like SANITY_CHECK
, PRE_CONDITION
combine the rungs of the ladder.
PRE_CONDITION
is used when something must be true before we can perform the function, but it is not an error if it is not true. If it is not true, we simply bail out and fall off the ladder.
is used when something must be true before we can perform the function, and it is an error if it is not true. If it is not true, we log the problem, then bail out and fall off the ladder.SANITY_CHECK
This makes the code less ‘wordy’.
Note that macros like SANITY_CHECK
are not allowed to be suppressed completely (e.g. for a compact production version) if the code called within the macro invocation has side effects.
I resolve this with a variant, OPTIONAL_SANITY_CHECK
, which I will only use on code that has no side-effects, so the whole OPTIONAL_SANITY_CHECK(...)
can ‘poof!’ away without problems.
FUNCTION_BREAK
kind of boils down to a break;
but there is more to it. A normal break
would not be able to reach the bottom of the function if it is inside, say, a while
loop. The FUNCTION_BREAK
macro has provisions so it bails out to the bottom of the function even when it is nested inside another construct (while/for/switch...
).
Next
If you’re interested in getting help with automating some part of a Creative Cloud-based workflow, please reach out to [email protected] . We provide developer training, we can create custom scripts, plugins and plug-ins, large and small, to speed up and take the dread out of repetitive tasks…
If you find this post to be helpful, make sure to give me a positive reaction on LinkedIn! I don’t use any other social media platforms. My LinkedIn account is here: