上篇Blog讲到:高内聚-低耦合是解决大型重构的手段,相对于编码规范,它能够在更高层次上提高代码的可读性和可维护性,是大型重构的重要手段,从面向对象设计思想层面角度就是:封装、抽象,从思想的最佳实践角度就是基于接口而非实现编程、多用组合少用继承,从设计原则层面角度就是:SRP、ISP、LOD,从设计模式角度就是:观察者模式、适配器模式等。从系统设计角度就是:模块化,从编程技巧角度就是:DI依赖注入。本篇Blog则了解小型重构处理手段:编码规范。说起编码规范,其实我之前有一个学习阿里巴巴编码规范的系列【阿里巴巴Java编程规范学习 一】,所以本篇也无需过于详细,阿里巴巴代码规范执着于术,本篇则重点于道,重点是感受。
命名注释
关于命名的一些核心思想
- 命名的关键是能准确达意。不同作用域的命名可以适当地选择不同的长度。作用域小的变量(比如临时变量),可以适当地选择短一些的命名方式,相反,对于类名这种作用域比较大的,更推荐用长的命名方式。命名中也可以使用一些耳熟能详的缩写,例如:sec 表示 second、str 表示 string、num 表示 number、doc 表示 document
- 可以借助类的信息来简化属性、函数的命名,利用函数的信息来简化函数参数的命名。例如:
User
类的属性userName
简化为name
,再比如uploadUserAvatarImageToAliyun(String userAvatarImageUri)
简化为uploadUserAvatarImageToAliyun(String imageUri);
- 命名要可读、可搜索。不要使用生僻的、不好读的英文单词来命名。除此之外,命名要符合项目的统一规范,不要用些反直觉的命名,例如大家都用
selectXXX
表示查询,你就不要用queryXXX
;大家都用insertXXX
表示插入一条数据,你就要不用addXXX
以上三条,分别从命名的长度控制,命名作用域简化,命名形式三个角度来明确如何命名
关于注释的一些核心思想
- 注释写什么内容:注释的目的就是让代码更容易看懂。只要符合这个要求的内容,就可以将它写到注释里。总结一下,注释的内容主要包含这样三个方面:做什么(功能是什么)、为什么做(提供给谁用,目的是什么)、怎么做(实现逻辑)。对于一些复杂的类和接口,可能还需要写明如何用
- 注释是不是越多越好:注释本身有一定的维护成本,所以并非越多越好。类和函数一定要写注释,而且要写得尽可能全面、详细,而函数内部的注释要相对少一些,一般都是靠好的命名、提炼函数、解释性变量、总结性注释来提高代码可读性
总而言之就是注释非常重要,但注释也不能太多,好注释用在刀刃上,命名和拆分能解决的问题,就不需要详尽的注释了。
代码风格
代码风格,很难说哪种风格更好。最重要的,也是最需要做到的,是在团队、项目中保持风格统一,让代码像同一个人写出来的,整齐划一。这样能减少阅读干扰,提高代码的可读性
- 类、函数多大才合适:对于函数代码行数的最大限制:最好不要超过50行;对于类的代码行数的最大限制:当一个类的代码读起来让你感觉头大了,实现某个功能时不知道该用哪个函数了,想用哪个函数翻半天都找不到了,只用到类的一个小功能要引入整个类(类中包含很多无关此功能实现的函数)的时候,这就说明类的行数过多了
- 一行代码多长最合适,一行代码最长不能超过 IDE 显示的宽度。需要滚动鼠标才能查看一行的全部代码,显然不利于代码的阅读
- 善用空行分割单元块,对于比较长的函数,如果逻辑上可以分为几个独立的代码块,在不方便将这些独立的代码块抽取成小函数的情况下,为了让逻辑更加清晰,除了用总结性注释的方法之外,还可以使用空行来分割各个代码块
- 类中成员的排列顺序,常用的排序规则如下:
- 在类中,成员变量排在函数的前面。
- 成员变量之间或函数之间,都是按照先静态(静态函数或静态成员变量)、后普通(非静态函数或非静态成员变量的方式来排列
- 成员变量之间或函数之间,还会按照作用域范围从大到小的顺序来排列,先写 public 成员变量或函数,然后是 protected 的,最后是 private 的
关于类成员的排列顺序,还有另外一种排列习惯,那就是把有调用关系的函数放到一块。比如,一个 public 函数调用了另外一个 private 函数,那就把这两者放到一块
编程技巧
掌握一些使用的编程技巧
- 把代码分割成更小的单元块,大段的代码逻辑会让阅读代码的人不至于迷失在细节中,要善于将代码拆分,需要注意
- 写代码时要有模块化和抽象思维,善于将大块的复杂逻辑提炼成类或者函数,屏蔽掉细节,这样能极大地提高代码的可读性。
- 只有代码逻辑比较复杂的时候,才建议提炼类或者函数。毕竟如果提炼出的函数只包含两三行代码,在阅读代码的时候,还得跳过去看一下,这样反倒增加了阅读成本。我就非常容易在这里矫枉过正。
- 避免函数参数过多,函数包含 4 个以内参数的时候还是能接受的,大于等于 5 个的时候,会影响到代码的可读性,解决方法有两种
- 考虑函数是否职责单一,是否能通过拆分成多个函数的方式来减少参数
- 将函数的参数封装成对象,这样可以动态添加参数,如果函数是对外暴露的远程接口,将参数封装成对象,还可以提高接口的兼容性,不需要改接口定义,只改对应参数
- 勿用函数参数来控制逻辑,不要在函数中使用【布尔类型的标识参数】或者【根据参数是否为 null】来控制内部逻辑,true 的时候走这块逻辑,false 的时候走另一块逻辑,这明显违背了单一职责原则和接口隔离原则。建议将其拆成两个函数,拆分之后的函数职责更明确,不容易用错,可读性上也要更好。
- 鉴于大多数场景使用标识是因为可能按照标识拆分后的两个函数大多数代码会重复,所以可以将这部分重复逻辑抽取为一个单独的函数,这种解决方式比标识更好一些。
- 函数设计要职责单一,SRP针对的是类、模块这样的应用对象。实际上,对于函数的设计来说,更要满足单一职责原则。相对于类和模块,函数的粒度比较小,代码行数少,所以在应用单一职责原则的时候,没有像应用到类或者模块那样模棱两可,能多单一就多单一
- 移除过深的嵌套层次,嵌套最好不超过两层,超过两层之后就要思考一下是否可以减少嵌套。过深的嵌套本身理解起来就比较费劲
- 解决嵌套问题最好的方式就是校验前置,也就是防卫式编程,使用编程语言提供的 continue、break、return 关键字,在不满足条件时退出,而不是满足条件时继续
- 学会使用解释性变量,例如常量取代魔法数字;解释性变量来解释复杂表达式,也就是把复杂判断条件总结为带有判断目的命名的变量
掌握以上编程技巧可以让代码可读性更强
总结一下
代码规范是一个永久的话题,每个人的理解不一样,但是一些通用的实践还是有帮助的。之前学习了《阿里代码规范》后有了一些进步,但还是有做的不好的地方:命名时想要最达意的英文,有时候单词虽然达意但生僻,自己再看都不知道什么意思;命名没有和大家整齐划一,之前项目大家用insert,我就使用create,项目体感比较杂乱;注释做的还是不够,重要方法注释只做到了:是什么,对于为什么、怎么做没有详细说明;类中成员方法的排序也没有按照作用域排列过,静态成员也没有放到动态成员的前面;在拆分代码时有时矫枉过正,单纯为了缩代码行数,不是复杂逻辑或独立逻辑也拆方法出去;方法中使用了一些标识参数以及通过参数为null进行业务逻辑判断,因为这个代码出过bug。at last,只能说:内功修炼永无止境。