前段时间,我们碰到过一个叫“资产膨胀”的需求。

客户侧的资产数量突然变得很夸张,已经不是“数据脏一点”的程度了,而是开始直接影响后面的判断、分析和处置。资产一多,噪音就会跟着变多;噪音一多,系统对外给出的视图就会失真。站在当时那个位置,这件事几乎没有争议:问题已经落在我们这里了,那这个需求自然也该由我们来接。

一开始,我们就是这么想的。

大家很快开始往“怎么收敛资产”这个方向拆。哪些资产明显不合理,哪些该优先处理,端测资产先不先做,网测资产先不先做,能不能补识别规则,能不能先把最夸张的异常压下去。那段时间做的事情很像最典型的研发推进方式:看到问题,拆方案,排优先级,开始干活。

但做着做着,我开始越来越别扭。

不是因为这事完全做不出来,也不是因为一点进展都没有,而是方案越往下拆,越像一个没有底的洞。因为每次讨论到最后,都会慢慢收敛到同一件事上:这个进来的 IP,到底算不算资产?

问题也就在这儿。

我们名义上是在治理资产,实际却越来越像在替别人做识别。那时候我脑子里反复冒出来的,其实是一个很不舒服的问题:

为什么是我们在兜底?

一、它一开始确实很像“我们的需求”

现在回头看,我很能理解为什么团队当时会直接往这个方向走。

因为从现象上看,它真的太像我们的问题了。我们本来就在平台的资产管理领域里,这类问题天然会先落到我们桌上。客户看到的是资产数量异常、视图失真、后面的判断和处置都被拖乱了,那第一反应当然是来找我们,不会先去追上游识别链路到底哪里报错了。

客户看到的是你这里资产太多,系统里脏的是你这里的数据,后面被噪音拖累的分析和处置流程也都发生在你这里。所有信号都在指向你,所以人很容易顺着这个惯性继续往下想:既然问题在我这里被看见,那就该由我来把它解决掉。

技术团队尤其容易掉进这种路径。

因为我们的工作习惯本来就是这样。系统有问题,先修;数据有问题,先挡;输入有问题,先过滤。很多时候,这种反应没有错,甚至是必要的。问题在于,一旦这种惯性没有被打断,后面就会很自然地从“先把影响接住”,慢慢滑成“把整件事都接下来”。

我们当时其实就处在这个滑动过程里。

表面上看,我们是在认真治理资产;不过如果把那些越来越重的方案往回看一眼,会发现里面已经开始越过“处理输入后的结果”这条线,滑向了“这个输入到底该不该进来”的判断。

这就是让我警觉的地方。

二、让事情翻过来的,不是方案,而是根因

后来事情翻过来,不是因为我们突然想出了一个特别好的方案,是因为继续往下查之后,我们终于把根因看清了。

所谓“资产膨胀”,根子不在我们这里管得松了,也不在我们缺了什么关键约束,而是上游一个叫 STA 的组件错报了大量 IP。

而对我们来说,一个 IP 进来,基本就会被当成一个资产。

就这么简单。

这时候问题就完全变了。

原来我们看到的是“资产很多”,但真正发生的事情其实是:上游先报错了一批 IP,我们在下游把这些 IP 接了进来,于是资产数量自然被带着一起膨胀。

我也是到这一步才反应过来,前面那种越做越重的感觉到底是从哪儿来的。

因为如果继续按原来的方向往下做,后面我们做的就已经超出“清理资产”的范围了——要在服务端再补一层判断:哪些 IP 其实不该算资产,哪些虽然进来了但要被压掉,哪些又该保留。说得直白一点,就是上游报完一遍,我们再替它重判一遍。

这件事一旦被看清,前面的方案站位就变了。

但事情到这一步,其实也没有马上变简单。因为我们虽然已经知道,根子更偏上游输入问题,可问题是跟着版本在走的。STA 那边发版没那么灵活,我们这边反而更容易动手,客户感知到的异常又已经落在我们这里,于是团队很自然地继续往下想:能不能先在我们这边兜一层,先把版本问题顶过去。

这也是容易误判的地方。你明知道根因不在自己这里,却还是会因为自己更容易改、更容易发、更容易先止血,就下意识把主方案接过来。

可我们后来慢慢发现,这件事其实兜不住。只要上游继续按原来的方式报,我们下游就只能不断补判断、补规则、补例外。看起来是我们在快速响应,实际上是在用开发方式长期接管一个本来应该由上游通过发版去修掉的问题。

我们一开始以为自己面对的是一个资产治理问题,后来才发现,它更接近一个上游输入质量问题。前面那堆方案也有价值,但一开始站位就错了。

一旦站位错了,后面的方案就只会越来越重。因为你嘴上在做清理,手上却已经开始接管一部分本来不该由你承担的识别职责。

三、问题出现在你这里,不代表问题应该由你主解决

这件事后来让我确定了一句话:

问题出现在你这里,不代表问题就应该由你来主解决。

这句话单独拿出来看很像常识,但在真实研发里,它反而特别容易被忘掉。因为研发每天面对的,往往都是“结果已经落下来”的问题。告警在你这里响,指标在你这里变差,用户在你这里感知异常,于是所有东西都会把你往一个方向推:既然你已经看见了,那你就顺手把它解决掉。

可“看见问题”和“主解决问题”,中间差得其实很远。

看见问题的人,未必最接近根因。
承接问题的人,未必最适合给出主方案。
谁最先痛,谁先动,这很常见;但谁先动,不等于谁就该把整件事吞下去。

放到这次资产膨胀里,道理其实很直接。

上游最清楚自己为什么会报这个 IP,用了什么信号,在什么上下文里做出的判断。既然识别是它的职责,那识别准确性也应该主要由它负责。我们当然不能什么都不做,但我们该做的,不是把自己改造成另一个识别系统,而是守住自己这层的边界:加必要约束,做轻量治理,做好生命周期管理,避免异常输入把系统持续拖垮。

这和“我来替你判断你报得准不准”完全不是一回事。

前者是在接管别人的职责。
后者是在守住自己的系统边界。

四、看清以后,方案反而收下来了

把这件事看清以后,后面的决策反而没那么复杂了。

我们最后没有在服务端做一套很重的真假资产识别,也没有试图把所有异常都拦在自己这里。更实际的做法是两件事:

一是推动上游去修,把识别准确性推回它本来该承担的位置;
二是把我们自己的方案收敛到边界内该做的治理上,比如生命周期管理、必要的约束和轻量兜底。

换句话说,上游负责把错报降下来,我们负责让异常输入即使进来了,也别持续把系统拖歪。

这个结果从外面看,甚至会显得有点“不够猛”。它没有那种“我来一把把问题解决掉”的痛快感。不过后来越想越觉得,克制本身就说明方向对了。

服务端当然要负责,但服务端负责,不等于服务端要长成另一个上游系统。上游该做的上游做,服务端做服务端自己的事。

五、这件事让我重新理解了“程序员怎么理解需求”

回头再看,这件事最有意思的地方,后来已经超出了“资产膨胀怎么处理”本身——它逼着我重新想了一遍:程序员到底该怎么理解需求。

以前我会觉得,理解需求主要是两步:听清楚别人要什么,再把它做出来。

现在我不这么看了。

因为很多需求最难的地方,根本不在听不听得懂。它一开始就可能被定义错了。你看到一个现象,就把它当成需求;你看到问题落在自己这里,就把它当成自己的责任;你发现技术上能做,就默认工程上应该做;你看到系统有洞,就下意识觉得兜底一定更稳。

可这些判断,没有一个是天然成立的。

现象不等于需求。问题落点不等于责任归属。能做不等于该做。

所以现在再让我说“程序员怎么理解需求”,我不会先说“把别人想要什么听准确”。

我更想先问另一件事:眼前这个东西,到底是不是一个被正确定义过的需求?

它是根因,还是表象?
它是职责,还是压力传导?
它是在解决问题,还是在错误分工里继续增加复杂度?
它看起来像负责,还是其实在替系统失衡续命?

这些问题不先想清楚,后面越认真,越可能南辕北辙。你不是不努力,你只是从一开始就在帮一个错误的问题找答案。

六、理解需求,很多时候先要判断“这到底是不是我的需求”

回头看这次“资产膨胀”,最后它并没有演变成一个多宏大的技术项目。我们没有做出一套复杂识别系统,也没有在服务端把所有真假资产都判清楚。我们真正做的事情其实很克制:推动上游去修,把职责推回该在的位置,同时把自己边界内该管的管住。

但也正是这个过程,让我对“理解需求”这几个字有了比以前更具体的认识。

原来理解需求,不只是“做什么”。
更是“为什么是我做”。
再往前一步,是“这到底是不是我的需求”。

这几个问题顺序一换,思维方式其实就完全不一样了。

前一种是接任务。
后一种才算真的在理解需求。

程序员的成长,很多时候不只体现在能不能把一个需求做出来,而是体现在一堆看起来都很紧急、很合理、很应该立刻动手的事情里,还能不能先分清楚:什么是现象,什么是根因,什么是职责,什么只是被一路传导过来的压力。

想清楚这些,再动手,代价通常会小很多。

人也会清醒很多。