This article is a call on how to avoid unecessary problems, when designing an API, with a specific example of a slider component for an immediate mode user interface.
But the principles here can be translated to lots of other cases.
So, I want to create a slider component for my IMGUI API. A really simple function declaration could look like that (this kind of design is seen in lots of immediate mode gui APIs such as imgui or raygui for example):
Super simple right? It’s easy to use.
What would the most naive definition for this function look like?
Probably something like the code below. We first calculate the percentage of the value between value_min and value_max to be able to draw the slider accordingly.
Are you starting to see the potential issues coming?
This code is error prone (I intentionnaly won’t mention null pointer error handling here, and in the rest of this post).
What if the user pass value_max = value_min for example? We can’t divide by zero, so it breaks.
What can we do? Easy fix, just add conditions to check for example if value_min is always lower than value_max. So we can potentially fix two problems in one (value_min = value_max and value_max < value_min).
Something like that:
Yes, we fixed the issue. But we added complexity… What if we could totally get rid of the division by zero, simply by slightly changing the API?
What if we can get rid of this check condition?
So the question to ask is: why can this kind of error occur in the first place?
Because we need to calculate the percentage of the value between the range value_min and value_max.
Could it be possible to delegate this calculation to the user?
Instead of asking for three parameters, we can directly ask for the percentage.
Okay, so pause here.
I know what you’re thinking, you’re right. We are just shifting the problem to the user, but wait a second… In fact, the user doesn’t even need to calculate the percentage at all!
If the user stores the current percentage of the slider, knows the value_min and the value_max… It’s easy to retrieve the current real value:
current_value = value_min + (percentage * value_max).
As a compromise, that means whoever wants to use this API, needs to store 3 values instead of one. I understand it could be a drawback. But the value_min and value_max information are constants anyway.
By shifting the problem, and providing examples to the user on how to use the api, what do we get in return?
– get rid of the division by zero (actually get rid of any division at all, we only have multiplications)
– simplify the API itself by demanding one parameter instead of 3 (but that means a little more code on user side)
– the API has one and only one function, which can then be used to get sliders values for whatever types you want. No more only float by default! It can be uint32_t, double, int8_t… As long as you pass and retrieve a percentage, the user can actually store whatever he wants as value_min and value_max.
This was a simple example, based on what I currently see with IMGUI sliders (from raygui, imgui…). But we can easily expand this kind of thinking to many problems.
There are always many ways to handle problems, the hardest part is not to find a way to solve it; it’s to ask yourself if there’s not a better way (it’s up to you to point what’s « better » means: code readibility, ease of use, performance…).
In term of APIs, less parameters, less declarations of functions, enum, structs… is always a win.
Provide the minimum to give the highest flexibility. And if you can, avoid « useless » problems like this one.
Simply ask yourself this kind of questions:
– « Why do I need to solve this error? »
– « Is this mandatory? Do I need to handle it? »
– « How can I make it differently? »
– « What if the computations of x values were done on user side? What would be the pros/cons? »
I’m sure, all together, we can design better APIs, with less bugs and error prone situations. We sometimes just need to think differently and take time to point out and evaluate our solutions, instead of instantly writing more code to « fix » them.