做最简单且可行的事情

做最简单且可行的事情

原始链接: www.seangoedecke.com/the-simples...

在设计软件系统时,请做最简单且可行的事情。

你会惊讶地发现,这条建议有多么实用。我甚至认为你可以永远遵循它。无论是修复 Bug、维护现有系统,还是设计新架构,你都可以采用这种方法。

很多工程师在设计时,总想弄出一个"完美"的系统:结构良好、无限可扩展、优雅的分布式设计等等。我认为这完全搞错了软件设计的方向。相反,你应该把时间花在深入理解现有系统上,然后做最简单且可行的事情。

简单的方案往往平平无奇

系统设计需要你掌握许多不同的工具:应用服务器、代理、数据库、缓存、队列等。随着对这些工具越来越熟悉,初级工程师自然会想去使用它们。把很多组件拼装成一个系统很有趣!在白板上画满各种框图和箭头也让人充满成就感------感觉自己正在做真正的"工程"。

然而,就像很多其他技能一样,真正的精通往往意味着懂得"少即是多"。这就像武侠电影里的经典桥段:新手动作眼花缭乱,上蹿下跳;而大师几乎一动不动。但新手的攻击总是差之毫厘,大师的最终一击却是一击致命。

在软件领域,这意味着出色的软件设计往往看起来平平无奇。它看上去好像没做多少工作。当你产生诸如"哇,原来这个问题这么简单"或"太好了,其实根本不需要弄得那么复杂"的想法时,你就知道自己面前是一个优秀的软件设计。

Unicorn 是一个极好的软件设计,因为它通过依赖 Unix 原生机制,以最朴素的方式提供了 Web 服务器最核心的保障(请求隔离、水平扩展、崩溃恢复)。行业标准的 Rails REST API 也是极好的软件设计,因为它用最枯燥无味的方式为你提供了一个增删改查(CRUD)应用所需的一切。我认为这些都算不上多么令人惊艳的软件 。但它们是令人惊艳的设计 ,因为它们做了最简单且可行的事情

你也应该这样做!假设你有一个 Golang 应用,想加一个限流功能。最简单可行的方法是什么?你的第一反应可能是引入某种持久化存储(比如 Redis),用漏桶算法来记录每个用户的请求数。这绝对可行!但你真的需要引入一个全新的基础设施组件吗?如果直接把用户的请求数存在内存里呢?当然,应用重启时会丢失一些限流数据,但那真的要紧吗?再想一步,你们的边缘代理是不是已经支持限流了?你能不能干脆不在代码里实现这个功能,只需在配置文件里加两行代码搞定?

也许你的边缘代理不支持限流。也许你并行运行的服务器实例太多,用内存限流误差太大。也许你的服务被请求轰炸,丢失限流数据是绝对无法接受的。在这些情况下,"最简单且可行"的事情就是引入持久化存储,那你就去用。但如果能用更简单的方法,你难道不想用吗?

你完全可以按这种方式从零构建一个应用:从绝对最简单的方案开始,只有在面临必须解决的新需求时才去扩展。这听起来有点蠢,但非常有效。你可以把 YAGNI(你不需要它) 视为终极设计原则:它凌驾于单一职责原则之上,凌驾于选择最佳工具之上,甚至凌驾于"好设计"之上。

采用最简单的方案有什么问题?

当然,"永远做最简单且可行的事情"会面临三个大问题。第一,因为不预判未来的需求,系统最后可能会变得死板,或者变成一团大泥球。第二,"最简单"的定义并不清晰,最坏的情况下,这句建议听起来就像废话:"想设计好,就去做好的设计"。第三,你应该构建可以扩展的系统,而不是只能在眼前跑通的系统。我们来逐一分析这三个反对意见。

大泥球(糟糕的代码库)

对一些工程师来说,"做最简单且可行的事情"听起来就像是在告诉他们放弃工程思维。如果最简单的办法通常是临时写个补丁(Hack),这难道不会必然导致代码库变成一团糟吗?我们都见过那些补丁摞补丁的代码库,它们绝对不算是好设计。

但是,临时补丁真的"简单"吗?我并不这么认为。临时补丁的坏处恰恰在于它 简单:它给代码库引入了你需要一直记在脑子里的额外复杂度。临时补丁只是更容易想到而已。找出正确的修复方案很难,因为这需要你理解整个代码库(或很大一部分)。事实上,正确的修复方案几乎总是比临时补丁简单得多。

做最简单且可行的事情并不容易。当你面对一个问题时,脑海里蹦出的前几个方案往往不是最简单的。想出最简单的方案需要你权衡许多不同的思路。换句话说,这需要你去做真正的工程设计。

什么是简单?

工程师们对于什么是"简单"的代码往往存在分歧。如果"最简单"已经等同于"好设计",那"做最简单且可行的事情"是不是一句废话?换个问法,Unicorn 真的比 Puma 简单吗?内存限流真的比引入 Redis 简单吗?以下是对"简单"的一个粗略直观的定义:

  1. 简单的系统"活动部件"更少:你在操作它们时需要考虑的事情更少。
  2. 简单的系统的内部耦合度更低。它们由接口清晰、直白的组件构成。

