在日常编程中,优雅的代码总是千篇一律,而"丑陋"的代码确是千奇百怪,"代码之丑"第二期,一起来看看吧
一、大类
如果一个类,代码杂糅在一起,你一眼看上去就不想读,这大概率是个"大类"(这里的类特指实体类)。
大类的产生
-
职责不单一
arduinopublic 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; ... }
上述代码,将用户字段、作者字段、编辑字段揉在一起,但是他们并不是并行字段,一个人不能是普通用户并同时拥有作者字段和编辑字段,这样后续再有不同职责的"用户"也会加入到这个类里面,造成大类的产生。可以把
User
、Author
、Editor
三个类拆开来,这样就会避免大类的产生,并且使用userId
做关联arduinopublic 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; ... }
-
字段未分组
arduinopublic class User { private long userId; private String name; private String nickname; private String email; private String phoneNumber; ... }
修改后的
User
类还可以继续拆分,根据类的不同信息做分组,例如将用户主体信息与联系方式信息拆分arduinopublic class User { private long userId; private String name; private String nickname; private Contact contact; ... } public class Contact { private String email; private String phoneNumber; ... }
二、长参数
我们应该写短的代码,逻辑单一明确,看上去不冗余。
聚沙成塔
arduino
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);
}
上述代码将一堆作品相关的参数揉在一起,那么我之后增加一个作品相关的参数时必然会直接在方法后面加,这样会导致参数列表非常庞大且不好维护。解决的方法是封装,这里参数都是跟作品相关的,所以封装一个作品类,然后使用该类作为参数即可
typescript
public void createBook(final NewBookParamters parameters) {
...
}
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;
...
}
但是这样的话使用 parameters
时需要调用一堆 get 方法,需要把他们一个个取出来,就像这样
scss
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);
}
站在设计的角度,我们这里引入的是一个新的模型,一个模型的封装应该是以行为为基础的。现在模型产生了,我们需要给他配套的行为,这个封装类的行为应该是构建一个作品出来,所以:
typescript
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();
}
}
这样创建作品的业务逻辑完成了简化,而作品本身作为一个模型也具备了其行为
java
public void createBook(final NewBookParamters parameters) {
...
Book book = parameters.newBook();
this.repository.save(book);
}
动静分离
考虑参数间的变化频率是否一致
并不是所有参数都可以关联到同个模型,下方代码中 bookId
是频繁变动的书籍 id,每次请求都不一样,而 httpClient
和 processor
很有可能是静态的,每次请求都携带相同的参数,这时候就可以将静态参数提取成所在类的变量,注意要考虑具体业务来判断该参数是否静态。
ini
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);
}
改变之后:
ini
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
参数标记了是否审核通过,根据这个标记会有不同的处理逻辑。在实际代码中,你是愿意看到分类明确、业务逻辑单一的代码,还是愿意看到一堆标记需要你排列组合、甚至标记注释都没有的代码?
注意:标记不只
boolean
,还有int
、枚举、String
的魔法值等等,如果一个标记代表了情况 A、情况 B,那当情况 C 发生的时候是否还需要继续耦合在这一个方法里?
arduino
public void editChapter(final long chapterId,
final String title,
final String content,
final boolean apporved) {
...
}
解决标记,最简单的就是将标记代表的不同路径拆分,或者你可以提取他们的公共模块,所以下述代码有可能可以分成三块:公共逻辑、普通编辑、直接通过的编辑
arduino
// 普通的编辑,需要审核
public void editChapter(final long chapterId,
final String title,
final String content) {
...
}
// 直接审核通过的编辑
public void editChapterWithApproval(final long chapterId,
final String title,
final String content) {
...
}
三、总结
在业务开发中,我们经常会为了业务便利而定义"大类"、使用长参数、使用标记,这看起来并没什么不好的,或者你会说"业务开发就是这样的,功能没问题就可以了,谁会管写法呢?",但我仍然认为遵守一定的代码规范并养成习惯,这是一个程序员的职业