书名:编写可读代码的艺术
作者:Dustin Boswell & Trevor Foucher
译者:尹哲,郑秀雯
以下是书里文字的引用与整理
前言
可读性基本定理:代码的写法应当使别人理解它所需的时间最小化。
一、表面层次的改进
1. 把信息装到名字里
1)选择专业的词
清晰和精确比装可爱好。
单词 | 更多选择 |
---|---|
Get | Fetch, Download |
Stop | Kill, Pause |
Send | Deliver, Dispatch, Announce, Distribute, Route |
Find | Search, Extract, Locate, Recover |
Start | Launch, Create, Begin, Open |
Make | Create, SetUp, Build, Generate, Compose, Add, New |
2)避免使用像tmp和retval这样泛泛的名字
retval这个名字没有包含很多信息,用一个描述该变量的值的名字来代替它。
tmp这个名字只应用于短期存在且临时性为其主要存在因素的变量。
如果你要使用像tmp、it或者retval这样空泛的名字,那么你要有个好的理由。
3)用具体的名字代替抽象的名字
在给变量、函数或者其他元素命名时,要把它描述得更具体而不是更抽象。
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 |
在对于这个变量存在危险或者意外的任何时候你也该采用它。
情形 | 变量名 | 更好的名字 |
---|---|---|
一个“纯文本”格式的密码,需要加密后才能进一步使用 | password | plaintext_password |
一条用户提供的注释,需要转义之后才能用于显示 | comment | unescaped_comment |
已转化为UTF-8格式的html字节 | html | html_utf8 |
以“url方式编码”的输入数据 | data | data_urlenc |
5)名字应该有多长
在小的作用域里可以使用短的名字,如果一个标识符有较大的作用域,那么它的名字就要包含足够的信息以便含义更清楚。
对于首字母缩略词和缩写,使用项目所特有的缩写词非常糟糕,经验原则是:团队的新成员是否能理解这个名字的含义。
丢掉没用的词:有时名字中的某些单词可以拿掉而不会损失任何信息。
6)利用名字的格式来传递含义
项目中使用一致的命名规范,使得对于下划线、连字符和大小写的使用方式也可以把更多的信息装到名字中。
2. 不会误解的名字
- 要多问自己几遍:“这个名字会被别人解读成其他的意义吗?”
- 与使用者的期望相匹配
- 用min和max来表示(包含)极限
- 用first和last来表示包含的范围
- 用begin和end来表示包含/排除范围
- 使用is和has这样的词来明确表示它是布尔值,避免使用反义的词
3. 审美
1)三条原则
- 使用一致的布局,让读者很快就习惯这种风格
- 让相似的代码看上去相似
- 把相关的代码行分组,形成代码块
2)方法
- 重新安排换行来保持一致和紧凑
- 用方法来整理不规则的东西
- 在需要时使用列对齐
- 选一个有意义的顺序,始终一致的使用它:
- 让变量的顺序与对应的HTML表单中<input>字段的顺序相匹配
- 从“最重要”到“最不重要”排序
- 按字母顺序排序
- 把声明按块组织起来
- 把代码分成“段落”
- 一致的风格比“正确”的风格更重要
4. 该写什么样的注释
注释的目的是尽量帮助读者了解得和作者一样多。
1)什么不需要注释
- 不要为那些从代码本身就能快速推断的事实写注释
- 不要为了注释而注释
- 不要给不好的名字加注释——应该把名字改好(好代码 > 坏代码 + 好注释)
2)记录你的思想
- 记录你对代码有价值的见解
- 为代码中的瑕疵写注释
- 给常量加注释
3)站在读者的角度
- 回答意料之中的提问
- 公布可能的陷阱
- “全局观”注释:类之间如何交互、数据如何在整个系统中流动、以及入口点在哪里
- 总结性注释
4)客服“作者心理阻滞”
- 不管你心里想什么,先把它写下来
- 读一下这段注释,看看有没有什么地方可以改进
- 不断改进
5. 写出言简意赅地注释
注释应当有很高的“信息/空间率”
- 让注释保持紧凑
- 避免使用不明确的代词
- 润色粗糙的句子
- 精确的描述函数的行为
- 用输入/输出例子来说明特别的情况
- 声明代码的意图
- “具名函数参数”的注释
- 采用信息含量高的词
二、简化循环和逻辑
1. 把控制流变得易读
把条件、循环以及其他对控制流的改变做得越“自然”越好
运用一种方式使读者不用停下来重读你的代码
1)条件语句中参数的顺序
比较的左侧 | 比较的右侧 |
---|---|
“被询问的”表达式,它的值更倾向于不断变化 | 用来作比较的表达式,它的值更倾向于常量 |
2)if/else语句块的顺序
- 首先处理正逻辑而不是负逻辑的情况
- 先处理掉简单的情况
- 先处理有趣的或者是可疑的情况
3)?:条件表达式(三目运算符)
默认情况下都用if/else,三目运算符?:只有在最简单的情况下使用。
4)避免do/while循环
do/while的奇怪之处是一个代码块是否会执行时由其后的一个条件决定的。通常来说,逻辑条件应该出现在它们所“保护”的代码之前,这就使得很多读者最后会读do/while代码两遍。
但仅仅是为了去掉do/while循环而重复一段代码是有点愚蠢的做法:
body
while(condition){
body(again)
}
幸运的是,我们发现实践当中大多数的do/while循环都可以写成这样开头的while循环:
public boolean ListHasNode(Node node, String name, int max_length){
while(node != null && max_length > 0){
if(node.name().equals(name)) return true;
node = node.next();
}
return false;
}
5)从函数中提前返回
有些程序员认为函数中永远不应该出现多条return语句,这是胡说八道。从函数中提前返回没有问题,而且常常很受欢迎。
想要单一出口点的一个动机是保证调用函数结尾的清理代码。但现代的编程语言为这种保证提供了更精细的方式:
语言 | 清理代码的结构化术语 |
---|---|
C++ | 析构函数 |
Java、Python | try finally |
Python | with |
C# | using |
6)最小化嵌套
- 当你对代码做改动时,从全新的角度审视它,把它作为一个整体来看待
- 通过提早返回来减少嵌套(if:return; for:continue)
2. 拆分超长的表达式
把你的超长表达式拆分成更容易理解的小块
1)用作解释的变量
拆分表达式最简单的方法就是引入一个额外的变量,让它来表示一个小一点的子表达式:
if line.split(':')[0].strip() == "root";
username = line.split(':')[0].strip();
if username == "root";
2)总结变量
总结变量的目的只是用一个短很多的名字来代替一大块代码,这个名字会更容易管理和思考:
原表达式 | 总结变量 |
---|---|
if(request.user.id == document.owner_id | user_owns_document |
if(request.user.id != document.owner_id | !user_owns_document |
3)使用德摩根定理
“分别取反,转换与/或”,有时你可以使用这个法则来让布尔表达式更具可读性。
4)滥用短路逻辑
要小心“智能”的小代码块——它们往往在以后会让别人读起来感到困惑。
3. 变量与可读性
1)减少变量
变量越多,就越难全部跟踪它们的动向
- 没有价值的临时变量:没有拆分任何复杂的表达式、没有做更多的澄清、只用过一次
- 减少中间结果:通过让代码提前返回来处理中间用来保存临时结果的变量
- 减少控制流变量
2)缩小变量的作用域
让你的代码对尽量少的代码行可见
- 把大的类拆分成小一些的类,并且这些类之间相互独立
- 大文件拆分成小文件,大函数拆分成小函数
- “尽量使方法变成静态的”,静态方法是让读者知道“这几行代码与那些变量无关”的好办法
- 把定义向下移,把每个定义移到它的使用之前
3)只写一次的变量更好
操作一个变量的地方越多,越难确定它的当前值
三、重新组织代码
1. 抽取不相关的子问题
积极地发现并抽取不相关的子逻辑:
(1)看看某个函数或代码块,问问你自己:这段代码高层次的目标是什么?
(2)对于每一行代码,问一下:它是直接为了目标而工作的吗?这段代码高层次的目标是什么?
(3)如果足够的行数在解决不相关的子问题,抽取代码到独立的函数中。
类型:
- 纯工具代码
- 其他多用途代码
- 通用代码
- 项目专有的功能
- 简化已有接口
- 按需重塑接口
2. 一次只做一件事
同时在做几件事的代码很难理解,应该把代码组织得一次只做一件事情:
(1)列出代码所做的所有“任务”,这里的“任务”没有很严格的定义——它可以小得如“确保这个对象有效”,或者含糊得如“遍历树中所有结点”;
(2)尽量把这件任务拆分到不同的函数中,或者至少是代码中不同的段落中。
3. 把想法变成代码
如果你不能把一件事解释给你祖母听的话说明你还没有真正理解它:
(1)想对着一个同事一样用自然语言描述代码要做什么
(2)注意描述中所使用的关键词和短语
(3)写出与描述所匹配的代码
- 清楚地描述逻辑
- 了解函数库是有帮助的
4. 少写代码
最好读的代码就是没有代码,知道什么时候不写代码可能对于一个程序员来讲是他所要学习的最重要的技巧。
1)质疑和拆分你的需求
不是所有的程序都需要运行得快,100%准确,并且能处理所有的输入。如果你真的仔细检查你的需求,有时你可以把它削减成一个简单的问题,只需要较少的代码。
2)保持小代码库
- 创建越多越好的“工具”代码来减少重复代码
- 减少无用代码或没有用的功能(你希望你的程序在内存耗尽的情况下仍能工作,因此你有很多耍小聪明的逻辑来试着从内存耗尽的情况下恢复,但为什么不通过一句简单的提示“系统内存不足,抱歉”并删除所有内存不足的代码呢?)
- 让你的项目保持分开的子项目状态
3)熟悉你周边的库
每隔一段时间,花15分钟来阅读标准库中的所有函数/模块/类型的名字。
以上是书里文字的引用与整理