你无法设计你不参与开发的软件
原始链接: https://www.seangoedecke.com/you-cant-design-software-you-dont-work-on
只有参与大型软件系统开发的工程师,才能真正参与系统设计。因为好设计离不开对系统具体细节的深入了解。换句话说,对于大部分实际的软件设计问题,通用的设计建议通常毫无用处。
通用的软件设计
什么是“通用的软件设计”?它指的是“针对问题进行设计”:也就是当你了解业务领域,但不怎么熟悉现有代码库时给出的建议。不幸的是,你在书本和博客里看到的几乎全都是这种建议。工程师们喜欢给出这类建议,就像所有技术人员都喜欢“聊专业”一样。但在把这些通用建议用到日常具体工作中时,你必须非常小心。
在实际工作中,具体因素比通用因素重要得多。 清楚代码现在的样子,远比掌握通用的设计模式或原则更重要。例如:
- 在大型代码库中,一致性比“好设计”更重要。我在 《工程师在大型老旧代码库中常犯的错误》 一文中详细讨论过这点。
- 真实的代码库往往充满了复杂且难以预测的连锁反应。为了安全地修改代码,你的实现方式通常只能局限在极少数的选择里。
- 大型共享代码库永远不会只体现一种完美设计,它们总是处于不同设计之间的过渡状态。因此,代码库在修改后能否平稳运行,远比你追求什么理想的“北极星”架构重要得多。
如果你能随意重写整个系统,通用设计建议会实用得多。有些项目确实可以!但大多数软件工程都是在无法安全重写的系统上进行的。这些系统不能光靠“软件设计”,而是必须依赖内部的一致性和工程师的细心。
具体的软件设计
那么,优秀的软件设计到底是什么样的?
根据我的经验,最实用的软件设计往往诞生于一小群工程师的讨论中,他们每天都在跟系统打交道,对系统有极深的理解。对外人来说,这些讨论通常极其枯燥,因为它们全围绕着系统中晦涩难懂的具体细节,而不是随便哪个技术人员都能插嘴评价的通用原则。
他们讨论的不是“DRY(不重复代码)好还是 WET 好”,而是“我们能把这个新功能放进子系统 A 吗?不行,因为它需要信息 B,而 B 在上下文 C 里对 A 是不可见的,如果不重写子系统 D 我们就拿不到,但如果把子系统 E 从这里和这里拆开……”。
深奥的设计哲学在这种讨论中毫无用处。相反,最有价值的贡献往往是纠正对具体细节的误解,比如:“哦,你以为 B 在上下文 C 里不可见,但我们最近重构了 C,现在需要的话可以直接把 B 传进去。”
通用软件设计什么时候有用
通用的设计建议虽然解决不了实际问题,但也不是完全没用。
它在从零开始建新项目时很有用。 就像前面说的,在现有系统加新功能,具体因素占主导。但在设计全新系统时,没有历史包袱,你完全可以跟着通用建议走。
它在打破具体设计僵局时很有用。 我不建议一开始就套用通用设计,但如果你有几个看起来都可以接受的具体方案,通用原则可以帮你做决定。
这在全公司层面尤其适用。换句话说,通用建议能帮助不同的代码库保持一致。这也是官方“软件架构师”最有用的职责之一:提供一套通用原则,让大家在面对具体决策僵局时,都能往同一个方向做选择。
通用原则还能指导全公司的架构决策。 我们的服务是跑在自建机房还是云上?用 k8s 吗?AWS 还是 Azure?一旦层面足够高,单个服务的具体细节几乎就不重要了,因为无论选哪条路都是巨大的工程量。尽管如此,具体细节依然很关键。有些事你在云上做不了(比如依赖定制的硬件配置),有些在自建机房做不了(比如把服务部署到 12 个边缘地区)。如果你的代码非常依赖这些特性,而在做公司级决策时又忽略了它们,你就会有大麻烦。
架构师与局部最优解
做好通用软件设计有上述好理由。但许多公司搞通用设计,仅仅是因为它在非技术人员听起来“是个好主意”。一旦开始这么干,就很难停下来。很多科技公司都陷入了这种局部最优解。
为什么不让薪水最高的工程师专门去做最抽象、影响最大的决策呢?毕竟,你会让结构工程师去画图,而不是去砌砖。我不知道建筑工程是不是这样,但我知道软件工程绝对不是。在实际操作中,一线的开发人员往往不得不无视那些软件架构建议。因为在现有系统的背景下,这些建议根本无法落地成他们能敲出来的代码。
然而,“让顶尖工程师只做通用设计”这种无效做法,生命力却出奇地顽强。这是因为架构师不用承担风险,他们的设计直接丢给工程团队去实现。既然设计不可能被完美实现,架构师就可以两头占理:成功了是设计的功劳(毕竟是我的设计),失败了就甩锅(要是这帮蠢货按我的设计来就好了!)
总结
在现有的庞大代码库中工作时,真正有用的软件设计讨论比很多人想象的要具体得多。它们通常涉及单个文件甚至某几行代码。因此,如果不深入熟悉代码库(在实践中,这通常意味着你必须是一线代码贡献者),你就无法做出有效的设计。
纯粹的通用架构并不是毫无用处,但它的作用应该仅限于:(a) 为全新系统铺路;(b) 在现有系统出现决策僵局时拍板;(c) 帮助公司做宏观的技术选型。
在我看来,那种全职负责给项目画初始大饼的“宏观软件架构师”角色注定会失败。听起来很美(对架构师本人也确实划算,能抢功又不用背锅),但对真正负责写代码的团队来说几乎毫无价值。
个人认为,谁提出软件设计,谁就应该对项目的成败负责。这样能迅速筛选出真正懂如何交付软件的人来做设计,也能确保那些真正的软件设计师——也就是那些不仅搞设计,还要处理代码库里各种坑和烂摊子的一线开发——为他们所做的艰难设计工作获得应有的认可。
补充:这篇文章在 Hacker News 上收到了一些 评论。有些评论不同意我关于“一致性”的观点,这让我有些意外,毕竟我记得大家对 《工程师在大型老旧代码库中常犯的错误》 的评价还挺正面的。我也毫不意外地看到有人说:“哈哈,这太虚伪了,因为这篇文章本身就是在给通用建议。”对于这点,我在上面的“通用软件设计什么时候有用”这一节里已经解释过了。
这篇文章在 Lobste.rs 上也有一些 讨论。这是少有的 Lobste.rs 评论质量不如 Hacker News 的情况:大家基本上在抠“通用”这个词的字眼,或者瞎猜我是不是用 AI 写的这篇文章(真不是)。
如果你喜欢这篇文章,可以考虑 订阅 我的新文章邮件通知,或者 [把它分享到 Hacker News](news.ycombinator.com/submitlink?… can't design software you don't work on)。下面是一篇有相同标签的相关文章预览:
纯粹与不纯粹的软件工程
为什么独立游戏开发者经常和大型科技公司的工程师吵架?为什么大公司高薪挖来的外部人才经常水土不服?为什么 AI 辅助开发对有些人是神器,对另一些人却毫无用处?
我认为这是因为不同工程师做的工作截然不同。这两类工程师常常觉得对方能力不行,但其实他们只是在不同类型的领域工作罢了。
继续阅读...