记得 2021 年夏天我接手了一个维护了七年多的老服务。代码里有一行注释原话大概是// TODO: 这里偶发超时很奇怪别管它重试就行。那行注释当时救了我——线上确实偶发超时加个重试确实能扛过去。但三个月后我又恨透了它没人知道它为什么奇怪没人记录过试过哪些方案谁都不敢动那段逻辑。它像一个没有说明书的炸弹你知道它在但不知道它什么时候响。后来我慢慢想明白一个道理工程里真正稀缺的不是把问题修好因为很多问题根本没法在当前条件下修好。真正稀缺的是给那些尚未解决的问题留下清晰、可追溯、对后来人有用的标记。Marking solutions to problems that are not solved——给未解决的问题标记解决方案。这句话表面看有点矛盾问题都没解决哪来的解决方案但恰恰是这种“承认未解决同时记录下所有尝试过的路径、绕开的坑、暂时奏效的规避手段”的思维才是一个团队或者一个人真正成熟的标志。这篇文章不是讲某个具体框架的用法而是讲一套我实践了几年、已经在多个团队验证有效的工作纪律。它适合后端开发、技术负责人、文档维护者也适合任何一个需要跟“悬而未决”共处的人。读完之后你可以直接把那套注释模板和清单结构拿去用不用改装。1. 为什么说“标记解决方案”比“解决”更重要在任何真实系统里未解决都是常态 先聊一个反直觉的结论一个正常运转的中型系统同时存在的“已知但未根治”的问题少说也有几十个。它们可能是某个偶发内存泄漏、某个只在特殊网络环境下出现的竞态、某个业务规则从来没跟产品对齐的模糊地带。你不可能把它们全部关掉也很少有人能把它们全部关掉。这不是团队懒散而是工程常态。一个系统上线三年如果要求所有已知问题全部根治只有两种可能要么系统规模太小要么产品已经停止迭代。只要业务还在往前走新功能就会不断暴露新的边界旧问题就会在特定场景下被重新激活。换句话说接受“有些问题暂时解决不了”不是妥协而是专业成熟度的体现。但接受未解决不代表放任自流。问题可以悬置认知不能悬置。真正可怕的是一个问题悬置了三个月团队里没有留下任何关于它的上下文某天它突然引爆所有人一脸茫然。未解决的问题就像一个长期存在的盲区你允许它存在就必须为它建立相应的可见性。“解决”与“标记”的三个本质差别 我花了不少时间才意识到“解决”和“标记”是两种完全不同的动作不能混为一谈。对比维度解决问题标记未解决问题时间属性终局性问题被关闭过程性记录探索轨迹作用对象问题本身我们与问题的关系价值兑现当前时刻立即生效在未来每一个接手人身上兑现失败代价问题没修好白费时间标记不全后人重复踩坑对能力要求需要技术判断力需要诚实与整理能力解决问题讲究“找到一个能跑的方案”面向当下标记未解决问题讲究“把局面说清楚”面向未来。我见过太多团队为了证明自己“在做事”拼命往未解决的问题上堆方案结果越修越乱。反而是一些慢下来、认真把问题边界、失败路径、当前兜底逻辑写清楚的团队后面越走越顺。他们的核心竞争力不是“修得快”而是“在没修好的情况下依然让系统可控、让团队可接力”。标记的三个核心价值止损、接力、信任 止损最好理解。两个不同的人在不同年份查同一个问题各自花费两个星期最后各自得出同一个“没解决”结论——这种浪费在我见过的项目里几乎每天发生。一份好的标记等于把“已经证明走不通的路”提前画出来后来的人不用再拿脚去丈量。接力是标记最有魅力的地方。很多未解决问题并不是永远无解而是解法依赖系统演进到某个阶段。比如一个底层框架的 bug可能因为业务规模没到而不值得修但当你把标记写清楚三年后系统重构、流量翻倍下一任接手的人看到标记会瞬间明白“原来这个问题在这里等着”他甚至能直接把方案接上。标记就是留给未来的接力棒。信任则更微妙。作为 leader我最怕的不是下属说“这个问题我没搞定”而是他默默改了一个 workaround既不写注释也不提 issue让问题彻底沉入海底。反过来当一个同学在产品群里说“这个点目前无法根治复现条件如下我已经上了规避方案后续跟进的 issue 在这里”他不仅没有显得无能反而让我觉得这个人极其靠谱。提示我这些年 review 文档和代码最看重的不是“这段代码解决了什么”而是“这段代码里有没有诚实地标记出那些它没有解决的问题”。2. 从TODO注释到未决问题档案我一直在用的一套标记法最常见的标记灾难 很多人其实已经在做标记最典型的就是TODO、FIXME、HACK这三个标签。但我在代码评审里看到的常态是// TODO: 优化性能 // FIXME: 这里会崩 // HACK: 先这么写吧这种注释写了等于没写。TODO后面挂着“待优化”一待三年没人知道要优化什么、优化到什么标准FIXME后面写着“会崩”但没写怎么复现、崩在哪一行、有没有临时兜底HACK更是重灾区完全没有上下文三个月后连作者自己都看不懂当初为什么要 hack。我一直认为注释不是给自己看的是给六个月后那个半夜被报警叫起来的人看的。如果一条注释没有包含“现象、尝试过的路径、失败原因、当前规避手段、下一步方向”它就不算一个合格的未决问题标记它只是代码里的一句情绪宣泄。我推行的“五要素”注释模板 后来我在团队里推行了一套注释结构规则不复杂凡是确认“暂时无法根治”的问题必须按下面这种格式写透。// UNRESOLVED: scrap-timeout-review-20250315 // 现象: 抓取任务偶发超时约 0.2%~0.4%集中在大促高峰时段 // 已尝试: // - 客户端超时 5s - 15s无改善排除应用层读超时 // - 连接池从 20 扩到 50无改善排除连接数瓶颈 // - 切换到备用 DNS无效排除单点 DNS 故障 // 当前规避: 失败自动重试一次 读缓存降级线上可接受 // 下一步: 怀疑与网关同签名并发转发有关需要压测复现 // 状态: OPEN / 负责人: 张工 / 最后更新: 2025-03-15不要小看这个结构。它的核心不是那几个字段而是强迫写注释的人过一遍自己的思考链条现象是什么我到底试过什么失败在哪一步我现在靠什么兜底下一步往哪走这五个问题一旦捋清楚哪怕这个问题一辈子不修后来的人也不会再在这个坑里浪费超过半天。我把这套东西叫“未决问题档案”它不需要什么重量级系统支撑它就是一套纪律你承认这个问题还开着就必须同时提交一份“关于这个问题的暂存知识”。注释与issue的分工 注释是给代码旁边的人看的但它不应该成为唯一的标记载体。真正的状态管理还是得到 issue 或缺陷管理平台上去做。我在团队里定过三条分工规则容器放什么更新频率谁负责代码注释UNRESOLVED现象、尝试、兜底、下一步每次排查有结论就更新最后触达问题的开发者issue 标题[未解决]长期状态、版本影响范围、负责人每两周过一遍模块负责人团队知识库文档横跨多个服务的复杂问题全景图月度复盘同步技术负责人注释负责“贴地气”它必须出现在第一现场让阅读代码的人立即感知风险issue 负责“管状态”确定优先级、排期、负责人知识库文档负责“串全景”当一个未解决问题牵扯多个服务时单独的注释和 issue 都装不下它。三条铁律需要特别记住第一注释里必须带 issue 编号否则注释会变成孤岛第二issue 标题用[未解决]前缀统一标识跟普通 bug 区分开筛选时一眼就能看到第三每次有人对问题做了新尝试必须回到原 issue 追加记录而不是另开新坑。开新 issue 而不关联旧 issue等于亲手切断了接力棒。3. 一个真实案例那个让我写下这篇博文的偶发超时我第一次踩到坑只花了十分钟 回到开头那个老服务。我当时刚接手线上报警说抓取任务偶发超时频率不高但每天总有那么几次。我翻了代码看到那行“别管它重试就行”的注释照做了——加了重试逻辑观察了两天报警确实少了。整个过程也就十分钟。那行注释在当时确实“救”了我让我不用从零开始排查。但注意它只是告诉我“怎么回避”没有告诉我“为什么回避”。我当时觉得足够了毕竟线上稳了。两个月后同事踩坑痛苦了三天 两个月后抓取任务的量级翻了一倍光靠重试扛不住了超时开始连续报警。另一个同事去排查他没有我这么幸运——他相信那句“别管它”但重试已经不灵了所以他必须自己从头查。那三天我看到他干了什么先抓日志发现超时集中在某几个上游节点然后怀疑 DNS换了库无效再怀疑连接池扩了线程无效最后怀疑操作系统层连接复用调了半天参数依然无效。他每一步都踩在我们当时已经踩过的路上但没有文本告诉他“前面有人走过了”。更讽刺的是他发现代码历史记录里两年前就有人为这个问题做过一次很深的排查结论写在一封已经没人翻的邮件里。那封邮件明确排除了三四个方向——正是他这三天反复验证的方向。我们后来补上的未决问题档案 这个问题最终也没有被“根治”我们找到的仍然是一个规避方案只是比“无脑重试”更稳失败重试一次 读缓存降级 对上游做熔断。但它从“地雷”变成了“可控风险”关键变化就是我们补了一份真正的标记。## [未解决] 抓取任务偶发超时 - 首次记录: 2023-11-02 | 最后更新: 2025-03-15 | 负责人: 张工 - 现状: 未彻底定位根因当前依赖 重试降级熔断 三件套兜底 - 复现特征: 高峰期与上游节点网络抖动高度相关概率 0.2%-0.4% - 已尝试且失败: 1. 客户端读超时 5s-15s2023-11陈工无效 2. 连接池 20-502023-12陈工无效 3. 备用 DNS2024-02李工无效 4. 操作系统层连接复用参数2024-05王工无效 - 已排除方向: 应用层超时配置、连接池容量、DNS 单点、连接复用 - 当前最可疑: 网关层对同签名请求并发转发时的连接建立风暴 - 下一步动作: 大促前做网关层压测复现若复现则推动网关团队介入 - 状态: OPEN这份档案贴在我们 wiki 的“未决问题”区三个月里被引用了十几次。每次有人看到相关日志都会先去翻它而不是重新发明轮子。这就是标记的价值它不解决问题本身但它解决了“被同一个问题反复消耗”的问题。4. 团队层面的未解决问题管理从会议记录到知识库最昂贵的流逝讨论到一半的方案 代码注释解决的是“已知风险”但团队里还有一类更隐蔽的损失发生在会议和即时通讯里。我参加过太多这样的会大家在白板前讨论一个棘手问题提出了四五个方向分析到一半发现依赖某个前置条件于是约定“下周三再对一下”。然后下周三来了人到齐了但没人记得上次到底聊到哪一步、谁负责验证哪个方向。上一次讨论产生的所有中间结论全部归零。IM 群聊更夸张。一个问题在群里反复拉扯中途还穿插着“这个 bug 怎么复现”“我这边环境起不来”“老板刚进了个新需求”。一个小时后结论淹没在 300 条消息里过两天连当事人自己都翻不到。我们把这种情况叫“讨论即流失”讨论了但没留下任何可以被检索的东西。一份轻量的未决问题清单如何运转 后来我们做了一件很小但极有效的事建了一份“未决问题清单”放在团队知识库固定位置任何人在会上或群里讨论问题时只要确认“当前无法根治”就顺手在上面加一行。字段设计如下字段要求问题摘要一句话说清现象不带背景故事首次记录日期 提出人最后更新每次追加必须改动这个日期已尝试路径哪怕失败也要写失败比成功更有指导性当前卡点“卡在什么上”比“卡在哪里”更有效当前兜底线上/当前阶段靠什么扛着负责人必须有人认领哪怕只是维护记录下一步动作没有下一步动作的问题视作非紧急备忘这份清单不需要做成系统用在线表格或者文档就行。关键是两个使用纪律第一任何没有“当前兜底”的问题必须升为紧急项因为说明团队正在裸奔第二任何超过一个月没有更新且没有负责人的条目直接归档不要让它继续污染清单。防止标记本身腐化 标记也是一种资产资产不维护就会贬值。我最怕看到的是清单建了三个月里面躺着二十条记录但没有一条进过周会讨论。这种清单很快会变成另一种形式主义的装饰品。我们的维护节奏很固定每周五下午的技术复盘会上拿出清单逐条过一遍。过的时候只问三个问题这条有没有新的进展当前兜底还成立吗负责人有没有变化没进展的条目不会被批评但一定要有人能当场说明“为什么还没进展”。如果说不出来说明这条标记已经腐化应该重新评估优先级或者直接归档。从实践效果看这份清单最大的贡献不是“解决了问题”而是把团队从“反复讨论同一个问题”里解放出来。任何新成员入职先读一遍未决问题清单他对系统风险的理解速度比读两个月代码还快。5. 假标记与低质标记比不标记更危险三类常见假标记 标记是好事但我也见过不少“假标记”它们比不标记更坑人。第一类是位置标记。“这里有坑”“这里有问题”“这里别动”——只告诉你有石头不告诉你石头长什么样。这种标记最大的问题是制造恐惧后来的人看到“别动”两个字连旁边的正常代码也不敢动了。我见过一段逻辑因为一行“这里很危险”的注释三年没人敢重构最后成了整个系统里最陈旧的角落。第二类是情绪标记。“求求了谁来修一下”“这个 bug 真的离谱”“我不行了你们谁有空看看”——这种话在代码注释和工作群都经常出现。它不是记录是情绪宣泄对后续排查毫无帮助。第三类是过期标记。半年前写了一条UNRESOLVED记录了当时的现象和兜底方案但半年里系统重构过、依赖升级过、数据量翻倍过这条标记已经完全不准确了。更危险的是后来的人看到标记写着“有兜底”就放心地没有继续排查结果兜底方案早就失效了。过期标记等于给后人提供一个“看起来安全的假象”比没有标记更可怕。标记也会说谎把“绕过”写成“解决” 还有一种特别隐蔽的污染就是把 workaround 说成 solution。我见过不少代码注释写的是“解决了偶发超时问题”实际做的是 catch 异常后静默重试。这两者的性质完全不同解决是问题消失绕过只是把问题藏到更深的地方。我后来在团队里立了一个规矩凡是用了重试、降级、熔断、兜底数据这类手段注释里不允许写“解决”这两个字统一写“规避”。词汇上较真是有意义的因为它强迫每个人诚实面对“我没把它修好我只是让它暂时不炸。”这个认知一旦模糊团队就会对线上的真实风险失去判断。我判断标记是否合格的三条标准 在代码评审和文档 review 里我判断一条未解决标记是否合格只看三件事。第一一个完全不了解前情的新人能否靠这条标记判断出“下一步可以做哪个动作”如果看完标记你依然不知道是该去压测、还是该去查日志、还是该去找某个团队那这条标记的信息密度就不达标。第二失败路径是否被诚实记录成功的尝试当然有价值但对后续排查而言失败的路径往往更值钱。“试过 DNS没用”这几个字能帮后来的人省下一天。很多低质标记只写“我在排查中”不写“我排除了哪些选项”这是对失败经验的浪费。第三有没有明确的“当前状态”和“最后更新者”状态标记了 open、pending、stalled 都行但不能缺失。最后更新者的名字则决定了一条标记在关键时刻是否可以找到活人问清楚。失去活人背书的标记早晚会腐化成一句流言。6. 把“标记思维”挪到个人复盘和团队文化里每周复盘里的未决问题区 这套思维不只适用于代码我后来把它搬进了个人复盘和工作日志。以前我写周报习惯只写“完成的三件事”没完成的要么不好意思写要么觉得下周再说。后来我改成每周末尾固定留一个区块叫“本周未决问题”。在这里写的不只是没做完的任务还包括没想通的问题、尝试过但没奏效的方案、停留在猜想阶段的方向。这个改动带来一个很实际的好处我的周报不再只是“成就清单”它变成了一本可以回溯的思维流水。三周后还能看到自己当时卡在哪个判断上、走过了哪条弯路这种记录对个人成长的帮助远大于只记录成果。承认“没解决”不会让你显得无能反而建立信任 很多同学不敢标记未解决问题是怕暴露自己的局限。我刚带团队的时候也有这个误区总觉得说“这个问题我搞不定”会被质疑能力。直到后来我想清楚一件事评估一个工程师水平看的不是他从未失败而是他在失败和未解决的问题面前能不能保持判断力、能不能把败局整理成对团队有用的信息。敢于把“我没解决、我试过什么、我卡在哪”写清楚的人通常才是真正理解系统的人。反而不写的人让问题一次次在暗处吞噬团队的时间那个代价是巨大的。如果只做三个起步动作 如果你读完这篇文章想改变现状但又不想立刻搞一整套流程那从这三个动作开始就够了。第一今天就在代码里找一个TODO或者FIXME把它补成完整的五要素格式。你不需要一次改完所有改一个就足以感受到两种注释之间的差别。当你看到一条写清楚“现象、已尝试、兜底、下一步”的注释时会觉得整个人都踏实了。第二在团队文档库里建一个“未决问题清单”就按上面那张表结构。拉两个最让你头疼的线上问题填进去下次周会花十分钟过一下。这份清单不需要多漂亮能跑起来就行。第三从下一次讨论会开始安排一个人专门记录“悬而未决项”。会议结束时念一遍“今天有三个问题没解决分别卡在哪、谁负责下一步”你会发现会议效率直接提升一个档次。我自己在这几年里最大的感触是一个团队真正的底气从来不是“所有问题都解决了”而是“每个没解决的问题都有一个清晰的标记”。那些标记见证了我们的反复尝试也提醒着后来的人别重复踏入同一条河流。深夜加班排查问题时如果翻代码能看到前人的一份完整档案真的会感觉有人在黑暗中替你举了一盏灯。