《编写可读代码的艺术》总结

花了一天时间将《编写可读代码的艺术》读完,这边对书中提到的知识点做下总结。

《编写可读代码的艺术》 的核心在于通过各种方式实现代码的高可读性。

那么怎么评判可读性?

你只要找一个对项目一点都不了解的人(但至少要有一定的编程知识),然后看他需要多长时间理解这段代码并可以做修改,而这个时间的长度就是可读性的高低。

怎么提高代码的可读性?

书中通过三个层面分别进行讲解:

  • 书面层面
  • 流程控制层面
  • 代码结构层面

书写层面

命名

* 类命名
* 字段命名
* 方法命名 

好的命名可以带来什么好处?

  • 更容易记忆
  • 提供更多信息

相比于字段 a 来说字段 age 能够更让人记忆深刻,而且提供了更多的信息。age 的含义是年龄的意思,而 a 你能说明它代表什么意思吗?

但是 age 提供的信息还不够完整,它到底是谁的年龄呢? 学生? 老师 ? 还是男孩?这个时候有两种方式,一种是直接在字段上体现:

// 学生年龄
int studentAge;

或者是联系上下文看它属于什么对象:

// 学生实体类
public class Student{
	int age;
}

相对于类和属性来说,方法上的有效命名性价比更高。通过好的命名你就知道这个方法的作用了,而不需要深入代码中研究半天才能理解这个方法的作用。

比如方法 sendUserRegistrationInfo 比起 send 来说更能让调用者理解这个方法的作用,前者能很明显的告诉调用者这是一个发送用户注册信息的方法,而后者需要调用者自己研究下到底发送了什么信息。

好坏命名的区别

我们知道了命名的好处,但是为什么我们经常感受不到这些好处呢?是因为命名也有好有坏,不好的命名不仅没有带来这些好处,相反还给我们增加了额外的负担,如:

  • 名字过长
  • 空泛的命名
  • 多重含义的命名
名字过长

名字过长会造成记忆负担,而造成这种情况的原因一般如下:

* 1.对于所要做的事情没有清晰的认识
* 2.所做的事情太多导致不能清晰的表达

对于第一点来说我的建议是在重新整理下需求,对于第二点的解决方法是将它拆分成多个方法,每个方法只做一件事。

空泛的命名

abc 这种没有含义的命名实在是没有出现的必要,但是在 for 循环中的 i 为什么不会引起这种问题呢?

原因是因为它的作用域很小,并且它的使用已经算是一种约定俗成的习惯,不会引起使用者的误解。

多重含义的命名

多重含义的命名带来的就是容易让人误解,到底我这个方法是要做什么行为呢?在不同的语境下相同的词常常会南辕北辙。如果你想不到好的词的话,留下一段注释可能也不失为一种解决办法。

注释

注释的作用如下:

  • 对代码行为做一个概括描述
  • 对代码未体现的事情做个描述
  • 对未完成的事做备注

基于以上两点可以看出好的注释的特征:

* 1.不是所有的行都需要注释
* 2.代码可以自体现的就不需要额外的注释(类、方法上面还是建议有概括性注释)
* 3.不同逻辑间可以做一个概括性描述,让人更容易理解代码逻辑

代码格式

代码格式对于可读性来说很重要,幸好目前的 IDE 都有智能格式化代码样式功能,所以这点对我们来说不需要太多的关注。

简化循环

我们先来看看常用的流程控制逻辑语法:

  • if else
  • for
  • do .. while 和 while
  • 三目表达式

if else

程序中 if else 判断自然是避免不了的,但是多重嵌套的 if else 判断实在不是一种可读性很好的编写方式:

Order order = orderMapple.selectByPrimaryKey(orderId);
if(order != null){
	List<OrderItem> orderItemList = orderItemMapple.selectByPrimaryKey(orderId);
	if(orderItemList!=null && orderItemList.size()>=0){
		.............
	}
}

上面的代码实现了根据订单号获取订单信息,如果订单信息存在的话就获取订单行项目列表,如果订单行项目列表存在的话,就进行一系列的操作。

可以看到这样的多重嵌套在项目中是很频繁的操作,但是如果 “一系列” 操作逻辑比较多的话,一个屏幕的空间都显示不下这个 if 作用域。对于使用者的记忆会有比较大的负担,我们可以采用逆向思维方式的方式来改变下这个代码的结构:

Order order = orderMapple.selectByPrimaryKey(orderId);
if(order == null){
	return ;
}
List<OrderItem> orderItemList = orderItemMapple.selectByPrimaryKey(orderId);
if(orderItemList==null || orderItemList.size()<0){
	return ;
}
.............

可以看到采用这种结构就将多重 if 判断结构转为一重 if 判断,是不是更清晰点呢?

for

继续我们之前的例子,如果订单行项目是多行的话,我们通常使用 for 循环来取值进行操作:

List<OrderItem> orderItemList = orderItemMapple.selectByPrimaryKey(orderId);
OrderItem orderItem = null;
for(int i = 0,size = orderItemList.size; i < size; i++){
	orderItem = orderItemList.get(i);
	......
}

我们尝试另一种风格的 for 循环看看:

List<OrderItem> orderItemList = orderItemMapple.selectByPrimaryKey(orderId);
for(OrderItem orderItem : orderItemList){
	......
}

是不是比第一种可读性更好呢?当然选择哪种风格的 for 循环可以根据使用场景来决定,有时候第一种风格可能更好。

do .. while 和 while

do .. whilewhile 的功能是一样的,唯一的区别在于 do .. while 不管条件是否成立都会执行一次作用域里的代码,而 while 必须条件成立才会执行。

通常我们都会从前往后读取代码,而 do .. while 会让人重复读两边代码(因为条件在最下面,导致会在看到条件后再重新读一遍逻辑)。所以书中建议最好不要使用 do .. while 而采用修改 while 条件的形式。

三目表达式

三目表达式是为了避免写如下代码而出现的:

if(a > b){
	return a;
}else{
	return b;
}

三目表达式形式如下:

return a > b ? a : b;

可以看到三目表达式更简洁,但这是针对于表达式比较简单的逻辑,如果是逻辑比较复杂的可读性会变的相当差:

return a > b ? xxx(a,z) : yyy(b,z);

在这里,三目表达式已经不只是从两个简单的值中做出选择,可读性已经变得不是很好。针对这种情况采用 if else 的方式会更好,虽然代码量增加了,但是可读性同时也增加了。

结构层面

  • 拆分超长的表达式
  • 让方法只做一件事
  • 复用代码实现少些代码

拆分超长的表达式

if(orderType != "0021" && orderType != "0022" 
	&& orderType != "0033" && orderType != "0034"
	&& orderType != "0043" && orderType != "0044"){
	......
}

如上所示,在项目中应该也会经常看到这种代码吧,单据类型不同所走的逻辑也不同。我们来做下调整:


if(isOtherOrder(order.getOrderType())){
	......
}

public boolean isOtherOrder(String orderType){
	List<String> orderTypeList = new ArrayList();
	orderTypeList.add("0021");
	orderTypeList.add("0022");
	orderTypeList.add("0033");
	orderTypeList.add("0034");
	orderTypeList.add("0043");
	orderTypeList.add("0044");
	return orderTypeList.contains(orderType);
}

是否可读性更好了,而且之后如果单据类型增加了,也只需要在 isOtherOrder 方法中增加即可。

让方法只做一件事

在之前我们讲命名过长的时候就已经讲到了要让方法只做一件事情。一方面是可以之后的复用,另一方面是为了更好的明确职责。

复用代码实现少些代码

减少程序 bug 最好的方式就是不写代码,当然这是不太可能的事。为了尽量达到这个目的,复用代码是不可缺少的一件事。

posted @ 2019-08-23 09:55  MarkLogZhu  阅读(428)  评论(0编辑  收藏  举报