为什么要写”短小“的代码呢?因为一个人理解的东西是有限的,没有人能同时面对所有细节。当一个事物足够复杂,就会超过一个人的理解范畴,解决的方式就是分而治之。
项目中坏味道的代码那么多,难道要一个个找去修改么?建议处理原则为:我现在添加的代码,是否让原有的代码更糟糕了,答案是的话,那就优化它。
让营地比你来时更干净 ——童子军
长函数
长函数需要拆分函数;多长算长函数?建议每个函数在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) {
...
}