Best practice for passing many arguments to method?
Asked Answered
S

18

122

Occasionally , we have to write methods that receive many many arguments , for example :

public void doSomething(Object objA , Object objectB ,Date date1 ,Date date2 ,String str1 ,String str2 )
{
}

When I encounter this kind of problem , I often encapsulate arguments into a map.

Map<Object,Object> params = new HashMap<Object,Object>();
params.put("objA",ObjA) ;

......

public void doSomething(Map<Object,Object> params)
{
 // extracting params 
 Object objA = (Object)params.get("objA");
 ......
 }

This is not a good practice , encapsulate params into a map is totally a waste of efficiency. The good thing is , the clean signature , easy to add other params with fewest modification . what's the best practice for this kind of problem ?

Swaraj answered 12/3, 2010 at 11:51 Comment(1)
Similar question in c# #440074Auntie
B
184

In Effective Java, Chapter 7 (Methods), Item 40 (Design method signatures carefully), Bloch writes:

There are three techniques for shortening overly long parameter lists:

  • break the method into multiple methods, each which require only a subset of the parameters
  • create helper classes to hold group of parameters (typically static member classes)
  • adapt the Builder pattern from object construction to method invocation.

For more details, I encourage you to buy the book, it's really worth it.

Bevin answered 12/3, 2010 at 12:8 Comment(9)
What is "overly long parameters"? When can we say that a method has too many parameters? Is there a specific number or a range?Lightman
@RedM I've always considered anything more than 3 or 4 parameters to be "overly long"Adame
@Adame is that a personal choice or are you following an official doc?Lightman
@RedM personal preference :)Adame
@RedM According to Rober Martin in his book Clean Code, methods should be Monadic (only one parameter) since more than one parameter would be hard to understand. He also notes that if you need to pass more than one parameter its best to make a wrapper class and pass that object.Capstan
With third edition of Effective Java, this is chapter 8 (methods), item 51Astrakhan
Perhaps this post might also help to illustrate in case of final attributes approach .Coarsegrained
Hello, if I need to write a method for an interface what is the best practice to define a Parameters helper class. For instance a generic type: void <T> Foo(T params) or perhaps a better practice is to create a class Parameter and use void <T extends Parameter> Foo(T params) ?Coarsegrained
breaking the method often does not help, because while you are not using the parameters in the main method - you still need to pass them down the call tree into your smaller methods which do use them.Laxative
H
80

Using a map with magical String keys is a bad idea. You lose any compile time checking, and it's really unclear what the required parameters are. You'd need to write very complete documentation to make up for it. Will you remember in a few weeks what those Strings are without looking at the code? What if you made a typo? Use the wrong type? You won't find out until you run the code.

Instead use a model. Make a class which will be a container for all those parameters. That way you keep the type safety of Java. You can also pass that object around to other methods, put it in collections, etc.

Of course if the set of parameters isn't used elsewhere or passed around, a dedicated model may be overkill. There's a balance to be struck, so use common sense.

Helaine answered 12/3, 2010 at 12:1 Comment(0)
G
27

If you have many optional parameters you can create fluent API: replace single method with the chain of methods

exportWithParams().datesBetween(date1,date2)
                  .format("xml")
                  .columns("id","name","phone")
                  .table("angry_robots")
                  .invoke();

Using static import you can create inner fluent APIs:

... .datesBetween(from(date1).to(date2)) ...
Glandule answered 13/3, 2010 at 19:51 Comment(2)
What if every parameters are required, not optional?Neoptolemus
You can also have default parameters this way. Also, the builder pattern is related to fluent interfaces. This should really be the answer, I think. Apart from breaking down a long constructor into smaller initialization methods which are optional.Pledge
I
16

It's called "Introduce Parameter Object". If you find yourself passing same parameter list on several places, just create a class which holds them all.

XXXParameter param = new XXXParameter(objA, objB, date1, date2, str1, str2);
// ...
doSomething(param);

Even if you don't find yourself passing same parameter list so often, that easy refactoring will still improve your code readability, which is always good. If you look at your code 3 months later, it will be easier to comprehend when you need to fix a bug or add a feature.

It's a general philosophy of course, and since you haven't provided any details, I cannot give you more detailed advice either. :-)

Insolvency answered 12/3, 2010 at 12:7 Comment(3)
will the garbage collection be a problem?Muckworm
Not if you make the parameter object local scope in the caller function, and if you don't mutate it. It'll most likely be collected and its memory reused pretty quickly under such circumstances.Insolvency
Imo, you should also have an XXXParameter param = new XXXParameter(); available, and then use XXXParameter.setObjA(objA); etc...Olympic
O
10

