《编写可读代码的艺术》个人总结

3,421 阅读8分钟

强烈安利一本书《编写可读代码的艺术》,一个程序猿写好的代码很重要。这篇文章是这本书的读后总结,今后我也会根据自己实际开发中的领域添加、修改这篇文章。

代码应该易于理解

  1. 写代码的关键思想是代码应当易于理解,也就算代码的写法应当使别人理解它所需的事件最小化。
  2. 减少代码行数是一个目标,但把理解代码的时间最小化更重要。绝大多数情况这两个目标是不冲突的。
    代码并不是越小越好,例如三元运算符可以简化代码缩小行数,但是滥用可能导致代码理解时间增长。

第一部分:表面层次

命名规范

  1. 名字表示对应信息,词语应更富有表现力。例如:
单词 更多选择
send deliver、dispatch、announce、distribute、route
find search、extract、locate、recover
make create、set up、build、generate、compose、add、new

  1. 避免像 tmp 和 retval、foo 这样无意义的词,但是如果只是为了临时存储变量、作用域只在几行之内,用这样的词命名也无可厚非。
  2. 循环迭代器中的 i、j、iter 和 it 等做索引很空泛,建议用有意义的名字,比如 user_i、member_j 或者直接 ui、mj 之类。
  3. 用具体的名字替代抽象的名字,且为名字附带更多的信息。例如 id,如果 id 的类型重要的话,可以改为 hex_id;变量如果是度量的话带上单位是个好习惯,例如:start_ms,progress_ms 之类
函数 参数名更好的选择
start(int delay) delay_secs
createCache(int size) size_mb
throttleDownload(float limit) limit_kbps
rotate(float angle) degrees_cw

注意:并不应该给程序中的没一个变量都加上一堆的属性,仅在需要的地方加上,注意我们的主旨:代码应该易于理解。

  1. 变量名并不是越长越好,作用域短的变量名字可以尽量简单;如果一个变量作用域很大,则应尽量包含更多的信息。
  2. 需要注意的,变量函数命名应尽量简单(这和上面并不冲突),在包含尽量多的信息下尽量简单。convertToString() 就不如 toString(),doServeLoop() 就不如简单的 serveLoop()

    注意:需要注意的是,命名的主旨:把信息塞入名字中。所做的一切都是为了让代码更好的理解。
    总结:1.使用专业的名字;2.避免空泛的名字;3.使用具体的名字进行描述;4.为变量增加细节;5.作用域大的变量使用更详细的名字

不会误解的名字

命名应该三思:这个名字会被别人解读成其他含义么?

  1. 推荐用 min 和 max 表示极限、first 和 last 表示包含的范围,begin 和 end 表示包含、排除的范围。
    例如,购物车最大容量,cart_too_big_limit 建议改为 max_items_in_cart,同样的命名还有 begin_index、end_index 之类
  2. 命名不要误导使用者。例如:get 和 size 方法一般认为是个轻量方法,所有在 get 方法中尽量不要做复杂、代价沉重的操作。如果有,请换个名字,例如:getPage() 可以换为 downloadPage() 之类。

    总结:变量命名应尽量准确无误解。

审美

  1. 使用一致的布局(有一个前同事经常随意换行、随意空格,他的代码简直逼死强迫症)
  2. 让相似的代码看上去相似(这个可以理解为,相似代码,通过行对齐和列对齐让它看上去一致)
  3. 相关代码分组形成代码块
  4. 代码的顺序尽量有意义。例如,有一堆变量,可以按重要性进行排序,也可以按字母顺序进行排序,也可以根据具体逻辑和业务场景进行排序。
  5. 风格的一致性。大括号放在命名后面还是单独换行都行,但是一定要保持一致。需要注意的是:一致的风格比“正确”的风格更重要。

注释

