Experienced programmers should have seen that a method has dozens or hundreds of parameters.
Why should a method have parameters?
Because different methods need to share information.
However, there are more than one way to share information between methods. In addition to the parameter list, there are also global variables. However, global variables always bring surprises. Therefore, canceling global variables is also a trend of major languages.
However, information needs to be transmitted between methods. Global variables cannot be used, so parameters become the only choice. Therefore, as long as you think of any information to be transmitted to a method, it will be directly added to the parameter list, and the parameter list will become longer and longer.
Long parameter list problem
If the parameter list is too long, it is difficult for you, a crud programmer, to fully control these logic!
Therefore, the crux is a large number. The key to solve this problem is to reduce the number of parameters.
Solution
many littles make a mickle
A simple way to create a blog:
public void createActicle(final String title, final String introduction, final URL coverUrl, final ActicleType type, final ActicleColumn column, final String protagonists, final String tags, final boolean completed) { ... Acticle acticle = Acticle.builder .title(title) .introduction(introduction) .coverUrl(coverUrl) .type(type) .column(column) .protagonists(protagonists) .tags(tags) .completed(completed) .build(); this.repository.save(acticle); }
The parameter list contains all kinds of information that a blog should have, such as blog title, blog introduction, cover URL, blog type, column to which the blog belongs, protagonist name, blog tag, whether the blog is finished
If you just want to understand the logic, you may also think that the parameter list is quite reasonable. After all, it passes all kinds of information needed to create a blog to the method, which is also the initial point of view for most people to understand the problem when facing a piece of code. Although writing code like this is easy to understand, it's not enough for you to find problems.
Now the product requires to add an information in the blog to identify whether the blog is a signed blog, that is, whether the blog can be charged. What should I do?
Very simple! I add a parameter directly. Many shit mountains come like this. A little makes a lot. Quantitative change leads to qualitative change!
All parameters here are necessary to create a blog. Therefore, what you can do is to encapsulate these parameters into a class, a parameter class for creating a blog:
public class NewActicleParamters { private String title; private String introduction; private URL coverUrl; private ActicleType type; private ActicleColumn column; private String protagonists; private String tags; private boolean completed; ... }
In this way, there is only one parameter left in the parameter list:
public void createActicle(final NewActicleParamters parameters) { ... }
So, encapsulate the parameter list as an object!
Just encapsulate a parameter list into a class, and then take them out one by one when using these parameters. Is this unnecessary? Like this:
public void createActicle(final NewActicleParamters parameters) { ... Acticle acticle = Acticle.builder .title(parameters.getTitle()) .introduction(parameters.getIntroduction()) .coverUrl(parameters.getCoverUrl()) .type(parameters.getType()) .channel(parameters.getChannel()) .protagonists(parameters.getProtagonists()) .tags(parameters.getTags()) .completed(parameters.isCompleted()) .build(); this.repository.save(acticle); }
If you think so, it means that you haven't formed an understanding of software design. We do not simply encapsulate the parameters into classes. From the design point of view, a new model is introduced here. The encapsulation of a model should be based on behavior. There was no such model before, so I can't think of what behavior it should have. Now that the model is produced, it should have its own supporting behavior.
What is the behavior of this model? If it is clear to build a blog object, the code can be further refactored:
public class NewActicleParamters { private String title; private String introduction; private URL coverUrl; private ActicleType type; private ActicleColumn column; private String protagonists; private String tags; private boolean completed; public Acticle newActicle() { return Acticle.builder .title(title) .introduction(introduction) .coverUrl(coverUrl) .type(type) .column(column) .protagonists(protagonists) .tags(tags) .completed(completed) .build(); } }
The method of creating a blog is greatly simplified:
public void createActicle(final NewActicleParamters parameters) { ... Acticle acticle = parameters.newActicle(); this.repository.save(acticle); }
"How to expand requirements"? If the requirements are expanded and the content required to create a blog needs to be added, the parameter list is unchanged and relatively stable.
Then this category will continue to expand and become a large category. What should we do? How to solve the problem?
Dynamic and static separation
In not all cases, parameters belong to one class:
public void getChapters(final long acticleId, final HttpClient httpClient, final ChapterProcessor processor) { HttpUriRequest request = createChapterRequest(acticleId); HttpResponse response = httpClient.execute(request); List<Chapter> chapters = toChapters(response); processor.process(chapters); }
Get the corresponding chapter information according to the blog ID. Purely based on the number theory of parameters, the number of parameters is small.
If you just look at this method, it may be difficult to find direct problems. Absolute quantity is not a key point, and the parameter list should be as few as possible.
Among these parameters, the acticleId passed in each time is different and changes with different requests. But the two parameters of httpClient and processor are the same, because they both have the same logic and have no change. That is, the change frequency of acticleId is different from that of httpClient and processor.
Different data change directions are also different concerns. Typical dynamic data (acticleId) and static data (httpClient and processor) are shown here. They are different concerns and should be separated.
In this scenario, static data can become a field of the class in which the method is located, and only the things changed each time can be passed as parameters. According to this idea, the code can be changed as follows:
public void getChapters(final long acticleId) { HttpUriRequest request = createChapterRequest(acticleId); HttpResponse response = this.httpClient.execute(request); List<Chapter> chapters = toChapters(response); this.processor.process(chapters); }
This bad smell is actually a software design problem. The code lacks the proper structure. Therefore, the part that should belong to the static structure is passed around in the form of dynamic parameters, which virtually lengthens the parameter list.
Although a long parameter list can be encapsulated by a class, the precondition for encapsulating this class is that these parameters belong to a class and have the same change reasons.
If the parameters of the method have different change frequencies, it depends on the situation. As we have seen before, the static part can become a part of the software structure. If there are multiple change frequencies, we can also encapsulate multiple parameter classes.
Farewell mark
public void editChapter(final long chapterId, final String title, final String content, final boolean apporved) { ... }
The ID, title and content of the chapter to be modified. The last parameter indicates whether the modification is directly approved.
The first few parameters are the necessary information for modifying a chapter, and the focus is on the last parameter. In terms of business, if the author edits, it will be reviewed later. If the editor edits, it will pass the review directly, because the editor itself plays the role of reviewer. So, you found that this parameter is actually a flag, indicating that the next processing flow will be different.
Using marked parameters is a common method for programmers to learn programming. It is this technique that is so easy to use that the flag in the code floats wantonly. There are markers not only in variables, but also in parameters. A long parameter list contains various tag parameters.
In the actual code, you must carefully judge the current value of each tag in order to handle it well.
A simple way to solve tag parameters is to split the different paths represented by tag parameters.
One method here can be divided into two methods. One method is responsible for "ordinary editing" and the other is responsible for "editing that can be approved directly".
// An ordinary editor needs to be approved public void editChapter(final long chapterId, final String title, final String content) { ... }
// Directly approved edits public void editChapterWithApproval(final long chapterId, final String title, final String content) { ... }
Tag parameters exist in many forms in code, some of which are Boolean values, enumeration values, strings or integers. They can be disassembled by splitting methods. In refactoring, this technique is called Remove Flag Argument.
Only short code, we can have a better grasp, and to write short code, we need to be able to "separate concerns".
summary
The main way to deal with long parameter lists is to reduce the number of parameters. The most direct way is to encapsulate the parameter list into a class. However, not all cases can be encapsulated into classes. We also need to analyze whether all parameters have the same change frequency.
If the change frequency is the same, it is encapsulated into a class. If the change frequency is different:
- Statically unchanged, can become a part of software structure
- Multiple variable frequencies can be encapsulated into several classes
In addition, marked parameters often appear in the parameter list, which is another important reason why the parameter list becomes longer. One solution for this tag parameter is to split the method into multiple methods based on these tag parameters.
Reduce the parameter list as few as possible.