First, I'd try to refactor the method. If it's using that many parameters it may be too long any way. Breaking it down would both improve the code and potentially reduce the number of parameters to each method. You might also be able to refactor the entire operation to its own class. Second, I'd look for other instances where I'm using the same (or superset) of the same parameter list. If you have multiple instances, then it likely signals that these properties belong together. In that case, create a class to hold the parameters and use it. Lastly, I'd evaluate whether the number of parameters makes it worth creating a map object to improve code readability. I think this is a personal call -- there is pain each way with this solution and where the trade-off point is may differ. For six parameters I probably wouldn't do it. For 10 I probably would (if none of the other methods worked first).

Outskirts answered 12/3, 2010 at 12:7 Comment(0)
M
9

This is often a problem when constructing objects.

In that case use builder object pattern, it works well if you have big list of parameters and not always need all of them.

You can also adapt it to method invocation.

It also increases readability a lot.

public class BigObject
{
  // public getters
  // private setters

  public static class Buider
  {
     private A f1;
     private B f2;
     private C f3;
     private D f4;
     private E f5;

     public Buider setField1(A f1) { this.f1 = f1; return this; }
     public Buider setField2(B f2) { this.f2 = f2; return this; }
     public Buider setField3(C f3) { this.f3 = f3; return this; }
     public Buider setField4(D f4) { this.f4 = f4; return this; }
     public Buider setField5(E f5) { this.f5 = f5; return this; }

    public BigObject build()
    {
      BigObject result = new BigObject();
      result.setField1(f1);
      result.setField2(f2);
      result.setField3(f3);
      result.setField4(f4);
      result.setField5(f5);
      return result;
    }
  }
}

// Usage:
BigObject boo = new BigObject.Builder()
  .setField1(/* whatever */)
  .setField2(/* whatever */)
  .setField3(/* whatever */)
  .setField4(/* whatever */)
  .setField5(/* whatever */)
  .build();

You can also put verification logic into Builder set..() and build() methods.

Mentality answered 16/9, 2014 at 20:14 Comment(1)
What would you recommend if many of your fields are final? This is the main thing that keeps me from writing helper functions. I suppose I can make the fields private and make sure I don't modify them incorrectly in that class's code, but I'm hoping for something more elegant.Almeria
S
7

There is a pattern called as Parameter object.

Idea is to use one object in place of all the parameters. Now even if you need to add parameters later, you just need to add it to the object. The method interface remains same.

Sinuosity answered 12/3, 2010 at 12:5 Comment(0)
A
5

You could create a class to hold that data. Needs to be meaningful enough though, but much better than using a map (OMG).

Amuck answered 12/3, 2010 at 11:53 Comment(4)
I don't think it's that necessary to create a class to hold a method parameter.Swaraj
I'd only create the class if there were multiple instances of passing the same parameters. This would signal that the parameters are related and probably belong together anyway. If you are creating a class for a single method the cure is probably worse than the disease.Outskirts
Yes - you could move related params into a DTO or Value Object. Are some of the multiple params optional i.e. the main method is overloaded with these additional params? In such cases - I feel it is acceptable.Disruption
That's what I meant by saying must be meaningful enough.Amuck
C
4

Code Complete* suggests a couple of things:

  • "Limit the number of a routine's parameters to about seven. Seven is a magic number for people's comprehension" (p 108).
  • "Put parameters in input-modify-output order ... If several routines use similar parameters, put the similar parameters in a consistent order" (p 105).
  • Put status or error variables last.
  • As tvanfosson mentioned, pass only the parts of a structured variables ( objects) that the routine needs. That said, if you're using most of the structured variable in the function, then just pass the whole structure, but be aware that this promotes coupling to some degree.

* First Edition, I know I should update. Also, it's likely that some of this advice may have changed since the second edition was written when OOP was beginning to become more popular.

Consolata answered 15/3, 2010 at 8:52 Comment(1)
The fourth bullet is very important. It's too easy to pass an object down the call chain, without thinking much about it. Until you need to call the method somewhere else and do not remember which part of the structure is really used! With a proper refactor, this becomes more explicit, thus easier to grasp. It's also where interfaces shines as you can then create a transfer object complying with that specific interface. And even if you pass the whole object, you did at least document which part is used.Leucoma
D
3

Using a Map is a simple way to clean the call signature but then you have another problem. You need to look inside the method's body to see what the method expects in that Map, what are the key names or what types the values have.

A cleaner way would be to group all parameters in an object bean but that still does not fix the problem entirely.

What you have here is a design issue. With more than 7 parameters to a method you will start to have problems remembering what they represent and what order they have. From here you will get lots of bugs just by calling the method in wrong parameter order.

You need a better design of the app not a best practice to send lots of parameters.

Draggletailed answered 12/3, 2010 at 12:2 Comment(0)
K
2

Good practice would be to refactor. What about these objects means that they should be passed in to this method? Should they be encapsulated into a single object?

