13

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.

  • 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 9: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 9:12
  • 2
    Well the book does later on go on to point out that side-effects are not desirable... – Neil Barnwell Sep 23 '16 at 9:17
  • 2
    Possible duplicate of Strategies for parameter wrapping – gnat Sep 23 '16 at 9:46
  • 2
16

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!

  • 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 7: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.

  • 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
0

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.

  • 5
    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
  • "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
0

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.