Parametrized vs. parameterless functions

How to decide how much logic should go into a function, and how many functions you need.

ยท

4 min read

Parametrized vs. parameterless functions

In this post I compare two ways of writing code. The first, is about using many simple functions, the second, is about merging together some of these functions into a more complex functions and using parameters, or some internal logic, to handle the cases.

I don't think that one way is inherently better than the other, I think it depends on the circumstances and it is not about taking either case to the extreme but about finding the sweet spot. Still, I think it is important to bring these topics to the table and to document the output of the team discussion to create a public document in which the criteria for using one or another way is available for the whole team.

I do think it is important to have a clear convention among the team. It can definitely save some time and make the development experience more enjoyable. Even if it is true that usually one way can be refactored into the other, I believe there is some optimal point in terms of code readability in which the balance between file isolation, cognitive load and working memory makes it easier for developers to work on the problem at hand instead of caring about the underlying code structure.

The example here is intentionally simple, just to give you and idea of how it could look like.

Using many parameterless functions

Simple functions are cool, because it is easy to reason about the purpose in a quick glance, but if you have so many that the whole screen area is full of single line functions (considering that depending on the coding style a single line function usually takes 5 lines: leading newline, declaration and opening, function statement, closing, trailing newline) it can be very distracting because of the boilerplate.

  <context-menu>
    <option id="one" onClick="clickOption1Handler()" />
    <option id="two" onClick="clickOption2Handler()" />
    <option id="three" onClick="clickOption3Handler()" />
  </context-menu>
// emitEvent is declared somewhere

function clickOption1Handler () {
  emitEvent('option-1');
}

function clickOption2Handler () {
  emitEvent('option-2');
}

function clickOption3Handler () {
  emitEvent('option-3');
}

Using a single parametrized function

Functions with parameters and flow control logic can be more concise, but up to a point. Remember that logic grows exponentially, with the form 2^n for the number of parameters when using nested if's, that is, a single parameter will need two flows, two parameters will need four and three will produce eight different paths. So the key is to keep in mind the cognitive load for the developer, caring about problems like the curse of knowledge.

  <context-menu>
    <option id="one" onClick="clickHandler(ClickEvents.ONE)" />
    <option id="two" onClick="clickHandler(ClickEvents.TWO)" />
    <option id="three" onClick="clickHandler(ClickEvents.THREE)" />
  </context-menu>
// emitEvent is declared somewhere
const ClickEvents = {
  ONE: 'option-1',
  TWO: 'option-2',
  THREE: 'option-3',
};

function clickHandler (event) {
  emitEvent(event);
}

Thoughts

Keep functions as pure as possible

Often parameterless functions can mask non-pure functions, in the example both ways are non-pure, since we are not even aware of the side effects caused by emitEvent. Sometimes methods are really influenced by the surrounding scope state and it might seem easier to have several functions that make use of it instead of having to deal with parameters and internal logic, but on the long term it will be difficult to test and reason about.

Keep parameters to a minimum

Even if you have string variables and the need for a case switch instead of a boolean variable, it is important that you keep in mind the internal complexity of the method, if it is hard to reason about probably it is a good idea to break it apart. If you have so many parameters that you are considering passing and options object instead, definitely you need to find another way to structure the function, it is hard to believe that the internal logic needs to know about all of them, because it implies complexity.

Complexity is not always computational complexity

I already mentioned it a few times, but the main criteria for me is to keep code simple to maintain and reason about. As developers we often neglect terms from cognitive psychology in favor of mathematical terms related to performance, and we do need to care about computational complexity, it is just that it won't be the complexity with a biggest impact in most parts of the code. Nested logic can be very tricky and error prone, but the good thing is that even if it is not easy to quantify, most developers can evaluate really quick if some piece of code is readable of not, an unrelated thing is to be able to accept what we know or hide it for personal or professional reasons.


Thanks for reading, I'll be happy to read your thoughts and comments. If you found this post interesting please consider following me. I am also in Twitter.

ย