注释应该尽量帮助读者理解代码的用意

  1. 不要写不需要的注释(不要给不好的命名添加注释,能迅速推断的事实也不需要注释)
  2. 用代码记录思想:
  • 为代码添加评论,记录“坑点”和“陷阱”;
  • 2.为代码中的瑕疵添加注释(例如:“// TODO:处理 JPEG 以外的图像格式”) 标记 | 通常的意义 -------- | --- TODO: | 我还没有处理的事情 FIXME: | 已知无法运行的代码 HACK: | 对一个问题不得不采用的比较粗糙的解决方案 XXX: | 危险!这里有重要问题
  • 为常量添加注释,表明取这个常量具体数值的意义
  1. 站在读者的角度去思考他们需要知道些什么。
  • 为读者答疑解惑,解释意料之外的行为。
  • 全局观注释和总结性注释,给整个类、模块、函数块添加注释解释模块运作,增强读者理解,不至于迷失在细节中。

写出言简意赅的注释

以下的写注释的技巧
// TODO

第二部分:代码逻辑优化

把控制流变得易读

关键思想:把条件、循环和其他对控制流的改变做的越“自然”越好,运用一种方式使读者不用停下来重读你的代码。

  1. 条件语句中参数的位置:变量在左侧,常量在右侧。if (length >= 10) 优于 if (10 <= length)
    有的时候应为担心漏写一个"="而写出 if (obj = NULL) 的 bug,程序员会把顺序调换一下为 if (NULL = obj),这是没有必要的,现代的编译器对于这种问题基本都有警告。
  2. if/else 语句块的顺序 if/else 里的语言遵循如下顺序:
  • 优先处理正逻辑而不是负逻辑
  • 先处理简单的情况,让 if 和 else 在整个屏幕内都可见
  • 先处理有趣和可疑的情况。“有趣”意味着更关心的情况、或者更符合认知的情况,可疑则意味着一些异常情况

    上面三个规则并没有优先级,有时可能会冲突,具体采用哪种就要程序员自己判断了
  1. 三目运算符可以缩减代码行数,但是可能会增加代码复杂度。
    追求最小代码行数,更好的度量方法是最小化人们理解它所需的时间。
  2. 避免 do/while 循环。do/while 循环将条件放在代码的最后面违反了常规的认知,而且写 do/while 循环语句不是那么必要,反正我从没写过。
  3. 从函数中提前返回。有些程序员认为函数中不应该出现多条 return 语言,这是不对的,提前返回没有问题,而且能提高代码可读性。 有的时候程序员很容易写出多层嵌套、多个 if 条件嵌套的代码,在这种情况下提早 return 就很有必要。

拆分超长的表达式

关键思想:把超长的表达式拆分成更容易理解的小块。

  1. 添加“解释变量”。if line.split(':')[0].strip() == "root" 改为 username = line.split(':')[0].strip(),if username == “root”,就是这个意思。
  2. 用德摩根定理操作逻辑表达式,例如 if (!(a && b) 改为 if (!a || !b), if (!(a || b) 改为 if (!a && !b) 更容易简洁。
  3. 复杂的逻辑条件应当拆分为小的语句。 if 语句中应该尽量不要超过两个值,可以用上面的提前 return 返回进行优化。

变量和可读性

关于变量有三个问题:

  1. 变量越多就越难追踪它们的动向
  2. 变量的作用域越大追踪动向花的时间越久
  3. 变量改动的越频繁越难追踪它的当前值
    作为程序员很烦看到一段程序定义了一堆大作用域的变量,然后到处赋值。 所有关于变量有如下规则:
  4. 减少变量,减少程序的中间结果
  5. 缩小变量的作用域,变量应该对尽量少的代码可见。访问控制越严格越好。
    有些编程规范喜欢将变量定义在函数和语句块的顶端,这会强迫读者马上思考这些变量,但这些变量要很久后才会用到,这是不对的。
  6. 变量应该尽量少的修改,只写一次的变量最好。

第三部分:重新组织代码

抽取不相关的子问题

一句话:一个函数一个类应该只干一件事,不相干的问题应该抽取到独立函数独立类中。但要注意的是,凡事过犹不及,过度拆分可能导致程序中过多的小函数,这个度由程序员把握。

一次只做一件事

和上面一样:应该把代码组织得一次只做一件事。

把想法变成代码

这一章难以总结,建议直接看书领悟。
// TODO

少写代码

嘿嘿,最好读的代码就是没有代码。

  1. 不要过度设计
  2. 重新考虑需求,解决版本最简单的问题,能完成工作就行。和上一条一样的目的,不要过度设计。
  3. 经常性通读标准库整个 API,保持一个熟悉程度。

该书最后给出了两个实际的项目问题,写的很好,建议看看。

这本书写的不错,建议花个一两天时间看一遍。 最近在读《iOS 和 macOS 性能优化》这本书,有时间的话写篇技术总结。