176

I've noticed a few functions I work with have 6 or more parameters, whereas in most libraries I use it is rare to find a function that takes more than 3.

Often a lot of these extra parameters are binary options to alter the function behaviour. I think that some of these umpteen-parametered functions should probably be refactored. Is there a guideline for what number is too many?

jchanger
  • 103

11 Answers11

155

I've never seen a guideline, but in my experience a function that takes more than three or four parameters indicates one of two problems:

  1. The function is doing too much. It should be split into several smaller functions, each which have a smaller parameter set.
  2. There is another object hiding in there. You may need to create another object or data structure that includes these parameters. See this article on the Parameter Object pattern for more information.

It's difficult to tell what you're looking at without more information. Chances are the refactoring you need to do is split the function into smaller functions which are called from the parent depending on those flags that are currently being passed to the function.

There are some good gains to be had by doing this:

  • It makes your code easier to read. I personally find it much easier to read a "rules list" made up of an if structure that calls a lot of methods with descriptive names than a structure that does it all in one method.
  • It's more unit testable. You've split your problem into several smaller tasks that are individually very simple. The unit test collection would then be made up of a behavioral test suite that checks the paths through the master method and a collection of smaller tests for each individual procedure.
Robert Harvey
  • 200,592
Michael K
  • 15,659
55

According to "Clean Code: A Handbook of Agile Software Craftsmanship", zero is the ideal, one or two are acceptable, three in special cases and four or more, never!

The words of the author:

The ideal number of arguments for a function is zero (niladic). Next comes one (monadic), followed closely by two (dyadic). Three arguments (triadic) should be avoided where possible. More than three (polyadic) requires very special justification—and then shouldn’t be used anyway.

In this book there is a chapter talking only about functions where parameters are large discussed, so I think this book can be a good guideline of how much parameters you need.

In my personal opinion, one parameter is better than no one because I think is more clear what is going on.

As example, in my opinion the second choice is better because is more clear what the method is processing:

LangDetector detector = new LangDetector(someText);
//lots of lines
String language = detector.detectLanguage();

vs.

LangDetector detector = new LangDetector();
//lots of lines
String language = detector.detectLanguage(someText);

About a lot of parameters, this can be a sign that some variables can be grouped into a single object or, in this case, a lot of booleans can represent that the function/method is doing more that one thing, and in this case, is better refactoring each one of these behavior in a different function.

Renato Dinhani
  • 3,075
  • 2
  • 19
  • 18
32

If the domain classes in the application are designed correctly, the number of parameters that we pass into a function will be automatically reduced - because the classes know how to do their job and they have enough data to do their work.

For example, say you have a manager class which asks a 3rd grade class to complete the assignments.

If you model correctly,

3rdGradeClass.finishHomework(int lessonId) {
    result = students.assignHomework(lessonId, dueDate);
    teacher.verifyHomeWork(result);
}

This is simple.

If you don't have the correct model, the method will be like this

Manager.finishHomework(grade, students, lessonId, teacher, ...) {
    // This is not good.
}

The correct model always reduces the function parameters between the method calls as the correct functions are delegated to their own classes (Single responsibility) and they have enough data to do their work.

Whenever I see the number of parameters increasing, I check my model to see if I designed my application model correctly.

There are some exceptions though: When I need to create a transfer object or config objects, I will use a builder pattern to produce small built objects first before constructing a big config object.

java_mouse
  • 2,657
  • 17
  • 23
18

One aspect that the other answers do not take up is performance.

If you are programming in a sufficiently low level language (C, C++, assembly) a large number of parameters can be quite detrimental to performance on some architectures, especially if the function is called a large amount of times.

When a function call is made in ARM for instance, the first four arguments are placed in registers r0 to r3 and remaining arguments have to be pushed onto the stack. Keeping the number of arguments below five can make quite a difference for critical functions.

For functions that are called extremely often, even the fact that the program has to set up the arguments before each call can affect performance (r0 to r3 may be overwritten by the called function and will have to be replaced before the next call) so in that regard zero arguments are best.

Update:

KjMag brings up the interesting topic of inlining. Inlining will in some ways mitigate this since it will allow the compiler to perform the same optimizations that you would be able to do if writing in pure assembly. In other words, the compiler can see which parameters and variables are used by the called function and can optimize register usage so that stack read/write is minimized.

