《架构师》2019年11月
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

编写代码

如何命名

我在工作中接触的第一项任务是开发一款React UI。当时我们拥有一个主组件,用于容纳其它所有组件。我喜欢在代码当中加点幽默元素,所以我把它命名为GodComponent。但在代码审查时,我才意识到为什么命名工作如此重要、也如此困难。

计算机科学领域有两大难题:缓存失效、命名以及缓冲溢出错误。

—— Leon Bambrick

我命名的每一段代码都包含隐藏的含义。GodComponent?这个组件的含义,就是我会把所有不知道该放在哪的组件都放在这里。它囊括一切。如果我把它命名为LayoutComponent,后续我才会意识到它的作用就是布局分配,其中不包含任何状态。

我发现的另一项心得在于:如果其体积过于庞大,就像是这里提到的包含大量业务逻辑的LayoutComponent,那么我就会意识到是时候进行重构了,因为通过名称就能看出业务逻辑并不属于这里。但使用GodComponent这个名称,我们无法判断业务逻辑出现在这里是否正常。

如何命名集群?最好是在运行了服务之后再对集群进行命名,而后根据运行内容的变化重新调整名称。最终,我们用自己的团队名称完成了集群命名。

函数命名的情况也是一样。doEverything()这个名字就不怎么样,其会带来严重的后果。如果这项函数能够完成所有操作,那么我们将很难测试函数当中的某些特定部分。而且无论这个函数有多大,我们都会觉得很正常,毕竟它的名字可是叫“everything”。所以,最好的办法当然是更换名称,进行重构。

但是,我们在命名中也要考虑到另一类问题。如果名称的含义太过具体并忽略了某些细微差别,该怎么办?例如,在SQLAlchemy当中调用session.close()时,关闭会话不会关闭基础数据库连接。(我本应该跳出手册限制,对这项bug进行处理,具体情况将在调试部分进一步说明。)

在这种情况下,我们可以考虑x, y, z这样的名称,而非count(), close(), insertIntoDB(),从而避免为其分配隐含的意义。太过具体,会迫使我们不得不在后续维护时费力检查这些函数到底是用来干嘛的。

最后,当时的我从来没想到命名会成为值得单独一提的重要工作。

遗留代码与下一位开发者

大家有没有面对一段代码时,感觉摸不着头脑?他们为什么要这么写?这完全说不通啊。

我就“有幸”接手过遗留代码库。其中就存在类似于“跟穆罕默德确认过情况之后,取消注释”这类说明。这话是谁说的?穆罕默德又是哪位?在这方面,我们不妨做个角色转换——考虑下一位接手我所编写代码的开发者。他们同样会发现我的代码非常奇怪。同行评审能够很好地解决这个问题。这不禁让我想到上下文原则,即:了解团队开展工作时的实际处境。

如果我跑去忙别的事,稍后又回来,我可能也无法重新建立这种上下文。我坐说,“当时我是怎么想的?这根本没道理……哦等等,我原来是这么干的。”

正是为了实现这种提示作用,文档与代码注释才会如此重要。

文档与代码注释

文档与代码注释的意义,在于保持上下文并分享知识。

正如Li在如何构建良好软件中所言,“软件的主要价值并不在于生成的代码,而在于生成代码的过程中开发者所积累下来的知识。”

“软件的主要价值并不在于生成的代码,而在于生成代码的过程中开发者所积累下来的知识。”

—— Li

我们当时有一套面向API端点的随机客户端,好像从来就没人用过。那么要不要把它删除掉?毕竟这也属于技术债务。

但如果我告诉大家,每年在特定的国家/地区,都会有10名记者将新闻发送到该端点,又该怎么办?我们是如何测试的?如果没有文档(也确实没有),我们找不到答案。因此,我们删除了该端点,并在对应时间点上发现了问题——这10名记者无法发送10份重要的报道,因为该端点已经不复存在。

了解产品的成员已经离开了团队,现在只能靠代码当中的注释来解释该端点的作用。

从这件事上,我意识到文档是每个团队都在努力解决、但却难以奏效的问题。除了代码文档之外,与代码相关的流程也有类似的情况。

时至今日,我们也没有找到完美的解决方案。

原子提交

如果必须要回滚(而且回滚需求早晚会出现,我们将在测试部分具体讨论),此次提交还是否有意义?

在删除垃圾代码时要充满信心

删除垃圾或者过时的代码总是让我感觉很不舒服。我总觉得以往的工作成果有种神圣不可侵犯的意义。我那时候认为,“在他们写与这些代码时,肯定是有所考量的。”这是一种传统的理解方式,而且与第一性原则有所冲突。出于类似的理由,我在每年进行代码审查与清理时也是困难重重。这样的糟糕习惯,让我吃了不少苦头。

我曾经尝试调整代码问题,也有些老成员习惯于绕过这些代码。但删除,删除听起来更严重正经。一个永远用不上的if语句、一个永远用不上的函数,会在我的一声令下彻底消失,这样不好。因此,我更多是把自己的函数覆盖在上面。但这并没有减少技术债务,只是增加了代码的复杂性与误导性。如此一来,后继者将更难把这些片段以有意义的方式拼凑起来。

我现在采取的方式是:总会存在我们无法理解的代码,也总会存在我们永远不会使用的代码。删除这些永远不会使用的代码,但对无法理解的代码保持谨慎的态度。

代码审查

代码审查是学习中的重要组成部分。审查的过程,就是从编写代码、到了解如何更好地编写代码的反馈循环。我们自己的编码思路,跟其他人的编码思路有何不同?我在每一次代码审查时都会问自己:“他们为什么要这样做?”如果实在找不到合理的答案,我就会跟他们当面聊聊。

在第一个月的过渡期结束之后,我开始疯狂地从同事的代码当中查找错误(当然,他们也不会放过我)。真的很疯狂,这也让评审工作变成一项有趣的调剂——或者说像是一种游戏,能够改善我们编码水平的小游戏。

我的心得:在理解代码作用之前,不要轻下断言。