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


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

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

    那么怎么评判可读性?

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

    怎么提高代码的可读性?

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

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

    书写层面

    命名

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

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

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

    相比于字段 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 最好的方式就是不写代码,当然这是不太可能的事。为了尽量达到这个目的,复用代码是不可缺少的一件事。

  • 相关阅读:
    第二节:如何正确使用WebApi和使用过程中的一些坑
    nodejs中function*、yield和Promise的示例
    关于nodejs访问mysql的思考
    nodejs使用log4js记录日志
    nodejs初识
    Spring学习笔记(入门)
    mybatis使用注解开发
    MyBatis配置文件中的常用配置
    using 自动释放资源示例
    Java将byte[]和int的互相转换
  • 原文地址:https://www.cnblogs.com/markLogZhu/p/11398266.html
Copyright © 2020-2023  润新知