查看原文
其他

代码恐怖故事:隐藏在复杂代码库中的恐怖秘密

CSDN 2023-07-27

本文讲述了开发者们在复杂代码库中工作的经历和教训,包括代码复杂性带来的问题、架构决策、第三方库引发的意外问题以及令人恐慌的编程错误,以及如何处理这些挑战。

原文链接:https://digma.ai/blog/coding-horrors-tales-of-codebase-complexity/

未经允许,禁止转载!


作者 | Daniel Beck       译者 | 明明如月
责编 | 夏萌
出品 | CSDN(ID:CSDNnews)

无论你是软件开发领域的老兵,或者刚刚踏上这个领域的新人,你可能都对在复杂的分布式软件环境中工作所带来的压力和那些让人瞠目结舌的惊魂时刻有着深深的认识。我们深信,你一定曾面临过代码库复杂性所引发的问题,也在调试的过程中历尽艰辛,或者因为错误的设计决策而有过自己的“代码恐怖故事”。

毕竟,许多开发者都有类似的故事可以分享。我们决定每次邀请一位知名的开发者,分享他们的代码恐怖故事。这一次,我们请来了 Daniel Beck——一位拥有丰富软件开发经验的专家、产品开发的用户体验顾问,由他和我们分享他的代码恐怖故事。

Daniel 在软件开发领域的经验相当丰富!他从业的时间甚至可以追溯到该行业的早期阶段。他开始工作时使用的浏览器还是 NCSA Mosaic。在那个时代,只需要掌握五个 HTML 标签,你就可以开始投入工作了。在他的职业生涯中,Daniel 积累了独特的见解,我们坚信你们可以从中受益。

大家如果感兴趣可以访问他的博客,大家的访问能给他带来更大的创作动力!danielbeck.net/blog

处理代码库的复杂性:教训和建议


  • 直观且易读的代码,无论其是否精巧,都优于那些晦涩难解的“聪明”代码。

  • 不应轻易接受厂商将问题归咎于硬件并推荐高价的服务器升级。

  • 认识到快速解决问题可能带来的隐患,理解代码底层工作原理的重要性。

  • 开启和关闭数据库连接是一个缓慢且开销大的操作。

  • 在安装新的 npm 模块或引入他人的代码时,必须深入考虑其潜在的风险和影响。

  • 警惕那些基于个人审美重构代码,却没有提供适当文档或确保代码完整性的团队成员。

  • 确保对代码的更改有充分的文档记录。

  • 对于那些进行无文档变更,为他人设置隐蔽陷阱的同事,应保持警惕。

  • 优化代码审查流程,以解决现有的问题。

  • 要认识到即使像 Facebook 这样的大公司也会犯错误,以免过分迷信他们,比如他们在 2021 年10月4日的 DNS 故障。

  • 避免在生产环境中由于不小心而导致的破坏。

  • 有 BUG 的代码总是可以修复的,而人的关系,那就要复杂得多,需要更加谨慎对待。

关于代码库复杂性,你是否有过令人惊恐的糟糕经历?

是什么引发了这场恶梦?


Daniel:这个问题真的只有在回首过去的时候才显得恐怖;当时,我倒是自认为这是我最得意的一次创作。(没错,这是我自己犯下的错误。)

这个项目的目标是设计一个内容管理系统,用于批量生成特殊类型的网站变体。我负责搭建前端系统,输入网站数据,通过一系列精心设计的模板,生成最终的网站。

那是在 20 世纪 90 年代末,那时整个行业都认为 XML 是行业标准,应当遵循。因此,我理所当然地决定使用 XSLT 来构建模板系统。

如果你对 XSLT 不熟悉,它是一种非常纯净的语言,用于将 XML 结构转换为其他 XML 结构,因为 XML 被认为是标准,所以 XSLT 也是用 XML 编写的。

其中一些有趣的挑战包括:XSLT 是严格的幂等的,从哲学的角度来看,这是很好的,但从实践的角度来看,这意味着所有的循环操作都必须通过递归完成,因为增加一个用于迭代循环的变量会被视为一个副作用,因此是不允许的。流程控制最好通过数据的分解而不是分支逻辑来完成。对于习惯了脚本语言和标记的人来说,这真的是一个大挑战。

