乐趣区

读书笔记编写可读代码的艺术

前言

我们曾经在非常成功的软件公司中和出色的工程师一起工作,然而我们所遇到的代码仍有很大的改进空间。实际上,我们曾见到一些很难看的代码,你可能也见过。但是当我们看到写得很漂亮的代码时,会很受启发。好代码会很明确告诉你它在做什么。使用它会很有趣,并且会鼓励你把自己的代码写得更好。本书旨在帮助你把代码写得更好。

每一章都会深入编程的某个方面来讨论如何使代码更容易理解。本书分成四部分:表面层次上的改进命名、注释以及审美——可以用于代码库每一行的小提示。简化循环和逻辑在程序中定义循环、逻辑和变量,从而使得代码更容易理解。重新组织你的代码在更高层次上组织大的代码块以及在功能层次上解决问题的方法。

第 1 章 代码应当易于理解

  • 代码的写法应当使别人理解它所需的时间最小化。
  • 减少代码行数是一个好目标,但把理解代码所需的时间最小化是一个更好的目标。
  • 经常想一想其他人是不是会觉得你的代码容易理解

第一部分 表面层次的改进

  • 选择好的名字
  • 写好的注释
  • 把代码整洁地写成更好的格式

第 2 章 把信息装到名字里

  • 把信息装进名字中
  • 选择专业的词

    • 避免使用 tmp、retval、it、foo 泛泛的词
    • 如果不把循环索引命名为(i、j、k),另一个选择可以是(club_i、members_i、user_i)或者,更简化一点(ci、mi、ui)
  • 用具体的名字替代抽象的名字
  • 使用前缀和后缀来给名字附带更多信息
  • 决定名字的长度

    • 在小的作用域里可以使用短的名字
  • 利用名字的格式来表达含义

第 3 章 不会误解的名字

  • filter 是个二义性单词。我们不清楚它的含义到底是“挑出”还是“减掉”。最好避免使用“filter”这个名字,因为它太容易误解。
  • 推荐用 min 和 max 来表示(包含)极限
  • 建议 命名极限最清楚的方式是在要限制的东西前加上 max_或者 min_。
  • 推荐用 first 和 last 来表示包含的范围
  • 推荐用 begin 和 end 来表示包含 / 排除范围
  • 通常来讲,加上像 is、has、can 或 should 这样的词,可以把布尔值变得更明确。
  • 很多程序员都习惯了把以 get 开始的方法当做“轻量级访问器”这样的用法,它只是简单地返回一个内部成员变量。如果违背这个习惯很可能会误导用户。相反,这个方法应当重命名为像 computeMean() 这样的名字,后者听起来更像是有些代价的操作。

第 4 章 审美

大家都愿意读有美感的代码。通过把代码用一致的、有意义的方式“格式化”,可以把代码变得更容易读,并且可以读得更快。下面是讨论过的一些具体技巧:

  • 如果多个代码块做相似的事情,尝试让它们有同样的剪影,使用一致的布局,让读者很快就习惯这种风格
  • 把代码按“列”对齐可以让代码更容易浏览
  • 如果在一段代码中提到 A、B 和 C,那么不要在另一段中说 B、C 和 A。选择一个有意义的顺序,并始终用这样的顺序
  • 用空行来把大块代码分成逻辑上的“段落”。

第 5 章 该写什么样的注释

注释的目的是帮助读者了解作者在写代码时已经知道的那些事情。本章介绍了如何发现所有的并不那么明显的信息块并且把它们写下来。什么地方不需要注释:

  • 能从代码本身中迅速地推断的事实
  • 用来粉饰烂代码(例如蹩脚的函数名)的“拐杖式注释”——应该把代码改好。你应该记录下来的想法包括:

    • 对于为什么代码写成这样而不是那样的内在理由(“指导性批注”)
    • 代码中的缺陷,使用像 TODO: 或者 XXX: 这样的标记
    • 常量背后的故事,为什么是这个值。
    • 站在读者的立场上思考:预料到代码中哪些部分会让读者说:“啊?”并且给它们加上注释
    • 为普通读者意料之外的行为加上注释
    • 在文件 / 类的级别上使用“全局观”注释来解释所有的部分是如何一起工作的
    • 用注释来总结代码块,使读者不致迷失在细节中

第 6 章 写出言简意赅的注释

本章是关于如何把更多的信息装入更小的空间里。下面是一些具体的提示:

  • 当像“it”和“this”这样的代词可能指代多个事物时,避免使用它们
  • 尽量精确地描述函数的行为
  • 在注释中用精心挑选的输入 / 输出例子进行说明
  • 声明代码的高层次意图,而非明显的细节
  • 用嵌入的注释(如 Function(/arg =/…))来解释难以理解的函数参数
  • 用含义丰富的词来使注释简洁

第二部分 简化循环和逻辑

第 7 章 把控制流变得易读