There are some problems with inlining though.

  1. Inlining causes the compiled binary to grow since the same code is duplicated in binary form if it is called from multiple places. This is detrimental when it comes to I-cache usage.
  2. Compilers usually only allow inlining up to a certain level (3 steps IIRC?). Imagine calling an inlined function from an inlined function from an inlined function. Binary growth would explode if inline was treated as mandatory in all cases.
  3. There are plenty of compilers that will either completely ignore inline or actually give you errors when they encounter it.
Leo
  • 280
11

When the parameter list grows to more than five, consider defining a "context" structure or object.

This is basically just a structure that holds all the optional parameters with some sensible defaults set.

In the C procedural world a plain structure would do. In Java, C++ a simple object will suffice. Don't mess with getters or setters because the sole purpose of the object is to hold "public"ly settable values.

5

No, there is no standard guideline

But there are some techniques that can make a function with a lot of parameters more bearable.

You could use a list-if-args parameter (args*) or a dictionary-of-args parameter (kwargs**)

For instance, in python:

// Example definition
def example_function(normalParam, args*, kwargs**):
  for i in args:
    print 'args' + i + ': ' + args[i] 
  for key in kwargs:
    print 'keyword: %s: %s' % (key, kwargs[key])
  somevar = kwargs.get('somevar','found')
  missingvar = kwargs.get('somevar','missing')
  print somevar
  print missingvar

// Example usage

    example_function('normal parameter', 'args1', args2, 
                      somevar='value', missingvar='novalue')

Outputs:

args1
args2
somevar:value
someothervar:novalue
value
missing

Or you could use object literal definition syntax

For example, here's a JavaScript jQuery call to launch an AJAX GET request:

$.ajax({
  type: 'GET',
  url: 'http://someurl.com/feed',
  data: data,
  success: success(),
  error: error(),
  complete: complete(),
  dataType: 'jsonp'
});

If you take a look at jQuery's ajax class there are a lot (approximately 30) more properties that can be set; mostly because ajax communications are very complex. Fortunately, the object literal syntax makes life easy.


C# intellisense provides active documentation of parameters so it's not uncommon to see very complex arrangements of overloaded methods.

Dynamically typed languages like python/javascript have no such capability, so it's a lot more common to see keyword arguments and object literal definitions.

