译:开发 25 年后我了解的七件事

原文:https://zverok.space/blog/2025-01-27-7things-euruko.html
作者:zverok
译者:Claude 3.5 Sonnet

编者注:这是一位乌克兰开发者在 EuRuKo 2024 会议上的演讲记录,他分享了 25 年开发经验中的七个重要心得:1) 你终将超越框架的限制,因为随着项目发展,任何框架都无法完全适应需求;2) 各种模式和方法论最终都会让你失望,它们往往只在教科书中完美;3) 项目规模只会不断增长,旧的处理方式总有失效的一天;4) 要关注具体的故事和场景,而不是一味追求架构和分层;5) 开发的终极目标是追求真相和清晰;6) 坚持真理可能会是孤独的体验,因为很多人更愿意遵循已有模式而非质疑和改变;7) 永远不要放弃寻求真理,作为软件作者,首要的是诚实地表达自己的认知。整个演讲既包含技术洞见,也融入了他作为乌克兰军人的独特视角。

这是我在 2024 年 9 月 EuRuKo 会议 上发表的主题演讲的大致记录。演讲的视频在这里。不幸的是,我只能通过录制来发言,但这仍然是一个巨大的荣誉。这个话题对我来说非常重要,所以我决定分享一个略有编辑的文字记录,以供那些更喜欢文字形式的人参考。

我已经从事软件编写大约 25 年了。在这其中的 20 年里,我一直使用 Ruby 作为我的主要语言。我为这门语言和其他开源项目做了一些贡献。我写了很多关于 Ruby 的文章,并且维护着 Ruby Changes 项目,你们中的一些人可能听说过。

我在美国公司 Hubstaff 是一名原则工程师,但只是“有点像”,因为……

关于我最重要的一点是,我是一名在武装部队服役的乌克兰人。目前,我不在战斗岗位上,而是在做一些我更擅长的其他事情(我的其他活动也是在这些职责之外进行的)。

我之所以加入军队,并不是因为我非常好战或喜欢战斗,而是因为我有保护我的祖国的责任。

对许多乌克兰人来说都是如此。

目前,我们的武装部队中有各种各样的人在服役,我有一个小型个人项目,就是将在军队服役的诗人翻译成英文。

所以,这次演讲的实际标题是

在 25 年的开发和 10 年的战争后,我所知道的七件事

它仍然关于开发和 Ruby,但我允许自己用我作为一个乌克兰人的经验来画一些类比。我希望这使得演讲更加丰富。我保证,没有任何过于生动/令人不安的内容。

让我们直奔主题,分享那七件我想分享的事情。

1. 你会超越每一个框架

在任何领域,不仅仅是技术领域,我们经常依赖各种框架和基础知识来知道该做什么和期望什么。

比如,在国际关系中,我们乌克兰人长期以来依赖于作为一个主权国家,其安全由全球大国保障。结果证明,这并不像我们所希望的那样坚固。

所以,作为程序员,我们经常依赖框架,作为 Rubyist,我们依赖于 The Framework,但我并不打算批评任何特定的框架。

重点是框架为我们提供了房间和盒子来放置我们的代码,并只实现业务逻辑时感到自信。

但我们都知道,最终,随着代码的增长,我们有了更多的概念,然后是更多,其中一些由你的框架提供,一些由团队引入。

无论我们如何扩展它,总有些东西突兀地凸显出来。

就像分形一样,每个特定的概念都从小开始,然后发展成为它自己的框架。就像现在常见的可调用对象,这是一个简单的想法。当你引入它时,它可能看起来像是你需要完成心智模型完整性的一个小步骤。

然后出现了新的需求和新的用例,最终,你有了一打 DSL 选项来定义一个。

然后不管怎样,总有些东西凸显出来,不适合之前的任何定义。如果你不处理它,你要么会有非常臃肿的概念,要么会有一些垃圾箱,像是 lib/ 文件夹或 app/services 文件夹,很快没人确定哪里是什么了。

为了处理这个问题,你应该准备好做出自己的架构决策,无论你依赖于哪些框架。但这里有一个问题。

2. 模式和方法论的失败

我确信的第二件事是,整个模式和方法论会让你失望并变得过时。

就像我们乌克兰的经验一样,整个国际安全架构变成了笑柄,国际人道主义结构证明了自己的无用,而人权捍卫者捍卫的却是除了人权之外的一切。

作为开发者,当我们需要做出决定时,我们……

……可能会依赖一些方法论、信赖的方法、书籍、模式,或者编码简单原则的有趣助记符。

然而,随着代码的增长,项目的成熟,行业发展新的观点,以及挑战的变化,许多疑问和问题浮现出来。

