DEV Community

Bartlomiej Filipek
Bartlomiej Filipek

Posted on

How to improve functions with toggle params?

On Toggle parameters

How to improve the code around functions like:

RenderGlyphs(true, false, true, false);
Enter fullscreen mode Exit fullscreen mode

It's not easy to reason about those true/false params.

We'd like not only to have more expressive code but to have safer code.

For example, what if you mix two parameters and change their order? The compiler won't help you much!

We could add comments:

RenderGlyphs(glyphs,
             /*useChache*/true, 
             /*deferred*/false, 
             /*optimize*/true, 
             /*finalRender*/false);
Enter fullscreen mode Exit fullscreen mode

And although the above code is a bit more readable, still we don't get any more safety.

So what can we do more?

Note: The article originally eppeared on my blog as: Bartek's coding blog: On Toggle Parameters.

Ideas

Here are some ideas that you can use to make such code better:

Small Enums

In theory we could write the following declarations:

enum class UseCacheFlag    { True, False };
enum class DeferredFlag    { True, False };
enum class OptimizeFlag    { True, False };
enum class FinalRenderFlag { True, False };

// and call like:
RenderGlyphs(glyphs,
             UseCacheFlag::True, 
             DeferredFlag::False, 
             OptimizeFlag::True, 
             FinalRenderFlag::False);
Enter fullscreen mode Exit fullscreen mode

Using enums is a great approach but have some disadvantages:

  • A lot of additional names required!
    • Maybe we could reuse some types, should we have some common flags defined in the project? how to organize those types?
  • Values are not directly convertible to bool, so you have to compare against Flag::True explicitly inside the function body.

The required explicit comparison was the reason Andrzej wrote his own little library that creates toggles with conversion to bool.

Initially, I though it's a disappointing that we don't have direct support from the language. But after a while, I changed my mind. The explicit comparison is not that hard to write, so maybe it would be overkill to include it in the language spec? Introducing explicit casts might even cause some problems.

Still, I am not quite happy with the need to write so many tiny enums... And since I am lazy I probably won't apply such rule to all of my code :)

Param Structure

If you have several parameters (like 4 or 5, depends on the context) why don't wrap them into a separate structure?

struct RenderGlyphsParam
{
    bool useCache;
    bool deferred;
    bool optimize;
    bool finalRender;
};
void RenderGlyphs(Glyphs &glyphs, const RenderGlyphsParam &renderParam);

// the call:
RenderGlyphs(glyphs,
             {/*useChache*/true, 
             /*deferred*/false, 
             /*optimize*/true, 
             /*finalRender*/false});
Enter fullscreen mode Exit fullscreen mode

OK... this didn't help much! You get additional code to manage, and the caller uses almost the same code.

So why could it help?

  • It moves the problem to the other place. You could apply strong types to individual members of the structure.
  • If you need to add more parameters then you can just extend the structure.
  • Especially useful if more functions can share such param structure.

BTW: you could put the glyphs variable also in the RenderGlyphsParam, this is only for example.

Eliminate

We could try to fix the syntax and use clever techniques. But what about using a simpler method? What if we provide more functions and just eliminate the parameter?

It's ok to have one or maybe two toggle parameters, but if you have more maybe it means a function tries to do too much?

In our simple example we could try:

RenderGlyphsDeferred(glyphs,
             /*useChache*/true, 
             /*optimize*/true);