Unix 进程比线程简单(因此 Unicorn 比 Puma 简单),因为进程之间的联系更少:它们不共享内存。我觉得这很有道理!但我认为这个定义并不能帮你分辨所有情况下的"最简单"。

那内存限流和 Redis 哪个更简单?一方面,内存限流更简单,因为你不需要去考虑搭建和维护一个独立的持久化服务涉及的种种问题。另一方面,Redis 更简单,因为它提供的限流保障更加清晰明了------你不用担心某个服务器实例认为用户被限流了,而另一个实例却不这么认为。

当我无法确定哪个方案"感觉"更简单时,我喜欢用这个决胜标准:简单的系统是稳定的 。如果你在对比软件系统的两个状态,在需求没有变化的情况下,其中一个状态需要更多的持续维护工作,那么另一个状态就更简单。Redis 需要部署、维护、可能发生故障、需要单独的监控,并且在服务所处的任何新环境中都需要单独部署。因此,内存限流比 Redis 更简单。

为什么不考虑扩展性?

肯定有工程师在心里大喊:"但是内存限流没法扩展啊!" 做最简单且可行的事情,绝对无法交付最具扩展性的系统,它只能交付一个在当前规模下运行良好的系统。这是不负责任的工程设计吗?

并不是。在我看来,大型科技 SaaS 公司工程设计中的最大罪过,就是对"扩展规模"的病态迷恋。我见过太多因为过度设计(试图为未来超出当前几个量级的规模做准备)而造成的无法避免的痛苦。

不要试图提前规划扩展性的主要原因是:这根本行不通。根据我的经验,对于任何有一定复杂度的代码库,你都无法预见当流量增加几个量级时它会有什么表现,因为你无法提前知道所有的性能瓶颈会在哪里。你最多只能确保自己准备好应对当前流量 2 倍或 5 倍的增长,然后随时待命,等问题出现时再去解决。

另一个不提前规划扩展性的原因是:它会让你的代码库变得死板 。把服务拆解成两个可以独立扩展的部分听起来很酷(我见过这种做法大概十次,但真正有必要独立扩展 的也许只有一次)。但这会让某些功能的开发变得极其困难,因为现在它们需要跨网络通信。在最坏的情况下,它们还需要跨网络的分布式事务,这是一个极其困难的工程问题。大多数时候,你根本不需要自找麻烦!

总结

在科技行业待得越久,我就越不相信我们能集体预测系统的未来走向。准确掌握系统目前的状况就已经够难了。实际上,这也是做优秀设计面临的主要实际困难:准确掌握系统的全局图景。大多数设计都是在没有这种理解的情况下做出的,所以大多数设计都很糟糕。

粗略地说,开发软件有两种方式。第一种是预测六个月或一年后你的需求会是什么样,然后为那个目的设计最好的系统。第二种是针对你当前实际的需求设计最好的系统:换句话说,做最简单且可行的事情。

更新:这篇文章在 Hacker News 上获得了一些评论。

其中一个有趣的评论线程提出,架构的简单性在大规模下并不重要,因为"实现中状态空间探索"的复杂性(我想这大概类似于我在这篇文章里写的内容)主导了任何其他的复杂性。我不同意这种说法------你的功能交互越复杂,简单的架构就越重要,因为此时你的"复杂度预算"已经几乎被耗尽了。

我还要特别感谢 Ward Cunningham 和 Kent Beck 发明了这句话------我之前真以为这是我自己想出来的表达,但我肯定只是记住了它。尴尬!感谢 HN 用户 ternaryoperator 指出这一点。



如果你喜欢这篇文章,可以考虑订阅我的邮件推送获取新文章,或者[在 Hacker News 上分享](news.ycombinator.com/submitlink?... the simplest thing that could possibly work)。以下是一篇具有相关标签的文章预览:

设计也许可行的软件

每当有人向我描述一款软件时,我都会思考我会如何构建它。软件工程师经常这样做,但很多人做得并不好。我之所以这么说,是因为我看到了很多关于一个总体计划中具体细节的技术讨论,而这个计划本身根本就不可行 。例如,争论到底该用属性透传(prop-drilling)还是上下文传递(context-passing)把一份我们根本没有、未来也不会有的数据传给前端;或者在一个必须保持无状态的后端服务里,争论到底该实现哪种持久化数据存储策略。
继续阅读...


相关推荐
jonjia2 小时前
在科技公司,如何识别真正重要的工作
程序员
jonjia2 小时前
设计切实可行的软件
程序员
jonjia2 小时前
大厂不需要英雄
程序员
Baihai_IDP7 小时前
HackerNews 热榜第一名:AGI 的 A,原来代表的是 Ads(广告)
人工智能·程序员·llm
kymjs张涛7 小时前
借助 API 手写一个 Transformer 架构
程序员
SimonKing9 小时前
SpringBoot整合秘笈:让Mybatis用上Calcite,实现统一SQL查询
java·后端·程序员
小兵张健21 小时前
Antigravity 403 账号可用了!!!
程序员
程序员飞哥1 天前
Block科技公司裁员四千人,竟然是因为 AI ?
人工智能·后端·程序员