你可能会发现,继承是一个只在教科书上好用的想法,或者整个面向对象编程(OOP)都让我们失望了。你开始怀疑 Active Record 是否是一个可行的方法,或者被动实体和仓库是否更为理智。

你可能会被提醒,SOLID 是一个由一个人为了营销目的而创造的首字母缩写词,它组合了可能有用但定义模糊且相互矛盾的原则。总的来说,对于任何常见的方法,都有很多关于其有用性或有害性的争论(以及反驳,以及对那些反驳的反驳)。

因此,我们为自己发现了更高层次的指导原则,如 DDD,它遵循一个非常理智的思想,即领域与其编码表示的同构性。然而,它仍然在设计决策中留下了许多空白,例如当一个小领域概念需要一个大算法来实现,或者当重要的领域概念对应于一个小的实现时。

它还引入了许多新术语,人们喜欢将其转化为字面类,所以最终,除了你的 MVC 类之外,你还有实体、聚合、有界上下文……或者,正如一位智者曾经说过

或者我们可能会谈论“六边形架构”,对吧?

…它因从不解释为什么是六边形,以及留给你自己决定整个“核心”架构的设计决策而著名。(我不是在这里说它不提供一些有用的概念。)

最终,这些可能会有帮助,但最终,设计新事物时你将独自一人。为什么会这样呢?

3. 规模只会随时间增长

因为规模只会随时间增长。这适用于世界上的许多事物,这也是一个相当平凡的陈述,但它经常被忽视的后果。

想象一下你维护过一段时间的任何 Rails 应用。我想你可能会同意,它所做的就是增长。

我们生活在一个资本主义的世界,你需要快速运行以保持相关性。无论是像切换到 SPAs 这样的新技术,扩大你的市场份额到之前比你小或更大规模的客户,试图在不失去老客户的情况下调整你的市场适应性,还是仅仅是线性增长。

无论发生了什么,都会有更多的代码、更多的表、更多的端点、更多的测试、更多的一切。

作为一个例子,我们可以看看 GitLab 开源代码库,它起初看起来就像你的常规 Rails 应用。

最初,它是什么?只是一个在 git 之上的协作 UI。

现在,如果你浏览它的主文件夹,你会看到比新 Rails 应用中的东西多一些,而且它的 app/ 文件夹中有很多自己命名的概念,如果你打开其中一个文件夹,你会看到越来越多、越来越多、越来越多。(注意我在最后的截图中只到了“c”字母。)

也许我们的项目并不都像 GitLab 那样规模庞大,但谈到任何商业项目,最终你都会需要报告、通知、更好的角色管理、支付系统、新的 API、批量操作、商业分析等等。

我所说的简单却经常被忽视的后果是:你旧有的处理问题的方式最终会失效,这是完全自然的。

用十个控制器的思维方式,每个控制器有四个标准方法,这样的方式无法扩展到数百个非平凡的端点。

那我们该怎么办呢?… 嗯,我有一个矛盾的答案!

4. 关注故事

我的答案是:关注单个故事。当我们处理软件的任何功能(实现一个新功能、调整一个旧功能,或者试图理解它为什么表现得不正确)时,我们总是有一个故事线来遵循:有一些原因和结果:输入、输出、转换、效果,这些都可以线性地讲述。

但是,通过跳跃层次和约定并讨论“更大的图景”,我们经常会失去那些特定的线索——然后失去更大的图景。

一个小例子:曾经有一次,我试图为 Rubocop 添加一个新功能(但因为一个不相关的原因没有成功),当我花了大约一天的时间试图理解一个分散在 2-3 个“简单”行方法中的算法时,我有了这个 AHA 时刻,并最终将其重写为一个半屏幕大小的方法。尽管如此,这并不符合预期的“复杂性指标”:

这里,作为对比,是原始算法的一部分(当时的样子),但我的屏幕不够大,无法截取整个内容:

这并不是关于“写得不好”的 Rubocop(这是一个由极其能干的人维护的了不起的软件项目),而是关于一些方法论上的深层次差异。

将一切拆分成极其小的方法,并将每个条件提取到一个谓词中,实际上是 Ruby 社区中广泛采用的方法(默认的 linter 复杂度阈值加强了这一点)。

作为一个相反的观点,在 Hubstaff,我们在“小型编码规则”文档中有这样一个片段,强调了“首先尝试将一切整合在一起,之后再强加架构”的想法:

“单一方法”在这里并不是关键!