我却很喜欢它。我不再需要从后端的团队那里获取我需要的具体数据,所有的数据都会以一个巨大的 XML 块的形式传给我,我可以通过我日渐庞大的 XSL 模板块生成我想要的任何东西。我觉得我有了超能力!我可以做任何事情!

现在,需要明确的是,整个过程我都乐在其中,我完全清楚我正在使用的是一门注定要失败的语言。这是在现代前端框架诞生之前很久的事情了,那个时候,“前端”技能通常意味着你熟悉 Photoshop、HTML、CSS,以及可能从其他网站“查看源代码”功能获取的一小部分 JavaScript 代码。相比之下,这里有一种非常复杂,看起来很奇怪的“前端”语言,它需要一些比当时的网络开发者所习惯的计算机科学概念更为高级。

但我却很喜欢它,而且正在学习这些高级概念,并因为能做到这一点而。在大约一年的时间里,我构建了一个相当大的模板集,随着我对我正在做的事情的理解逐渐深入,它们变得越来越不那么糟糕。到最后,我在编写高度解耦的,符合最佳实践的代码,我觉得它们聪明,甚至有时候会觉得有些“优雅”。我按照计划发布了产品,收取了我的合同费用,然后带着满满的信心投入到了下一份工作。

我得到的消息是,在接下来的五年里,他们一直没对这些 XSL 模板做过维护,直到产品被全部淘汰并替换,因为在公司里真的没有其他人能理解它们是如何运行的。显然,那些我自认为聪明、优雅的代码是多么糟糕。我的早期,简单直接的代码反而更易于理解和维护。

这是一个重要的教训。直观、易读的代码,无论其是否精巧,都优于那些聪明且复杂的代码

请描述一个在项目中遇到的可怕的架构决策。

您是如何处理的?


这是我作为自由职业开发者的岁月中的一次经历。一家经常将大部分开发工作外包的中等规模公司,也是我的长期客户,有些惶恐地找到我:他们将一个小型项目托付给了一个新的供应商,工作已经接近尾声,即将达到上线的时刻。然而,一旦他们开始使用实际数据替代测试数据,他们便开始遇到严重的性能问题:当网站需要处理超出小数据量时,整个网站就会显著地变慢。供应商将问题归因于硬件,并建议升级到更强大、更昂贵的服务器。在掏出这笔钱之前,客户让我来对此进行实地考察。

长话短说,问题最后被定位到是供应商编写的一个函数导致的,这个函数使得他们与数据库的交互变得更加便捷:你只需要将 SQL 查询语句输入给它,它会打开数据库连接,执行查询,返回结果,然后清理并关闭连接。

这的确使他们的代码非常易读。然而,问题在于——我相信许多读者可能已经意识到——开启和关闭数据库连接是一个缓慢且耗资源的过程。理想情况下,你应当在开始时打开一次,执行所有查询,然后在全部完成后再关闭。但是,由于这些人的编程方式是对每一个独立的操作都开启和关闭连接,这就意味着有时需要打开和关闭数百次甚至数千次:一次是为了加载数据列表,然后每一项列表数据都需要再次开启和关闭。难怪服务器会变得如此慢!

最后,问题得到了简单的解决——只需将 'open' 和 'close' 操作从实用程序中移除,将它们移到程序的起始和终止部分,而非在循环中重复执行。然而,这是一个极好的例证,展示了快捷方式的潜在危险,以及使事情运行起来和理解为何它能运行以及它在后台做了什么之间的差别。如果他们每次开启和关闭数据库都必须编写代码,他们可能就不会遇到这个问题了,但事实却是,这个操作被隐藏在辅助函数里,所以人们很容易忽略。

我每次安装一个新的 npm 模块或者以其他方式引入别人的代码时,都会想起这个故事……这个模块在做什么看似合理的事情,有可能会给我的工作带来困扰吗?

重构之殇——如何走向失败?


我们拥有两个独立却相关的网络产品,由各自的团队分别使用不同的编程风格在各自的代码库中进行开发。我们的目标是将这两者整合成一个产品。

由于两个代码库的复杂性和庞大规模,要将它们重构成一个整洁、统一的代码库无疑是一个长期的项目;与此同时,我们急需一个短期的解决方案。

