个人精进系列-好的代码-命名/注释

为什么需要好的代码?

大部分时间用于阅读和理解代码

  • 词不达意的命名将影响思考和注意力
  • 好代码会明确告诉你它在做什么

借鉴学习

  • 当我们看到写得很漂亮的代码时,会很受启发
  • 鼓励你把自己的代码写得更好

防止破窗

破窗效应

  • 环境中的不良现象如果被放任存在,就会诱使人们仿效,甚至变本加厉。

  • 一幢有少许破窗的建筑,如果破窗不被修理好,可能将会有破坏者破坏更多的窗户。
    最终,他们甚至会闯入建筑内,如果发现无人居住,也许就在那里定居或者纵火。

  • 一面墙,如果出现一些涂鸦而没有被清洗掉,那么很快,墙上就布满了乱七八糟、不堪入目的东西

  • 一条人行道有些许纸屑,不久后就会有更多垃圾,最终人们会视若理所当然地将垃圾顺手丢弃在地上。

  • 这个现象,就是犯罪心理学中的“破窗效应”。

如何防止

  • 任何不良现象会无线扩散,不要做“打破第一扇窗”的人
  • 整洁的代码需要每个人的精心呵护,需要整个团队都具备一些工匠精神
  • 编码前,多想想别人是否觉得你的代码容易理解
  • 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;
```

站在读者的角度

意料之中的提问

  • 当别人阅读你的代码时,有些部分更可能让他们有这样子的想法:

    什么?

    为什么会这样?

  • 你的工作就是要给这部分加上注释。

公布可能的陷阱

  • 当作为一个函数或者类写注释时,可以问自己这样的问题:

    这段代码有什么出人意料的地方?

    会不会被误用?

  • 未雨绸缪,预料到人们使用你的代码时可能会遇到的问题。

“全局观”注释

对于团队的新成员来讲,最难的事情之一就是理解“全局观”。

// 这段代码把我们的业务逻辑与数据库粘在一起。任何应用层代码都不应该直接使用它。

// 这个类看上去很复杂,但它实际上只是个奇妙的缓存。它对系统中的其他部分一无所知。

这正是那种应该包含在高级别注释中的信息。

克服“作者心理障碍”

当你对写注释犹豫不决时,

最好的办法就是直接把你心里所想写下来,

虽然这种注释可能是不成熟的:

// 哦,天啊,如果一旦这个东西在列表中有重复的话会变得很难处理的。

调整

  • 不管你心里想什么,先把它写下来
  • 读一下这段注释,看看有没有什么地方可以改进
  • 不断改进

// 小心:字段代码不会处理列表中的重复数据(因为这很难做到)

你可能感兴趣的:(个人精进系列-好的代码-命名/注释)