关键是从哪个角度出发,将代码聚焦于“展示这里发生了什么”作为主要目标。我们遵循的大致原则列表是:

  • 紧凑的代码;
  • 表达性的语言;
  • 丰富的词库
  • 频繁的小重写;
  • 对单页(屏幕)代码的关注。

这导致了相关概念的良好命名和结构的发现——比在架构规划阶段就试图发明它们并将其适应于“大局”要可靠得多。

但大多数时候,根本就没有单一简单的“大局”(任何尝试为成熟项目制作类或流程图的人都会同意)。

这只是一系列相互链接的故事,一个有着穿越其中小径的花园,而不是一个混凝土摩天大楼。但为什么这些故事如此重要呢?…

5. 目标是真理和清晰度

因为我们作为开发者和人类的最终目标是真理和清晰度。而这是通过倾听故事而显现出来的。有人可能会说,当谈论软件架构时,“真理”是一个奇怪的词;它更多来自哲学(或法律)领域……

…但我的说服力是,直观层面上,我们所有的原则和方法论都被引入作为寻找真理的方式。

就像面向对象编程(OOP)在简单的时代被发明出来,希望能够模拟整个世界一样,行为驱动开发是一种在实现软件之前,真诚地描述对软件的期望的方式,等等。

我们的问题在于,我们将任何好的和不精确的想法转变为一门学科,对其进行形式化,然后最终,我们不是试图真实地反映问题,而是用严格遵循规则来替代它。

作为一个小例子,Ruby 社区曾经利用语言的表达能力发明了一个非常强大的测试 DSL,名为 RSpec,但最终却说服自己测试不应该是表达性的,它们应该遵循一些最原始代码可能的规则:

因此,许多 Ruby 开发者被教导编写的测试比它测试的代码更冗长,有时冗长的程度高出几个数量级。他们理所当然地讨厌它,并且被禁止甚至梦想思考可能有其他方式。

实际上,确实可以。

无论你喜欢这个例子还是讨厌它,我真心希望你能在这里看到一个观点,这个例子讲述了期望行为的故事。尝试让它工作也说明了“讲故事”方法对词库的影响。为了使其工作,你需要一些新的高级抽象,这些抽象定义一次,然后改善许多规范作者的生活。

然而,众所周知,像这样的方法经常遭遇激烈的反对。 这里是我的第六件事。

6. 这可能是一种孤独的体验

试图坚持真实的故事可能是一种非常孤独的体验。首先,因为人们有他们的“大局观”,他们从你的故事中期望看到的第一件事是看它如何适应这些大局观。

因此,当试图“推销”专注于“这里真正发生了什么”的方法时——无论是在代码审查、对编程,还是讨论样式指南时——一个人可能会遇到从完全漠不关心到积极敌对的反应。

因为寻找真相是关于不断调整你的心智模型并质疑自己的经验。一般来说,人们,尤其是资深专业人士,特别是软件开发者,对此并不上心。我的意思是,我们可能每天都在更换工具,对吧,但是内化的说服力和机械习惯呢?永远不会!

有人可能会注意到,最近在行业内,有很多强烈的情绪反对“智能代码”和一般的表达性,或者,更广泛地说,关于我们编写代码的特定方式的不重要性,以及由此产生的,对代码审查实践的强烈反感。

我明白,我确实明白。

一般来说,在解决问题时,人们更喜欢写代码而避免阅读和编辑。这导致了一种悲哀的状态,即代码审查的实践变成了参与者(他们不知道除了走过场外该怎么做)的一项繁琐的正式职责,并最终被诋毁为一种浪费时间和确立优势的有害且无意义的方式。

但在我看来,阅读和讨论彼此的代码是确保你讲述的故事对其他人来说是有意义的,它不与其他故事矛盾,也不会误用共同的词汇表以错误的方式说出不该说的话的唯一方式。

这是一项深刻的人文活动,没有人能说服我认为不是。 这引出了我最后一个观点。有点感伤的那种。

7. 永远不要放弃寻求真相

永远不要放弃寻求真相,无论它多么不舒服。寻求知识。调整你的世界观。提问。重写过时的代码。放弃错误的假设和不可靠的基础。

软件作者,首先是一名作家。他们是一个站立着说:“这就是我现在所知道的,这是我尝试解释它的最佳方式。”的人。拥有这种立场,优先考虑它而不是其他一切,并且隐藏在术语、概念和权威背后,是长期项目成功的宝贵品质。

或者,基本上适用于任何长期的人类活动成功。

这是我学到的最重要的事情。对于这么长的职业生涯来说,可能并不算太多,但这就是我个人和专业上的立场。

谢谢。我希望有一天能亲自见到你们所有人。

请,即使不记得这次演讲,也请记得帮助乌克兰。