我们原本应该保持原有的代码不变,只需在统一的用户界面上添加各个产品间的导航链接。

然而,我们实际上做的是将产品 A 的所有代码复制到产品 B 的代码库的子目录中,希望能够逐步重构两个产品的代码,使之互相匹配,并在此过程中找出并消除重复功能。

这个方法可能会取得好的结果!然而,一个事实打破了这种可能性,那就是那位负责复制代码的工程师认为在此过程中进行部分重构是个好主意。他根据自己的编程风格,对两边的代码进行了大量的重大改动,完成程度各不相同。

在这个过程中,他大部分时间都在设法避免对代码产生明显的破坏,然而他同事们并不知道他已经做了一些未记录的改动,这些改动也留下了难以察觉的陷阱。然后,他立即辞职,加入了一家竞争公司。

此事件确实反映了该组织在管理和代码审查流程方面的重大缺陷。

所以,我们最终以原本预计的两倍时间投入完成了那个艰难的重构,同时也进行了代码审查。我们最终完成了,但整个过程非常痛苦,是我至今为止最令人后悔的一次经历之一。

最后我听说,那家公司正在“拆分单体应用”,并开始将那个统一的前端分解为独立却相关的产品

你是否曾被第三方库或框架带来的意料之外问题或复杂性困扰?


在 2021 年 10 月 4 日,Facebook 因 DNS 故障导致自身以及其相关 API 大部分时间不可用。这也使得我们的应用程序由于过度依赖这些 Facebook 的 API(我们默认它们永远在线)而被迫下线。毕竟,这是我们都信任的 Facebook,没人会预料到 Facebook 的服务会出现这样的问题。

为了修复这个问题,我们紧急进行了大量代码重写。然而,就在我们几乎完成部署并让应用程序在没有那个 API 的情况下也可以正常运行的瞬间,他们的 API 服务恢复了。

好吧,至少我们为下次问题做好了准备

你是否经历过令人恐慌的惊魂时刻,

或者犯了令你心惊胆战的编程错误?


我一直严格确保自己不去访问生产服务器,因为我不想成为那个无意间在生产环境中破坏某些东西的人。

然而,我还是犯了错误:“这里还有一些未合并的代码,让我登录服务器来处理一下。”结果造成了意外!那天在 Slack 上的情况,我都截图保存了下来:

但这还只是代码问题,代码问题比较好解决,人就难搞定了。

我职业生涯中真正的恐怖瞬间,则要追溯到 Slack 和 Hipchat 之前,回到电子邮件的时代。

那是在一家小而成熟的初创公司。我们的一位客户支持人员在深夜向团队的一部分人发送了一条消息,寻求处理一个特别苛刻的客户问题的建议。我已经不记得具体的问题是什么了,但那个问题演变成了一场工作时间之外的抱怨会,我们大家都在电子邮件中抱怨这些客户多么难以应对,他们的一些要求多么不合理。名单越来越长,从销售到工程到首席执行官,每个人都对此进行了抨击。最后,客服代表得到了她需要的答案,并将其发送给了客户。

然后这个消息回到了列表中:“呃...伙计们?我想我可能不小心把整个电子邮件线程都转发给了客户。”

接下来的十分钟内,所有人都陷入了沉默,心跳加速地翻阅整个回复链,寻找自己可能需要道歉的言论。最后结果证明是虚惊一场,她实际上并没有将整个电子邮件线程发送给客户。但那确实是一个我希望再也不要经历的惊魂时刻。有 BUG 的代码很容易修复,但人的关系破坏了就很难修复了。

本文是“代码恐怖故事”系列第二篇,第一篇《代码恐怖故事:揭秘形成复杂代码库的常见原因》欢迎大家回顾。

大多数开发者都有自己独特的故事。你是否也曾亲历过一些“代码恐怖故事”?欢迎在留言区分享讨论。

推荐阅读:

▶Mac 上能跑国产系统了!深度 deepin 官宣:正式适配 M1 芯片

代码恐怖故事:揭秘形成复杂代码库的常见原因

突发!ChatGPT 紧急暂停 Bing 集成,下线搜索功能


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存