我花了 20 多年才学到关于开发的这一课

2025-06-05

我花了 20 多年才学到关于开发的这一课

我的软件开发生涯始于一名 Web 开发人员。我曾在大大小小的公司工作过,包括在惠普和谷歌工作过。在微软,我担任首席工程师。后来我离开微软,成为 Fermyon 的联合创始人兼首席执行官。

我并非一个经常给出建议的人。每个人的经历各不相同,一个人经历的轶事证据很少具有很好的普遍性。但在我的职业生涯中,我一次又一次地犯过同样的错误(并且观察到其他人也犯过同样的错误),因此我觉得有一条很好的建议可以分享:

请记住,您编写的每一行代码都是您将支持的一行代码。

这句格言的内涵比乍一看要丰富得多。我来分享一下它的内容:

  • 编码是一种手艺
  • 非我发明综合症
  • 复杂性的代价
  • 减少未来支持需求
  • 以及为什么你(和其他人)未来的人会感谢你今天编写的代码

编码是一种手艺

让我们先从这句格言的乐观应用开始。“工匠精神”这个词或许常常与过去的时代联系在一起。它意味着对一门手艺的执着,精通需要随着时间的推移、辛勤的付出和频繁的活动才能显现出来。与“工程”一词不同,“工程”一词指的是一种注重规划的实践,而“工匠精神”则意味着亲自动手的努力。编写代码,包括调试、测试和重构,都是一种亲自动手的努力。

构建优秀的软件需要技能、知识和持续改进的渴望。在编写代码时,要花时间将其做到极致。诚然,“完美是优秀的敌人”,你需要通过编写代码来完成工作。但遵循良好的编码约定、合理命名变量以及深入思考要解决的问题,这些做法都能让你的代码更易于长期维护。简而言之,当你将编码视为一个动手实践的过程,并随着时间的推移逐渐精通时,你就是一位工匠了。

支持糟糕的代码很痛苦。维护优秀的代码却能激发我们的智力。工匠精神让我们专注于编写优秀的代码,并将其作为我们日常实践的一部分。

“非我发明”是你的敌人

我们都经历过这种感觉。当然,肯定有库或工具可以做到这一点。但你可以做得更好!

每个软件工程师都至少做过一次这样的事。我花了15年的职业生涯才最终领悟到这一点。我重写了从模板引擎到底层数据结构的所有东西。而且我总是有这样一些理由:

  • 当然,还有其他库,但它们太大/太臃肿/太复杂/用途太特殊……
  • 我有一种别人没有想到的新颖的做事方式
  • 我不信任/不了解/不放心别人的图书馆
  • 我刚刚读了一本启发我的书/文章/博客文章
  • 相比使用别人的方法,自己做可以学到更多(确实如此,但成本比我意识到的要高得多)
  • 我没时间看

在所有这些情况下,我忽略了两件事:

  • 其他人已经花了多少时间解决这个问题。(代码成熟度的标志)
  • 未来的我需要花多少时间来修复、调整、概括和维护该代码

经验丰富的工程师称之为“非我发明”(NIH)综合症。这是一个陷阱,它会导致重复他人的工作,同时承担新的维护负担。所以,当你发现自己可以选择一个现成的库或工具,但又认为自己可能做得更好时,问问自己,在接下来的几年里,你对维护这段代码有多大的热情。

这个应用有例外吗?当然有!但例外应该很少,而且要先认真研究过现有解决方案是否足以完成任务。

代码越复杂,调试就越困难

我开发过一个包管理器,里面有一个复杂的字符串解析器。一时灵感迸发,我一口气写完了整个解析器。而且,我只用了几个函数就完成了。虽然这些函数不是很长,但它们的概念很复杂。用专业术语来说,我的代码的圈复杂度非常高。这两个函数有很多不同的路径,很难一眼就看出使用这两个函数的所有潜在结果。

我要说的是,我写了测试,也至少写了一点文档。我的代码格式也无可挑剔。最棒的是,代码运行良好,而且我多年来都没怎么动过(参与这个项目的其他十几位开发人员也没动过)。

但这一切都无法弥补它难以理解的事实。

后来有一天,我们收到了一个 CVE(通用漏洞公告)。在一次极其狡猾的攻击中,黑客可以强制解析器分配超过系统内存的内存。我已经三四年没碰(也没想过)这段代码了,现在我得去修复它。我的眼睛扫过这些代码行,感觉完全陌生。接下来的几天里,我不得不拆解自己的代码,试图弄清楚它在做什么,以及如何修复这个后来被证明是隐藏得很好的 bug。

德米特定律是一项值得遵循的良好软件实践。它以希腊女神德米特命名,表明限制代码的圈复杂度(决策树的数量)是通过将代码拆分为有用的单元而实现的美德。虽然这与其说是真正的定律,不如说只是一条经验法则,但它确实是一项很好的定律。它可能会迫使你编写更多行代码(因为你将复杂的任务分解为函数),但如果它能带来更易于调试和维护的代码,那么它就是美德。

