common branching errors
I often see people forgetting to handle all possible branching scenarios properly. It comes up in all kinds of shapes in different programming languages.
The two common errors I see are
- non-exhaustive branching
- unchecked assumptions in a branch
The two problems are closely related.
non-exhaustive branching
The basic idea is that you forget to handle a special case, which often results in undefined and unwanted behavior at runtime. For example assume msg
can have one of the values val1
val2
. The obvious buggy code would be
if msg == 'val1':
something
else if msg == 'val2':
another thing
rest of code
Why is the code buggy? Because there's no catch-all else
to handle
- the distant future when
msg
is allowed to haveval3
- when someone fat-fingers the wrong value
- when unexpected input arrives from some other system
Less obvious examples usually involve multiple variables.
The problem is every programming language tries to reinvent some syntax and has multiple ways to express control logic, so your pattern recognition might not save you. You have to actively think about the possibility of this bug.
Eg with python exceptions where the programmer was lazy and explicitly disabled the catch-all case from erroring out while he was testing.
try:
code
except RuntimeError as e:
save me
except Exception as e:
pass
Eg in bash
case $msg in
val1)
something
;;
val2)
another thing
;;
*)
catch all you shall not forget
print error
exit 1
;;
Java might try to save you by erroring out when you switch on enums and you forget to add the default/return/throw statement in the end.
enum MSG {val1, val2};
...
switch(msg)
{
case val1:
return 1;
case val2:
return 2;
default:
do not forget me
}
Haskell has optional warnings on incomplete patterns with -fwarn-incomplete-patterns
but it can be off.
fn " " = " "
fn "" = "did you forget me, the empty list?"
unchecked assumptions
As a project evolves, assumptions that held in the past, no longer do.
if msg == 'val1':
something
else: # msg can only be 'val2'
another thing
but that assumption is not checked, so who knows if it's still correct?
msg
now allows a third value, so you're in trouble.
When you don't know what to do with the future, it's probably a good idea to throw an error. Better than overwriting data silently, because you were hoping something else will crash.
if msg == 'val1':
something
else if msg == 'val2':
another thing
else:
raise RuntimeError("unknown msg {}".format(msg))
Here's how to be safe with exceptions.
try:
code
except KnownError as e:
I know how to thandle this error
except Exception as e:
logger.warning('something is wrong, so better log it ASAP')
# reraise the same exception
# someone might know what to do with it
raise