背景
公司的测试工具平台开发也快一年了,积累的功能也越来越多,最近刚好需要对测试用例平台模块的相关功能进行修改。 修改的过程真的是痛苦。。。虽然原来已经看过一遍clean code了,但时间久了又回到了老样子。。。
我们趟过代码的水域。我们穿过灌木密布、瀑布暗藏的沼泽地。我们拼命想找到出路,期望有点什么线索能启发我们到底发生了什么事;但目光所及,只是越来越多死气沉沉的代码。 一一 代码整洁之道
整本书看完后,并不觉得所有的东西都一定要按照书中的来做,但有以下的一些经验还是可以参考和采纳的。
有意义的命名
名副其实
选个好名字要花时间,但省下来的时间比花掉的多。注意命名,而且一旦发现有更好的名称,就换掉旧的。如果名称需要注释来补充,那就不算是名副其实。
❌ 错误实例
1 | int d; // 消逝的时间,以日计 |
✅ 正确实例
1 | int elapsedTimeInDays; |
避免误导
别用accountList来指称一组账号,除非它真的是List类型。所以,用accountGroup或bunchOfAccounts,甚至直接用accounts都会好一些。 特别是用小写字母l和大写字母O作为变量名。
❌ 错误实例
1 | int a = l; |
做有意义的区分
以数字系列命名(a1、a2,……aN)是依义命名的对立面。这样的名称纯属误导——完全没有提供正确信息;
❌ 错误实例
1 | public static void copyChars(char a1[], char a2[]) { |
如果 参数名改为source和destination,这个函数就会像样许多。
使用能读出来的名称
尽量避免使用缩写。。。大部分情况,没人知道你这个缩写是怎么缩的。
❌ 错误实例
1 | class DtaRcrd102 { |
✅ 正确实例
1 | class Customer { |
使用可搜索的名称
很多时候不一定记得方法或者变量名在哪个地方,可能需要通过搜索关键词来进行搜索。此时找STUDENT_ID很容易,但想找数字7就麻烦了。
❌ 错误实例
1 | for (int j=0; j<34; j++) { s += (t[j]*4)/5; } |
✅ 正确实例
1 | int realDaysPerIdealDay = 4; |
类名
类名和对象名应该是名词或名词短语。
✅ 正确实例:如 Customer、WikiPage、 Account和AddressParser
❌ 错误实例:如 Manager、Processor
方法名
方法名应当是动词或动词短语。
✅ 正确实例:如 如postPayment、deletePage或 save。
函数
短小
函数不应该大到足以容纳嵌套结构。建议:函数的缩进层级不该多于一层或两层。这样的函数易于阅读和理解。
PS:根据个人经验,只要这个函数超过了30行。。。过一两个月,基本就没办法一眼就能看明白该函数里面做了什么。
1 | def delete_service_instance(namespace, service_name, new_instance_name, deploy_func): |
只做一件事
尽量把逻辑相关的操作都抽象到一个函数内,这也比较容易满足只做一件事。(但事情又可大可小,所以这个看自己去把控了)
1 | # 求给定数中的偶数 |
每个函数一个抽象层级
可以简单理解 :实现的细节以及步骤。
1 | // 全部是步骤 |
别重复自己
抽离出Try/Catch代码
✅ 正确实例
1 | public void delete(Page page) { |
注释
注释不能美化糟糕的代码, 写注释的常见动机之一是糟糕的代码的存在。
我们编写一个模块,发现它令人困扰、乱七八糟。我们知道,它烂透了。我们告诉自己:“喔,最好写点注释!”。。不然过段时间都不知道这函数是在干嘛。。。😂
好注释
- 提供信息的注释,返回某个方法具体的返回值类型以及大致数据格式;
- 对意图的解释;
- 警示;
1
2// Don't run unless you
// have some time to kill. - TODO注释;
- 公共API或者方法中的Javadoc;
坏的注释
多余的注释,把方法实现的逻辑解释了一遍;
1
2
3
4
5
6
7
8
9// Utility method that returns when this.closed is true. Throws an exception
// if the timeout is reached.
public synchronized void waitForClose(final long timeoutMillis) throws Exception {
if(!closed) {
wait(timeoutMillis);
if(!closed)
throw new Exception("MockResponseSender could not be closed");
}
}误导性注释,很多时候表达的意思可能不是代码中真正实现的意思,或者由于很久未维护导致注释与真实内容不一致;
日志式注释;
1
2
3
4
5* Changes (from 11-Oct-2001)
* --------------------------
* 11-Oct-2001 : Re-organised the class and moved it to new package * com.jrefinery.date (DG);
* 05-Nov-2001 : Added a getDescription() method, and eliminated NotableDate * class (DG);
...废话注释;
1
2
3
4
5
6
7
8/** The day of the month. */
private int dayOfMonth;
/** * Returns the day of the month.
** @return the day of the month. */
public int getDayOfMonth() {
return dayOfMonth;
}长期注释掉的代码;
信息过多 or 不明显的联系;
过滤器字节是什么?与那个+1 有关系吗?或与*3 有关?还是与两 者皆有关?为什么用200?1
2
3/* * start with an array that is big enough to hold all the pixels
* (plus filter bytes), and an extra 200 bytes for header info */
this.pngBytes = new byte[((this.width + 1) * this.height * 3) + 200];
测试
- 避免测试不足;
- 采用覆盖率工具;
- 别掠过小测试;
- 被忽略的测试就是对不确定事物的疑问;
- 测试边界条件;
- 全面测试相近的缺陷 ;
- 测试失败的模式有启发性;
- 测试应该快速 。
如何写出上面所述的代码呢?
写代码和写别的东西很像。在写论文或文章时,你先想什么就写什么,然后再打磨它。初稿也许粗陋无序,你就斟酌推敲,直至达到你心目中的样子。
写函数时,一开始都冗长而复杂。有太多缩进和嵌套循环。有过长的参数列表。名称是随意取的,也会有重复的代码。
然后还需要做的:
- 配上一套单元测试,覆盖每行丑陋的代码;
- 然后打磨这些代码,分解函数、修改名称、消除重复,同时保持测试通过;