Kissable answered 12/3, 2010 at 11:53 Comment(1)
yes they should . For example , a large search form , has many unrelated constraints and needs for pagination. you need pass currentPageNumber , searchCriteria , pageSize ...Swaraj
T
1

Create a bean class, and set the all parameters (setter method) and pass this bean object to the method.

Tegument answered 12/3, 2010 at 12:24 Comment(0)
U
1
  • Look at your code, and see why all those parameters are passed in. Sometimes it is possible to refactor the method itself.

  • Using a map leaves your method vulnerable. What if somebody using your method misspells a parameter name, or posts a string where your method expects a UDT?

  • Define a Transfer Object . It'll provide you with type-checking at the very least; it may even be possible for you to perform some validation at the point of use instead of within your method.

Umbles answered 15/3, 2010 at 8:17 Comment(0)
L
1

The Effective Java advice all sounds correct. One concrete approach to

create helper classes to hold group of parameters (typically static member classes)

would be to use record class. Here is an example for your case:

record Params(Object objA, Object objectB, Date date1, Date date2, String str1, String str2) {}
public void doSomething(Params params) {}

This will get you the same result as creating your own model or parameter class, but with less boiler plate code.

Loggerhead answered 12/1 at 15:12 Comment(0)
A
0

This is often an indication that your class holds more than one responsibility (i.e., your class does TOO much).

See The Single Responsibility Principle

for further details.

Actinon answered 12/3, 2010 at 12:31 Comment(0)
Z
0

If you are passing too many parameters then try to refactor the method. Maybe it is doing a lot of things that it is not suppose to do. If that is not the case then try substituting the parameters with a single class. This way you can encapsulate everything in a single class instance and pass the instance around and not the parameters.

Zamora answered 12/3, 2010 at 14:55 Comment(0)
A
0

I would say stick with the way you did it before. The number of parameters in your example is not a lot, but the alternatives are much more horrible.

  1. Map - There's the efficiency thing that you mentioned, but the bigger problem here are:

    • Callers don't know what to send you without referring to something
      else... Do you have javadocs which states exactly what keys and
      values are used? If you do (which is great), then having lots of parameters isn't a problem either.
    • It becomes very difficult to accept different argument types. You can either restrict input parameters to a single type, or use Map<String, Object> and cast all the values. Both options are horrible most of the time.
  2. Wrapper objects - this just moves the problem since you need to fill the wrapper object in the first place - instead of directly to your method, it will be to the constructor of the parameter object. To determine whether moving the problem is appropriate or not depends on the reuse of said object. For instance:

Would not use it: It would only be used once on the first call, so a lot of additional code to deal with 1 line...?

{
    AnObject h = obj.callMyMethod(a, b, c, d, e, f, g);
    SomeObject i = obj2.callAnotherMethod(a, b, c, h);
    FinalResult j = obj3.callAFinalMethod(c, e, f, h, i);
}

May use it: Here, it can do a bit more. First, it can factor the parameters for 3 method calls. it can also perform 2 other lines in itself... so it becomes a state variable in a sense...

{
    AnObject h = obj.callMyMethod(a, b, c, d, e, f, g);
    e = h.resultOfSomeTransformation();
    SomeObject i = obj2.callAnotherMethod(a, b, c, d, e, f, g);
    f = i.somethingElse();
    FinalResult j = obj3.callAFinalMethod(a, b, c, d, e, f, g, h, i);
}
  1. Builder pattern - this is an anti-pattern in my view. The most desirable error handling mechanism is to detect earlier, not later; but with the builder pattern, calls with missing (programmer did not think to include it) mandatory parameters are moved from compile time to run time. Of course if the programmer intentionally put null or such in the slot, that'll be runtime, but still catching some errors earlier is a much bigger advantage to catering for programmers who refuse to look at the parameter names of the method they are calling. I find it only appropriate when dealing with large number of optional parameters, and even then, the benefit is marginal at best. I am very much against the builder "pattern".

The other thing people forget to consider is the role of the IDE in all this. When methods have parameters, IDEs generate most of the code for you, and you have the red lines reminding you what you need to supply/set. When using option 3... you lose this completely. It's now up to the programmer to get it right, and there's no cues during coding and compile time... the programmer must test it to find out.

Furthermore, options 2 and 3, if adopted wide spread unnecessarily, have long term negative implications in terms of maintenance due to the large amount of duplicate code it generates. The more code there is, the more there is to maintain, the more time and money is spent to maintain it.

Adcock answered 2/8, 2020 at 17:53 Comment(0)
S
0

... and Bob's your uncle: No-hassle fancy-pants APIs for object creation!

https://projectlombok.org/features/Builder

Springclean answered 4/10, 2021 at 13:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.