All posts | Code

Code Complete: Explicit and Implicit Function Parameters

Code Complete: Explicit and Implicit Function Parameters main picture

In dynamic languages, there are two main approaches when designing the input parameters of functions: the first is to use explicit, positional arguments, and the second is to pass a structure containing everything that the function expects. In real code, both explicit and implicit methods of passing arguments are equally common, and, at the same time, it’s not always clear which one should be chosen for a given function. That's what we're going to discuss.

Let's start with a simple example, a function that finds the highest integer among passed:

// Explicit parameters
const max1 = (n1, n2) => (n1 > n2 ? n1 : n2);
max1(1, 2); // 2

// Implicit parameters
const max2 = ({ n1, n2 }) => (n1 > n2 ? n1 : n2);
max2({ n1: 1, n2: 2 }); // 2

Explicit parameters make the code self-documenting: the function signature immediately indicates which arguments are expected, and the good names suggest their types. The apply method makes it much easier to call the function if the parameters are stored in an array:

// Built-in js function
Math.max(3, 2); // 3

const numbers = [3, 2];
Math.max.apply(null, numbers);
// Instead of Math.max(numbers[0], numbers[1]);

Now, let's talk about the drawbacks. First and foremost, the function is too argument-dependent. If you change the number of arguments, including adding and removing default parameters, you must then rewrite all function calls. This is especially true for high-level functions that handle a large amount of data:

// The user can have a dozen or two fields
// Some of them are optional
// All of this could change during the course of the program
// Rigid tie for the order, although in the sense of the order there is no
const user = new User(firstName, lastName, email);
user.save();

When implicit arguments are used, the situation changes. Self-documentation level decreases dramatically. It's hard to sort out what can be passed to a function without first describing or examining its contents. The definition of such functions, on the other hand, rarely changes since the number of inputs is increased automatically:

const data = { firstName, lastName, email };
// The order is no longer important
// Any data can be passed, provided the function can handle it
const user = new User(data);
user.save();

Which option is preferable in a given situation? According to the previous, explicit arguments work best with simple and low-level functions, such as mathematical ones. As a result, all functions that work with strings or numbers in any language have explicit arguments. Almost every other built-in function and method is the same.

This also applies to users’ functions with a clear, rarely changing structure and a limited number of arguments.

// A function that writes data to a file
writeFile('path/to/file', 'data');

Implicit arguments are a little more complicated. Here is when it’s better to use them:

  • When a function does not directly use a given input but instead passes it further down the call chain. At this point, explicit arguments will only get in the way because of the need to constantly edit them, although the function does not use them
  • When there is a lot of data, especially, if it can mutate frequently in the process. Or when many of these parameters are optional. That is exactly what the user example below is about. In such cases, data is frequently given as an object (an associative array), and it is more convenient to pass it all at once. In addition, this approach is commonly used for configurations:

    // http client
    import axios from 'axios'
    
    const url = 'https//ru.hexlet.io/users';
    const data =  {
      firstName: 'Fred',
      lastName: 'Flintstone',
    };
    
    // Creating a user account
    axios({ method: 'post', url, data, });
    
    // By the way, axios also has an explicit interface
    // it's used in simple cases
    axios.post(url, data);
    
  • Library functions that can work with arbitrary parameters and structures. A common situation is an ORM or a library for making requests to some system, for example, GitHub. In this case, the library developers simply cannot predict what and how the user will do, so they leave the specific content to those who will use the code:

    // Client for requests to the Github API
    import Octokit from '@octokit/rest';
    
    const octokit = new Octokit();
    
    octokit.repos
      .listForOrg({ // The method takes an object with parameters as input
        org: 'octokit',
        type: 'public',
      });
    

In some languages, such as Python or Ruby, there are special named parameters. They are explicitly stated as positional parameters in the definition, making them self-documenting. However, they can be provided in any order by specifying the parameter name when called. This method does not replace the others, but it does simplify the code in some cases.

But not all situations can be decomposed so easily. If you're not sure what to do, start with explicit arguments and only then move to implicit ones if necessary. Common sense should be your main guideline.

Additional materials:

User avatar Kirill Mokevnin
Kirill Mokevnin 01 June 2022
Suggested learning programs

From zero to a developer. Refunds in case you won't get a job

Frontend Developer icon
Profession
New
Development of front-end components for web applications
start anytime 10 months