- 豆瓣🔗:《编写可读代码的艺术》
关于编码规范和编写高质量代码类的书籍,印象中第一本看完的应该是《阿里巴巴Java开发手册》,《编写可读代码的艺术》是第二本读完的书籍。读的纸质书,小小一册,几天很快就读完了(本来有中英两种版本可选,可为了快速看完而且豆瓣中文版评分也还不错,就偷懒选择了中文版😬),但最难的是将书中优秀的部分真正应用于自己的代码编写中。
Clean Code《代码整洁之道》
、Clean Coder《程序员的职业修养》
、Code Complete《代码大全》
、Programming Pearls《编程珠玑》
等好书都是自己豆瓣想读但仅仅翻过几页的书籍,所以还是要挤出时间慢慢读完这些经典之作。
大概总结如下:
1、“可读性基本定理”:代码的写法应当使别人理解它所需的时间最小化。
2、把信息塞入名字中:仅通过读到名字就可以获得大量信息。
- 使用专业的单词——例如,不用
Get
,而用Fetch
或者Download
可能会更好,这由上下文决定。 - 避免空泛的名字,像
tmp
和retval
,除非使用它们有特殊的理由。 - 使用具体的名字来更细致地描述事物——
ServerCanStart()
这个名字就比CanListenOnPort
更不清楚。 - 给变量名带上重要的细节——例如,在值为毫秒的变量后面加上
_ms
,或者在还需要转义的,未处理的变量前面加上raw_
。 - 为作用域大的名字采用更长的名字——不要用让人费解的一个或两个字母的名字来命名在几屏之间都可见的变量。对于只存在于几行之间的变量用短一点的名字更好。
- 有目的地使用大小写、下划线等——例如,你可以在类成员和局部变量后面加上"
_
“来区分它们。
3、不会误解的名字是最好的名字——阅读你代码的人应该理解你的本意,并且不会有其他的理解。遗憾的是,很多英语单词在用来编程时是多义性的,例如filter
、length
和limit
。
在你决定使用一个名字以前,要吹毛求疵一点,来想象一下你的名字会被误解成什么。最好的名字是不会误解的。
当要定义一个值的上限或下限时,max_
和min_
是很好的前缀。对于包含的范围,first
和last
是好的选择。对于包含/排除范围,begin
和end
是最好的选择,因为它们最常用。
当为布尔值命名时,使用is
和has
这样的词来明确表示它是个布尔值,避免使用反义的词(例如disable_ssl
)。
要小心用户对特定词的期望。例如,用户会期望get()
或者size()
是轻量的方法。
4、有美感的代码:通过把代码用一致的、有意义的方式“格式化”,可以把代码变得更容易读,并且可以读得更快。
具体技巧:
- 如果多个代码块做相似的事情,尝试让它们有同样的剪影。
- 把代码按“
列
”对齐可以让代码更容易浏览。 - 如果在一段代码中提到
A、B和C
,那么不要在另一段中说B、C和A
。选择一个有意义的顺序,并始终用这样的顺序。 - 用
空行
来把大块代码分成逻辑
上的“段落”
5、注释的目的是帮助读者了解作者在写代码时已经知道的那些事情。
什么地方不需要注释:
- 能从代码本身中迅速地推断的事实。
- 用来粉饰烂代码(例如蹩脚的函数名)的“
拐杖式注释
”——应该把代码改好。
应该记录下来的想法:
- 对于为什么代码写成这样而不是那样的内在理由(“
指导性批注
”)。 - 代码中的缺陷,使用像
TODO:
或者XXX:
这样的标记。 - 常量背后的故事,为什么是这个值。
站在读者的立场上思考:
- 预料到代码中哪些部分会让读者说:“啊?”并且给它们加上注释。
- 为普通读者意料之外的行为加上注释。
- 在
文件/类
的级别上使用“全局观
”注释来解释所有的部分是如何一起工作的。 - 用注释来总结代码块,使读者不致迷失在细节中。
6、如何把更多的信息装入更小的空间里。
- 当像”
it
“和”this
“这样的代词可能指代多个事物时,避免使用它们。 - 尽量精确地描述函数的行为。
- 在注释中用精心挑选的输入/输出例子进行说明。
- 声明代码的高层次意图,而非明显的细节。
- 用嵌入的注释(如
Function(/* *arg=* */……)
)来解释难以理解的函数参数。 - 用含义丰富的词来使注释简洁。
7、让代码的控制流更易读:
在写一个比较时(while(bytes_expected>bytes_received)
),把改变的值写在左边并且把更稳定的值写在右边更好一些(while(bytes_received<bytes_expected)
)。
可以重新排列if/else
语句中的语句块。通常来讲,先处理正确的/简单的/有趣的情况。有时这些准则会冲突,但是当不冲突时,这是要遵循的经验法则。
某些编程结构,像三目运算符(:?)
、do/while
循环,以及goto
经常会导致代码的可读性变差。最好不要使用它们,因为总是有更整洁的代替方式。
嵌套的代码块需要更加集中精力去理解。每层新的嵌套都需要读者把更多的上下文“压入栈
”。应该把它们改写成更加“线性”的代码来避免深嵌套。
通常来讲提早返回可以减少嵌套并让代码整洁。“保护语句”(在函数顶部处理简单的情况时)尤其有用。
8、巨大的表达式很难思考,拆分表达式的方法:
- 引入“
解释变量
”来代表较长的子表达式。这种方式有三个好处:- 它把巨大的表达式拆成小段。
- 它通过用简单的名字描述子表达式来让代码文档化。
- 它帮助读者识别代码中的主要概念。
- 用德摩根定理来操作逻辑表达式——这个技术有时可以把布尔表达式用更整洁的方式重写(例如
if(!(a &&!b))
变成if(!a||b))
。
9、程序中的变量会变得快速累积而变得难以跟踪。可以通过减少变量的数量和让它们尽量“轻量级”来让代码更有可读性。具体有:
- 减少变量,即那些妨碍的变量。可以通过立刻处理结果来消除“
中间结果
”变量。 - 减小每个变量的
作用域
,越小越好
。把变量移到一个有最少代码可以看到它的地方。眼不见,心不烦。 - 只写一次的变量更好。那些只设置一次值的变量(或者
const
、final
、常量
)使得代码更容易理解 。
10、把一般代码和项目专有的代码分开。
大部分代码都是一般代码。通过建立一大组库和辅助函数
来解决一般问题,剩下的只是让程序与众不同的核心部分。它使程序员关注小而定义良好的问题,这些问题已经同项目的其他部分脱离。对于这些子问题的解决方案倾向于更加完整和正确,也可以在以后重用它们。
11、一个组织代码的简单技巧:一次只做一件事情。
如果你有很难读的代码,尝试把它所做的所有任务列出来。其中一些任务可以很容易地变成单独的函数(或类)。其他的可以简单地成为一个函数中的逻辑“段落”。具体如何拆分这些任务没有它们已经分开这个事实那样重要。难的是要准确地描述你的程序所做的所有这些小事情 。
12、一个简单的技巧:用自然语言描述程序然后用这个描述来帮助你写出更自然的代码。这个技巧出人意料地简单,但很强大。看到你在描述中所用的词和短语还可以帮助你发现哪些子问题可以拆分出来。
但是这个“用自然语言说事情”的过程不仅可以用于写代码。例如,某个大学计算机实验室的规定声称当有学生需要别人帮它调试程序时,他首先要对房间角落的一只专用的泰迪熊解释他遇到的问题。令人惊讶的是,仅仅通过大声把问题描述出来,往往就能帮这个学生找到解决的办法。这个技巧叫做“橡皮鸭技术
”。
另一个看待这个问题的角度是:如果你不能把问题说明白或者用词语来做设计,估计是缺少了什么东西或者什么东西缺少定义。把一个问题(或想法)变成语言真的可以让它更具体。
13、写越少代码越好的。每行新的代码都需要测试、写文档和维护。另外,代码库中的代码越多,它就越“重”,而且在其上开发就越难。 通过以下方法避免编写新代码:
- 从项目中消除不必要的功能,不要过度设计。
- 重新考虑需求,解决版本最简单的问题,只要能完成工作就行。
- 经常性地通读
标准库的整个API
,保持对它们的熟悉程度。
14、在测试代码中,可读性仍然很重要。如果测试的可读性很好,其结果是它们也会变得很容易写,因此大家会写更多的测试。并且,如果你把事实代码设计得容易测试,代码的整个设计会变得更好。
如何改进测试的几个具体要点(最重要的是,要使它易于改动和增加新的测试):
- 每个测试的最高一层应该越简明越好。最好每个测试的输入/输出可以用一行代码来描述。
- 如果测试失败了,它所发出的错误消息应该能让你
容易跟踪并修正这个bug
。 - 使用最简单的并且能够完整运用代码的测试输入。
- 给测试函数取一个有完整描述性的名字,以使每个测试所测到的东西很明确。不要用
Test1()
,而用像Test_<FunctionName>_<Situation>
这样的名字。