专栏首页架构师修炼从条件运算符说起,反思什么是好代码

从条件运算符说起,反思什么是好代码

那什么样的代码才是优秀的代码呢?对于这个问题,我想每个人心中都会有自己的答案。今天我就来和你聊聊我的思考。

对于条件运算符(?:)的使用,我估摸着你看到过相关的争论,或者自己写代码的时候也不知道到底该不该使用条件运算符,或者什么情况下使用?这些微不足道的小话题随时都可以挑起激烈的争论。

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 语言也不支持条件运算符。

我们看到,现实环境的变化,影响着我们对于代码“好”与“坏”的判断标准。

01

“好”的代码与“坏”的代码

虽然对于“什么是优秀的代码“难以形成一致意见,但是这么多年的经验,让我对代码“好”与“坏”积累了一些自己的看法。

比如说,“好”的代码应该:

  1. 容易理解;
  2. 没有明显的安全问题;
  3. 能够满足最关键的需求;
  4. 有充分的注释;
  5. 使用规范的命名;
  6. 经过充分的测试。

“坏”的代码包括:

难以阅读的代码;

  1. 浪费大量计算机资源的代码;
  2. 代码风格混乱的代码;
  3. 复杂的、不直观的代码;
  4. 没有经过适当测试的代码。

当然,上面的列表还可以很长很长,长到一篇文章都列不完、长到我们都记不住的程度。

02

优秀的代码是“经济”的代码

大概也没人想记住这么多条标准吧?所以,关于优秀代码的特点,我想用“经济”这一个词语来表达。这里的“经济”,指的是使用较少的人力、物力、财力、时间、空间,来获取较大的成果或收益 。或者简单地说,投入少、收益大、投资回报高。为了方便,你也可以先理解为节俭或者抠门儿的意思。

当然,使用一个词语表达肯定是以偏概全的。但是,比起一长串的准则,一个关键词的好处是,更容易让人记住。我想这点好处可以大致弥补以偏概全的损失。

该怎么理解“经济”呢?这需要我们把代码放到软件的整个生命周期里来考察。

关于软件生命周期,我想你应该很熟悉了,我们一起来复习一下。一般而言,一个典型的软件生命周期,大致可以划分计划、分析和设计、代码实现、测试、运营和维护这六个阶段。在软件维护阶段,可能会有新的需求出现、新的问题产生、旧问题的浮现,这些因素可能就又要推动新一轮的计划,分析、设计、实现、测试、运营。这样,这个周期就会反复迭代,反复的循环,像一个周而复始的流水线。

当我们说投入少的时候,说的是这整个生命周期,甚至是这个周而复始的生命周期的投入少。比如说,代码写得快,可是测试起来一大堆问题,就不是经济的。

现代的大型软件开发,一般都会有比较细致的分工,在各个阶段参与的人是不同的;甚至在相同的阶段,也会有多人参与。一个稍有规模的软件,可能需要数人参与设计和实现。而为了使测试相对独立,软件测试人员和软件实现人员也是相对独立的,而且他们具备不同的优势和技能。

所以,当我们考虑投入的时候,还要考虑这个生命周期里所有的参与人员。这些参与人员所处的立场、看问题的角度,所具有的资源禀赋,可能千差万别。比如说,如果客户需要阅读代码,才知道系统怎么使用,就不是经济的。

是不是所有的软件都有这六个阶段呢?显然不是的,我本科的毕业论文程序,就完全没有运营和维护阶段,甚至也不算有测试阶段。我当时的毕业论文是一个关于加快神经网络学习的数学算法。只要验证了这个算法收缩得比较快,程序的使命就完成了,程序就可以退出销毁了。所以,运营和维护阶段,甚至测试阶段,对当时的我而言,都是不需要投入的阶段。

在现代商业社会里,尤其我们越来越倾向于敏捷开发、精益创业,提倡“快速地失败、廉价地失败”,很多软件走不到维护阶段就已经结束了。而且,由于人力资源的限制,当然包括资金的限制,一个程序员可能要承担很多种角色,甚至从开始有了想法,到软件实现结束,都是一个人在战斗,哪里分什么设计人员、测试人员。

对软件开发流程选择的差异,就带来了我们对代码质量理解,以及对代码质量重视程度的千差万别。比如说,一个创业公司是万万不能照搬大型成熟软件的开发流程的。因为,全面的高质量、高可靠、高兼容性的软件可能并不是创业公司最核心的目标。如果过分纠缠于这些代码指标,创始人的时间、投资人的金钱可能都没有办法得到最有效的使用。

当然,越成熟的软件开发机制越容易写出优秀的代码。但是,最适合当前现实环境的代码,才是最优秀的代码

所以,当我们考虑具体投入的时候,还要考虑我们所处的现实环境。如果我们超出现实环境去讨论代码的质量,有时候会有失偏颇,丧失我们讨论代码质量的意义。

既然具体环境千差万别,那我们还有必要讨论什么是优秀的代码吗?优秀的代码还能有什么共同的规律吗?即使一个人做所有的事情,即使代码用完一次就废弃,我们长期积累下来的编写优秀代码的经验,依然可以帮助到很多人。

比如说,虽然创业公司的软件刚开始最核心的追求不是全面的高可靠性。可是,你也要明白,创业的目的不是为了失败,一旦创业公司稳住了阵脚,这个时候如果它们没有高可靠性的软件作为支撑,很快就会有反噬作用。而程序员背锅,就是反噬的其中一个后果。

