Avoid multiple boolean parameters

2/20/2024
4 minute read

Boolean parameters are nice, but it's hard to keep track of what each one does when you have multiple of them. In this blog post, we will see why it's better to avoid multiple boolean parameters and how to refactor them.

Why are multiple boolean parameters bad?

Let's have a look at the following function:

/// <summary>
/// Refreshes the control.
/// </summary>
/// <param name="force">Forces a refresh even if the renderer is suspended.</param>
/// <param name="lazy">Refreshes the control only if a set amount of time
/// has passed since the last refresh.</param>
void Refresh(bool force, bool lazy);

This is an example from a component library named ImageListView. The problem here is that two boolean parameters indicate four different combinations of values:

Refresh(true, true); 
Refresh(true, false);
Refresh(false, true);
Refresh(false, false);

And to some extent, you would expect 4 different behaviors. But here I have two issues.

  1. If we have a look at the implementation (see below), we can see that is totally not the case. There are only certain combinations allowed. On top of that, "force" and "lazy" seem contradictory in this context.
  2. It's hard to remember what each combination does. You could say it somewhat violates the "Single Responsibility Principle" because the function does multiple things.

Here is the code:

internal void Refresh(bool force, bool lazy)
{
    if (force)
        base.Refresh();
    else if (lazy)
    {
        rendererNeedsPaint = true;
        lazyRefreshTimer.Start();
    }
    else if (CanPaint())
        base.Refresh();
    else
        rendererNeedsPaint = true;
}

Source: ImageListView.cs

Another example I saw in many other code bases is that we have a function that is already present and some new feature has to extend/modify the behavior. For the sake of simplicity, let's say we need some additional filtering or sorting. The easiest way to do this is to add a boolean parameter:

- IReadOnlyCollection<Item> GetItems(FilterOption filter);
+ IReadOnlyCollection<Item> GetItems(FilterOption filter, bool sort);

// or "worse":
+ IReadOnlyCollection<Item> GetItems(FilterOption filter, bool sort, bool descending);

The problem here is to reason about what is going on. Again, the more parameters, the more combinations, and the harder it gets to understand the function. Especially because you might call this function throughout your whole code base, so there is maybe a controller/service/whatnot called "GetAllItems," and now you have to pass in some unrelated boolean parameters.

How to refactor multiple boolean parameters

The simplest and, for me, the most clear way is to have every one of the combinations as their own function. In that way, all variations are transparent, and you can use a more expressive name for the function. Given our example from above, we could refactor it like this:

void Refresh();
void RefreshForce();
void RefreshLazy();

Now it is clear that there is no valid invariation allowing lazy and force. With this, we push the responsibility to the caller to decide which function to call. This is good because the caller knows what he wants to do and can express it more clearly. Internally, those methods can certainly share much of the code if they have to/want to. Big plus point: If you need to diverge somewhat in the behavior of one of the methods, it is way easier to do as there is less coupling between them.

Now, here is a grain of salt: More often than not, I distinguish between a public API and private methods. For sure, I do have multiple boolean parameters in private methods. But I try to avoid them in public APIs. The reason is that the public API is the interface to the outside world and should be as clear as possible. The private methods are only used within the class and are not exposed to the outside world. So, it's not that bad to have multiple boolean parameters there.

Another approach you could take is to utilize enums and showcase all possible versions:

enum RefreshMode { Default, Force, Lazy }
void Refresh(RefreshMode mode);

While this shows all possible modes (and only the possible ones), it's not as clear as the previous approach (to me). It is still possible that your function does too much, but that is hard to generalize in a blog post. It's always a trade-off between too many parameters and too many functions. But I think it's easier to refactor multiple functions than to refactor multiple parameters.

Conclusion

Multiple boolean parameters can (as in not always, and there are absolutely cases where it is fine to have them) make your code more difficult to reason about. Oftentimes, having expressive methods is the better choice and allows refactorings more easily.

C# 12: Default Parameters in Lambdas

There is still a long road ahead of us until the release of .NET 8, but the first new language constructs are getting public. The first one I want to present is: Default Parameters in Lambdas.

Fluent API to await multiple calls and get their respective results

Sometimes, you have multiple async calls to make, and you want to do that asynchronously and get the results afterward. Let's build a fluent API to do that.

Organizing Parameters in Minimal API with the AsParametersAttribute

Even though it was introduced in .NET 7, I came across recently the AsParametersAttribute. Let's have a look what it is good for.

An error has occurred. This application may no longer respond until reloaded. Reload x