为什么需要好的代码?
大部分时间用于阅读和理解代码
- 词不达意的命名将影响思考和注意力
- 好代码会明确告诉你它在做什么
借鉴学习
- 当我们看到写得很漂亮的代码时,会很受启发
- 鼓励你把自己的代码写得更好
防止破窗
破窗效应
环境中的不良现象如果被放任存在,就会诱使人们仿效,甚至变本加厉。
一幢有少许破窗的建筑,如果破窗不被修理好,可能将会有破坏者破坏更多的窗户。
最终,他们甚至会闯入建筑内,如果发现无人居住,也许就在那里定居或者纵火。一面墙,如果出现一些涂鸦而没有被清洗掉,那么很快,墙上就布满了乱七八糟、不堪入目的东西
一条人行道有些许纸屑,不久后就会有更多垃圾,最终人们会视若理所当然地将垃圾顺手丢弃在地上。
这个现象,就是犯罪心理学中的“破窗效应”。
如何防止
- 任何不良现象会无线扩散,不要做“打破第一扇窗”的人
- 整洁的代码需要每个人的精心呵护,需要整个团队都具备一些工匠精神
- 编码前,多想想别人是否觉得你的代码容易理解
- Code Review机制,及时发现和修复“破窗”
什么才是好的标准?
代码约少越好?
-
单行代码:
findId(x) >= 0 ? y * (1 + y) : y / (1 - z);
-
等价代码:
if (findId(x) >= 0) { y * (1 + y); } else { y / (1 - z); }
好的标准
别人理解代码所需的时间最小化
命名
命名很难
- 表达性好的名字需要深思熟虑
- 抽象和思考过程(人是懒惰的)
- 找不到合适名字,说明对问题理解不够透彻
保持一致性
每个概念一个词
- 贯穿其中
- 常用方法名称约定
- 新增create
- 添加add
- 删除remove
- 修改update
- 查询(单个结果)get
- 查询(多个结果)list
- 分页查询page
- 统计count
使用对仗词
- 保持对称
- 常用组合
- add/remove
- increment/decrement
- open/close
- begin/end
- insert/delete
- show/hide
- create/destroy
- lock/unlock
- source/target
- first/last
- min/max
- start/stop
- get/set
- next/previous
- up/down
- old/new
后置限定词
- 表示计算结果的变量,限定词置后
- 突出主要含义
- revenueTotal(总收入)
- expenseTotal(总支出)
- revenueAverage(平均收入)
- expenseAverage(平均支出)
统一业务语言
- 减少各方沟通成本
统一技术语言
- 如ddd术语
为名字附带更多信息
例
public String start(long delay)
调整
若变量是一个度量(如时间、长度),带上它的单位。
public String start(long delaySecs)
用具体代替抽象
例1
// 检查服务是否可以监听某个指定的TCP端口
public void canStart(int port)
// 完成后触发
public void trigger()
调整
要具体,不能空泛
public void canListenOnPort(int port)
public void triggerAfterCompletion()
例2
processingData
调整
validateUserCredentials
例3
popRecord()
调整
要体现做什么,而不是怎么做
getLatestEmployee()
显化隐藏的计算过程
例
Matcher matcher = headerPattern.matcher(line);
if(matcher.find()){
headers.put(matcher.group(1), matcher.group(2));
}
调整
命名有含义中间变量,显化隐藏的计算过程
Matcher matcher = headerPattern.matcher(line);
if(matcher.find()){
String userId = matcher.group(1);
String userName = matcher.group(2);
headers.put(userId, userName);
}
名字应该有多长?
采用非常具体的描述性的名字?
public String newNavigationControllerWrappingViewControl()
只用单个单词或者单一字母的名字?
public String s2d()
名字应该恰如其分
在小的作用域可以使用短的名字
if (debug) {
Map m = new HashMap();
lookUpNamesNumbers(m);
System.out.println(m);
}
为作用域大的名字采用更长的名字
Map userNameAgeMap = new HashMap();
// more code
public void someMethod() {
lookUpNamesNumbers(userNameAgeMap);
}
缩略词
doc => document
str => string
丢掉没用的词
convertToInt() => toInt()
不会误解的名字
例1
假设有一个函数用于剪切段落的内容:
public String clip(String text, int length)
猜想
- 从尾部删除length的长度?
- 截掉最大程度为length的一段?
调整
public String truncate(String text, int maxLength)
例2
public static final int CART_TOO_BIG_LIMIT = 10;
CART_TOO_BIG_LIMIT的边界?
使用代码
if(shoppingCart.size() >= CART_TOO_BIG_LIMIT){
System.out.println("Too many items in cart.")
}
购物车逻辑为最多不能超过10件商品
调整
public static final int MAX_ITEMS_IN_CART = 10
给布尔值命名
boolean readPassword = true;
猜想
- 我们需要读取密码?
- 已经读取了密码?
调整
boolean needPassword = true;
boolean userIsAuthenticated = true;
更多
加上像is、has、can或者should,把布尔值变得更加明确
与使用者的期望想匹配
例
public double getMean() {
// Iterate through all samples
// and return total / samplesSize
}
- 期望中的get应该是“轻量级访问器”,可以随意调用。
- 假如存在大量的数据,随意调用将付出很大的代价。
调整
public double computeMean()
注释
什么不需要注释?
不要为那些从代码本身就能快速推断的事实注释
// Find the Node in the given subtree,
// with the given name, using the given depth.
Node findNodeInSubtree(Node subtree, String name, int depth);
不要复述功能
-
对于这种注释,要么删除,要么改进
// Find a Node with the given 'name' or return NULL. // If depth <= 0, only 'subtree' is inspected. // If depth == N, only 'subtree' and N levels below are inspected. Node findNodeInSubtree(Node subtree, String name, int depth);
不要给不好的名称加注释
// Releases the handle for this key.
// This doesn't modify the actual registry.
void deleteRegistry(String key);
调整
void releaseRegistryHandle(String key);
好代码 > 坏代码 + 好注释
代码即文档
加入评论
例1
// 出乎意料的是,对于这些数据用二叉树比用哈希表快40%
// 哈希运算的代价比左/右比较大得多
例2
// 作为整体可能会丢掉几个词,这没有问题,要100%调整太难了
例3
注释也可以用来解释为什么代码写得不那么整洁:
// 这个类正在变得越来越乱
// 也许我们应该建立一个ResourceNode子类来帮助整理
为代码中的瑕疵写注释
// TODO 采用更快的算法
随时把代码将来应该如何改动的思想用注释记录下来 {:&.moveIn}。
这种注释给读者带来对代码质量和当前状态的宝贵见解,甚至可能会给他们指出如何改进代码的方向。
给常量加注释
例
public final static int NUM_THREADS = 8;
这一行看上去可能不需要注释,但很可能选用这个值的程序员需要知道得比这个要多。
调整
```java
// as long as it's >= 2 * num_processors, that's good enough.
public final static int NUM_THREADS = 8;
```
站在读者的角度
意料之中的提问
-
当别人阅读你的代码时,有些部分更可能让他们有这样子的想法:
什么?
为什么会这样?
你的工作就是要给这部分加上注释。
公布可能的陷阱
-
当作为一个函数或者类写注释时,可以问自己这样的问题:
这段代码有什么出人意料的地方?
会不会被误用?
未雨绸缪,预料到人们使用你的代码时可能会遇到的问题。
“全局观”注释
对于团队的新成员来讲,最难的事情之一就是理解“全局观”。
// 这段代码把我们的业务逻辑与数据库粘在一起。任何应用层代码都不应该直接使用它。
// 这个类看上去很复杂,但它实际上只是个奇妙的缓存。它对系统中的其他部分一无所知。
这正是那种应该包含在高级别注释中的信息。
克服“作者心理障碍”
当你对写注释犹豫不决时,
最好的办法就是直接把你心里所想写下来,
虽然这种注释可能是不成熟的:
// 哦,天啊,如果一旦这个东西在列表中有重复的话会变得很难处理的。
调整
- 不管你心里想什么,先把它写下来
- 读一下这段注释,看看有没有什么地方可以改进
- 不断改进
// 小心:字段代码不会处理列表中的重复数据(因为这很难做到)