第三部分:理论五
第三部分:理论五
命名
1、命名多长最合适?
- 长的命名可以包含更多的信息,更能准确直观地表达意图,但最好不要长到两行的程度,影响代码的可读性。
- 在足够表达其含义的情况下,命名当然是越短越好。对于一些默认的、大家都比较熟知的词,我比较推荐用缩写。
- 对于作用域比较小的变量,我们可以使用相对短的命名,比如一些函数内的临时变量。
- 对于类名这种作用域比较大的,我更推荐用长的命名方式。
2、利用上下文简化命名
- 文中举例,在 User 类这样一个上下文中,我们没有在成员变量的命名中重复添加“user”这样一个前缀单词,而是直接命名为 name、password、avatarUrl。比如:user.getName()。这个问题还挺常见的。
- 函数参数也可以借助函数这个上下文来简化命名。
未简化命名:
public class User {
private String userName;
private String userPassword;
private String userAvatarUrl;
//...
}
借助对象上下文简化命名:
User user = new User();
user.getName(); // 借助 user 对象这个上下文
借助函数上下文简化命名:
public void uploadUserAvatarImageToAliyun(String userAvatarImageUri);
// 利用上下文简化为:
public void uploadUserAvatarImageToAliyun(String imageUri);
3、命名要可读、可搜索
- 我这里所说的“可读”,指的是不要用一些特别生僻、难发音的英文单词赖命名。
- 我们在 IDE 中编写代码的时候,经常会用“关键词联想”的方法来自动补全和搜索。我们在命名的时候,最好能符合整个项目的命名习惯。大家都用“selectXXX”表示查询,你就不要用“queryXXX”。这点也挺好挺有用的。
4、如何命名接口和抽象类
- 对于接口的命名,一般有两种比较常见的方式:
- 一种是加前缀“I”,表示一个 Interface。比如 IUserService,对应的实现类命名为 UserService。
- 另一种是不加前缀,比如 UserService,对应的实现类加后缀“Impl”,比如 UserServiceImpl。
- 对于抽象类的命名,也有两种方式:
- 一种是带上前缀“Abstract”,比如 AbstractConfiguration。
- 另一种是不带前缀“Abstract”。
- 对于接口和抽象类,选择哪种命名方式都是可以的,只要项目里能够统一就行。
注释
- 命名很重要,注释跟命名同等重要。
- 很多人说,好的命名完全可以替代注释。如果需要注释,那说明命名不够好,需要在命名上下功夫,而不是添加注释。
- 作者认为,这样的观点有点太过极端。命名再好,毕竟有长度限制,不可能足够详尽,而这个时候,注释就是一个很好的补充。
- 简直完全同意,项目通篇不写一个字注释的,不是装逼就是懒。
1、注释到底该写什么?
- 注释的目的就是让代码更容易看懂。
- 注释的内容主要包含这样三个方面:做什么、为什么、怎么做。
- 有些人认为,“做什么、怎么做”在代码中都可以体现出来,只需要写清楚“为什么”,表明代码的设计意图即可。我个人不是特别认可这样的观点:
- 注释比代码承载的信息更多。对于类来说,包含的信息比较多,一个简单的命名就不够全面详尽了。这个时候,在注释中写明“做什么”就合情合理了。
- 注释起到总结性作用、文档的作用。在注释中,关于具体的代码实现思路,我们可以写一些总结性的说明、特殊情况的说明。这样能够让阅读代码的人通过注释就能大概了解代码的实现思路,阅读起来就会更加容易。我们可能还需要在注释中写清楚“如何用”,举一些简单的 quick start 的例子,让使用者在不阅读代码的情况下,快速地知道该如何使用。
- 一些总结性注释能让代码结构更清晰。对于逻辑比较复杂的代码或者比较长的函数,如果不好提炼、不好拆分成小的函数调用,那我们可以借助总结性的注释来让代码结构更清晰、更有条理。
注释示例:
/**
* (what) Bean factory to create beans.
*
* (why) The class likes Spring IOC framework, but is more lightweight.
*
* (how) Create objects from different sources sequentially:
* user specified object > SPI > configuration > default object.
*/
public class BeansFactory {
// ...
}
2、注释是不是越多越好?
- 注释太多和太少都有问题。太多,有可能意味着代码写得不够可读,需要写很多注释来补充。
- 注释太多也会对代码本身的阅读起到干扰。而且,后期的维护成本也比较高,有时候代码改了,注释忘了同步修改,就会让代码阅读者更加迷惑。
- 类和函数一定要写注释,而且要写得尽可能全面、详细,而函数内部的注释要相对少一些,一般都是靠好的命名、提炼函数、解释性变量、总结性注释来提高代码的可读性。
代码风格
类、函数多大才合适?
- 类或函数的代码行数太多,一个类上千行,一个函数几百行,逻辑过于繁杂,阅读代码的时候,很容易就会看了后面忘了前面。
- 类或函数的代码行数太少,在代码总量相同的情况下,被分割成的类和函数就会相应增多,调用关系就会变得更复杂,阅读某个代码逻辑的时候,需要频繁地在 n 多类或者 n 多函数之间跳来跳去,阅读体验也不好。
- 对于函数代码行数的最大限制,不要超过一个显示屏的垂直高度,让一个函数的代码完整地显示在 IDE 中,大概是50行左右。
- 对于类的代码行数的最大限制,这个就更难给出一个确切的值了。当一个类的代码读起来让你感觉头大了,实现某个功能时不知道该用哪个函数了,想用哪个函数翻半天都找不到了,只用到一个小功能要引入整个类(类中包含很多无关此功能实现的函数)的时候,这就说明类的行数过多了。
一行代码多长最合适?
- 一行代码最长限制为 100 个字符左右。
- 一行代码最长不能超过 IDE 显示的宽度。
善用空行分割单元块
- 对于比较长的函数,如果逻辑上可以分为几个独立的代码块,可以使用空行来分割各个代码块。
- 在类的成员变量与函数之间、静态成员变量与普通成员变量之间、各函数之间、甚至各成员变量之间,我们都可以通过添加空行的方式,让这些不同模块的代码之间,界限更加明确。
四格缩进还是两格缩进?
- 到底应该是两格缩进还是四格缩进,只要项目内部能够统一就行了。
- 作者个人推荐两格缩进,节省空间。在代码嵌套层次比较深的情况下,累计缩进较多会导致一个语句折成两行。这块说的有一定道理,不过我们团队推行的是四格缩进,PHP用四格缩进的比较多。
- 另外,作者反对用tab键缩进。这里不能认同,只要大家约定好缩进格数,也是可以统一风格的。我这里甚至推荐用tab键缩进,减少键盘敲击次数。
大括号是否要另起一行?
- 首先还是只要团队统一、业内统一、跟开源项目看齐就好了,没有绝对的优劣之分。
- 作者推荐将括号放到跟语句同一行的风格。我平时喜欢另起一行的风格,用vim折行方便整齐,还可以露出函数名方便查看。
类中成员的排列顺序
- 在类中,成员变量排在函数的前面。
- 成员变量之间或函数之间,都是按照“先静态(静态函数或静态成员变量)、后普通(非静态函数或非静态成员变量)”的方式来排列的。
- 成员变量之间或函数之间,还会按照作用域范围从大到小的顺序来排列,先写 public 成员变量或函数,然后是 protected 的,最后是 private 的。也有的排序把有调用关系的函数放到一块。
- 我在排序这块以前好像一直都是瞎写的。。。
其余技巧
把代码分割成更小的单元块
- 大部分人阅读代码的习惯都是,先看整体再看细节。
- 将大块的复杂逻辑提炼成类或者函数,屏蔽掉细节,让阅读代码的人不至于迷失在细节中,这样能极大地提高代码的可读性。
- 如果提炼出的函数只包含两三行代码,在阅读代码的时候,还得跳过去看一下,这样反倒增加了阅读成本。但是作者在后面一个例子就把一串ifelse分成三个只有两三行代码的小函数。迷惑。。。
避免函数参数过多
- 函数包含 3、4 个参数的时候还是能接受的,大于等于 5 个的时候,就有点过多,影响代码可读性,使用起来也不方便。
- 一般有两种解决方法:
- 考虑函数是否职责单一,是否能通过拆分成多个函数的方式来减少参数。
- 将函数的参数封装成对象。这种我比较常用,尤其是一组同类参数,封装成对象是最好的,参数有增减也方便。
勿用函数参数来控制逻辑
- 不要在函数中使用布尔类型的标识参数来控制内部逻辑,true 的时候走这块逻辑,false 的时候走另一块逻辑。这明显违背了单一职责原则和接口隔离原则。我建议将其拆成两个函数,可读性上也要更好。
- 不过,如果函数是 private 私有函数,影响范围有限,或者拆分之后的两个函数经常同时被调用,我们可以酌情考虑保留标识参数。比如根据标识参数来判断要调用两个函数中的哪一个。
- 除了布尔类型作为标识参数来控制逻辑的情况外,还有一种“根据参数是否为 null”来控制逻辑的情况。针对这种情况,我们也应该将其拆分成多个函数。拆分之后的函数职责更明确,不容易用错。
函数设计要职责单一
- 对于函数的设计来说,更要满足单一职责原则。相对于类和模块,函数的粒度比较小,代码行数少,所以在应用单一职责原则的时候,没有像应用到类或者模块那样模棱两可,能多单一就多单一。
- 作者分别把电话、用户名、邮箱是否为空的检查,分成三个函数,每个里面就两行代码,这里是否划分过细,是个思考点。
移除过深的嵌套层次
- 嵌套最好不超过两层,超过两层之后就要思考一下是否可以减少嵌套。
- 过深的嵌套本身理解起来就比较费劲,除此之外,嵌套过深很容易因为代码多次缩进,导致嵌套内部的语句超过一行的长度而折成两行,影响代码的整洁。
- 解决嵌套过深的方法也比较成熟,有下面 4 种常见的思路:
- 去掉多余的 if 或 else 语句。文中举例,有些情况其实不需要else,只要if处理特殊情况,剩下的代码接着往下走就行。
- 使用编程语言提供的 continue、break、return 关键字,提前退出嵌套。这种方案还是比较常用的。
- 调整执行顺序来减少嵌套。这个需要一点小技巧性,编码时要多动脑,多思考。有时一下想不出来,也可以先把逻辑写出来,看哪里别扭啰嗦,再调整。
- 将部分嵌套逻辑封装成函数调用,以此来减少嵌套。
学会使用解释性变量
- 常量取代魔法数字。如果多次使用,好处更大。
- 使用解释性变量来解释复杂表达式。在PHP中没用过,也不知道是否支持,这是用一个变量来代替一个长的表达式。