Consider the following code, which might be part of a custom Font class to draw fancy text.
procedure DrawText(Text: String; Position: TRect; Color: dword);
Looks good, right? Parameters are clear, order makes sense, and it’s easy to understand what the code does when you find it invoked:
// some random stuff Font.DrawText('Hello!',Window.Position,COL_WHITE); // more random stuff
No need to go searching for the function implementation, or hovering over using the IDE to figure out what the parameters mean. It’s very clear. But now, say you want to add the ability to draw a “drop shadow” behind the text. No worries, let’s add a boolean parameter:
// Method declaration procedure DrawText( Text: String; Position: TRect; Color: dword; Shadow: boolean); // Invocation Font.DrawText('Hello!',Window.Position,COL_WHITE,true);
Hmm. It’s not terrible, and if you use DrawText a lot you will soon pick up the meaning, but a little bit of clarity has been lost. You might get smarter and make Shadow an optional parameter which defaults to false, to prevent breaking any existing code. But now… you want to add the ability for text to be horizontally and/or vertically centered within the position rect. Let’s see:
// Method declaration procedure DrawText( Text: String; Position: TRect; Color: dword; Shadow,CentreX,CentreY: boolean); // Invocation Font.DrawText('Hello!',Window.Position,COL_WHITE,true,true,false);
OK, at this point the function’s invocation is no longer readable by itself. Worse, no matter how many times you use it, you’re bound to forget the order of those parameters occasionally. I should know, the above function was ripped directly out of my old custom bitmap-font based class. But when I needed to add wordwrapping and other alignment options, it needed to go.
In general, if the meaning of a boolean parameter isn’t made totally obvious by the function name, it might not be the most readable solution. One of the golden rules of programming: it’s harder to read code than to write, so any extra effort to make it easier to read later is time well spent.
Use Sets as parameters!
It’s a little bit more setup, but you (and anyone else reading your code) will thank you if you take the time to implement a set. (if your language doesn’t support sets, constant values achieve the same result, but are very slightly less readable, and also don’t protect you against invalid values being passed).
// Type declaration TTextStyles = ( TS_DropShadow , TS_CentreX , TS_CentreY , TS_WordWrap ); TTextStyle = set of TTextStyles; // Method declaration procedure DrawText( Text: String; Position: TRect; Color: dword; Style: TTextStyle); // Invocation Font.DrawText('Hello!',Window.Position,COL_WHITE,[TS_DropShadow,TS_CentreX]);
It’s instantly obvious what the invocation does; no need to look up parameters. A bonus feature is the set of available styles can easily be extended without breaking any existing code.
The main downside is that it’s harder to generate set values inline compared to boolean functions, so if the parameters to your particular method tend to change depending on conditional evaluation, it can be a bit of a pain. But in this example, the readability benefit is worth the extra effort during declaration.