Do Not Use Boolean Parameters

Mai 31, 2017 7:16 pm Veröffentlicht von

I’m absolutely serious, don’t do it. “But hey”, one may ask, “what’s wrong with boolean parameters?” That’s what I got asked recently when I did a code review. And to all of you who have the same question in mind, here is my answer.
There is only one reason to use a boolean parameter in a function interface, and that’s when it’s the only parameter – in a setter function (and even there one could argue to not use it).

Unreadable Function Calls

Let’s take a look at a first example:

 1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
#include "concat.h"
#include <string>
#include <vector>

int main ()
{
Concat concat;
std::vector<const std::string> values {"Line One", "Line Two", "Line Three"};
std::cout << concat.concatVector(values, false, true);
return 0;
}

In line 10 you can see a call to the function concatVector which seems to be used to concat all the values of a vector to a single string. But what are those 2 boolean parameters passed? To find out what they are used for you have to take a look at the declaration of that function. (Or, depending on the IDE you are using, at least hover over the function call to see the names of that parameters, while hoping the names are speaking) Here is the corresponding header file:

1
2
3
4
5
6
7
#include <vector>
class Concat {
public:
const std::string concatVector(const std::vector<const std::string> strings, const bool unixStyle, const bool endWithNewline);
private:
const std::string newLine(const bool unixStyle);
};

Okay. Now we know the first one is used to tell the function if it should use unix-style new lines to separate the strings or DOS-style new lines. The second one tells if the string should end with a newline. Took already some time, and it will take that time again when you want to understand it a week later, because you won’t remember at least the order of those parameters.
One first step towards better readability of that code would be to store the parameters first to local variables, so the reader of the code knows what they are used for.

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
#include "concat.h"
#include <string>
#include <vector>

int main ()
{
Concat concat;
std::vector<const std::string> values {"Line One", "Line Two", "Line Three"};
bool useUnixStyle = false;
bool endWithNewLine = true;
std::cout << concat.concatVector(values, useUnixStyle, endWithNewLine);
return 0;
}

Okay, now we can at least understand what the code does, without visiting the header or implementation of that function. Great.

Even better would be to make them member of the Concat class, so the user of the class is forced to explicitly spell the usage in the client code:

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
#include "concat.h"
#include <string>
#include <vector>

int main ()
{
Concat concat;
std::vector<const std::string> values {"Line One", "Line Two", "Line Three"};
concat.setUseUnixStyle(false);
concat.setEndWithNewline(true);
std::cout << concat.concatVector(values);
return 0;
}

That way, you even can specify suitable defaults for that boolean parameters without setting them in the function definition (which you also shouldn’t do, but that’s a separate topic).

 1
2
3
4
5
6
7
8
9
10
11
#include <vector>
class Concat {
public:
const std::string concatVector(const std::vector<const std::string>& strings);
void setUseUnixStyle(bool _useUnixStyle){useUnixStyle = _useUnixStyle;};
void setEndWithNewline(bool _endWithNewline){endWithNewline = _endWithNewline;};
private:
const std::string newLine();
bool useUnixStyle {false};
bool endWithNewline {false};
};

Multi-purpose functions

Often, boolean parameters are used to specify the behaviour of a function. This indicates that the function in question may serve multiple purposes, which functions should never do. Hence, often it is useful to split those functions up in separate ones, each fulfilling one purpose. This results in a cleaner interface of the functions as well as easier to understand function implementations.
Here is the current implementation of the Concat class.

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <string>
#include <sstream>
#include "concat.h"

const std::string Concat::concatVector(const std::vector<const std::string>& strings)
{
std::stringstream str;
for (std::vector<const std::string>::const_iterator it = strings.begin(); it != strings.end(); ++it) {
if (it != strings.begin())
str << newLine();
str << *it;
}
if (endWithNewline)
str << newLine();
return str.str();
}

const std::string Concat::newLine() {
if (useUnixStyle)
return "n";
else
return "rn";
}

We now split it up into two separate functions concatVectorWithNewlineEnding and concatVectorWithoutNewlineEnding (one might come up with better function names).

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <string>
#include <sstream>
#include "concat.h"

const std::string Concat::concatVector(const std::vector<const std::string>& strings)
{
if (endWithNewline)
return concatVectorWithNewlineEnding(strings);
return concatVectorWithoutNewlineEnding(strings);
}

const std::string Concat::concatVectorWithNewlineEnding(const std::vector<const std::string>& strings)
{
const auto& concatWithoutNewline = concatVectorWithoutNewlineEnding(strings);
return appendNewline(concatWithoutNewline);
}

const std::string Concat::concatVectorWithoutNewlineEnding(const std::vector<const std::string>& strings)
{
std::stringstream str;
for (std::vector<const std::string>::const_iterator it = strings.begin(); it != strings.end(); ++it) {
if (it != strings.begin())
str << newLine();
str << *it;
}
return str.str();
}

const std::string Concat::appendNewline(const std::string& string)
{
std::string str(string);
return str.append(newLine());
}

const std::string Concat::newLine() {
if (useUnixStyle)
return "n";
else
return "rn";
}

Now we have separate functions, each serving one purpose only, which play well together. Even though we generated more code now than in the beginning, it is much more readable and every function does exactly what its name promises.

Summary

Here are the main reasons not to use boolean parameters in your functions:
  1. Code which uses your function is easier to read if you find a different way than boolean parameters
  2. Boolean parameters indicate functions with more than one purpose, which you should split up whenever possible
I used a simple example to show how you can improve such code. One can imagine, the effect gets even bigger in a huge code base with long and unreadable functions (and a high amount of function parameters).
Stichwörter:

Kategorisiert in: Allgemein

Dieser Artikel wurde verfasst von Manuel Dewald