编写可读代码的艺术

编写可读代码的艺术

代码应当易于理解

Q: 可读性基本定理?

可读性基本原理:使别人理解它所需的时间最小化。

Q: 代码总是越小越好?

减少代码行数是一个好目标,但是把理解代码所需的时间最小化是一个更好的目标。

表面层次的改进

把信息装到名字里

(1)选择专业的词

def getPage(url)

上述例子,get 词没有表达出很多信息。从本地缓存得到一个页面,还是从数据库中,或者从互联网中?如果从互联网中,应该使用更为专业的名字:fetchPage(url)downloadPage(url)

class BinaryTree {
    int size();
}

上述例子,size() 返回的是什么?树的高度,节点树,还是树在内存中所占的空间?size() 没有承载更多的信息,更专业的词汇是 height()numNodes()memoryBytes()

英语是一门丰富的语言,有很多词汇可以选择。下面是一些例子,这些单词更富有表现力,可能更适合你的语境:

单词 更多选择
send deliver、dispatch、announce、distribute、route
find search、extract、locate、recover
start launch、create、begin、open
make create、set up、build、generate、compose、add、new

(2)避免像 tmp 和 retval 这样泛泛的名字

除非你有更好的理由!

(3)用具体的名字代替抽象的名字

searchCanStart()canListenOnPort() 更具体一些,这个名字直接描述了方法所做的事情。

(4)为名字附带更多信息

附带单位

单词 更多选择
start(int delay) delay -> delay_secs
createCache(int size) size -> size_mb
throttleDownload(float limit) limit -> max_kbps
rotate(float angle) angle -> degrees_cw

附带其它重要属性

(5)名字应该有多长

作用域大的名字采用更长的名字,不要用让人费解的一个或两个字母的名字来命名在几屏之间都可见的变量。对于只存在于几行之间的变量用短一点的名字更好。

不会误解的名字

(1)例子:filter()

results = database.allObjects.filter("year <= 2011");

这里的 filter挑出还是减掉

(2)例子:clip(text, length)

这里的 clip 是从尾部删除掉 length 的长度?还是裁掉最大长度为 length 的一段?

(3)推荐用 min 和 max 来表示极限

优化前

CART_TOO_BIG_LIMIT = 10;

if shoppingCart.numItems() >= CART_TOO_BIG_LIMIT:
    error("too many items in cart")

优化后

static final MAX_ITEMS_IN_CART = 10;

if shoppingCart.numItems() > MAX_ITEMS_IN_CART:
    error("too many items in cart.")

(4)推荐用 first 和 last 来表示包含的范围

set.printKeys(first = "Bart", last = "Maggie")

(5)推荐用 begin 和 end 来表示包含/排除范围

(6)给布尔值命名

  • 通常加上 ishascanshould 这样的词,可以把布尔值变得更明确(存疑:JSON 序列化是否有影响?)
  • 最好避免使用反义名字

(7)与使用者的期望相匹配

如果 getMean() 做的是一个比较重的任务,并不是一个轻量级访问器,那么这样的命名换成 computeMean() 会更好一些。

审美

(1)重新安排换行来保持一致

NO

YES

(2)个人风格与一致性

一致的风格比 “正确” 的风格更为重要

该写什么样的注释

好的注释记录你在写代码时的重要想法

写出言简意赅的注释

(1)保持紧凑

NO

YES

(2)具名函数参数的注释

NO - 让人困惑

YES

简化循环和逻辑

把控制流变得易读

(1)条件语句中参数的顺序

  • if (length >= 10)if (10 <= length) 第一个更易读
  • whiler (bytes_received < bytes_expected)while(bytes_expected > bytes_received) 第一个更易读

通用的规则是什么?怎么决定 a < b 好还是 b > a 好?

上述指导原则与英语的语法一致。

(2)if/else 语句块的顺序

  • 首先处理正逻辑,而非负逻辑。
  • 先处理掉简单的情况。
  • 先处理有趣或者可疑的情况。

(3)?: 条件表达式

下述代码应该是用 if/else 拆分开:

return exponent >= 0 ? mantissa * (1 << exponent) : mantissa / (1 << -exponent);

默认情况下都用 if/else,三目运算发 ?: 只有在最简单的情况下使用。

(4)避免 do/while 循环

我的经验是,do 语句是错误和困惑的来源……我倾向于把条件放在“前面我能看到的地方”。其结果是,我倾向于避免使用 do 语句。— C++ 开创者 Bjarne Stroustrup《C++ 程序设计语言》

(5)从函数中提前返回

从函数中提前返回,常常很受欢迎。

public boolean contains(String str, String substr) {
    if (str == null || substr == null) {
        return false;
    }

    if (substr.equals("")) {
        return true;
    }

    // do other things ...
}

(6)最小化嵌套

每个嵌套层次都在读者的 “思维栈” 上又增加了一个条件。

  • 提早返回来减少嵌套
  • 在循环中,提早返回的技术是:continue

拆分超长的表达式

(1)用作解释的变量

NO

if line.split(':')[0].strip() == 'root'

YES

username = line.split(':')[0].strip()
if username == 'root'

(2)总结变量

NO

if (request.user.id == document.owner_id) {
    // user 可疑编辑文档
}

if (request.user.id != document.owner_id) {
    // 文档只可读
}

YES

final boolean user_own_document = (request.user.id == document.owner_id);

if (oser_own_document) {
    // user 可以编辑文档
}

if (!oser_own_document) {
    // 文档只可读
}

变量与可读性

  • 让你的变量对尽量少的代码行可见
  • 操作一个变量的地方越多,越难确定它的当前值。

重新组织代码

抽取不相关的子问题

  • 纯工具函数抽取到一起,从项目中拆分出的独立库越多越好
  • 永远都不要安于使用不理想的接口,你总是可以创建你自己的包装函数来隐藏接口的粗陋细节
  • 不要太过:引入很多小函数对可读性是不利的,因为读者要关注更多东西。

把一般项目和项目专有的代码分开,其结果是,大部分代码都是一般代码,其余的是让你的程序与众不同的核心部分。

一次只做一件事

  • 列出所有任务,一些任务变为单独的函数或类,另外一些可以称为一个函数中的逻辑段落

把想法变成代码

  • 自然语言描述程序,然后用这个描述来帮助你写出更自然的代码

NO

易读无反义(即时有空语句体)

少写代码

最好读的代码就是没有代码

  • 从项目中消除不必要的功能,不要过度设计
  • 重新考虑需求,解决版本最简单的问题,只要能完成工作就行
  • 经常性地通读标准库的整个 API,保持对它们的熟悉程度

精度代码

测试与可读性

  • 测试应当具有可读性,以便其他程序员可以舒服地改变或增加测试
  • 对使用者隐去不重要的细节,以便更重要的细节会更突出
  • 选择一组最简单的输入,它能完整地使用被测代码

扫描下面二维码,在手机端阅读: