能跑:为什么简单优先是最高原则

2 阅读9分钟

第1篇给了四阶段框架。这篇把「能跑」拆开——为什么简单优先在这个阶段是最高原则,什么情况下它不适用,以及怎么判断自己该进入下一阶段。

一、为什么需要简单:复杂度的真正代价

复杂度的代价不是「写的时候费劲」,是「改的时候绝望」。代码写一次,被读几十次、改几十次。每多一层抽象、多一个中间件,改的时候就要多排查一个可能的出错点。MVP 阶段需求必然变化——今天对的抽象,明天就可能是错的。简单意味着改起来便宜。

但这只说了一半。复杂的真正代价在于系统的不确定性:你不知道用户会不会用、不知道数据量会多大、不知道瓶颈在哪里。每多引入一个组件,就多一个可能出错的点,就多一份调试时的茫然。简单不是偷懒——是在信息最少的时候,用最少的东西降低意外发生的概率。

更隐蔽的代价是复杂度会自我繁殖。一个抽象的引入会推动周围代码向它靠拢——上了消息队列,上下游都要适配它的投递语义。这些不是一次性的决策,是会持续影响后面每一个迭代的约束。在「不知道要做什么」的阶段引入这些约束,等于在没看清路之前先把鞋带系死了。

还有一个更根本的视角:知道不做什么,比知道要做什么更重要。 你在「能跑」阶段做的每一个减法——不引入消息队列、不做抽象层、不上微服务——都是在对系统的不确定性说「我认」。你承认自己不知道用户会不会用、不知道瓶颈在哪,所以你选择先不做。而过度设计的人不是在犯错,是在用「万一需要」给自己壮胆:万一流量大了呢?上消息队列。万一查询复杂了呢?做 DSL。万一需要扩展呢?拆微服务。每一个「万一」听上去都有道理,但把它们加在一起,你得到的不是一个更稳健的系统,是一个还没跑起来就被复杂度压垮的系统。

二、如何避免复杂:砍、硬、短

砍:砍到刚好能验证假设

不是砍到最小,是砍到「刚好够判断这个方向值不值得继续」。用户管理?先用 admin 账号顶着。权限?先不做。通知?先不做。每多一个功能就多一个迷惑项——用户反馈好,你不知道是因为核心价值还是因为那个附加的辅助功能。砍到只剩核心链路,反馈才是干净的。

怎么判断该不该砍:问自己「这个功能不做,核心假设还验证得了吗?」。验证不了,必须留。验证得了但可以让体验更好——砍,下一个阶段再说。

硬:硬编码不是技术债,是期权

一个功能只在一种场景下用到,就别为它建一套配置体系。一个参数只有一个值,就别为它加环境变量。硬编码之所以被鄙视,是因为在「跑得稳」阶段它确实会成为瓶颈。但在「能跑」阶段,它是性价比最高的选择:写死只需一行代码,抽象需要一套配置、一个 loader、一个默认值、一个 fallback 逻辑。

更重要的是——硬编码是期权。验证通过了,再回头抽象,你手里有真实的业务语义,知道什么东西该配、什么东西不变、什么边界值会出问题。验证没通过,整个功能直接扔掉,一行重构代码都不用写。为了一个想象中的灵活性提前抽象,你买的不是保险,是一张不知道什么时候才能兑现的彩票。

短:代码短,文件少,依赖轻

一个能跑的原型,1000 行代码能搞定的事不要搞成 10 个微服务。每一行代码都是维护成本——不是这一周的成本,是接下来每一周的成本。在「能跑」阶段,标准是「能不用就不用」——不是反库,是反不必要的库。一个 SQLite 能搞定的数据存储,别上 Postgres;一个单文件能跑的服务,别拆成三个模块。

三、如何权衡:简单优先的边界

简单优先不是绝对的教条。它在不同项目类型里权重不同:

0→1 探索型项目——你不知道用户会不会用、不知道核心假设对不对。这时候简单优先是最高原则,复杂度是负债。做当下信息量下能做的选择,不做「万一将来需要」的设计。

确定性交付型项目——业务方把需求拆到字段级别,数据量、并发量都有预估。这时候简单优先要让位于「该预留的扩展点」。但不是所有扩展点都该留——只留那些「业务方明确说过下个季度会做」的东西。为「万一」预留的扩展点依然是过度设计。

技术验证型项目——目标不是上线,是搞清楚一个技术能不能用。这时候简单优先依然适用,但「砍」的标准变了:不砍技术探索路径,砍的是业务功能和边缘 case。先跑通一个最小闭环,技术可行性被验证了就停下来。