I prefer object literal definitions (even in C#) for managing complex methods because you can explicitly see which properties are being set when an object is instantiated. You'll have to do a little more work to handle default arguments but in the long run your code will be a lot more readable. With object literal definitions you can break your dependence on documentation to understand what your code is doing at first glance.

IMHO, overloaded methods are highly overrated.

Note: If I remember right readonly access control should work for object literal constructors in C#. They essentially work the same as setting properties in the constructor.


If you have never written any non-trivial code in a dynamically typed (python) and/or functional/prototype javaScript based language, I highly suggest trying it out. It can be an enlightening experience.

It's can be scary first to break your reliance on parameters for the end-all, be-all approach to function/method initialization but you will learn to do so much more with your code without having to add unnecessary complexity.

Update:

I probably should have provided examples to demonstrate use in a statically typed language but I'm not currently thinking in a statically typed context. Basically, I've been doing too much work in a dynamically typed context to suddenly switch back.

What I do know is object literal definition syntax is completely possible in statically typed languages (at least in C# and Java) because I have used them before. In statically typed languages they're called 'Object Initializers'. Here are some links to show their use in Java and C#.

Evan Plaice
  • 5,785
4

Personally, more than 2 is where my code smell alarm triggers. When you consider functions to be operations (that is, a translation from input to output), it is uncommon that more than 2 parameters are used in an operation. Procedures (that is a series of steps to achieve a goal) will take more inputs and are sometimes the best approach, but in most languages these days should not be the norm.

But again, that's the guideline rather than a rule. I often have functions that take more than two parameters due to unusual circumstances or ease of use.

Telastyn
  • 110,259
3

Very much like Evan Plaice is saying, I'm a huge fan of simply passing associative arrays (or your language's comparable data structure) into functions whenever possible.

Thus, instead of (for example) this:

<?php

createBlogPost('the title', 'the summary', 'the author', 'the date of publication, 'the keywords', 'the category', 'etc');

?>

Go for:

<?php

// create a hash of post data
$post_data = array(
  'title'    => 'the title',
  'summary'  => 'the summary',
  'author'   => 'the author',
  'pubdate'  => 'the publication date',
  'keywords' => 'the keywords',
  'category' => 'the category',
  'etc'      => 'etc',
);

// and pass it to the appropriate function
createBlogPost($post_data);

?>

Wordpress does a lot of stuff this way, and I think it works well. (Though my example code above is imaginary, and is not itself an example from Wordpress.)

This technique allows you to pass a lot of data into your functions easily, yet frees you from having to remember the order in which each must be passed.

You'll also appreciate this technique when comes time to refactor - instead of having to potentially change the order of a function's arguments (such as when you realize you need to pass Yet Another Argument), you don't need to change your functions's parameter list at all.

Not only does this spare you from having to re-write your function definition - it spares you from having to change the order of arguments each time the function is invoked. That's a huge win.

0

A previous answer mentioned a reliable author who stated that the less parameters your functions have, the better you are doing. The answer did not explain why but the books explains it, and here are two of the most convincing reasons as why you need to adopt this philosophy and with which I personally agree with:

  • Parameters belong to a level of abstraction which is different from that of the function. This means the reader of your code will have to think about the nature and the purpose of the parameters of your functions: this thinking is "lower level" than that of the name and the purpose of their corresponding functions.

  • The second reason to have as less parameters as possible to a function is testing: for example, if you have a function with 10 parameters, think about how many combinations of parameters you have to cover all the test cases for, for instance, a unit test. Less parameters = less tests.

0

To provide some more context around the advice for the ideal number of function arguments being zero in Robert Martin's "Clean Code: A Handbook of Agile Software Craftsmanship", the author says the following as one of his points:

Arguments are hard. They take a lot of conceptual power. That's why I got rid of almost all of them from the example. Consider, for instance, the StringBuffer in the example. We could have passed it around as an argument rather than making it an instance variable, but then our readers would have had to interpret it each time they saw it. When you are reading the story told by the module, includeSetupPage() is easier to understand than includeSetupPageInto(newPageContent). The argument is at a different level of abstraction that the function name and forces you to know a detail (in other words, StringBuffer) that isn't particularly important at that point.

For his includeSetupPage() example above, here's a small snippet of his refactored "clean code" at the end of the chapter:

// *** NOTE: Commments are mine, not the author's ***
//
// Java example
public class SetupTeardownIncluder {
    private StringBuffer newPageContent;

    // [...] (skipped over 4 other instance variables and many very small functions)

    // this is the zero-argument function in the example,
    // which calls a method that eventually uses the StringBuffer instance variable
    private void includeSetupPage() throws Exception {
        include("SetUp", "-setup");
    }

    private void include(String pageName, String arg) throws Exception {
        WikiPage inheritedPage = findInheritedPage(pageName);
        if (inheritedPage != null) {
            String pagePathName = getPathNameForPage(inheritedPage);
            buildIncludeDirective(pagePathName, arg);
        }
    }

    private void buildIncludeDirective(String pagePathName, String arg) {
        newPageContent
            .append("\n!include ")
            .append(arg)
            .append(" .")
            .append(pagePathName)
            .append("\n");
    }
}

The author's "school of thought" argues for small classes, low (ideally 0) number of function arguments, and very small functions. While I also don't fully agree with him, I found it thought-provoking and I feel that the idea of zero function arguments as an ideal can be worth considering. Also, note that even his small code snippet above has non-zero argument functions as well, so I think it depends on the context.

(And as others have pointed out, he also argues that more arguments make it harder from a testing point of view. But here I mainly wanted to highlight the above example and his rationale for zero function arguments.)

sonnyb
  • 139
-1

Ideally zero. One or two are ok, three in certain cases.
Four or more is usually a bad practice.

As well as the single responibility principles that others have noted you can also think of it from testing and debugging perspectives.

If there is one parameter, knowing it's values, testing them and finding error with them is 'relatively easy as there is only one factor. As you increase the factors, the total complexity increases rapidly. For an abstract example:

Consider a 'what to wear in this weather' program. Consider what it could do with one input - temperature. As you can imagine, the results of what to wear are pretty simple based on that one factor. Now consider what the program might/could/should do if it is actually passed temperature, humidity, dewpoint, precipitation, etc. Now imagine how hard it would to debug if it gave the 'wrong' answer to something.