代码优化(二):编写"短小"的代码

原创
03/03 13:51
阅读数 38

为什么要写”短小“的代码呢?因为一个人理解的东西是有限的,没有人能同时面对所有细节。当一个事物足够复杂,就会超过一个人的理解范畴,解决的方式就是分而治之。

项目中坏味道的代码那么多,难道要一个个找去修改么?建议处理原则为:我现在添加的代码,是否让原有的代码更糟糕了,答案是的话,那就优化它。

让营地比你来时更干净 ——童子军

长函数

长函数需要拆分函数;多长算长函数?建议每个函数在20行以下,越短越好。

大类

把类写小,越小越好。

产生大类的原因主要有:1.职责不单一 2.字段未分组

1. 职责不单一

类需要保持单一职责。

如这个User对象:

public class User {
  private long userId;
  private String name;
	//昵称
  private String nickname;
  private String email;
  private String phoneNumber;

	//作者类型
  private AuthorType authorType;
	//作者审核状态
  private ReviewStatus authorReviewStatus;
	//编辑类型
  private EditorType editorType;
  ...
}

普通的用户既不是作者,也不是编辑。所以作者、编辑相关字段,对普通用户没有意义。

所以应该根据职责,拆分成多个对象:

//用户
public class User {
  private long userId;
  private String name;
  private String nickname;
  private String email;
  private String phoneNumber;
  ...
}
//作者
public class Author {
  private long userId;
  private AuthorType authorType;
  private ReviewStatus authorReviewStatus;
  ...
}
//编辑
public class Editor {
  private long userId;
  private EditorType editorType;
  ...
}

2. 字段未分组

字段应该根据修改频率进行分组。

可能同个职责的对象下,字段还是很多,那就可能是没有对字段做分组,如:

public class User {
  private long userId;
  private String name;
  private String nickname;
  private String email;
  private String phoneNumber;
  ...
}

userId、name、nickname是基本信息,更改的频率很低。而email、phoneNumber是联系方式,更改的频率比较高,所以可以拆分成两组:

//用户基本信息   很少修改
public class User {
  private long userId;
  private String name;
  private String nickname;
	//联系方式类
  private Contact contact;
  ...
}
//联系方式  稳定性差一点
public class Contact {
  private String email;
  private String phoneNumber;
  ...
}

这样子,就算对联系方式的修改,也不会改动到User类。

拆分类也产生问题,就是类的数量变多了,会增加复杂度。但实际上,只要做好命名的规范精准,利用包、命名空间的机制,就可以实现细节的封装。本来就是提倡面对接口编程。

因为优秀代码的拆分很多层,所以阅读优秀源码时,如果不分析类名、方法名,一个劲钻到具体实现里,很快就会迷失在代码的细节当中。

长参数

参数列表,越短越好。

参数封装对象

public void createBook(final String title, 
                       final String introduction,
                       final URL coverUrl,
                       final BookType type,
                       final BookChannel channel,
                       final String protagonists,
                       final String tags,
                       final boolean completed) {
  ...
  Book book = Book.builder
    .title(title) 
    .introduction(introduction)
    .coverUrl(coverUrl)
    .type(type)
    .channel(channel)
    .protagonists(protagonists)
    .tags(tags)
    .completed(completed)
    .build();
    
  this.repository.save(book);
}

参数比较多时,应该根据业务封装成对象

public class NewBookParamters {
  private String title;
  private String introduction;
  private URL coverUrl;
  private BookType type;
  private BookChannel channel;
  private String protagonists;
  private String tags;
  private boolean completed;
  ...
}
public void createBook(final NewBookParamters parameters) {
  ...
}

封装成对象之后,是否每次都要parameters.getXXX来获取参数呢,不是更麻烦么?

public void createBook(final NewBookParamters parameters) {
	...
	Book book = Book.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(book);
}

实际上,一个模型的封装应该是以行为为基础的。所以要把get行为隐藏到模型类中

public class NewBookParamters {
  private String title;
  private String introduction;
  private URL coverUrl;
  private BookType type;
  private BookChannel channel;
  private String protagonists;
  private String tags;
  private boolean completed;
  
  public Book newBook() {
    return Book.builder
      .title(title) 
      .introduction(introduction)
      .coverUrl(coverUrl)
      .type(type)
      .channel(channel)
      .protagonists(protagonists)
      .tags(tags)
      .completed(completed)
      .build();
  }
}

参数动静分离

public void getChapters(final long bookId, 
                        final HttpClient httpClient,
                        final ChapterProcessor processor) {
  HttpUriRequest request = createChapterRequest(bookId);
  HttpResponse response = httpClient.execute(request);
  List<Chapter> chapters = toChapters(response);
  processor.process(chapters);
}

动数据:bookId经常改变; 静数据:httpClient 和 processor基本不变

静数据:改为类参数

public void getChapters(final long bookId) {
  HttpUriRequest request = createChapterRequest(bookId);
  HttpResponse response = this.httpClient.execute(request);
  List<Chapter> chapters = toChapters(response);
  this.processor.process(chapters);
}

移除标记参数

移除标记参数的做法是 分函数

//apporved标记,执行不同处理流程
public void editChapter(final long chapterId, 
                        final String title, 
                        final String content, 
                        final boolean apporved) {
  ...
}

这里apporved用来判断执行哪个处理流程,应该将标记参数去掉,拆分为两个函数

// 普通的编辑,需要审核
public void editChapter(final long chapterId, 
                        final String title, 
                        final String content) {
  ...
}

// 直接审核通过的编辑
public void editChapterWithApproval(final long chapterId,
                                    final String title,
                                    final String content) {
 ...
}

参考资料:

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部