写给2025年的我:在保持真我与适应社会之间找到平衡点

在这个复杂多变的世界里,每个人都以自己独特的方式存在着,而我,则被周围人贴上了“憨”、“固执”这样的标签。面对这些评价时,我的内心经历了从最初的困惑不解到后来逐渐接受的过程。起初,我试图去改变自己,让自己看起来更加圆滑、世故,但很快我发现,这样的尝试不仅让我感到疲惫不堪,更重要的是,它违背了我的本心——那个渴望真实表达自我的灵魂。

DRY的幻觉:你消灭的重复,正以耦合的形式回来找你

DRY——Don't Repeat Yourself。这四个字母可能是软件工程里被引用最多、被误解最深的原则。

每个程序员入行第一天就被告知:重复代码是邪恶的,看到重复就要提取,看到提取就要抽象,看到抽象就要泛化。于是我们疯狂地消灭重复——公共函数、工具类、基础库、共享模块......代码库越来越"干净",重复率越来越低,CI里没有任何duplicate code的警告。

然后某一天,一个业务需求来了——需要改动某个"公共"逻辑。你打开那个被47个地方引用的utils函数,改了一行,跑了一下测试——绿了。上线,结果三个业务线同时报警。你突然发现,那三个业务线依赖的是同一个函数的三种不同行为,被那个"公共"函数强制统一了。

你消灭了重复代码,但你制造了耦合。而耦合,是比重复贵十倍的技术债。

 

重复代码不是问题,问题是不知道为什么重复

 

所有关于DRY的讨论,都默认了一个前提:重复等于坏。但这个前提本身就是幻觉。

重复代码至少有三种完全不同的面目。

第一种是意外重复——两个开发者不知道对方写了同样的功能,造了两个轮子。这种重复才是真正需要消灭的,因为它意味着信息浪费和潜在的不一致。

错误处理的谎言:你以为在兜底,其实在埋雷

打开任何一个前端项目的代码,搜索 `catch`,你大概率会看到这样的画面:

```javascript
try {
await fetchData();
} catch (e) {
console.error(e);
message.error('操作失败,请稍后重试');
}
```

后端也好不到哪去:

```javascript
try {
await processOrder(orderId);
} catch (err) {
logger.error('订单处理失败', { orderId, error: err.message });
throw new BusinessException('系统异常,请重试');
}
```

这两段代码有一个共同的特质——它们让你觉得错误被"处理"了。日志打了,提示也弹了,异常也抛了,一切看起来都很体面。

但你仔细想想:用户看到"操作失败,请稍后重试"之后能做什么?重试?然后呢——再失败一次?运维看到一条"订单处理失败"的日志,能定位到什么?是库存扣减超时,还是支付回调丢失,还是风控拦截?

前端状态管理的终极错觉:我们都在重新发明数据库

前端的现状有点荒诞:我们嘴上说自己在写 UI,实际上大部分时间在跟数据较劲。Redux、Zustand、React Query、SWR、Dva、Pinia——每出一个新方案,大家就觉得"这次终于对了"。但如果你把所有这些方案放在一起,去掉它们的外壳看本质,会发现一个尴尬的事实:它们解决的问题,数据库几十年前就解决了,而且解决得更好。

我们不是在做状态管理,我们是在重新发明数据库。只是发明得很差。

 

一个 Redux Store 就是一个简化版数据库

 

Redux 可能是最明显的例子。你写一个 Redux store 的时候,本质上在做什么?

定义一个全局的数据结构——这叫 Schema。写 reducer 处理 action——这叫事务(Transaction),action 本身就是 WAL 日志。写 selector 查询数据——这叫查询引擎。写 middleware 拦截 action——这叫触发器(Trigger)。用 normalize 归一化数据——这不就是数据库范式吗?

接口契约的幻象:为什么类型对齐救不了前后端协作

前后端分离之后,我们发明了一个安慰自己的概念——"接口契约"。Swagger 文档一摆,TypeScript 类型一生成,团队觉得天上地下都對齐了:字段名对齐了,类型对齐了,必填选填对齐了。然后上线第一天就出了 bug——status 为 3 的情况前端压根没处理,amount 的单位后端返回的是分前端当成了元,createTime 在后端是 UTC 前端按本地时间解析了。

没错,类型是对上了。但类型对上和意思对上之间,隔的不是一行代码,是一个认知深渊。

 

接口文档给了我们一种虚假的安全感

 

我做过很多次前后端协作的接口评审,也主持过不少次。每次的流程都差不多:后端同事打开 Swagger 或 YApi,一个字段一个字段地过,前端同事点头说"收到"。大家都很认真,评审记录也写了,看起来协作很顺畅。

但评审过程有个微妙的问题——我们在确认的是"形状",不是"语义"。

什么叫形状?`{ orderId: string, status: number, amount: number }` 这就是形状。字段名有了,类型有了,看起来很完整。但这句话里藏了多少没说清楚的东西?

合规才是最好的架构师:为什么限制越多设计越健康

博客分类: 

