代码之丑
最近在极客时间APP上学习郑烨老师的经典编程课程"代码之丑",很有感触。
其实早在刚入行的时候就看过一点,但是那时觉得写的这些都是很常见的,不足为奇。可是随着自己工作年限的增长,过手的项目越来越多,代码量也越来越丰富。回过头来看这些文章,只能说这才是经典呀,那些在编程过程中只关注于业务的实现,却忘却如何更优美的表达,写出让后来者叹为观止的程序。.
一个优秀的工程师能按时按要求完成需求的内容是基本的能力,但是能设计出鲁棒性,容错性,兼容性都兼顾的系统才是我们大家一直所要追求的。
最近拖更的时间有点长了,接下来我会不定期在此分享郑烨老师的这一系列文章,希望对大家能有帮助。
关于CVS 重复代码
对于一个新的需求,我们很多工程师都会选择在网上找对应的实现方式,本着不重复造轮子的理念,能用尽用的手法,先把功能实现了,后期再进行优化。
这样的做法没有问题,但是在做的过程中可能会出现一些不经意的遗留问题,比如直接拷贝已有代码,不认真学习分析代码细节,如果引用后不出现问题那一切都OK,但是若出现Bug,那么必将让你陷入难堪的境地。
复制粘贴最容易产生重复的代码,这块郑烨老师给的建议是:
不要使用复制粘贴,真正应该做的是,先提取出函数,然后,在需要的地方调用这个函数。
咱们看看下面这几段代码
@Task
public void sendBook() {
try {
this.service.sendBook();
} catch (Throwable t) {
this.notification.send(new SendFailure(t)));
throw t;
}
}
@Task
public void sendChapter() {
try {
this.service.sendChapter();
} catch (Throwable t) {
this.notification.send(new SendFailure(t)));
throw t;
}
}
@Task
public void startTranslation() {
try {
this.service.startTranslation();
} catch (Throwable t) {
this.notification.send(new SendFailure(t)));
throw t;
}
}
这三段代码表示的业务背景是,一个系统要把作品的相关信息发送给翻译引擎。比如发送作品信息,发送章节,启动翻译。
不难看出每个业务都不同,可是仔细看却又有几分相似,就是异常处理过程中,它会把出现异常的情况统一发送给某个人。
其实这块就存在重复的代码,如果说这种业务处理多的话,或者对于异常情况负责处理的人多了,那么修改起来是特别麻烦的。那么此时我们可以在此处进行优化。
从面向对象的设计来说,提取出一个接口。
private void executeTask(final Runnable runnable) {
try {
runnable.run();
} catch (Throwable t) {
this.notification.send(new SendFailure(t)));
throw t;
}
}
通过这个结构,前面几个方法就可以进行简化。
@Task
public void sendBook() {
executeTask(this.service::sendBook);
}
@Task
public void sendChapter() {
executeTask(this.service::sendChapter);
}
@Task
public void startTranslation() {
executeTask(this.service::startTranslation);
}
我们利用语言的特性,接口来实现了优化,这个时间看,这样子是不是简要多了,如果后期异常的处理人员增多,那么就只需求修改executeTask()方法即可。
关于代码中If的使用
在日常的业务编码过程中,经常会遇到对某种情况的判断,若满足是一种情况,不满足是另外一种情况。
if (user.isEditor()) {
service.editChapter(chapterId, title, content, true);
} else {
service.editChapter(chapterId, title, content, false);
}
这是对一段章节内容的审核,若审核通过之后,做后续的处理。但是调用时必须看是否为编辑者。
这段代码当时写的时候,作者的脑子里应该只想到了if语句判断之后要做什么,而没有想到这个if语句判断的到底是什么。
仔细看if判断完之后的内容调用的方法几乎都是一样的,只是最后的参数不同而已。
按照一般的逻辑我们是不是直接可以写成这样子。
service.editChapter(chapterId, title, content, user.isEditor());
由于此方法只是最后的Boolean参数发生了变化,那么直接给将引起变化的值直接赋值即可。
哈哈哈,我也是这么想的,这样子代码量也可以减少,而且不必做更多的判断。这么做没有问题,但是仔细看这种写法代码的可读性很差,如果想了解这个方法的参数那么必须得调试进行才可以,而且这种写法不方便调试。
大家来看郑烨老师的写法。
boolean approved = user.isEditor();
service.editChapter(chapterId, title, content, approved);
如果说审核的条件发生了变化,那么此时还可以提取出一个函数,把可能的变化都集中在这个函数中。
boolean approved = isApproved(user);
service.editChapter(chapterId, title, content, approved);
private boolean isApproved(final User user) {
return user.isEditor();
}
大家看是不是可读性提高了很多,从上到下阅读起来很顺畅。
所以我们要注意:只要在代码中看到了if语句的出现,而且if和else的代码块长得又比较像,多半就是出现了这个坏味道。
知识点回顾
复制粘贴的代码、结构重复的代码、if和else代码块中的语句高度类似。
Don't Repeat Yourself(不要重复自己,简称 DRY)
复制粘贴的代码和结构重复的代码,虽然从观感上有所差异,但本质上都是重复。
作为一个精进中的程序员,我们一定要把DRY原则牢记在心,时时刻刻保持对“重复”的敏感度,把各种重复降到最低。