RenderGlyphsForFinalRender(glyphs,
             /*useChache*/true, 
             /*optimize*/true;
Enter fullscreen mode Exit fullscreen mode

We can make the change for parameters that are mutually exclusive. In our example, deferred cannot happen together with the final run.

You might have some internal function RenderGlyphsInternal that would still take those toggle parameters (if you really cannot separate the code). But at least such internal code will be hidden from the public API. You can refactor that internal function later if possible.

So I think it's good to look at the function declaration and review if there are mutually exclusive parameters. Maybe the function is doing too much? If yes, then cut it into several smaller functions.

After writing this section I've noticed a tip from Martin Fowler on Flag Arguments. In the text he also tries to avoid toggles.

You can also read this article from Robert C. Martin's Clean Code Tip #12: Eliminate Boolean Arguments. And more in his book Clean Code: A Handbook of Agile Software Craftsmanship

What's in future C++2z?

There's a paper: Designated Initialization, P0329R0 that might go into C++20.

Basically you could use similar approach as in C99 and name arguments that you pass to a function:

copy(.from = a, .to = b);
Enter fullscreen mode Exit fullscreen mode

There's even a CLang implementation already, see this: Uniform designated initializers and arguments for C++.

Stronger Types

Using small enums or structures is a part of a more general topic of using Stronger Types. Similar problems might appear when you have several ints as parameters or strings...

You can read more about:

One example

Recently, I had a chance to apply some ideas of enum/stronger types to my code. Here's a rough outline:

// functions:
bool CreateContainer(Container *pContainer, bool *pOutWasReused);

void Process(Container *pContainer, bool bWasReused);

// usage
bool bWasReused = false;
if (!CreateContainer(&myContainer, &bWasReused))
   return false;

Process(&myContainer, bWasReused);
Enter fullscreen mode Exit fullscreen mode

Briefly: we create a container, and we process it. But the container might be reused (some pool, reusing existing object, etc., some internal logic).

I thought that it doesn't look nice. We use one output flag and then it's passed as input flag to some other function.

What's more, we pass a pointer, so some additional validation should happen. Also, the output parameters are discouraged in modern C++, so it's not good to have them anyway.

How can we do better?

Let's use enums!

enum class ContainerCreateInfo { Err, Created, Reused };
ContainerCreateInfo CreateContainer(Container *pContainer);

void Process(Container *pContainer, ContainerCreateInfo createInfo);

// usage
auto createInfo = CreateContainer(&myContainer)
if (createInfo == ContainerCreateInfo::Err);
   return false;

Process(&myContainer, createInfo);
Enter fullscreen mode Exit fullscreen mode

Isn't it better?

There are no outputs via pointer stuff here; we have a strong type for the 'toggle' parameter.

Also, if you need to pass some more information in that CreateInfo enum you can just add one more enum value and process it in proper places, the function prototypes don't have to change.

Of course in the implementation, you have to compare against enum values (not just cast to bool), but itis not difficult and even more verbose.

Summary

Toggle params are not totally wrong, and it's probably impossible to avoid them entirely. Still, it's better to review your design when you want to add third or fourth parameter in a row :)

Maybe you can reduce the number of toggles/flags and have more expressive code?

Some questions to you:

Do you try to refactor toggle parameters?
Do you use strong types in your code?

Call To Action

If you like more programming stories from me, just signup to my weekly newsletter: Bartek's coding blog newsletter
(You'll also get some C++17 bonuses)

Top comments (1)

Collapse
 
tobias_salzmann profile image
Tobias Salzmann • Edited

Great article!

In my last project, we used tagged types to further discriminate types of Strings from each other. The Implementation (in Scala) is straightforward:

trait Tagged[U]

type @@[+T, U] = T with Tagged[U]

implicit class TagWrapper[B](value: B) {
  def as[T <: B @@ _]: T = value.asInstanceOf[T]
}

We declared them with type aliases to have more human readable types. Taking your examples, that might look like this:

trait UseCacheTag
type UseCacheFlag = Boolean @@ UseCacheTag

trait DeferredTag
type DeferredFlag = Boolean @@ DeferredTag

They still can be used like values of the base type:

def getThing(useCache: UseCacheFlag, deferred: DeferredFlag) =
  if(useCache && deferred) ??? else ???

and here's how we used them with literals:

getThing(useCache = true.as[UseCacheFlag], deferred = false.as[DeferredFlag])

In case you're unfamiliar with scala, the as method is the one defined in the implicit class TagWrapper. During compile time, this gets translated into completely type safe code.

We made good experiences with tagged types, because they are very lightweight and serialize/deserialize well.
They are a good example of getting the benefit of type safety without making the code much harder to write.