You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

172 lines
14 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 01 | 从条件运算符说起,反思什么是好代码
写出优秀的代码是我们每一个程序员的毕生追求,毕竟写代码本身就是个技术活,代码的好坏,其实也就是我们工艺的好坏。作为一个技术类的工种,我们没有理由不去思考如何写出优秀、让人惊叹的代码。
那什么样的代码才是优秀的代码呢?对于这个问题,我想每个人心中都会有自己的答案。今天我就来和你聊聊我的思考。
对于条件运算符(?:)的使用,我估摸着你看到过相关的争论,或者自己写代码的时候也不知道到底该不该使用条件运算符,或者什么情况下使用?这些微不足道的小话题随时都可以挑起激烈的争论。
C语言之父丹尼斯·里奇就属于支持者。在《C程序设计语言》这本书里他使用了大量简短、直观的条件运算符。
然而还有一些人对条件运算符完全丧失了好感甚至把“永远不要使用条件运算符”作为一条C语言高效编程的重要技巧。
比如说吧,下面的这个例子,第一段代码使用条件语句,第二段代码使用条件运算符。 你觉得哪一段代码更“优秀”呢?
```
if (variable != null) {
return variable.getSomething();
}
return null;
```
```
return variable != null ? variable.getSomething() : null;
```
同样使用条件运算符,你会喜欢下面代码吗?
```
return x >= 90 ? "A" : x >= 80 ? "B" : x >= 70 ? "C" : x >= 60 ? "D" : "E";
```
十多年前作为一名C语言程序员我非常喜欢使用条件运算符。因为条件运算符的这种压缩方式使代码看起来简短、整洁、干净。 而且,如果能把代码以最少的行数、最简短的方式表达出来,心里也颇有成就感。
后来,我的一位同事告诉我,对于我使用的条件运算符的部分代码,他要仔细分析才知道这一小行代码想要表达的逻辑,甚至有时候还要翻翻书、查查操作符的优先级和运算顺序,拿笔画一画逻辑关系,才能搞清楚这一小行代码有没有疏漏。
这么简单的代码,为什么还要确认运算符的优先级和运算顺序呢?因为只是“看起来”对的代码,其实特别容易出问题。所以,一定要反复查验、确认无误才能放心。
这么简单的代码,真的需要这么认真检查吗?超级简单的代码的错误,往往是我们最容易犯的一类编码错误。我个人就是犯过很多次这种低级、幼稚的错误,并且以后一定还会再犯。比如下面的这段有问题的代码,就是我最近犯的一个非常低级的代码错误:
```
// Map for debug logging. Enable debug log if SSLLogger is on.
private final Map<Integer, byte[]> logMap =
SSLLogger.isOn ? null : new LinkedHashMap<>();
```
正确的代码应该是:
```
// Map for debug logging. Enable debug log if SSLLogger is on.
private final Map<Integer, byte[]> logMap =
SSLLogger.isOn ? new LinkedHashMap<>() : null;
```
你可能会说,这个代码错误看起来太幼稚、太低级、太可笑了吧? 确实是这样的。这段错误的代码我的眼睛不知道看过了它们多少次可是这个小虫子bug还是华丽丽地逃脱了我的注意进入了**JDK 11的最终发布版**。
如果使用条件语句,而不是条件运算符,这个幼稚错误发生的概率会急剧下降。 **坚持使用最直观的编码方式,而不是追求代码简短,真的可以避免很多不必要的错误**。所以说啊,选择适合的编码方式,强调代码的检查、评审、校验,真的怎么都不算过分。
现在,如果你要再问我喜欢哪种编码方式,毫无疑问,我喜欢使用条件语句,而不是条件运算符。因为,用条件语句这种编码方式,可以给我确定感,我也不需要挑战什么高难度动作;而看代码的人,也可以很确定,很轻松,不需要去查验什么模糊的东西。
这种阅读起来的确定性至少有三点好处,第一点是可以减少代码错误;第二点是可以节省我思考的时间;第三点是可以节省代码阅读者的时间。
**减少错误、节省时间,是我们现在选择编码方式的一个最基本的原则。**
《C程序设计语言》这本C程序员的圣经初次发表于1978年。那个年代的代码多数很简单直接。简短的代码意味着节省昂贵的计算能力是当时流行的编码偏好。而现在计算能力不再是瓶颈如何更高效率地开发复杂的软件成了我们首先需要考虑的问题。
有一些新设计的编程语言,不再提供条件运算符。 比如Kotlin语言的设计者认为编写简短的代码绝对不是Kotlin的目标。所以Kotlin不支持条件运算符。 Go语言的设计者认为条件运算符的滥用产生了许多难以置信的、难以理解的复杂表达式。所以Go语言也不支持条件运算符。
我们看到,**现实环境的变化,影响着我们对于代码“好”与“坏”的判断标准。**
## “好”的代码与“坏”的代码
虽然对于“什么是优秀的代码“难以形成一致意见,但是这么多年的经验,让我对代码“好”与“坏”积累了一些自己的看法。
比如说,“好”的代码应该:
1. 容易理解;
2. 没有明显的安全问题;
3. 能够满足最关键的需求;
4. 有充分的注释;
5. 使用规范的命名;
6. 经过充分的测试。
“坏”的代码包括:
1. 难以阅读的代码;
2. 浪费大量计算机资源的代码;
3. 代码风格混乱的代码;
4. 复杂的、不直观的代码;
5. 没有经过适当测试的代码。
当然,上面的列表还可以很长很长,长到一篇文章都列不完、长到我们都记不住的程度。
## 优秀的代码是“经济”的代码
大概也没人想记住这么多条标准吧?所以,**关于优秀代码的特点,我想用“经济”这一个词语来表达**。这里的“经济”,指的是使用较少的人力、物力、财力、时间、空间,来获取较大的成果或收益 。或者简单地说,**投入少、收益大、投资回报高**。为了方便,你也可以先理解为节俭或者抠门儿的意思。
当然,使用一个词语表达肯定是以偏概全的。但是,比起一长串的准则,一个关键词的好处是,更容易让人记住。我想这点好处可以大致弥补以偏概全的损失。
该怎么理解“经济”呢?这需要我们把代码放到软件的整个生命周期里来考察。
关于软件生命周期,我想你应该很熟悉了,我们一起来复习一下。一般而言,一个典型的软件生命周期,大致可以划分计划、分析和设计、代码实现、测试、运营和维护这六个阶段。在软件维护阶段,可能会有新的需求出现、新的问题产生、旧问题的浮现,这些因素可能就又要推动新一轮的计划,分析、设计、实现、测试、运营。这样,这个周期就会反复迭代,反复的循环,像一个周而复始的流水线。
![](https://static001.geekbang.org/resource/image/14/04/144679d37d552e4d5c436cab88582f04.png)
当我们说投入少的时候,说的是这整个生命周期,甚至是这个周而复始的生命周期的投入少。 比如说,代码写得快,可是测试起来一大堆问题,就不是经济的。
现代的大型软件开发,一般都会有比较细致的分工,在各个阶段参与的人是不同的;甚至在相同的阶段,也会有多人参与。一个稍有规模的软件,可能需要数人参与设计和实现。而为了使测试相对独立,软件测试人员和软件实现人员也是相对独立的,而且他们具备不同的优势和技能。
![](https://static001.geekbang.org/resource/image/89/df/89bb4250efc6702f638f8981030927df.png)
所以,当我们考虑投入的时候,还要考虑这个生命周期里所有的参与人员。这些参与人员所处的立场、看问题的角度,所具有的资源禀赋,可能千差万别。比如说,如果客户需要阅读代码,才知道系统怎么使用,就不是经济的。
是不是所有的软件都有这六个阶段呢?显然不是的,我本科的毕业论文程序,就完全没有运营和维护阶段,甚至也不算有测试阶段。我当时的毕业论文是一个关于加快神经网络学习的数学算法。只要验证了这个算法收缩得比较快,程序的使命就完成了,程序就可以退出销毁了。 所以,运营和维护阶段,甚至测试阶段,对当时的我而言,都是不需要投入的阶段。
在现代商业社会里,尤其我们越来越倾向于敏捷开发、精益创业,提倡“快速地失败、廉价地失败”,很多软件走不到维护阶段就已经结束了。而且,由于人力资源的限制,当然包括资金的限制,一个程序员可能要承担很多种角色,甚至从开始有了想法,到软件实现结束,都是一个人在战斗,哪里分什么设计人员、测试人员。
对软件开发流程选择的差异,就带来了我们对代码质量理解,以及对代码质量重视程度的千差万别。 比如说,一个创业公司是万万不能照搬大型成熟软件的开发流程的。因为,全面的高质量、高可靠、高兼容性的软件可能并不是创业公司最核心的目标。如果过分纠缠于这些代码指标,创始人的时间、投资人的金钱可能都没有办法得到最有效的使用。
当然,越成熟的软件开发机制越容易写出优秀的代码。但是,**最适合当前现实环境的代码,才是最优秀的代码。**
所以,当我们考虑具体投入的时候,还要考虑我们所处的现实环境。 如果我们超出现实环境去讨论代码的质量,有时候会有失偏颇,丧失我们讨论代码质量的意义。
既然具体环境千差万别,那我们还有必要讨论什么是优秀的代码吗?优秀的代码还能有什么共同的规律吗? 即使一个人做所有的事情,即使代码用完一次就废弃,我们长期积累下来的编写优秀代码的经验,依然可以帮助到很多人。
比如说,虽然创业公司的软件刚开始最核心的追求不是全面的高可靠性。可是,你也要明白,创业的目的不是为了失败,一旦创业公司稳住了阵脚,这个时候如果它们没有高可靠性的软件作为支撑,很快就会有反噬作用。 而程序员背锅,就是反噬的其中一个后果。
如何使用最少的时间、最少的资源,提供最可靠的软件,什么时候开始把可靠性提高到不可忽视的程度,有没有可能一开始就是高可靠的, 这些就都是一个富有经验的创业公司技术负责人不得不考虑的问题。而我们总结出来的编写代码的经验,毫无疑问,可以为这些问题提供一些思路和出路。
为什么我们要从“经济”这个角度来衡量优秀的代码呢? 因为这是一个可以让我们更加理性的概念。
一个营利性的公司,必须考虑投入产出比,没有人愿意做亏本的买卖,股东追求的是利润最大化。作为程序员,我们也必须考虑投入和产出。 首先,我们的产出必须大幅度大于公司对我们的投入,否则就有随时被扫地出门的风险。然后,我们必须使用好我们的时间,在单位时间内创造更多的价值,否则,真的是没有功劳,只有徒劳。
编写代码的时候,如果遇到困惑或者两难,你要想一想,怎么做才能做到投资少、收益大?
即便具体环境千差万别,我还是有一些例子,可以和你一起分享:
1. 代码写得又快又好,是“经济”的;代码写得快,但是错误多,不是一个“经济”的行为。
2. 代码跑得又快又好,是“经济”的;代码跑得快,但是安全问题突出,不是一个“经济”的行为。
3. 代码写得精简易懂,是“经济”的;代码写得精简,但是没人看得懂,不是一个“经济”的行为。
## 总结
对于所有的程序员来说,每个人都会遇到两个有名的捣蛋鬼,一个捣蛋鬼是“合作”,另一个捣蛋鬼是“错误”。
要合作,就需要用大部分人都舒服的方式。程序员间合作交流最重要的语言便是代码,换句话说,这就需要我们规范地编写代码,使用大家都接受的风格。不规范的代码,我们可能节省了眼前的时间,但是测试、运营、维护阶段,就需要更多的时间。而一旦问题出现,这些代码会重新返工,又回到我们手里,需要阅读、修改,再一次浪费我们自己的时间。对于这些代码,每一点时间的付出,都意味着投入,意味着浪费,意味着我们损失了做更有意义事情的机会。
人人都会犯错误代码都会有bug可是有些错误的破坏力是我们无法承受的其中最典型的就是安全问题。很多安全问题对公司和个人造成不容忽视的恶劣影响。我见过因为安全问题破产的公司。这时候甚至都不要谈什么投入产出比、经济效益了所有的投入归零公司破产员工解散。这需要我们分外地卖力拿出十二分的精神来处理潜在的安全威胁编写安全的代码。
如果我们把规范和安全作为独立的话题,优秀的代码需要具备三个特征: 经济、规范、安全。这些内容就是我们接下来要在专栏里一起学习的主体。
好了,今天我们一口气聊了很多,主要是在探讨到底什么样的代码才是优秀的代码。这个问题你之前考虑过吗?和我今天讲的是否一样呢?欢迎你在留言区写写自己的想法,我们可以进一步讨论。也欢迎你把今天的文章分享给跟你协作的同学,看看你们之间的理解是否一致。