三种项目类型,权重不一样,但原则本身不变:做当下信息量下最合理的决策。信息少的时候不瞎猜,信息多的时候不偷懒。

四、真实案例

案例 1:定时任务调度,为什么不上 Airflow

曾经做过一个定时任务调度系统。刚开始的时候觉得「以后任务会越来越多,肯定需要一个成熟的调度平台」,于是直接上了 Airflow。搭集群、配 DAG、搞监控,前前后后花了不少时间。一年后回头看,任务总共只有两个,但 Airflow 本身从来没消停过——Scheduler 挂了、Worker 失联、元数据库锁表、版本升级不兼容,每个月都要花时间处理。这个项目的核心价值是那两个任务产出的数据,不是调度平台本身。Airflow 带来的不是「更可靠的调度」,是「更多要维护的东西」。如果当时用 cron + 一个 shell 脚本,省下的不是搭建的那几周,是之后一年里每个月的运维时间。在任务只有两个的时候,Airflow 的复杂度不是在解决问题,是在制造问题。

案例 2:一个业务数据处理系统,配置抽象过头

做过一个业务数据处理系统。一开始想得很大,把几乎每个处理环节都做了配置抽取——数据源、清洗规则、输出格式、告警阈值,全抽成配置文件,想着「以后换任何一个环节都不用改代码」。结果配置文件越堆越多:几十个 YAML,每个都有 implicit default、环境覆盖、链式 fallback。一个参数最终用了哪个值,要顺着三层 fallback 规则才能定位。出了问题不是查代码,是查配置文件之间的 fallback 链。最讽刺的是,线上跑了半年,真正被「灵活切换」过的配置一个都没有——所有环节都用的是最初的默认值,从来没变过。为了一个从来没被调用过的灵活性,写了上千行约束校验代码。

案例 3:新产品验证,把 MVP 做成了平台

公司为了开拓一个新市场,决定做一个面向新市场的实时数据产品。一开始雄心勃勃:要做一个完整的实时处理框架,从数据接入、实时计算、结果分发一条龙全自研。投入了大量人力从头搭建新框架,功能做得非常强大,覆盖场景非常多。两年过去了,系统架构越来越复杂,稳定性一直上不去,而那个新市场——始终没有给出任何反馈。不是产品不好,是市场本身就不存在。最后项目关停,大量裁员。这不是执行力的问题,是验证顺序的问题:应该先用最简单的方案去市场里试一下,确认有需求了再大投入。但他们把「验证」和「建设」的顺序颠倒了——先花两年把东西建好,再去看有没有人要。到头来建的东西越复杂,沉没成本越大,越舍不得叫停,越陷越深。

三个案例的共同特征

三个案例都在同一件事上栽了跟头:在信息最少的时候,用「确定性交付」的标准去要求一个「探索型」项目。Airflow 那个不知道任务量会不会涨就开始建平台;配置那个不知道什么东西会变就把所有东西都抽成了配置——为了空想的灵活性引入了不必要的抽象,结果抽象本身成了最大的维护负担;新产品那个不知道市场存不存在就开始做全栈。每一个「万一」听上去都有道理,但把它们加在一起,你得到的不是一个更稳健的系统,是一个还没跑起来就被复杂度拖垮的系统。

不是「别想太多」——是「想太多想错了地方」。你该想的是核心假设怎么验证,不该想的是技术架构怎么完美。

五、退出信号

什么时候该离开「能跑」阶段?第1篇给过一个信号:当你开始问「这样写对不对」而不是「能不能跑」。你在关心代码质量、命名规范、抽象合理性——说明核心链路已经跑通了,你已经从「能不能跑」的焦虑里出来了。

还有一个更具体的信号:当你开始被同一个问题反复绊倒。一个参数忘了传、一个边界条件没处理——每次都是同样的问题。说明这个地方的「硬编码」开始产生真实的维护成本了,该抽象了。但你是在被绊倒几次之后才动手,不是第一遍就动手——区别在于,现在的你有经验了,知道这个抽象的边界在哪,抽象出来的东西不会被下个月的现实打脸。

两个信号都满足,说明该进下一阶段了。缺一个,继续砍、硬、短。

收尾

「能跑」阶段的简单优先,不是在说「别想太多」——是在说「想对地方」。你该花时间想的不是技术架构完不完美,是核心假设能不能被验证、怎么用最小的成本验证它。知道不做什么,比知道要做什么重要得多。