如何使用最少的时间、最少的资源,提供最可靠的软件,什么时候开始把可靠性提高到不可忽视的程度,有没有可能一开始就是高可靠的, 这些就都是一个富有经验的创业公司技术负责人不得不考虑的问题。而我们总结出来的编写代码的经验,毫无疑问,可以为这些问题提供一些思路和出路。

为什么我们要从“经济”这个角度来衡量优秀的代码呢?因为这是一个可以让我们更加理性的概念。

一个营利性的公司,必须考虑投入产出比,没有人愿意做亏本的买卖,股东追求的是利润最大化。作为程序员,我们也必须考虑投入和产出。首先,我们的产出必须大幅度大于公司对我们的投入,否则就有随时被扫地出门的风险。然后,我们必须使用好我们的时间,在单位时间内创造更多的价值,否则,真的是没有功劳,只有徒劳。

编写代码的时候,如果遇到困惑或者两难,你要想一想,怎么做才能做到投资少、收益大?

即便具体环境千差万别,我还是有一些例子,可以和你一起分享:

  1. 代码写得又快又好,是“经济”的;代码写得快,但是错误多,不是一个“经济”的行为。
  2. 代码跑得又快又好,是“经济”的;代码跑得快,但是安全问题突出,不是一个“经济”的行为。
  3. 代码写得精简易懂,是“经济”的;代码写得精简,但是没人看得懂,不是一个“经济”的行为。

03

总结

对于所有的程序员来说,每个人都会遇到两个有名的捣蛋鬼,一个捣蛋鬼是“合作”,另一个捣蛋鬼是“错误”。

要合作,就需要用大部分人都舒服的方式。程序员间合作交流最重要的语言便是代码,换句话说,这就需要我们规范地编写代码,使用大家都接受的风格。不规范的代码,我们可能节省了眼前的时间,但是测试、运营、维护阶段,就需要更多的时间。而一旦问题出现,这些代码会重新返工,又回到我们手里,需要阅读、修改,再一次浪费我们自己的时间。对于这些代码,每一点时间的付出,都意味着投入,意味着浪费,意味着我们损失了做更有意义事情的机会。

人人都会犯错误,代码都会有 bug,可是有些错误的破坏力是我们无法承受的,其中,最典型的就是安全问题。很多安全问题对公司和个人造成不容忽视的恶劣影响。我见过因为安全问题破产的公司。这时候,甚至都不要谈什么投入产出比、经济效益了,所有的投入归零,公司破产,员工解散。这需要我们分外地卖力,拿出十二分的精神来处理潜在的安全威胁,编写安全的代码。

如果我们把规范和安全作为独立的话题,优秀的代码需要具备三个特征:经济、规范、安全,这些我们后面一起来学习。

本文分享自微信公众号 - 架构师修炼(jiagouxiulian),作者:架构师修炼

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-03-10

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 客服MM被投诉说下单耗时很长,老板下令必须控制在1秒以内

    近期,公司的订单量一直在持续增加,客服经常被投诉到说我们的下单时间过长,有时要好几秒,然后客服 MM 就反映到我们技术部门,老板得知后就说,这怎么行呢?不能让我...

    架构师修炼
  • 要想精通java,你必须得知道java的内存模型,不忽悠

    我们在写一个java程序的时候,然后将其编译成class字节码,最后将字节码放到Java虚拟机(JVM)中运行。也就是是它是java运行的载体,可见这个JVM有...

    架构师修炼
  • 设计模式实战-建造者模式,做任何事情都需要步步为营

    所谓万丈高楼平地起,但是我们建造(Build)高楼时,需要经历很多阶段,比如打地基、搭框架、浇筑水泥、封顶等,这些都是很难一气呵成的。所以一般我们是先建造组成高...

    架构师修炼
  • 世间本无完美代码,不要苦苦追寻了

    一些代码比其他代码重要 通过研究代码如何随时间变化,Michael Feathers 确定了一个代码库的冥曲线。每个系统都有代码,通常有很多是一次性写成,永远都...

    BestSDK
  • 程序员的5种类型

    在我的代码旅程和编程经历中,已经遭遇很多奇特的对手,还有更为奇特的盟友。我至少发现有5种代码勇士,有些是出色的战友,其他则似乎都在搅黄我的每个计划。 不过他们都...

    企鹅号小编
  • 高级Python工程师教你如何正确写代码

    我接手的第一样东西就是React UI。我们有一个主要组件,它容纳了其他所有组件。我喜欢在代码中加入一点幽默感,我想把它命名为GodComponent。在cod...

    小小科
  • Dead Code为什么能在代码库中永生?

    在一些遗留系统中,经常会看到大片大片灰掉的代码(被注释掉了),这种代码是死代码吗?如果要我下定义,我认为这些不是死代码,因为它们连代码都称不上,如何又能叫死代码...

    袁慎建@ThoughtWorks
  • JS逆向时碰到了恶心的死代码怎么办?手把手教你解决!

    你是否也曾有过「跟着代码跳了很久之后,才发现那一大坨代码其实没有任何作用」的惨痛经历?

    青南
  • JS逆向时碰到了恶心的死代码怎么办?手把手教你解决!

    你是否也曾有过「跟着代码跳了很久之后,才发现那一大坨代码其实没有任何作用」的惨痛经历?

    崔庆才
  • 不要浪费时间写完美代码

     一个系统可以维持5年,10年,甚至20年以上,但是代码和设计模式的生命周期非常短,当对一个解决方案使用不同的方法进行迭代的时候,通常只能维持数月,数日,甚至几...

    用户1289394

扫码关注云+社区

领取腾讯云代金券