做金融业务的前端,最常听到的抱怨就是"合规又来了"。每次需求评审,合规团队提出的数据隔离、操作留痕、访问控制要求,都像是在给工程师戴枷锁。我刚转金融业务那几年也是这么想的——好好的架构被合规要求弄得支离破碎,什么都要审计,什么都要脱敏,什么都要分权。

但做了几年之后,我发现一个反直觉的现象:那些被合规逼出来的设计,反而比"自由发挥"的架构更经得起时间考验。而那些早期没有合规约束、随意生长的系统,后来几乎没有一个不重构的。

这个观察让我重新理解了约束和设计的关系。

 

自由是设计的天敌

 

我见过两类系统的演化路径,差别很大。

第一类是金融业务系统。从第一天起就有明确约束:用户数据不能明文存储、敏感操作必须留审计日志、同一页面不同角色的数据要隔离、接口调用要有频率限制和幂等保证。这些约束在项目初期像紧身衣一样让人难受——写个列表页要考虑字段脱敏,做个按钮要判断权限再判断状态再判断是否可重复点击,发个请求要带traceId还要处理幂等。

第二类是内部工具和运营后台。没有合规约束,怎么方便怎么来。接口随便调,数据随便传,状态随便改,上线快、迭代快,大家都觉得效率高。

猜猜三年后哪个更健康?

实时数据的幻觉:你看到的"现在"其实是"过去"

每个产品经理都想要"实时"。实时报价、实时持仓、实时通知、实时风控——但凡跟"实时"沾边的需求,优先级都高得离谱。好像只要数据不是实时推送的,这个功能就不值得做。

但这里有个问题少有人追问:你看到的"实时",到底实时到什么程度?

我见过太多团队在"实时"这两个字上栽跟头。不是技术做不出来,是做出来了才发现——用户根本分不清实时和准实时的区别,而团队却为那500毫秒的"实时"付出了整个架构的代价。

 

WebSocket的承诺和账单

 

先从最常见的"实时"方案说起。

WebSocket火了之后,几乎每个前端团队都想把长连接加上。HTTP轮询?那是不体面的技术债务。SSE?那是不完整的实时。只有全双工的WebSocket才配得上"实时"这两个字。

但WebSocket不是免费的午餐,它的账单分三层。

第一层是连接管理。一个WebSocket连接意味着服务端要维护一条持久化的TCP通道。听起来不复杂,但你算一下:10万个在线用户,10万条长连接,每条连接都有心跳、超时、重连逻辑。你的服务端不再是无状态的HTTP服务器了,它变成一个有状态的连接管理中心。这个转变的代价远比大多数团队预估的大。

微前端的悖论:拆得越独立,耦合越深

2021年前后,微前端是前端圈最火的架构概念。qiankun、single-spa、Module Federation,各种方案轮番上阵,每过几个月就有一篇"微前端最佳实践"刷屏。团队的架构升级PPT里如果没挂一个微前端的示意图,都不好意思拿出来汇报。

三年过去了,真正在线上跑过微前端的人,大多有一种心照不宣的沉默:方案落地了,页面跑起来了,但总觉得哪里不对劲。

不对劲的地方,通常不在PPT里。

 

三个承诺和一个现实

 

微前端刚出来的时候,给了三个承诺:技术栈无关、独立部署、团队自治。每一个听起来都很美,每一个在实践中都打了折。

技术栈无关意味着子应用可以自由选择框架。React的团队用React,Vue的团队用Vue,听起来皆大欢喜。但"无关"只存在于第一个版本。三个月后,你会发现共享组件怎么办——A子应用用React写了一套表格组件,B子应用用Vue想要相同的交互,是再写一遍还是搞iframe嵌套?六个月后,你的招聘成本翻倍了,因为新人得同时熟悉两套技术栈的代码风格。一年后,某个子应用的技术栈要升级,但它和其他子应用之间的样式隔离出了问题——因为当初"无关"的时候,没人定义样式边界的规范。

技术栈无关真正的代价不是今天允许你自由选择,是明天让你无法统一收口。

你写的不是代码,是期权:用金融思维重新理解技术决策

每次做技术选型的时候,我都会想起期权定价模型。不是因为我爱装,是因为这玩意儿真的解释了大部分技术决策为什么会翻车。

做金融业务的人应该对这个逻辑不陌生——一个期权的价值,不取决于标的资产今天的价格,而取决于它未来的波动率、到期时间和执行价格。

技术选型也一样。你选一个框架、一个架构、一个技术栈,本质上是在买入一个期权。而大部分团队在做这个决策的时候,只看了"执行价格"——也就是今天的采用成本,完全忽略了到期时间和隐含波动率。

 

执行价格:你以为的成本只是首付

 

选React还是Vue?选微服务还是单体?选自研还是开源?这些决策的讨论,90%都聚焦在"今天迁移要花多少人力"上。

这就是只看执行价格。

我见过一个团队花了三个月从Vue迁到React,理由是"React生态更丰富"。三个月后确实迁完了,但接下来的一年里,团队不断在解决React生态的碎片化问题——路由、状态管理、样式方案,每一个都要做一个子决策。而当初评估迁移成本的时候,这些子决策根本没算进去。

就像买房只算了首付,没算物业费、维修基金和房产税。

页面