有几种方法可以让代码的控制流更易读

  • 在写一个比较时(while (bytes_expected > bytes_received)),把改变的值写在左边并且把更稳定的值写在右边更好一些(while (bytes_received
  • 你也可以重新排列 if/else 语句中的语句块。通常来讲,先处理正确的 / 简单的 / 有趣的情况。有时这些准则会冲突,但是当不冲突时,这是要遵循的经验法则
  • 某些编程结构,像三目运算符(:?)、do/while 循环,以及 goto 经常会导致代码的可读性变差。最好不要使用它们,因为总是有更整洁的代替方式
  • 嵌套的代码块需要更加集中精力去理解。每层新的嵌套都需要读者把更多的上下文“压入栈”。应该把它们改写成更加“线性”的代码来避免深嵌套。通常来讲提早返回可以减少嵌套并让代码整洁。“保护语句”(在函数顶部处理简单的情况时)尤其有用。

第 8 章 拆分超长表达式

  • 把超长表达式拆成更容易理解的小块
  • 引入解释变量

第 9 章 变量与可读性

关于程序中的变量是如何快速累积而变得难以跟踪的。你可以通过减少变量的数量和让它们尽量“轻量级”来让代码更有可读性。具体有:

  • 减少变量,即那些妨碍的变量。我们给出了几个例子来演示如何通过立刻处理结果来消除“中间结果”变量
  • 减小每个变量的作用域,越小越好。把变量移到一个有最少代码可以看到它的地方。眼不见,心不烦
  • 只写一次的变量更好。那些只设置一次值的变量(或者 const、final、常量)使得代码更容易理解。

第三部分 重新组织代码

我们会讲到三种组织代码的方法:

  • 抽取出那些与程序主要目的“不相关的子问题”
  • 重新组织代码使它一次只做一件事情
  • 先用自然语言描述代码,然后用这个描述来帮助你找到更整洁的解决方案

第 10 章 抽取不相关的子问题

  • 所谓工程学就是关于把大问题拆分成小问题再把这些问题的解决方案放回一起。把这条原则应用于代码会使代码更健壮并且更容易读。
  • 积极地发现并抽取出不相关的子逻辑

    • 纯工具代码

      • 通常来讲,如果你在想:“我希望我们的库里有 XYZ() 函数”,那么就写一个!(如果它还不存在的话)经过一段时间,你会建立起一组不错的工具代码,后者可以应用于多个项目。
    • 其他多用途代码

      • 当 format_pretty() 中的代码自成一体后改进它变得更容易。当你在使用一个独立的小函数时,感觉添加功能、改进可读性、处理边界情况等都更容易。
    • 创建大量通用代码
    • 项目专有的功能
    • 简化已有接口
    • 按需重塑接口
    • 过犹不及

      • 引入这么多小函数实际上对可读性是不利的,因为读者要关注更多东西,并且按照执行的路径需要跳来跳去

第 11 章 一次只做一件事

  • 应该把代码组织得一次只做一件事情。

第 12 章 把想法变成代码

  • 用自然语言描述程序然后用这个描述来帮助你写出更自然的代码。这个技巧出人意料地简单,但很强大。看到你在描述中所用的词和短语还可以帮助你发现哪些子问题可以拆分出来。但是这个“用自然语言说事情”的过程不仅可以用于写代码。

第 13 章 少写代码

  • 不是所有的程序都需要运行得快,100% 准确,并且能处理所有的输入。如果你真的仔细检查你的需求,有时你可以把它削减成一个简单的问题,只需要较少的代码。
  • 我们所描述的是宇宙的自然法则——随着任何坐标系统的增长,把它粘合在一起所需的复杂度增长得更快。最好的解决办法就是“让你的代码库越小,越轻量级越好”,就算你的项目在增长。那么你就要:

    • 创建越多越好的“工具”代码来减少重复代码(见第 10 章)
    • 减少无用代码或没有用的功能
    • 让你的项目保持分开的子项目状态
    • 总的来说,要小心代码的“重量”。让它保持又轻又灵。
  • 熟悉你周边的库

    • 很多时候,程序员就是不知道现有的库可以解决他们的问题。或者有时,它们忘了库可以做什么。知道你的库能做什么以便你可以使用它,这一点很重要。

我的总结

在日常开发中,比较注重编码规范及命名等细节,个人认为命名和注释写得好是需要观察、积累和总结的,这也很重要,同时在阅读 jdk、spring、mybatis 等优秀框架源码也发现好的命名的重要性,下面总结了一些命名及编码方式,周知的驼峰命名、常量大写等就不列举了。只写一些书中没提到的。

命名相关

  • 动宾格式命名方法

    • prepareContext、prepareEnvironment
  • 前缀命名

    • spring 中 doXXX 是真正做事情的方法,如 doLoadBeanDefinitions、doRegisterBeanDefinitions
    • preXXX、postXXX:前置、后置处理方法
    • loadXXX:loadBeanDefinitions
  • 后缀命名

    • XXXListener:一看就知道是监听器
    • XXXFactory、XXXDelegate、XXXTemplate:使用了工厂模式、委派模式、模板模式等
  • 善用词性

    • listeners.starting()、listeners.started(context) 等不用说就知道区别,表示不同阶段
  • 名词单复数

    • user、users:分别代表一个用户和多个用户

代码风格

  • 按功能划分代码,一个类不超过千行
  • 常量统一写在头部
  • 多次使用的抽象成工具类
  • 语义化、函数式编程

更多信息可以关注我的个人博客:逸竹小站或逸竹小站

也欢迎关注我的公众号:yizhuxiaozhan,二维码:

退出移动版