Linux

开会成瘾的技术团队,病根不在日历

上周清理日历的时候顺手做了个统计——我所在的技术团队,10个人,一周62个会议。人均每周6.2个,平均每天1.2个。如果按每个会议40分钟算,将近四小时。还没算上会前准备和会后消化,还没算上那些"5分钟碰一下"的临时通话。

这不是个别现象。我跟同行聊过,金融科技领域尤其严重——合规对齐要开会,风险评审要开会,跨团队排期要开会。一个需求的开发周期里,真正写代码的时间可能还不到一半。

很多团队的第一反应是"减会"——砍掉每日站会,缩短周会,设立无会议日。然后呢?信息断了,对齐塌了,出问题的概率反而上升。砍完的会议像野草一样换个形式长回来。

我后来想明白了这件事:会议不是问题,会议是症状。就像发烧不是病,是身体在对抗感染。你把体温压下去,不等于治好了。

 

每个砍不掉的会,都在替系统还债

 

技术团队里的会议,大致可以分几类。每一类背后都藏着不同的问题。你不可能一刀切地减掉它们,因为砍掉会议后露出来的那个洞,可能比会议本身更危险。

同步会:你的模块边界画错了

两个子系统需要"定期同步",这是最常见的一种会。前端和后端每周对齐,A团队和B团队双周同步。听起来很合理,但仔细想想——如果两个模块之间需要定期人工同步,说明什么?

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

