“All generalizations are false, including this one.”
— Mark Twain
In programming, little is as obscure as code that passes boolean literals to functions.
As an example, consider the following invocation of a method that sets logging options on a logger object:
|
logger.setOutputControlMode(true, true); |
What does this really mean? Even looking at the declaration of ‘setOutputControlMode’ doesn’t give a satisfying answer:
|
void Logger::setOutputControlMode(bool toStdErr, bool newline); |
You need to delve into the implementation of the ‘Logger’ class to really see the intent:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
void Logger::setOutputControlMode(bool toStdErr, bool newline) { toStdErr_ = toStdErr; newline_ = newline; } void Logger::log(const char* text) { ... if (loggingEnabled_) { logstream << text; if (newline_) { logstream << std::endl; } if (toStdErr_) { std::cerr << text; if (newline_) { std::cerr << std::endl; } } } } |
There you go! After calling ‘setOutputControlMode’ with ‘true’ as the first argument, subsequent logging will additionally go to ‘stderr’; passing ‘true’ as second argument will automatically append a newline character after every log statement.
While you could argue that the wishy-washy name of method ‘setOutputControlMode’ was badly chosen, it was probably done so to accommodate even more options in the future. I’m convinced that sooner or later another boolean parameter will pop up — ‘buffered’, to specify whether logging should be buffered or not.
How should you deal with such obviously inferior interfaces? The strategy depends on whether you own the offending code or not. Let’s start with the first case, where you’re using some 3rd party library that you can’t directly modify.
HIDING THE DIRT
First of all, make it a habit to never feed these boolean monsters with boolean literals. Don’t even do this if the method is part of a standard library, like Java’s ‘Component.setVisible(boolean b)’ method. Stand your ground and shame the original author by working around it. But how?
One easy solution is to define a couple of boolean constants in your code:
|
const bool OUTPUT_CONTROL_COPY_TO_STDERR = true; const bool OUTPUT_CONTROL_DONT_COPY_TO_STDERR = false; const bool OUTPUT_CONTROL_EMIT_NEWLINE = true; const bool OUTPUT_CONTROL_DONT_EMIT_NEWLINE = false; |
At the expense of a little bit more typing, code that calls ‘setOutputControlMode’ becomes much more expressive:
|
logger.setOutputControlMode( OUTPUT_CONTROL_COPY_TO_STDERR, OUTPUT_CONTROL_DONT_EMIT_NEWLINE); |
The only risk that’s not mitigated is that one can still confuse the order of arguments. After all, the compiler will happily accept this slip:
|
logger.setOutputControlMode( OUTPUT_CONTROL_DONT_EMIT_NEWLINE, OUTPUT_CONTROL_COPY_TO_STDERR); |
Which is, of course, not what the developer had in mind.
A safer approach is to use wrapper functions. This is particularly useful if you don’t need all flag combinations in practice. Let’s assume you always want to emit newlines at the end of every log entry and only sometimes want to enable/disable echo to ‘stderr’. All you need to do is define two straightforward helper functions in your own code base:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
namespace logger_utils { void LoggerEnableCopyToStdErr(Logger* lg) { lg->setOutputControlMode( OUTPUT_CONTROL_COPY_TO_STDERR, OUTPUT_CONTROL_EMIT_NEWLINE); } void LoggerDisableCopyToStdErr(Logger* lg) { lg->setOutputControlMode( OUTPUT_CONTROL_DONT_COPY_TO_STDERR, OUTPUT_CONTROL_EMIT_NEWLINE); } } |
Using these wrapper functions is both, highly readable and safe:
|
LoggerEnableCopyToStdErr(logger); ... LoggerDisableCopyToStdErr(logger); |
AVOIDING THE DIRT
If you can modify the source code, you have even better options. Let’s assume that a colleague of yours developed a GUI framework that has the same weakness as Java’s java.awt.Component class; that is, it provides a method which takes a boolean argument:
|
void Component::setVisible(boolean b) { ... } |
Now, there’s probably tons of code that uses this method, as ugly as it may be. In order to maintain backwards compatibility, I would designate ‘setVisible’ as deprecated and add two wrapper functions to your colleague’s class which clearly communicate their intend:
|
void Component::show() { setVisible(true); } void Component::hide() { setVisible(false); } |
If backwards compatibility is not an issue or if you’re designing a completely new method, you should do it the right way, and the right way is to use enums instead of booleans:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
enum { VISIBILITY_SHOW, VISIBILITY_HIDE } VISIBILITY; void Component::setVisibility(VISIBILITY visibility) { switch (visibility) { case VISIBILITY_SHOW: ... break; case VISIBILITY_HIDE: ... break; default: assert(false); // Invalid visibility. }; } |
In exchange for roughly one additional minute of work for the developer, everyone gets a readable interface:
|
mainWindow.setVisibility(VISIBILITY_SHOW); |
It’s a given fact of programming, that when there are two states or modes, it won’t be long until a third one pops up. No problem, if you used enum parameters from the outset:
|
enum VISIBILITY { VISIBILITY_SHOW, VISIBILITY_HIDE, VISIBILITY_TRANSPARENT, }; |
In the visibility example, we have mutually exclusive modes. In the ‘setOutputControlMode’ example, however, options can be combined. Still, enums are a pragmatic solution:
|
enum OUTPUT_CONTROL_FLAGS { OCF_COPY_TO_STDERR = (1 << 0), OCF_EMIT_NEWLINE = (1 << 1), OCF_BUFFERED = (1 << 2), }; void Logger::setOutputControlMode(OUTPUT_CONTROL_FLAGS flags); |
Notice that I added the suffix ‘FLAGS’ as a hint to the reader that these options can be freely mixed with the binary OR operator:
|
setOutputControlMode(OCF_COPY_TO_STDERR | OCF_EMIT_NEWLINE); |
This, of course, only works in plain C. A C++ compiler will complain that the integer result that operator ‘|’ yields cannot be converted back to an enum type, so in order to please your C++ compiler you have to apply a ‘static_cast’:
|
setOutputControlMode(static_cast<OUTPUT_CONTROL_FLAGS>( OCF_COPY_TO_STDERR | OCF_EMIT_NEWLINE)); |
Such casts are never pretty, but when they get the job done, they’re acceptable.
Let me bring this post to a close by driving home the following guidelines:
1. Never pass boolean literals (‘true’/’false’) to functions. Define meaningful symbolic constants instead.
2. Alternatively, consider writing wrappers around functions taking boolean arguments. Remember that the best (most readable) functions take zero arguments:
“The ideal numbers of arguments for a function is zero (niladic). Next comes one (monadic), followed closely by two (dyadic). Three arguments (triadic) should be avoided where possible. More than three (polyadic) requires very special justification ‐ and then shouldn’t be used anyway.”
— Uncle Bob (“Clean Code”)
3. If you are the author of a method that takes options, spend the effort and code dedicated, niladic setters. If that doesn’t feel right, use enum parameters. But on no account should you ever resort to boolean arguments.