概述:
编写程序,如何让程序持续可维护是一项难题。通过对代码的可读性优化,可以让程序变得相对容易维护一些。因此,我们有必要研究研究代码的可读性。《编写可读代码的艺术》这本书读起来感觉不错。花了大半天的时间通读了一遍,随手做了些笔记,当做学习的一部分。有兴趣读的朋友可以在文末免费获取该书的电子档。
以下为通读笔记:
总的来说,目标只有一个—让代码变得易于理解(可读性基本定理)基于此扩展出其他详细细节与方法,当其他方法与该原则冲突时,以遵循此原则为主。
基于可读性基本定理,并不建议代码越小越好,而是应当使别人理解它所花的时间最小化。具体做法有以下十点:
1 写好命名 – 把信息装进名字里
2 不使用歧义的名字 – 考虑该命名是否被误解
3 追求审美 – 统一代码风格
4 写好注释 – 标注好该标注的事情
5 控制流优化 – 把控制流变得易读
6 不使用超长表达式 – 拆分超长表达式
7 变量的可读性 – 尽可能少的变量、尽可能缩小作用域、尽可能减少变量赋值
8 重新封装 – 抽取不相关子问题,重构封装
9 拆分函数 – 每次只做一件事
10 少写代码 – 剔除无用功能,避免过度设计过度开发
以下为脉络笔记:
第一部分 — 表面层次的改进
第一章 — 代码应当易于理解
-
可读性基本定理 — 代码的写法应当使别人理解它所需的时间最小化
-
代码不是越小越好
-
理解代码所需时间与其他目标有冲突时,考虑第一条原则
-
选择好的名字
-
写好的注释
-
把代码整洁的写成更好的格式
第二章 — 把信息装到名字里
无论是命名变量、函数还是类,都可以使用很多相同的原则,把名字当做一条小小的注释
关键思想 — 把信息装入名字中
- 选择专业的词
- 避免泛泛的名字(要知道什么时候使用它)【给名字带上属性】
- 用具体的名字代替抽象的名字
- 使用前缀或后缀给名字附带更多信息(更具体的变量,如带单位等)
- 决定名字的长度
- 首字母缩略词和缩写经验原则
- 丢掉没用的词
- 利用名字的格式来表达含义
名字的长度:
- 在小的作用域里可以使用短的名字
- 在大的作用域里名字就要包含足够的信息以便含义更明确
首字母缩略词和缩写经验原则:团队的新成员是否能理解这个名字的含义,如果能,那就可能没有问题。
丢掉没用的词
名字的格式(以C++为例)
CamelCase — 类名
lower_separated — 变量名
kConstantName — 常量
MACRO_NAME — 宏
offerset_ — 类成员变量
第三章 — 不会误解的名字(关键思想—多问自己几遍,“这个名字会被别人误解成其他含义吗”)
- 推荐用min和max来表示(包含)极限
- 推荐用first和last来包含范围
- 推荐用begin和end来包含/排除范围
- 给布尔值命名(避免使用反义词命名)
- 阅读你代码的人应理解你的本意,而且不会有其他的理解。在决定使用一个名字以前,吹毛求疵一点,想象一下名字会被误解成什么,最好的名字是不会被误解的。
第四章 — 审美
三条原则
-
使用一致的布局,让读者很快习惯这种风格
-
让相似的代码看上去相似
-
把相关的代码行分组,形成代码块
-
重新安排换行来保持一致和紧凑
-
用方法来整理不规则的东西
-
在需要时使用列对齐
-
选一个有意义的顺序,始终一致的使用
-
从最重要到最不重要
-
按字母顺序排序(如果在一段代码中提到ABC,那么不要在另外一段代码中变成BCA,选择有意义的顺序,并始终使用)
-
把声明按块组织起来
-
把代码分成“段落”
-
个人风格与一致性。(个人的风格要与团队的风格保持一直,若冲突则舍弃)
第五章 — 注释
- 了解什么不需要注释(不要为那些从代码本身就能快速推断的事实写注释,不要为了注释而注释)
- 用代码记录你的思想
- 站在读者的角度,去想想他们需要知道什么
- 不要给不好的名字加注释—应该把名字改好
- 记录你的思想
- 加入“导演评论”
- 给常量加注释
- 站在读者角度(意料之中的提问,当别人读你的代码时,有部分可能让他们有种为什么的想法,你的工作就是要给这些部分加上注释)
- 公布可能的陷阱(这段代码有什么出人意料的地方,会不会被误用)
- 全局观注释
- 总结性注释
- 克服“作者心里阻滞”(不想写注释),【因此,当你对注释犹豫不决时,直接把心里想的写下来就好了,然后读一下看看有什么可以改进的,最后不断改进】
注释的标记:
TODO:我还没处理的事情(可以用todo:(小写)表示次要缺陷)
FIXME:已知无法运行的代码
HACK:对一个问题不得不采用的比较粗糙的解决方案
XXX:危险!这里有重要问题
第六章 — 写出言简意赅的注释
- 让注释保持紧凑
- 避免使用不明确的代词
- 润色粗糙的句子
- 精确的表述函数的行为
- 对输入/输出例子来说明特别的情况
- 声明代码的意图
- “具名函数参数”的注释(不加注释的参数有些看不懂)【如connect(10,false); 10和false的含义使函数调用有点难以理解】
- 采用信息含量高的词
第二部分 — 简化循环和逻辑
第七章 — 把控制流变得易读
比较式的左边更倾向于不断变化
比较式的右边更倾向于常量
if/else语句块的顺序
- 先处理正逻辑
- 先处理简单情况
- 先处理有趣的或者可疑的情况
三目运算
默认情况都用if/else ,只有在最简单的情况下使用三目运算
避免do/while循环
实践当中大多数do/while都可以写成while
从函数中提前返回(有结果的可以尽可能早的return)
避免使用goto
最小化嵌套
- 嵌套是如何积累而成的(当你对代码改动时,把它看做一个整体重新审视对待)
- 通过提早return减少嵌套
- 减少循环内的嵌套
理解执行流程
理想的情况是整个程序的执行路径都很容易理解—从main函数开始一步步执行,直到程序结束
低层次控制流:循环、条件、跳转
高层次控制流:线程、信号量/终端处理程序、异常、函数指针和匿名函数、虚方法
(高层次程序流程如何变得不清晰的)
线程:不清楚什么时间执行代码
信号量/终端处理程序:有些代码随时都可能执行
异常:可能会从多个函数调用中像冒泡一样执行
函数指针和匿名函数:很难知道会执行什么代码,因为在编译时还没决定
虚方法:可能会调用一个未知子类的代码
第八章 — 拆分超长的表达式
【研究表明,大多数人同时只能考虑3-4件事,简单来说,代码中的表达式越长,它就越难理解】
把超长的表达式拆分成更容易理解的小块
- 用做解释的变量(拆分表达式最简单的方法就是引入一个额外的变量,让他来表示一个小一点的子表达式)
- 总结变量(把一个不需要解释的表达式装入一个新的变量中仍然有用,我们叫它做总结变量,目的是只用一个很短的名字来代替一大块代码,这样更容易管理和思考)
- 使用德摩根定理(分别取反,转换与/或)
- 滥用短路逻辑
- 找到更优雅的方式。
- 拆分巨大的语句
- 另一个简化表达式的创意方法 — 使用宏
第九章 — 变量与可读性
- 减少变量
- 没有价值的临时变量
- 减少中间结果
- 减少控制流变量
- 缩小变量的作用域
- 把定义向下移(把每个定义移动到对它使用前,而不是定义在类最上面)
- 只写一次的变量更好(操作一个变量地方越多,越难确定它当前值)
第三部分 — 重新组织代码
第十章 — 抽取不相关的子问题(积极发现并抽取不相关的自逻辑)
【所谓工程学,就是把大问题拆分成小问题,再把这些问题的解决方案放回一起】
- 看看某个函数或代码块,问问自己,这段代码高层次目标是什么
- 对于每一行代码,问一下,它是直接为了目标而工作吗,搞层次目标是什么
- 如果足够行数在解决不相关的子问题,抽取代码到独立的函数中
- 纯工具代码
- 其他多用途代码
- 创建大量通用代码
- 项目专有的功能
- 简化已有接口
- 按需重塑接口
第十一章 — 一次只做一件事
- 列出代码所做的所有任务
- 任务可以很小
- 从对象中抽取值
- 尽可能把这件任务拆分到不同的函数中,或者至少是代码不同的段落中
- 应用一次只做一件事原则
第十二章 — 把想法变成代码
- 清楚的描述逻辑
- 了解函数库
- 把方法应用于更大的问题
- 用自然语言描述解决方案
- 递归地使用这种方法
第十三章 — 少写代码
- 从项目中消除不必要的功能,不要过度设计
- 质疑和拆分需求,解决版本最简单的问题,只要能完成工作就行
- 保持小代码库
- 经常了解标准库的API,保持对它们的熟悉读
第四部分 — 精选话题
第十四章 — 测试与可读性
- 使得测试易于阅读和维护(测试应当具有可读性,以便其他程序员可以改变或增加测试)
- 每个测试的最高一层应越简明越好,最好每个测试的输入输出都可以用一行代码来描述
- 如果测试失败了,它所发出的错误消息应能让你容易跟踪并修正这个bug
- 使用最简单且完整运用代码的测试输入
- 给测试函数取一个有完整性描述的名字,不要用test1这种名字
第十五章 — 设计并改进“分钟/小时计数器”
略
研究iOS的朋友可以入群一起讨论,Q群号:201708926