前端的现状有点荒诞:我们嘴上说自己在写 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 }` 这就是形状。字段名有了,类型有了,看起来很完整。但这句话里藏了多少没说清楚的东西?

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

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

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

我见过太多团队在"实时"这两个字上栽跟头。不是技术做不出来,是做出来了才发现——用户根本分不清实时和准实时的区别,而团队却为那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嵌套?六个月后,你的招聘成本翻倍了,因为新人得同时熟悉两套技术栈的代码风格。一年后,某个子应用的技术栈要升级,但它和其他子应用之间的样式隔离出了问题——因为当初"无关"的时候,没人定义样式边界的规范。

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

Node.js的运行时幻觉:为什么你的服务能跑全靠运气

很多从前端转 Node.js 的开发者,对这门运行时的理解停留在"写 JavaScript,能跑服务"这个层面。开发环境跑得好好的,上了生产环境,问题一个接一个冒出来,而且每一个都反直觉。

这不是水平问题,是 Node.js 的运行时设计给了你一种安全感——一种在开发环境完全验证不了、只有在流量压上来的那一刻才会破碎的安全感。

我自己在做 BFF 层的这些年,踩过太多这种坑。最深的感触是:Node.js 不会替你兜底,它只是让你以为它会。

 

事件循环不是安全网

 

Node.js 的文档里有一句很关键的话:事件循环在单个线程中运行。但很多人对这句话的理解是——"异步代码就不会阻塞"。

这是最大的幻觉。

`async/await` 只是语法糖,它让异步代码看起来像同步代码,但它不会把同步操作变成异步的。当你在一个请求处理函数里做了这些事情,整个事件循环都会停住:

- `JSON.parse` 一个 50MB 的请求体
- 用正则表达式匹配一个精心构造的字符串(回溯攻击)
- 在同步循环里做密集计算,比如加密、排序大数据集
- 调用了某个底层 C++ 扩展的同步接口

Serverless:复杂度没有消失,只是换了地址

2018年前后,Serverless在国内技术圈火了一阵。各大云厂商轮番布道,"按需付费"、"零运维"、"自动扩缩容"——每个口号都精准戳中了团队的痛点。我当时也投入了不少精力研究,毕竟谁不想不用管服务器?

几年过去了,当初那些All in Serverless的团队,相当一部分已经在往回走。不是Serverless不好,而是很多人慢慢意识到:当初以为消灭的复杂度,其实只是搬了个家。

 

那些承诺里没写的小字

 

Serverless的核心承诺有三条,每一条都有对应的现实版本。

"不用管服务器"——开发者只需写业务逻辑,基础设施平台托管。这对前端团队尤其有吸引力,写完函数丢上去就能跑,不用折腾部署和运维。但现实是,你很快会发现自己需要关注函数的内存配置、超时时间、并发限制、冷启动策略、VPC配置、IAM权限……这些不叫"服务器运维",但本质上还是在管基础设施,只是换了个仪表盘。

"按需付费"——代码不运行就不收钱,空转成本消失。但下一句没人告诉你的是:按需付费的反面是成本不可预测。一个函数死循环触发重试,一个爬虫突然疯爬你的API,一个配置错误导致无限调用——这些异常场景下的账单,会让你深切怀念固定成本的日子。

你选的 Git 工作流,暴露了你的发布能力

每次团队讨论 Git 工作流,场面都很热烈。有人搬出 Git Flow 的严谨分支模型,有人推荐 Trunk-Based Development 的极简主义,还有人觉得 GitHub Flow 够用就行。辩论往往持续一个下午,最后投票选一个,README 里写上规范,全员执行。

然后三个月后你会发现:分支还是乱成一锅粥,release 分支上堆了二十个 cherry-pick,hotfix 从 master 拉出来合不回去,有人偷偷开了 feature branch 三周没合,还有人在 master 上直接 commit——"就改了一行,没必要开分支"。

工作流选错了?不,工作流没选错。是大部分团队在选工作流的时候,选的是自己向往的工作方式,而不是自己实际具备的发布能力。

 

Git Flow:为发布窗口而生的妥协

 

Vincent Driessen 在 2010 年提出 Git Flow 的时候,他的假设非常明确:你的软件有固定的发布周期。develop 分支积累功能,release 分支冻结测试,master 分支只放生产代码,hotfix 从 master 拉出紧急修复。每一步都有清晰的语义。

日志的谎言:为什么你的日志比没有日志更危险

凌晨两点,线上出了一个支付失败的问题。你登录日志平台,输入订单号,回车。三千条日志滚动出来。你加上时间过滤,缩小到五百条。再加 ERROR 级别,剩三十条。你一条一条看——全是超时告警,没有一条告诉你为什么超时。

你把级别放宽到 WARN,三百条。大多是重试成功的记录,看起来没啥用。你换了个关键词搜,又出来几百条。折腾了一个小时,你发现关键信息藏在一个 INFO 级别的日志里,存的是上游服务的返回值,但打印的时候没用 JSON 格式,正则匹配不到。

最后你靠着回忆和猜,定了位。第二天写故障报告,复盘建议第一条写着:"增加更多日志。"

这个场景大概不需要太多解释,每个值过班的工程师都经历过。但真正值得思考的不是"日志不够"这个表面结论,而是更深的问题:你有成千上万条日志,为什么还需要靠猜?如果日志真的有用,为什么越紧急的时刻,日志越帮不上忙?

 

日志的安全感是假的

 

很多团队对日志有一种近乎宗教性的信任——出了问题看日志,没问题也要看日志确认一下。代码评审的时候,一句"这里加个日志吧"几乎不会被拒绝,因为加日志没有成本,看起来还体现了严谨。

但日志给你的安全感是虚假的。

技术决策的囚徒困境:为什么正确的方案总是输给能落地的方案

做了这么多年技术,我发现一个挺有意思的现象:几乎每次技术方案评审,都会有人提出一个"理论上最优"的方案,但最后落地的,往往是那个"看起来不够优雅"的方案。

这不是偶然。背后有一套残酷的逻辑。

 

那个"完美方案"为什么总是死在会议室里

 

上周技术评审会,前端团队提出要引入微前端架构,理由很充分:模块解耦、独立部署、技术栈自由、团队自治。PPT做得精美,架构图画得漂亮,甚至连落地路线图都准备好了。

然后架构师问了一句:"谁来维护基座?iframe降级方案谁负责?跨应用通信出问题谁排查?"

会议室安静了30秒。

最后的决定是:继续用现有的Monorepo + 模块化方案,虽然不够"先进",但团队都熟悉,出问题知道怎么修。

这不是个例。我见过太多"技术上正确"的方案,死在各种现实问题上:

- 服务化改造,拆到一半发现团队根本撑不起这么多服务
- TypeScript全面迁移,三个月后发现一半团队还在用any
- GraphQL替代REST,半年后发现后端团队都不会写resolver
- Kubernetes上云,结果运维团队连基本的Pod调试都不会

页面