如何消除代码shi山中的一坨参数列表?

发布于 2022-6-2 16:32
浏览
0收藏

     

   正文   
 

1 方法为何要有参数?
因为不同方法之间需要共享信息。

但方法间共享信息的方式除了参数列表,还有全局变量。但全局变量总能带来意外之喜,所以,取消全局变量也是各大语言趋势。于是参数列表就成了唯一选择,于是,只要你想到有什么信息要传给一个方法,就会直接将其加入参数列表,导致参数列表越来越长!

 

2 长参数列表怎么了?
参数列表一旦过长,你一个 crud boy就很难完全掌控这些逻辑了呀!所以症结是数量多,解决关键也就是降低参数数量。

 

3 解决方案
3.1 聚沙成塔
一个简单的创建博客的方法:

 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);
 }

 

参数列表包含了一篇博客所要拥有的各种信息,比如:博客标题、简介、封面 URL、类型、归属专栏、主角姓名、标签、是否完结......

若只是想理解逻辑,可能你还会觉得这参数列表挺好啊,把创建一篇博客所需的信息都传给了方法,这也是大部分人面对一段代码时理解问题的最初角度。虽然这样写代码容易让人理解,但不足以让你发现问题。

现在产品要求在博客里增加一项信息,标识这部博客是否收费,咋办?很简单啊!我直接新增一个参数。很多屎山就这么来的,积少成多,量变引起质变!

这里所有参数都是创建博客所必需,所以,可以做的就是将这些参数封装成一个类,一个创建博客的参数类:

 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 void createActicle(final NewActicleParamters parameters) {
   ...
 }

所以, 将参数列表封装成对象吧 !

只是把一个参数列表封装成一个类,然后,用到这些参数的时候,还需要把它们一个个取出来,这会不会是多此一举呢?就像这样:

 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);
 }

 

若你这样想,说明还没有形成对软件设计的理解。我们并非只是简单地把参数封装成类,站在设计角度,这里引入的是一个新模型。一个模型的封装应该以【行为】为基础。之前没有这个模型,所以想不到它应该有什么行为,现在模型产生了,它就该有自己配套的行为。

该模型的行为就是构建一个博客对象,则代码就能进一步重构:

 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();
   }
 }

 

创建博客的方法就得到极大简化:

 public void createActicle(final NewActicleParamters parameters) {
   ...
   Acticle acticle = parameters.newActicle();
     
   this.repository.save(acticle);
 }


如此,一旦后续需求又扩展了,需要增加创建博客所需的内容,则该参数列表是不变的,也就是说它是稳定的!

3.2 动静分离
若这个类不断膨胀,变成一个大类,该咋办?毕竟并非所有场景下,参数都属于一个类:

 // 根据博客 ID 获取对应章节信息
 public void getSections(final long acticleId, 
                         final HttpClient httpClient,
                         final SectionProcessor processor) {
   HttpUriRequest request = createChapterRequest(acticleId);
   HttpResponse response = httpClient.execute(request);
   List<Section> sections = toSections(response);
   processor.process(sections);
 }

单论参数的个数,数量并不多。若你只看这个方法,很难发现直接问题。绝对数量不是core,参数列表也应该是越少越好。

但注意,每次传进来的参数:

  • acticleId 都随请求不同而改变
  • 但 httpClient、processor 两个参数一样,因为都有相同逻辑,没啥变化。即acticleId 的变化频率同 httpClient 和 processor 这两个参数变化频率不同。
     

不同的数据变动方向也是不同关注点。这里表现出来的就是典型的动数据(acticleId)和静数据(httpClient、processor),它们是不同关注点,应该分离。

 

针对该案例:

  • 静态不变的数据完全可以成为这个方法所在类的一个字段
  • 只将每次变动的东西作为参数传递即可
    因此,代码重构如下:
 public void getSections(final long acticleId) {
   HttpUriRequest request = createChapterRequest(acticleId);
   HttpResponse response = this.httpClient.execute(request);
   List<Section> sections = toSections(response);
   this.processor.process(sections);
 }

这个坏味道是个软件设计问题,代码缺乏应有的结构,所以,原本应该属于静态结构的部分却以动态参数的方式传来传去,无形中导致参数列表变长。

长参数列表固然可以用一个类进行封装,但能够封装成这个类的前提是:这些参数属于一个类,有相同的变化原因!

若方法的参数有不同变化频率,就要看情况了。对静态部分,本小节案例已经看出,它可以成为软件结构的一部分,而若有多个变化频率,则还可以封装出多个参数类。

3.3 再见了,标记!

 public void editSection(final long sectionId, 
                         final String title, 
                         final String content, 
                         final boolean apporved) {
   ...
 }

待修改章节的ID、标题和内容,最后一个参数表示这次修改是否直接审核通过。

前面几个参数是修改一个章节的必要信息,重点在最后这个参数。从业务上说,如果是作者进行编辑,之后要经过审核,而如果编辑来编辑的,那审核就直接通过,因为编辑本身扮演了审核人的角色。所以,你发现了,这个参数实际上是一个标记,标志着接下来的处理流程会有不同。

使用标记参数,是程序员初学编程时常用的一种手法。正是这种手法实在太好用,导致代码里flag肆意飘荡。不仅变量里有标记,参数里也有。很多长参数列表其中就包含了各种标记参数。

在实际的代码中,必须小心翼翼地判断各个标记当前的值,才能做好处理。

解决标记参数,一种简单的方式就是,将标记参数代表的不同路径拆分出来。

这里的一个方法可以拆分成两个方法,一个方法负责“普通的编辑”,另一个负责“可以直接审核通过的编辑”。

 // 普通的编辑,需要审核
 public void editSection(final long sectionId, 
                         final String title, 
                         final String content) {
   ...
 }
 // 直接审核通过的编辑
 public void editSectionWithApproval(final long sectionId,
                                     final String title,
                                     final String content) {
  ...
 }


标记参数在代码中存在的形式很多,有的是布尔值、枚举值、字符串或整数。都可以通过拆分方法的方式将它们拆开。在重构中,这种手法叫做移除标记参数(Remove Flag Argument)。

只有短小的代码,我们才能有更好地把握,而要写出短小的代码,需要我们能够“分离关注点”。

 

4 总结
应对长参数列表主要的方式就是减少参数的数量,最直接的就是将参数列表封装成一个类。但并不是说所有的情况都能封装成类来解决,我们还要分析是否所有的参数都有相同的变动频率。

变化频率相同,则封装成一个类。变化频率不同,则:

  • 静态不变的,可以成为软件结构的一篇分
  • 多个变化频率的,可以封装成几个类
    参数列表中经常会出现标记参数,可根据这些标记参数,将方法拆成多个方法。

参考:

[1]. 《重构》
[2]. 《代码设计之丑》

 

文章转自公众号: JavaEdge

分类
标签
已于2022-6-2 16:32:58修改
收藏
回复
举报
回复
添加资源
添加资源将有机会获得更多曝光,你也可以直接关联已上传资源 去关联
    相关推荐