现在为你的代码编写测试意味着以后对你的代码的支持会减少

我听过软件开发人员说过的最大胆的话之一——这话出自一位拥有20多年经验的程序员之口——是“要求进行单元测试意味着告诉你的团队你不信任他们”。他第一次对我说这句话时,我惊呆了。这是多么巨大而不可思议的飞跃啊!在我们生活的每一个重要行业中,从银行的账单发放到药房的装瓶,再到为新制作的牛仔裤贴标签,任何优秀的系统都会有一系列的质量检查。

代码也应该有这个。

更酷的是,有了代码,添加质量检查其实非常容易。我们只需要编写代码来测试代码就行了!

虽然我上面提到的开发人员认为信任是主要原因,但我发现,在我自己的工作中,我会提出另一个不写测试的理由:太匆忙。我发现了一些应对匆忙的好方法,而且这些方法几乎都是外部的:

  • 使用代码覆盖率工具,确保我的代码覆盖率至少达到 80%。并且每次我编写新代码时,测试覆盖率都不会低于 80%。
  • 任何代码在通过代码审查之前都必须进行测试。我坚信代码审查的价值,而这正是代码审查能够真正发挥作用的地方。你或许还能找到其他方法来帮助你坚持测试方案。我的一位朋友,优秀的程序员 Adam Reese,曾经告诉我,编写优雅而严格的测试对他来说是一项智力挑战。将测试视为一项挑战——一个需要解决的难题——帮助他保持动力,最终实现覆盖率目标。

无论你的策略是什么,如果你现在不测试,以后就得调试。甚至可能在更紧急的情况下,造成更严重的后果。

未来的你不会记得现在的你在想什么

所以记录一切!

我们人类有一种奇怪的偏见。我们以为明天就能记住今天发生的一切。我们也以为,当我们回首往事时(甚至多年以后),我们还能回忆起事件发生时自己的精神状态。但事实并非如此。我们大多数人都很难回忆起几天前午餐吃了什么。

我以前是个哲学家。早期现代哲学家大卫·休谟曾雄辩地表示,他甚至怀疑我们是否应该认为自己今天和昨天是“同一个人”。我们的思维极其流动,休谟担心我们往往会夸大现在的我与过去的我或未来的我之间的相似之处。

将来,您将不会记得为什么以今天的方式编写此代码的细节,或者为什么命名该变量fhr,或者您希望该命名不当的函数执行什么操作,或者为什么// FIXME later在第 235 行留下注释。

克服自身记忆力限制的最好方法是让代码清晰易懂。这意味着:

  • 自由评论
  • 给事物起好名字
  • 编写更高级别的文档
  • 在版本控制系统中编写有用的提交消息

未来的你会感谢现在的你。或者至少不会生你的气。

推论:如果别人不能理解,那永远都是你的问题

因此,请以其他人能够理解的方式记录下来。

真的,如果你上一步做得很好,这一步应该也能搞定。但问题就在这里,可能会变得更加棘手(或者至少更加烦人)。

之前我讲过我写的字符串解析器的故事,以及几年后如何出现了一个 CVE,然后我必须修复它。我没有提到的是,当我被叫去修复它的时候,我甚至还没有真正地在做这个项目。我已经去做其他事情了。

但由于我的代码功能不明确,其他人都觉得自己没法修复这个bug。我不得不暂停其他项目几天,重新回到这个项目,修复我的代码。

这有什么教训呢?如果我的代码难以理解,它就会反过来困扰我。因为其他人无法理解它。他们肯定会git blame理解的。他们会追着你跑。他们会不让你喝咖啡吃披萨,直到你解决问题。这可真让人不爽。

知道什么更好吗?让你的代码变得如此简单易懂,以至于其他人可以在你不知情的情况下修复你的代码。

结论

我建议采用以下格言来推动自己改进自己的代码:

请记住,您编写的每一行代码都是您将支持的一行代码。

它将为你节省未来的时间和精力。它将避免你的同事感到沮丧。它将使你更容易将今天的代码转换为明天的代码。而且它很可能会让其他人也认为你也是那些超级程序员之一。

编程是一门手艺,一门工匠精神。而达到精通的最佳方式,就是仔细思考你正在构建的东西,然后将这种思考倾注到代码中。

我再说一遍:支持糟糕的代码很痛苦,支持好的代码却很快乐。

文章来源:https://dev.to/fermyon/it-took-me-20-years-to-learn-this-lesson-about-dev-1mep
PREV
5 个用于有效 ML 测试的开源工具 Giskard-AI/giskard 🐢 confidence-ai/deepeval ✍️ promptfoo/promptfoo 🔎 deepchecks/deepchecks ✅ great-expectations/great_expectations 📊
NEXT
TypeScript 和 JSX 第一部分 - 什么是 JSX?