这是一本关于如何写好代码的书,是一本关于“如何编写别人能看懂的代码”的书。从价值观、原则、模式三个层面解读如何优化代码结构,减少代码维护成本。
关于这本书:
图:章节概述
模式就是我们在编写代码时固定的规则(force)。
图:模式
举个栗子:chestnut: 编程中,我们想写一个函数,需要给它命名。那么映入我们脑海的是:
图:给函数命名的模式
图: 编程理论-价值观
图:编程理论-原则
上面提到了个关键词,分别是:模式、原则、价值观。
模式是编码是的一些约束(force),众多的模式共同构筑了一种编程风格。
价值观是编程过程中统一支配性主题,影响了我们在编程中所作的每个决策。
原则是模式和价值观之间搭建的桥梁,在遇到没有现有模式可以解决的问题的时候,原则往往可以让我们“无中生有”的创造一些东西,而这些东西往往都是很不错的。
图: 模式-原则-价值观
摘录书中的一句总结:模式描述了要做什么,价值观提供了动机,原则把动机转化成了实际行动。
由上面的讨论可以知道,价值观为模式的形成提供了动机。那么主要的动机之一是软件设计应该致力于减少整体成本。
经过统计,人们发现,维护成本远远高于初始成本。也就是说维护的代价很大。因为理解现有代码需要耗费很多时间,而且容易出错,改动之后还需要重新测试和部署。
通过合理的模式的使用,可以降低经济成本。
关于类,书中讲解了很多细小的点。有的比较有意思,值得一说。
通读本章后,进行了梳理整合,做如下总结。
图:逻辑委派
可插拔的选择器和逻辑委派很相似,比如有一个打印类的函数列表的工具方法,传给它不同的类名,就打印不同的类的函数信息。
把一些公共的方法放到一个公共的类中,声明为static,当成工具来调用。可以简化代码,看起来更清晰。
1 . 直接访问与间接访问
在类中有很多变量,每一个变量都会存在很多状态,比如boolean有true和false两种状态,一般类的范围内进行直接访问,对外部的访问一般强制间接访问,也就是通过public的函数访问。 比如下面这种方式一般是类内访问。
door.isOpen = true
而下面这种方式一般用在类外访问。
public boolean isOpen() {
return this.isOpen;
}
其实就是合理使用Java中的private、protected、public修饰。
2 . 变量的分块
在一个类中有很多变量,比如private成员变量、static静态变量、public公共变量,除了变量名命名要规范外,还要分区分块。比如static类型的应该放到最上面,并且与其他类型的变量隔开。
3 .参数
一个变量想传递到其他地方有很多方法。比如可以设置成public+static,让别人直接访问。但是这种方式耦合程度太高,比如改一个变量名,所有引用的地方都要修改。可以使用参数,直接把一个状态传递到另外一个地方,而不涉及二者之间的耦合。
4 .变量的初始化:及早初始化和延迟初始化
初始化就是给变量一个默认的状态。 对于对性能影响较低的初始化可以及早初始化,可以保证变量在后续的使用过程中是已经初始化了的。并且变量的初始化要尽量放到一起,这样逻辑更清晰,也更美观。比如Android中的findViewById()在初始化时就要尽量放到一起。 延迟初始化是考虑到了性能问题,如果初始化成本很高,就可以考虑通过延迟初始化来实现快速启动。
1 .主体流
虽然Java是面向对象编程,但是在一个函数中,也是由上到下依次执行。主体流中包含了一些异常判断和卫述句(guard clause)。
2 .卫述句
卫述句是异常检查的一种方式。在程序的主体流中,需要判断某些不适用主体流的情况。
void init() {
if (isInited) {
return;
}
....
}
我们习惯了if-then-else这种约束,当看到if的时候,很容易就想到需要else语句。卫述句则不需要else子句,它是表达的另一种情形,只是一次主体流中的异常判断。
3 .异常传播
当程序发生异常,不能直接将日志信息输出,这样被有心之人抓取到了会产生安全隐患。同样也不能置之不理,这样后面排查问题就很困难。正确的做法是调用异常处理工具,比如写入日志文件等等。
把大块的逻辑分成许多块,每一块都是一个方法,每个方法起一个顾名思义的名称。这样的代码理解起来更容易,更易于维护和重用。
1 .安全复制
如果在一个类中有一个List,存放着若干个类Demo的对象实例,比如
List<Demo> list = new ArrayList<Demo>();
如果外界想访问这个列表呢,通常的做法是这样的
public List<Demo> getDemoList() {
return this.list;
}
这样的话会有安全隐患,我们必须要考虑,外界的逻辑是否会对我们的数据产生影响
getDemoList().clear(); // 数据被莫名修改,会发生一些问题。
考虑更高的安全性,我们可以这么做
public List<Demo> getDemoList() {
List<Demo> copyList = new ArrayList<Demo>();
copyList.addAll(this.list);
return copyList;
}
当然,安全复制不可滥用,如果数据很大,这种复制会占用耗费的内存和性能,所以更好的做法是综合考虑安全性和性能的平衡。
2 .方法注释
对于沟通良好的代码来说,很多注释完全是多余的。在强调命名的沟通性和表达性的时候,注释显得处在一个很别扭的层次上。注释不是越多越好,在必要的时候添加适当的注释是合理的。当然,在后期的代码修改的时候,记得及时更新代码注释,否则注释不正确反倒对阅读人造成了误解。
1 .使用容器应该关心的一个问题
在将容器暴露给外界使用的时候,需要考虑外界是否可以对容器进行修改。比如Iterable无法保证容器中的数据不被修改,外界可以直接调用它的remove方法进行修改,而不通知容器所有者,这样会出现问题。解决办法之一就是返回一个安全拷贝。
2 .不可变的容器
当只想把容器暴露给外界而不希望被修改时,除了安全拷贝外,还可以创建不可修改的容器,当容器的元素被外界修改时,程序会throw exception。
图:不可变的容器
3 .容器的继承和委派
有时候我们可能需要继承一个容器类。比如新建一个书籍列表的类Library,我们的目的是给他提供类似集合一样的增删改查操作。一个很直接的想法是让它继承ArrayList。
public class Library extends ArrayList{
....
}
这种操作实现了Library的add、remove、迭代以及其他容器操作,但是这种方式会带来一些问题。比如一般我们不需要ArrayList的toArray操作,但是如何避免用户调用呢,总不能把所有不需要的方法都加以实现并抛出UnsupportedOperationException来取消继承。
这里可以使用委派来实现这种操作。
public class Library{
Collection<Book> books = new ArrayList<Book>();
}
使用这种方式可以只暴露一些需要的方法。我在头像墙widget中就是这样做的。
public class AvatarWallViewPager{
protected RollViewPager mViewPager;
}
1 .更新框架时的向前兼容性
在更新框架的时候,一个非常重要的点就是向后兼容。更新框架的功能不能影响到现有代码的逻辑。在设计框架的时候,保持代码尽可能简单的同时,也要尽可能提高代码的适用范围,考虑后续框架的更新是否容易。
2 .兼容性更新—增量式更新
包(package)为框架代码的增量式更新提供了一条途径,可以在新的包下创建相同类名的新类。比如把更新后的org.junit.Assert放到org.junit.improved.Assert这个类中,这样的话客户代码只需要更新imports语句就可以实现更新。修改import更加简单,影响更小。
3 .抽象—接口和抽象类
多使用接口和抽象类可以提高框架的灵活度。只将必要的逻辑暴露给外部使用,内部逻辑是private的。当修改框架代码时,只要接口和抽象类的抽象方法不变,其他的逻辑可以灵活修改,外部调用者不会受到任何影响。
这部分主要是介绍了一个度量操作性能的框架。介绍了设计框架时要注意的一些事情。
设计操作度量框架要注意以下几个要点:
将操作执行多次,取平均值,克服精度问题。 在测量时要考虑Java的自动优化机制会不会影响操作执行的时间。
这本书主要是着力于编程时的思维层面上的考虑。开发者大脑中要时刻铭记沟通、简单、灵活的价值观,遵循实现模式,并学会在遇到前所未有的问题时,能在价值观的指导下,利用现有原则找到更为恰当的解决方案。
ps:KM的MarkDown渲染貌似有些问题,某些效果没有我本地MarkDown编辑器渲染的好。附件里提供PDF文档,有需要的可以直接下载了看。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。