Jonathan Boccara's blog

How to Design Function Parameters That Make Interfaces Easier to Use (3/3)

Published November 30, 2018 - 7 Comments

Daily C++

This is the final article in the series about function parameters. This series contains:

  • Part 1: interface-level parameters, one-parameter functions, const parameters,
  • Part 2: calling contexts, strong types, parameters order,
  • Part 3: packing parameters, processes, levels of abstraction.

To pack or not to pack?

As a general rule, functions interfaces tend to become unclear when they take too many parameters. One way to bring down the number of function parameters is to group them into bigger objects.

Consider the following example of a function that draws a polygon that has 4 sides:

void drawQuadrilateral(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4);

Even if this function has a simple role, the first glance to its interface is not a pleasant one. For this reason, the classical way to fix this is to introduce a structure that groups parameters together:

struct Point
{
    int x;
    int y;
};

void drawQuadrilateral(Point p1, Point p2, Point p3, Point p4);

And this is a relief.

But I think there is more to it than that: it’s not just about reducing the numbers of function parameters. In fact I know functions that take a lot of parameters, and I don’t think that reducing them improves readability.

For example, in the domain of finance, the model of Black and Scholes allows to compute the value of an option (such as a stock option) from a set of parameters. It is a mathematical formula that takes the following parameters related to the option to determine its value (you don’t need to understand their financial meaning to understand what follows):

  • the stock price,
  • the stock’s dividends,
  • the monetary rates,
  • the repurchase rates for that stock,
  • the time to maturity of the option,
  • the strike of the option (the promised price to buy the stock),
  • the stock’s volatilities.

And that’s just for a simple option. So here is a possible function prototype for this:

double computeOption(double price,
                     std::vector<Dividend> const& dividends,
                     std::vector<Rates> const& monetaryRates,
                     std::vector<Rates> const& repoRates,
                     double yearsToMaturity,
                     double strike,
                     double std::vector<Volatility> const& volatilities);

That may seem like a lot, but most of those parameters are not related, and I find it unnatural to bundle them in a pack. They are merely the inputs of a mathematical formula.

What if we went to the extreme and packaged them all together into one parameter, in order to make the interface super simple?

double computeOption(OptionParameters const& optionParameters);

Then this parameter becomes mute. The interface has one parameter that doesn’t reveal anything the function name didn’t already expressed. We were better off with all the parameters like before.

So what’s the rule then?

I think that making groups is a way to rise the level of abstraction of the function parameters. When the number of parameters makes the interface confusing, it’s because their level of abstraction is too low compared to the one of the function. So I suggest to group parameters until they reach the level of abstraction of the function’s interface. But don’t group them further.

Making clear interfaces a process

I once heard about a team that put in place an interesting process to rise the quality of their interfaces. Quite annoyingly, I can no longer place where this team was now. I may have read it in Code Complete. Anyway, the fact of the matter is that every interface in the codeline was supposed to be clear enough so that a developer could use it even if he or she wasn’t the one who had designed it.

Whenever someone failed to understand how to use an interface, they would call up its author. But what made this process special is that they weren’t supposed to ask for an explanation about how to use the interface. Instead, they only stated to the author what they found unclear when they tried to use it.

The author would then acknowledged this statement, and go back to rework on the interface. After the author (or maintainer) improved it to make it clearer, they would get back to the user and proposed them the new version.

If the user could now use the interface easily, then it would stop here. But if it was still unclear, then the user would explain why, and the author would go back to work and refine the interface again. Until it got crystal clear to use. And at no point would the author explain to the user how they meant the interface to be used. The interface had to tell it by itself.

I’m not saying that you should take on this process in your team. I’m not opposed to it either. But what we can learn from them is that giving an explanation about how to use a bad interface isn’t a great way to go. It will only allow someone to call it once, at once place in the code. But it won’t help the other people that use it at other times, and above all, all the readers that will read those usages multiple times.

It is a good investment to work on interfaces, and making function parameters obvious to understand is a good step towards that.

It all comes down to respecting levels of abstraction

You probably noticed that many of the practices we saw in these articles shared a core idea: adjusting the function parameters until they match the level of abstraction of the function. Like so many things in programming, it comes down to respecting levels of abstraction.

How does this apply in the context of function parameters? When you speak about the specification of your function to someone, it sounds like this: this function does X, based on Y and Z. And this description should make sense.

To respect levels of abstraction, the function should be named X, and its parameters should be named Y and Z. Another way to see this is that X answers the questions “what does the function do?”, and Y and Z answer “What is it based on?”. “What”, not “how” or anything else. This is the vocabulary we use when talking about levels of abstraction.

Consider following those guidelines when you try to fill the #1 objective for your function parameters: being obvious as to what to pass for them.

Your feedback is welcome on this series of post. I hope it has been useful to you. And if you use other techniques to write clear function parameters in your interfaces, I would love to hear about them.

Don't want to miss out ? Follow:   twitterlinkedinrss
Share this post!Facebooktwitterlinkedin

Comments are closed