Hardly Readable - Boolean Flags and Mode Parameters
A boolean flag usually starts with good intentions: two methods differ in a single decision, so the difference becomes a parameter and one method serves both callers. Inside the method that is a fair bargain. At the call site it leaves createUser(name, true) - and every reader has to open the method to find out what true means.
This part of the Hardly Readable series shows why a flag is a decision the caller has no words for, how named methods and typed parameters give it those words back, and when a plain boolean is perfectly fine.
One method instead of two
Nobody writes an unreadable call on purpose. A boolean flag arrives as the answer to a real question: two operations are almost the same, they differ in a single decision, and copying the rest of the body to keep them apart is exactly the duplication we have been taught to remove. So the difference is lifted out into a parameter, the two bodies are merged, and one method serves both callers.
Seen from inside the method, the bargain is a good one: one body instead of two, one place to fix a bug, no drift between versions that were meant to stay in step. Seen from the outside, it reads like this:
createUser(name, true); // true for what?
render(node, false, true); // which false, which true?
true is not an argument - it is a token. The caller already knows which behaviour it wants; it simply has no way to say so. So it passes a flag that the method decodes internally, and every reader of the call has to do the decoding in reverse: open createUser, find the branch on the boolean, and map true back to a meaning.
That is the defect. A boolean flag is a deferred decision pushed into the caller. The decision about what should happen is made at the call site, but it is expressed as which branch to take - a fact about the implementation, not about the caller’s intent. The name createUser describes the method; nothing at the call site describes what true selects. The reader cannot trust the name and move on, because the name does not cover the flag.
Multiple flags compound the problem multiplicatively. render(node, false, true) asks the reader to remember two independent mappings at once and to keep them in the right order. With three flags the call site is a small puzzle whose solution lives in another file.
The fixes
The remedy is to put the meaning back at the call site, where the decision is actually made.
Named overloads. When the flag selects between a small, fixed set of behaviours, give each behaviour its own name:
createActiveUser(name);
createInactiveUser(name);
The branch is now in the name, which is exactly where a reader looks first. There is no token to decode.
A typed parameter. When the variants are genuinely a set the domain talks about, a type carries the meaning and makes illegal combinations unrepresentable:
createUser(name, Status.ACTIVE);
render(node, Layout.COMPACT, Visibility.HIDDEN);
Status.ACTIVE is self-describing at the call site; the reader needs no second location. The enum also documents the full set of options in one place and lets the compiler reject anything else - which a bare boolean (with exactly two anonymous values) never can.
When a boolean is fine
The problem is not the type boolean; it is a boolean that selects a mode of the callee. A boolean that is plain domain data - setVisible(true), account.setActive(false) - reads cleanly, because true here is the value of a named property, not a hidden switch over two behaviours. The test is the same one as everywhere in this series: does the reader trust the name and move on, or feel the pull to open the method? If true sends them looking for what it means, the flag has to go.
Conclusion
A boolean flag is not a small blemish on an otherwise readable call. It is a decision taken at the call site and written down in the vocabulary of the callee: the caller knows perfectly well that it wants an inactive user or a compact layout, and the parameter list gives it no way to say so, so it says true and leaves every later reader to look up which behaviour that selects. The method name covers everything about the call except the one thing the caller actually chose.
Both remedies are cheap, and both do the same thing - move the meaning to where the decision is. Give each behaviour its own name when there are few of them; give the choice a type when the domain already talks about it as a set. Neither requires a new abstraction; both replace a token with a word. And the question that tells you when one is needed is the usual one of this series, only asked at the call site instead of inside the method: if the reader has to open the callee to learn what true means, the flag has already cost more than the duplication it saved.
More
For related discussion and background, see:
- Code and Cognition - why “hard to read” has a measurable cognitive cost, and why a meaning that lives in another file is the expensive kind
- Source Code Is Language - the call read as a sentence, where
trueoccupies a slot the grammar expects to carry meaning and carries none - Flag Argument (Martin Fowler) - the same smell named from the call-site direction, with splitting into separate methods as the standard cure
- The Pitfalls of Boolean Trap (Ariya Hidayat) - the API-design version, collected from real libraries, including what a double negative like
setDisabled(false)does to a reader