17

In Clean Code, it is written that "the ideal number of arguments for a function is zero". The reasons why are explained and make sense. What I'm after is techniques to refactor methods with 4 or more arguments to solve this issue.

One way is to extract the arguments into a new class, but that would surely lead to an explosion of classes? And those classes are likely to end up with names that violate some of the naming rules (ending with "Data" or "Info" etc)?

Another technique is to make variables used by multiple functions a private member variable to avoid passing them, but that expands the scope of the variable, possibly such that it's open to functions that don't actually need it.

Just looking for ways to minimise function arguments, having accepted the reasons why it's a good idea to do so.

gnat
  • 21,442
  • 29
  • 112
  • 288
Neil Barnwell
  • 442
  • 1
  • 4
  • 9
  • 21
    Tbh I do not agree with clean code at all. If the number of arguments to a function is zero then that implies that the function has side effects and probably changes state somewhere. While I do agree that less than 4 arguments may be a good rule of thumb - I would rather have a function with 8 arguments that is static and has no side effects than a non-static function with zero arguments that changes state and has side effects. – wasatz Sep 23 '16 at 09:06
  • 4
    "*In Clean Code, it is written that "the ideal number of arguments for a function is zero".*" Really? That is so wrong! The ideal number of parameters is one, with a return value that is deterministically derived from that one parameter. In practice, the number of parameters doesn't much matter though; what matters is that, whenever possible, the function should be pure (ie, it derives its return value only from its parameters with no side effects). – David Arno Sep 23 '16 at 09:12
  • What's wrong with changing state? – Adrian Iftode Sep 23 '16 at 09:15
  • 2
    Well the book does later on go on to point out that side-effects are not desirable... – Neil Barnwell Sep 23 '16 at 09:17
  • 1
    @AdrianIftode, it makes the code harder to understand (often requiring the use of a debugger to help you understand what it is doing) and makes running parts in parallel more difficult. – David Arno Sep 23 '16 at 09:22
  • @NeilBarnwell, and how does it deal with that self-contradiction? – David Arno Sep 23 '16 at 09:23
  • 1
    I understand this, functional programming tends to have no side effects, but imperative programming is about side effects. Your whole point of view assumes that the question is related to functional programming only, but there are more paradigms that use functions. – Adrian Iftode Sep 23 '16 at 09:25
  • 1
    Under the hypothesis that Bob's statement is wrong or irrelevant to your case, do you still have a problem? If not, move on and remember that his books contains general principles that are highly biased by his own experience and environment. Sometimes they apply to yours, sometimes not. –  Sep 23 '16 at 09:36
  • His statement is relevant (it's not specific to functional programming, and does specifically mention Java while I'm on C#). I'm just coming at this from the point of view that while I don't like the alternatives, that's not justification for ignoring the advice entirely and going my own way. – Neil Barnwell Sep 23 '16 at 09:41
  • 2
    Possible duplicate of [Strategies for parameter wrapping](http://programmers.stackexchange.com/questions/270940/strategies-for-parameter-wrapping) – gnat Sep 23 '16 at 09:46
  • 2
    see also: [Are there guidelines on how many parameters a function should accept?](http://programmers.stackexchange.com/questions/145055/are-there-guidelines-on-how-many-parameters-a-function-should-accept) and [Passing compound object for parameters](http://programmers.stackexchange.com/questions/152965/passing-compound-object-for-parameters) – gnat Sep 23 '16 at 09:47
  • 1
    @DavidArno: I can see at least two ways in which a function of zero arguments does not have side-effects. #1: it's a constant function. #2: Bob Martin incorrectly uses the term "function" to mean "method", and uses the term "zero arguments" to mean "zero arguments except the implicit `this` argument". In both cases, a "function" can be perfectly referentially transparent (and in the latter case even be non-trivial). – Jörg W Mittag Sep 23 '16 at 10:33
  • @JörgWMittag, a constant function is a nonsense construct; use a constant value instead. "function" == "method". The latter is just the term for function favoured by OO folk. And a function with an implicit `this` argument is only referentially transparent if `this` is fully immutable. – David Arno Sep 23 '16 at 10:46
  • See also: http://programmers.stackexchange.com/questions/190120/should-i-use-dependency-injection-or-static-factories for reducing the number of constructor parameters. – Doc Brown Sep 23 '16 at 11:02
  • The problem with your question is that it would either require a "list-of-things" answer, or a very long-winded essay about function design. So I guess it is [too broad](http://meta.programmers.stackexchange.com/questions/6483/why-was-my-question-closed-or-down-voted/6490#6490) for this site. – Doc Brown Sep 23 '16 at 12:06
  • Just read the paragraph, and it has a preference for fewer arguments for sure, but it doesn't say there shouldn't be an method with an argument or two. The idea is that it should be easy to understand the intent of the code, and why it is called. Beyond that there can be a bunch of very valid arguments. – Berin Loritsch Jun 18 '18 at 16:17
  • @wasatz I don't think you understand the definition of a _side-effect_. You're saying that any method/function that changes any kind of state has side-effects. But that's absurd. A side-effect is when "your function promises to do one thing, but it also does other _hidden_ things" (Chapter 3: Functions - Have No Side Effects). If a method/function is expected to change state, and then proceeds to change state, then that isn't a side-effect. – Krzysztof Czelusniak Apr 09 '19 at 15:24
  • 1
    @KrzysztofCzelusniak A side effect is the modification of any state outside of a functions local env. A function that is free from side effects can modify state _as long as it is local to the function_. Expectations has nothing to do with it, even if you expect the database to be dropped the dropping of the database is still a side effect in the comp sci meaning. Clean code may write something else in chapter 3, but Clean Codes definition is more absurd since it is basically useless (it hinges on "what the programmer expects" instead of an actual provable characteristic of the code). – wasatz Apr 10 '19 at 14:20
  • 1
    @wasatz Thanks for the clarification! After Googling, it appears that you're right and that Clean Code uses a weird definition. – Krzysztof Czelusniak Apr 10 '19 at 15:32

4 Answers4

19

The most important thing to remember is that those are guidelines, not rules.

There are cases where a method simply must take an argument. Think about the + method for numbers, for example. Or the add method for a collection.

In fact, one might even argue that what it means to add two numbers is dependent on context, e.g. in ℤ 3 + 3 == 6, but in ℤ|5 3 + 3 == 2, so really the addition operator should be a method on a context object that takes two arguments instead of a method on numbers that takes one argument.

Likewise, a method for comparing two objects must either be a method of one object taking the other as an argument, or a method of the context, taking two objects as arguments, so it simply doesn't make sense to have a comparison method with less than one argument.

That said, there are a couple of things that can be done to reduce the number of arguments for a method:

  • Make the method itself smaller: Maybe, if the method needs that many arguments, it is doing too much?
  • A missing abstraction: If the arguments are closely correlated, maybe they belong together, and there is an abstraction you are missing? (Canonical text book example: instead of two coordinates, pass a Point object, or instead of passing username and email, pass an IdCard object.)
  • Object state: If the argument is needed by multiple methods, maybe it should be part of the object state. If it is needed by only some of the methods but not others, maybe the object is doing too much and should really be two objects.

One way is to extract the arguments into a new class, but that would surely lead to an explosion of classes?

If your domain model has many different kinds of things, then your code will end up with many different kinds of objects. There's nothing wrong with that.

And those classes are likely to end up with names that violate some of the naming rules (ending with "Data" or "Info" etc)?

If you cannot find a proper name, maybe you either grouped too many arguments together or too few. So, you either have just a fragment of a class or you have more than one class.

Another technique is to make variables used by multiple functions a private member variable to avoid passing them, but that expands the scope of the variable, possibly such that it's open to functions that don't actually need it.

If you have a group of methods all of which operate on the same arguments, and another group of methods that don't, maybe they belong in different classes.

Note how often I used the word "maybe"? That's why those are guidelines, not rules. Maybe your method with 4 parameters is perfectly fine!

Jörg W Mittag
  • 101,921
  • 24
  • 218
  • 318
  • 7
    @BrunoSchäpper: Sure: (1) "*Make the method itself smaller: Maybe, if the method needs that many arguments, it is doing too much?*". Number of params is a poor test of this. Optional/boolean params and lots of lines of code are strong indicators of a method doing too much. Many params is at best a weak one. (2) "*Object state: If the argument is needed by multiple methods, maybe it should be part of the object state*". No, no and thrice, no. Minimise object state; not function parameters. If possible pass a value to all methods via parameters to avoid object state. – David Arno Sep 23 '16 at 12:28
  • The example you gave for addition is flat-out wrong. The `add` function for natural numbers and the `add` function for the ring of integers mod n are two different functions actions on two different types. I don't understand what you mean by "context" either. – gardenhead Sep 23 '16 at 19:45
  • Thx @DavidArno. 1) agreed, not a strong indicator in and of itself. But a good one nonetheless. I often see a few methods, with a few objects passed arround. There is no object state. Thats good, but 2) better option IMHO is refactoring those methods, move the implicit state to a new class, which takes all these params as explicit arguments. You end up with one public zero argument method, and lots of zero-to-one argument internal methods. State is not public, global or even kept alive for long, but code is a lot cleaner. – Bruno Schäpper Sep 24 '16 at 07:51
6

Note that zero arguments doesn't imply side effects, because your object is an implicit argument. Look how many zero-arity methods Scala's immutable list has, for example.

One useful technique I call the "lens focusing" technique. When you focus a camera lens, it's easier to see the true focus point if you take it too far, then back it off to the correct point. The same is true of software refactoring.

Especially if you are using distributed version control, software changes are easy to experiment with, see if you like how they look, and back off if you don't, but for some reason people often seem reluctant to do so.

In the context of your current question, that means writing the zero or one argument versions, with several split off functions first, then it's relatively easy to see which functions need combining for readability.

Note that the author is also a huge advocate of test driven development, which tends to produce low-arity functions at the beginning because you start with your trivial test cases.

Karl Bielefeldt
  • 146,727
  • 38
  • 279
  • 479
  • 1
    Like your "lens focusing" analogy - Especially when refactoring, it's important to use the wide-angle lens instead of the close-up one. And looking into # of parameters is simply too close-up – tofro Sep 23 '16 at 19:35
1

An approach that simply (and naively - or should I even say blindly) just aims to reduce the number of arguments to functions ist probably wrong. There's absolutely nothing wrong with functions having a large number of arguments. If they are required by the logic, well they are required... A long parameter list doesn't worry me at all - as long as it is properly formatted and commented for readability.

In the case that all or a subset of arguments belong to one unique logical entity and are commonly passed around in groups throughout your program, it maybe makes sense to group them into some container - Typically a struct or other object. Typical examples might be some sort of message or event data type.

You can easily overdo that approach - once you find that packing and unpacking stuff to and from such transport containers generates more overhead than it improves readability, you've probably gone too far.

OTOH, large parameter lists can be a sign that your program may be not properly structured - maybe the function that requires such a large number of parameters is just trying to do too much and should be split into several smaller functions. I'd rather start here than worrying about # of parameters.

tofro
  • 891
  • 6
  • 10
  • 6
    Blindly reducing number of arguments is wrong, of course. But I disagree to *"There's absolutely nothing wrong with functions having a large number of arguments."*. In my opinion, when you hit a function with large number of arguments, in 99,9% of all cases there is a way to improve the code's structure in a deliberate way which (also) reduces the number of arguments of the function. – Doc Brown Sep 23 '16 at 11:59
  • @DocBrown That's why there is this last paragraph and the recommendation to start there.... And another one: You have probably never tried to program against an MS Windows API ;) – tofro Sep 23 '16 at 12:14
  • 1
    "maybe the function that requires such a large number of parameters is just trying to do too much and should be split into several smaller functions." I totally agree, although in practice don't you just end up with another function higher up the stack that calls those several smaller functions? You could then refactor them into an object, but that object will have a ctor. You could use a builder blah blah blah. Point is it's an infinite regression - somewhere, there are a number of values that are needed for the software to do it's job, and they have to get to those functions somehow. – Neil Barnwell Sep 23 '16 at 12:20
  • 1
    @NeilBarnwell In the ideal case (worth refactoring) you might be able to split a function that needs 10 arguments into three that need 3-4 arguments each. In the not-so-ideal case, you end up with three functions that need 10 arguments each (better leave it alone, then). With regards to your higher-up stack argument: Agree - Might be the case, but not necessarily - arguments come from somewhere, and that retrieval could be somewhere inside the stack as well and just needs to be put into the proper place there - Mileage tends to vary. – tofro Sep 23 '16 at 12:28
  • Software logic never requires more than four parameters. Only a compiler might. – theDoctor Jun 18 '18 at 15:34
  • @theDoctor What's the evidence for that? – Mehdi Charife Jun 26 '23 at 21:18
  • 1
    @MehdiCharife: Your universe. It hasn't needed more than 4 dimensions to accomplish everything. – theDoctor Jun 27 '23 at 22:46
1

There is no magic way: you must master the problem domain to discover the right architecture. That's the only way to refactor: mastering the problem domain. More than four parameters is just a sure bet that your current architecture is faulty and wrong.

The only exceptions are compilers (metaprograms) and simulations, where the limit is theoretically 8, but probably only 5.

theDoctor
  • 107
  • 5