本文讨论的是编码风格、原则和艺术。
至于如何编码实现系统的功能和性能、如何架构设计、如何提高易用性、如何团队协作、如何项目管理,均不在本文讨论范畴之内。
本文也不讲大括号应该放在哪里,代码如何缩进,运算符周围是否加上空格这种规范,虽然这也很重要。
编码风格的所有目标
1、代码易读
2、代码易改
这就足够了。
而且这很难做到。
最重要的技巧
1、小函数;(注意这个技巧排在第一位)
2、小文件、小类;(小的东西,易读,易改)
3、做好单元测试!(所有都测到了,你就敢改)
4、少注释;(而是增加表达力!让人一眼看懂的代码不需要注释)
5、消除重复。(重复永远是丑陋的)
如果你记不住这么多条,就记一条:小函数。
七个习惯
一、保持精简
“小就是美”。
告诉你一个秘诀,即便你不会编码,也可以简单而准确地判断一个人是不是编程高手。
你只需要略微看一下他的代码就可以。
如果他代码中的函数,绝大多数都在20行以内,这就是高手。
如果基本都在10行以内,这是顶尖高手。
如果都在3、4行左右,这是大师。
如果有很多超于100行的函数,这是新手、低手。
类似地,看他代码中每个文件的长度,应该大多都在200行或者400行以内,才是正常的,虽然这无法直接判断是不是高手。
但如果动辄都在2000行以上,这是新手、低手。
大致而言,高手能做到:
1、小函数、小文件、小类;
2、小的单元测试用例。
3、函数有尽量少的参数。
最理想的函数参数数量是0个,其次是1个,再次是2个,应尽量避免3个及以上。
参数越少,越好测试,越不容易搞错参数的顺序。(为减少参数,可考虑封装成类。)
怎么做到这点?只要你有保持精简的意识,你总会做到的。
任何一个文件,一个函数,只要感觉它大到一定程度(比如超过10行),就着手拆分。
下面举个例子:
上图左边是一个长达15行的函数,经过整理后,可以写成右边那样,但这仍然不够。
右边每种颜色的代码块都应该由一个函数完成,所以,左边这个函数,最终变成4行。(每行都是一个函数调用)
类似的,稍微看到一点比较繁复的东西,就抽象它,精简它,让你的程序看上去非常简洁才可以。
这就是在践行KISS原则。(Keep It Simple,Stupid)
也是在践行“松耦合、高内聚”原则。(一个小函数、小类,必然是高度内聚的)
二、提高表达力
编写整洁、易读的代码。
高效能人士写的代码,即便在10年后,他把代码已经忘光光的时候,只要需要,他拿出来一看,就能轻松理解。
我写代码,从来不是为了别人看,我只是怕自己忘记。
我的记性不太好,而且我从来不依赖我的记性,所以,我必须写得非常易读。我很难忍受看不懂自己代码而产生的那种嫌弃感。(不是嫌弃现在的自己,而是以前的自己)
“代码的写法应当使别人理解它所需的时间最小化。”
如何做到?下面谈5点。
1、让代码读起来像自然语言。(不断磨练这点)
2、高度重视变量和函数的命名,要明确、无歧义。
所有函数命名,都以动词为开头。(类名用名词)
不要怕命名长,只要有助于理解,长点没事。
慎重选择用词,考虑不会让人误解。
举个例子:
上面这段代码是拷贝字符串的,如果参数名改为source和destination,就会清晰很多,而且,引用者会不太容易搞错参数顺序。
3、在实在没有办法用代码表达意图的时候,写注释。
除去版权和许可证这种必要的注释,绝大多数情况下,我只在觉得自己以后可能看不懂的情况写注释。
在比较古板的条条框框下,注释应该占代码的1/5以上。
然而,对高手而言:在大多数情况下,注释表明了一种失败。
所以,在你想注释的时候,再努力一下,看能不能仅用代码就可以明白表达。
“不要给不好的名字加注释——应该把名字改好”
注释不是代码,所以它是难以管理和测试的。由于人的懒惰天性,注释通常不会跟着代码的改动而改动,所以注释往往是过时的、错误的(而且难以发现)。
“不准确的注释要比没注释坏得多。它们满口胡言。”
4、照顾阅读者的感受
把关系相近的函数,放到一起。
如果函数 A 调用函数 B,那么把 A 放到 B 前面,人就会自然阅读下去。而且阅读者会有一个预期,看到一个新的函数,知道很快就能读到它的实现。
(不过我的习惯是:把 B 放到 A 前面,因为有些编程语言,要求一个函数只能调用前面出现过的函数,如果不加声明的话。所以,看我的程序,需要从文件底部看起。)
同样地,概念相关的代码,放到一起,用空行分开。
不要再用微软提倡的匈牙利标记法(比如 szAuthor 这种命名),那已经过时了。使用大小写或者下划线分割单词的命名(比如 deletePage)就很好,匈牙利标记法比较丑陋,直接影响阅读感受。
再举一个例子:为了便于阅读,避免使用 do / while 语句。
do / while 这种写法很丑陋,通常来讲,逻辑条件应该出现在它们所“保护”的代码之前,if / while 和 for 语句就是这么干的。而 do / while 把条件放在后面,这很反常,以至于大多数人读到 do / while 时,都需要读两遍。
5、不要重复!
“重复可能是软件中一切邪恶的根源。
“重复”会浪费编码者的时间、浪费阅读者的时间、浪费维护者的时间、浪费编译、运行的时间。而且,还浪费存储。
看到重复,就改写它,这是一个基本素质。
三、边写边测
程序如果易读,就很有利于修改。
但仅仅“易读”是远远不够的。
很多程序员敢写代码,但不敢改,他们都知道什么是“牵一发而动全身”。
所以,每次你让他们改程序的时候,他们的脸色都不好看的。
只有顶尖高手,才会“欣然改程序”,他们知道,这又是一次展示自己能力的机会。
他们是怎么做到这点的?
他们的底气来自测试。
测试覆盖率越高,你就越放心,你就可以放心地修改,甚至放心地改架构!
下面是TDD(来自极限编程)的方法论,值得参考和借鉴。(说实话,我并没有严格做到)
1.在写功能代码前,先写一个小的单元测试。(不要写太多,否则你会没有动力)
2.运行所有测试(先看这个测试是否能工作),测试应该失败。(因为还没有写生产代码)
3.编写仅仅能通过这个测试的最简单代码。(先不用写很好,后面还会重构;也不要写更多,因为其他还没写测试)
4.通过所有测试。(注意是所有测试,这保证了一切都是对的。)
5.根据需要重构,每次重构后都测试。(始终保持一切都是对的。)
6.重复上述循环。(测试和编码应该是小型和渐进的)
测试用例应该有这样的结构:设置、执行、验证以及可能需要的清理。
测试用例应该尽量小,一次只干一件事。
坚持TDD原则,你就会有覆盖率足够高的测试代码,否则可就不一定。
注意:“测试代码和生产代码一样重要。它可不是二等公民。它需要被思考、被设计和被照料。它该像生产代码一般保持整洁。”
当然,写测试代码本身是需要成本的,但是你要好好想想,值不值?
四、时时清理
所有东西,如果不维护,总是会变脏、变坏。(这就是这两年很多人喜欢说的“熵增”)
代码也一样。(除非你再也不用它了)
所以,每次你改动代码时,都要让代码变得更整洁。
正如美国童子军军规:“让营地比你来时更干净。”
注意,你敢于清理的前提是,你有足够的测试用例。
每次清理时,告诫自己,这会给自己带来巨大好处:可以避免今后巨大的麻烦。
这可以用在工作和生活中的任何方面,包括你的思想。
五、同时保持两种截然相反的思维
很多思维,都有截然相反的对立面,并且都有其拥趸。
比如有人说WEB3是未来,就有人说WEB3是垃圾。
有人说公司要人性化管理,就有人说必须要狼性管理。
再举两、三个例子:
1、应该自顶向下编程,还是自底向上编程?
正确答案是,这两种思维都要用。
2、先写代码,再写测试;还是先写测试,再写代码?
你自己看着办。
3、“三目运算符”的可读性也是有争议的。
拥护者认为这种方式简洁,只有一行;反对者则说这可能会造成阅读的混乱。
是下面这样好,
time_str += (hour >= 12) ? "pm" : "am";
还是下面这样好?
if (hour >= 12) {
time_str += "pm";
} else {
time_str += "am";
}
正确做法:默认情况下都用if/else,只在最简单的情况下使用“三目运算符”。
保持两种思维的好处在于,你可以始终对这两种提法进行实践、测试、反思,在两种极端的说法中找到平衡,找到各自适用的场景,找到更适合自己的做法。
这会让你更从容、更灵活。
甚至,不要一味追求“小就是美”,偶尔大一下也没啥。
六、知道不做什么
最好读的代码就是没有代码。
如果有现成的,不要自己去做。
如果现在还用不到,就先不要写。(YAGNI原则:You Ain’t Gonna Need It)
你所写的每一行代码都是要测试和维护的。
你要节省自己的时间,你还有更重要的事要做。
七、更新自我
自FORTRAN语言在上世纪50年代后期出现之后,在编程思想方面,人们就在不断的学习和长进。
人们首先认识到封装的重要性,ALGOL语言的设计者在ALGOL60中采用了以Begin……End为标识的程序块,使块内变量名是局部的,以避免它们与程序中块外的同名变量相冲突,这是编程语言中首次提供封装的尝试。
上世纪60年代后期,随着《程序结构理论》和《GOTO语句有害论》的提出,证明了任何程序的逻辑结构都可以用顺序结构、选择结构和循环结构来表示,确立了结构化程序设计思想,这使得程序更容易维护。
上世纪80年代,面向对象程序设计思想经过20年的研究和发展逐渐成熟,一大批面向对象语言相继出现。
2001年2月,在美国犹他州瓦萨奇山,17个来自于各类敏捷方法的实践者共同达成了一个共识:《敏捷宣言》,提出了十二条敏捷原则。
其中第十条原则和本文密切相关:
简洁是敏捷的精髓,它是极力减少不必要工作的艺术。
Simplicity–the art of maximizing the amount of work not done–is essential.
然后,设计模式、重构开始普遍流行。
再然后,有了CI/CD,有了Devops。
以前的程序员,是不懂这些的。
如果你是搞IT的,你就会发现,编程思想、方法、语言、工具,层出不穷、学无止境。
真正的高效能人士,永远不会停止学习,永远不会停止更新自我。
因为永远会有更好的东西问世。
编程之外
我在命名这7个习惯时,刻意使用了普适的说法。
这些高度抽象的理论,完全可以用在其他领域。
- 保持精简
- 提高表达力
- 边写边测
- 时时清理
- 同时保持两种截然相反的思维
- 知道不做什么
- 更新自我
这些一样可以用在写作、工作、生活以及做人上。
成熟的人,都明白这点。
本文用标题和图示向Stephen R. Covey致敬,感谢他的神书《高效能人士的七个习惯》。
本文引用主要来自以下两本广为人知的名著:
Robert C. Martin. 代码整洁之道. 人民邮电出版社.
Dustin Boswell,Trevor Foucher. 编写可读代码的艺术. 机械工业出版社.
文|卫剑钒
本文被阅读了:1,602次