ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

告别过度设计:功能切片与API规约锁定需求边界

告别过度设计:功能切片与API规约锁定需求边界 1. 为什么你的系统总是越做越重这几年我带过的项目里几乎每一个出问题的系统都有同一个病根——不是技术选型不对也不是程序员水平不够而是需求边界从一开始就没划清楚。大家闷头写代码写着写着就把系统写成了“瑞士军刀”什么功能都想往里塞到最后连当初为什么要做这个系统都快忘了。过度设计这东西特别有意思它很少出现在项目刚启动的时候。刚立项那会儿人人都说“先跑通再说”可一旦核心功能做完了各种“顺手加一个”“将来可能用得上”“这个不复杂”的需求就开始冒出来。我一个朋友做过一个内部审批系统本来需求就三条提交申请、两级审批、结果通知。结果半年之后系统里多了消息推送、数据看板、附件预览、消息已读回执、多部门流转、角色权限矩阵……每一个新功能单独看都合情合理合在一起就是一个谁也不敢动的巨型怪兽最后光维护这套系统就得一个专职开发。这个问题的本质是我们一直在用“加法思维”做软件没有人用“边界思维”去约束它。需求源源不断进来开发来者不拒代码像雪球一样越滚越大。真正的解法不是靠更强的意志力去“忍住不做”而是从工作方法上就建立一套机制让需求的边界从一开始就是清晰的、可验证的、能拒绝的。我用的这套方法就两个核心工具功能切片和API规约。前者解决“做哪些”的问题后者解决“做到什么程度算完”的问题。两个工具配合起来等于给项目装了两道闸门一道闸在需求侧挡无效功能一道闸在技术侧挡隐形膨胀。这篇文章是更新版我把实际用下来的经验和踩过的坑重新梳理了一遍比初版多了不少实战细节希望能帮你少走点弯路。2. 功能切片从用户价值出发拆出“最小可交付单元”2.1 切片不是拆任务是拆“可感知的价值”很多团队做需求拆分拆的是“任务”比如“前端写个表单”“后端建个表”“联调一下”。这种拆法最大的问题是任务和用户价值之间隔着好几层。你按任务拆出来的东西做完了用户也感知不到任何变化于是需求方就会不断往里加新东西——“既然你们都在做了顺便把那个也做了吧”。正确的拆法应该是按用户可感知的价值来切。什么叫可感知就是用户能明确地说出“哎现在我能做某件事了”。举个例子一个电商系统“用户能用手机号登录”是一个切片“用户能用验证码找回密码”是另一个切片“用户能同时用手机号和微信登录”是第三个切片。这三个切片各自独立每一个交付了用户都能真实地感受到系统多了一个能力。我的习惯是拿到需求后先不动手设计先列场景清单。把用户从头到尾用系统的动作全部过一遍然后问一个问题哪个环节是用户可以停下来的用户能在“选完商品”停下那“选商品”就是一个切片用户能停在“提交订单”这里那“提交订单”就是另一个切片。这样切出来的东西天然就是可独立交付的因为用户的使用路径本身就允许在那里暂停。这套方法对技术选型也有帮助。每个切片独立交付你在切片之间就有天然的“换引擎”机会。第一个切片用最土的技术栈跑通第二个切片觉得不行再换成本是可控的——因为你换的只是某一个切片不是推倒整个系统。我后来带的几个项目都是这么干的先上最朴素的技术方案等确实验证了需求价值再逐步演进从来没出过大问题。2.2 切片粒度怎么定三种尺度的实践经验切片粒度是个特别容易走极端的事。切得太粗一个切片做两个月中途几乎无法反馈切得太细每个切片就几十行代码光开会评审的时间就比写代码还多。我试下来觉得分三种尺度比较合理故事级切片一个开发在1~3天内能完成交付后用户有明确的感知变化。这个尺度适合日常迭代是团队里的主要工作单元。能力级切片由多个故事级切片组合而成比如“完整的订单流程”通常在一个迭代1~2周内完成。这是和产品经理对齐颗粒度的主要单位。目标级切片对应一个阶段性的业务目标比如“支持新用户从注册到首次下单”可能要两三个迭代。这种切片一般不直接用于排期更多是用来划定范围和控制节奏。我见过不少团队把精力花在争论切片粒度上其实没太大必要。同一个功能你觉得“发通知”是一个切片我觉得“发短信”“发邮件”“发站内信”各算一个切片——都对关键是判定标准要一致。我的标准就两条第一能不能独立测试第二能不能独立验收。能独立验收的就是一个切片不能的就继续往下拆。另一个我比较坚持的原则是每个切片必须有一个“做完”的定义而且要白纸黑字写下来。不是“联调通了”就叫完成应该具体到“用户输入手机号点获取验证码60秒内收到短信输入正确验证码后能登录进系统首页登录态保持7天有效”。这个定义写清楚了开发和测试对“完成”的理解才不会跑偏需求方也没法到时候说“我不是这个意思”。2.3 反向切片明确定义“这版不做什么”功能切片这套方法里我后期觉得最有用的其实是它的对立面——反向切片。就是明确列出“这版明确不做什么”哪怕这些功能未来确实要做。为什么一定要这么做因为需求方心里其实没那么清楚自己要什么但当你明确说“这版不做X”的时候他反而会认真想一下——这个X到底需不需要。很多时候需求方说“我要一个导入功能”你问“这版临时用Excel模板行不行”他想了想说行。这时候你就防住了一个系统的复杂度大坑。我用反向切片时会在切片清单的最后专门开一个“明确不做”的分区里面列的是被排除的需求。每一行都写清楚需求描述、排除原因、可能触发它重新排期的条件。比如“消息已读回执暂不做等用户反馈说分不清消息到底看没看再排期”。这样做的价值在于以后任何人质疑为什么没做某个功能你都能拿出当时的决策依据而不是含糊地说“当时没提”。记住一点反向切片不是“不干活”的遮羞布而是和需求方一起做的风险管理。你只是在管理“什么时候做”不是永远不做。这个区分很重要否则需求方会觉得你在消极怠工反而会加剧他想把所有东西都塞进来的冲动。3. API规约用契约文本把需求边界“焊死”3.1 规约不是接口文档是切片的“法律条文”功能切片解决的是“做哪些”但光是知道做哪些还不够你还得约束“怎么做才算做完”。我在实际项目中遇到过大量这种场景功能切片看起来清晰结果开发做的时候发现“登录”这个切片的API到底返回什么字段、参数校验规则是什么、出错时返回什么错误码全凭前端和后端现场商量。商量一次两次还行多了就乱套而且每个人对边界的理解都会漂。API规约就是给每个切片立一个“法律条文”。它不是传统意义上那种写完之后就吃灰的接口文档而是开发过程中所有交互方都必须遵守的技术契约。一份合格的API规约至少要包含四块内容数据模型请求和响应里出现的每个字段名字、类型、是否必填、取值范围、默认值全部列清楚。状态变化资源有哪些状态状态之间允许怎么流转由哪个操作触发——比如订单从“已创建”到“已支付”必须经过“支付回调成功”这个事件不允许直接跳变。错误语义每种失败情况用什么错误码要不要重试重试的退避策略是什么。错误码不能是个数字了事要有稳定的可读语义。权限约束哪些角色能调用这个接口哪些字段是按角色返回不同的值。这块不写清楚安全审计的时候你头都要大。我特别强调“法律条文”这个说法是因为它有约束力。规约评审通过后任何一方的改动都需要按流程走变更评审不能谁觉得“加一个字段不影响别人”就私自加。我见过太多次线上事故就是因为前端以为后端返回的是数组后端悄悄改成了对象两边对着文档一看都有道理——但文档早就没人维护了。规约这东西维护不维护倒不是最关键的关键是它得是唯一的、权威的、被强制执行的信息源。3.2 写好一份API规约的关键步骤写API规约是个技术活但也不是高深到只有架构师才能干。我建议按这五步走每一步都有产出物评审的时候对着过一遍就清楚。第一步识别交互方。不是只有前后端才需要规约。第三方系统、定时任务、数据运维脚本、测试脚本凡是会调这个切片的接口的都算交互方。每个交互方列出来想清楚它的角色和数据权限。第二步定义核心数据模型。把切片涉及的实体和字段全部列出来。这里我有个习惯字段的名字和含义必须和使用它的业务术语一致不允许开发自己发明缩写。比如业务上叫“下单时间”API里就不能叫“createTime”然后解释说是“下单时间”必须统一。第三步描述状态流转。这一步最能暴露需求边界是否清楚。一张状态图列出来如果状态之间有两三条以上的“捷径”跳转说明状态设计有问题大概率是需求没想明白。正常业务的状态流转应该是清晰的、单向的、有明确触发条件的。第四步写错误语义。这是最容易偷懒的一步也是最影响使用体验的一步。我的要求是每个错误码必须有明确的用户侧解释和开发侧处理建议。不要写“E1001系统错误”这种废话要写“E1001余额不足用户应看到充值引导开发者应检查账户余额后再发起扣款”。第五步评审并冻结。评审会必须有需求方参加。规约里有一个细节和需求理解不一致当场就能发现——比如“支付成功”到底是指“用户点完支付按钮”还是“收到支付渠道的回调”这种事在评审会上是必问的问题也是需求边界最后一次被模糊的机会。评审通过后契约冻结后面只走变更流程。工具方面我试过几种现在最顺手的是OpenAPI规范配合JSON Schema做数据模型校验。OpenAPI生态成熟各种代码生成、Mock工具、文档工具都能接团队上手成本低。更激进一点的可以考虑微软开源的TypeSpec它比OpenAPI抽象层次高声明起来更简洁适合规约比较多的中大型项目。但工具都只是载体真正的核心是团队有没有把规约当成“必需品”的认知。3.3 规约重新定义“完成”从代码能跑到契约生效很多团队的项目管理混乱根源在于对“完成”的定义不同步。开发觉得“功能能跑了”就算完成测试觉得“bug清零”才算产品觉得“用户愿意用”才算领导觉得“顺利上线不出事故”才算。你们都在说同一个词但脑子里想的根本不是一件事。引入API规约之后我对“完成”重新做了定义一个切片完成的标志是它的规约通过评审并冻结而不是代码跑通。听起来反直觉对吧代码还没写呢凭什么就算完成了理由其实很朴素的——规约冻结说明这个切片的需求边界已经锁定了后面不会东加一块西补一块了这才是真正可以稳定投入开发的起点。代码跑通只是“实现完成”如果边界一直在动实现完成就毫无意义。我见过最惨的一个项目前后端联调了四版因为需求一直在小步调整每次调整都改接口改到后面前端直接摆烂说“你们定死了我再写”。这种浪费纯粹是需求边界没锁死导致的。所以我现在的项目节奏是这样的先花一到两天把下一个迭代要做的切片的规约全部写完并评审冻结然后才进入开发。开发阶段几乎不讨论需求问题只讨论实现问题哪个接口该返回什么打开规约一看便知。新需求来了先冻结到下一个迭代的切片里绝不允许插入到当前迭代。这套节奏跑顺之后开发效率和交付质量都肉眼可见地提升。4. 实操复盘一个“会员系统”从失控到回归边界4.1 失控现场一个“顺手”引发的一连串灾难去年我接手过一个会员系统的维护重构项目被过度设计坑得够呛拿来复盘特别有代表性。这个系统的原始需求特别简单用户付费买会员会员有效期内能看付费文章。就这么一句话的需求系统最终长成了什么样呢我接手的时候代码库里躺着会员等级、积分体系、签到奖励、连续签到翻倍、分享得积分、积分兑换优惠券、会员日专属折扣、好友邀请得天数、生日双倍积分……光是会员状态就分“未激活、体验中、付费中、已过期、已退款、已封禁、已注销”七种每种状态还有一堆组合关系。这些东西是怎么来的呢我翻了提测记录和聊天记录发现每一个功能几乎都以同样的方式被加入的——起初就是一句“顺手做一下不复杂”。会员等级是产品经理说“用户以后可能会想要身份感顺手做个等级呗”签到奖励是运营说“别的平台都有签到我们也顺手加一个”积分体系是老板说“积分以后能对接很多活动现在先建个表放着”。每一个“顺手”都有人拍板每一个“不复杂”单独看确实也不复杂——但它们合在一起就变成了一个复杂到没有人能说清楚“这个系统到底是干嘛的”的怪物。更可怕的是这些功能之间还互相耦合。签到能得积分积分能兑会员天数会员等级能加速积分累积会员日双倍积分还能叠加。你动一个规则另外三条规则跟着受影响测试一次要回归的场景几十上百。接手那阵子团队每天的状态就是救火这个报表数据不对那个活动积分没到账谁也不敢随便改代码因为牵一发动全身。4.2 用切片和规约把系统“减”回原形我做的第一件事是组织了一次全员参加的需求盘点会。过程挺痛苦的要把系统里所有已有的功能全部列出来逐个回答三个问题这个功能当前有谁在用、真实使用量是多少、没了它会导致什么事。答不上来的就标记为“待验证”不急着删但也不允许再往里加东西。盘点结果和我预料的差不多——系统里大概40%的功能属于“有人用但价值存疑”的灰色地带还有20%属于“做完就没人碰”的僵尸功能。那一堆会员等级、签到、积分规则使用数据相当惨淡。但我不主张直接删毕竟有些功能还在跑活动贸然下线有业务风险。我的做法是先冻结变更再把核心路径用功能切片重新梳理出来。重新梳理之后核心路径特别清楚就三个切片用户购买会员、用户身份验证、用户文章访问权限判定。这三个切片通过API规约重新定义了接口边界购买会员产生的数据是什么身份验证返回什么权限判定的输入输出是什么。至于积分、签到、等级那些全部通过规约隔离出去——它们的接口仍然能对外提供服务但内部已经不再影响核心链路的逻辑都是在主流程外侧挂的扩展点。最有价值的一件事是我把当初那些“顺手做”的功能的接口规约全部补了一遍补的过程中发现了很多逻辑漏洞。比如签到积分规则里有一条“连续签到翻倍”但翻倍因子和会员等级加成是乘在一起的这个规则的定义从没写清楚过线上日志显示有时候翻倍有时候不翻倍。这类问题如果规约评审时就能发现根本不用等上线后用户来投诉。重构后的系统核心代码量砍掉了将近一半测试回归的场景从上百个降到了不到三十个。更重要的是团队终于能在一个迭代内完成从需求到上线的完整交付了这在之前是根本不敢想的事。4.3 一个被规约“逼出来”的重大设计缺陷这个项目里还有一个特别典型的案例我觉得值得单独说。原系统的订单状态定义得很随意开发过程中前后端各自维护了一个状态枚举前台显示“已完成”的订单后台数据库里存的可能是“CLOSED”也可能是“FINISHED”还有历史数据是“SUCCESS”。三个值含义相同但彼此不兼容查数据统计的时候要么UNION一堆条件要么就得做数据清洗别提多痛苦了。我们用API规约重新定义订单状态机的时候把线上真实状态全部扒出来归拢了一遍。最终定义成五态待支付、已支付、已取消、已退款、已完成。每种状态明确写出触发条件待支付超过30分钟自动取消已支付后48小时内可以发起退款申请退款成功后进入已退款状态。转移路径不允许出现“待支付直接跳已完成”这种跳跃认为这种跳变不符合业务逻辑。这个状态机定义出来以后开发实现就变得特别机械——不再需要“看情况”处理各种历史污数据只要按状态机的转移表写代码就行了。测试用例也好设计了把每一条合法的转移路径和不合法的跳变组合列出来就是一张完整的测试矩阵。如果一开始就做了这件事后面那一堆状态混乱的幺蛾子根本不会发生。5. 常见问题与排查技巧实录我把自己和身边团队用这套方法时遇到过的典型问题整理成了一张速查表遇到的问题基本都是下面这些你可以直接对照着查。典型症状根本原因排查思路解决建议切片切得太碎接口数量暴涨联调成本飙升把任务当成了切片而不是按用户价值切检查每个切片是否对应一个用户可感知的“完成动作”以用户能停下来验收为标准重新合并切片规约写了但开发不看照样各写各的规约游离在开发流程之外没有强制约束检查代码评审是否把关了“实现是否遵守规约”把规约评审放进完成的定义里不通过不算完成需求方不停加东西切片清单不断膨胀缺少“明确不做”的反向切片流程看看“明确不做”清单是不是压根没建过每个迭代都做反向切片明确写出排除项和触发条件契约冻结后需求方反悔说“当时没确认这个细节”评审时需求方没参与或者参与了但没表态查评审记录需求方是否只在最后签了字评审流程里把需求方的反馈逐条记录并确认接口字段经常改动连带前端和测试返工数据模型定义不完整枚举和校验规则模糊检查规约里数据模型部分是否覆盖了全部字段用JSON Schema给数据模型做强制校验不合法直接编译报错状态流转混乱一个订单有多个“完成”含义状态定义不是源头的、唯一的各端各自维护全量扒线上数据值梳理真实存在的状态用显式状态机定义状态集和转移条件并做迁移工具对齐历史数据规约文档上线后没人维护很快腐烂把它当成一次性文档而不是活着的契约观察代码变更时是否同步更新规约把规约放进代码仓库走和代码一样的版本管理和评审流程5.1 最隐蔽的坑功能切片和API规约被做成了两张皮做功能切片和API规约这件事我最担心的一种情况是团队把两个动作完全割裂开了。切片规划是产品经理在做的感觉像是在开需求评审会大家把用户故事贴在白板上然后就没有然后了API规约是后端开发在做的感觉像是在写接口文档写完了传到一个Wiki页面里也没人真正去校验。两套东西各自为政谁也不跟谁对齐那这套方法的效果就大打折扣了。正确的做法应该是每个切片落地时必须同时输出它的API规约。切片描述的“用户能做什么”和规约定义的“系统向外部承诺什么”是同一枚硬币的两面。我一般在切片看板上的每一项描述后面都会挂一个规约的链接点进去就能看到这个切片的接口定义、数据模型、状态流转、错误说明。开发和测试看同一个入口不会各拿一份信息对不上。还有一个细节容易忽略切片是讲给业务听的规约是讲给机器听的。切片的描述不能太技术化比如“订单模块增加一个创建订单接口”这种业务方根本不想听也听不懂你提了反而让他觉得你不尊重他的输入而规约里的字段、状态、枚举又不能太业务化必须精确到开发人员直接能写代码的程度。这两种语言各有各的受众别混着用否则最后就是业务觉得你敷衍开发觉得你啰嗦。5.2 两个实用小技巧最后分享两个我实测下来特别管用的小技巧都是文档里找不到的。第一个是给每个切片取一个“克制”的名字。名字不要太崇高不要叫“用户增长引擎”“智能推荐平台”这种要叫“用户手机号注册”“文章详情页缓存”“订单支付回调处理”。越具体越克制越不容易膨胀。我见过不少过度设计都是从名字开始的——“统一消息中心”这个切片顺理成章地就包含了短信、邮件、站内信、App推送但实际上可能当前只需要发个通知。改叫“验证码短信发送”之后整个系统的复杂度预期立刻降下来了。第二个是用Mock工具把规约“跑”起来。规约评审通过之后不要等后端实现直接用Mock工具按规约生成一套模拟接口。前端拿这套Mock开发页面测试拿Mock数据设计用例后端在这套Mock的基础上做适配。等到后端真实实现完成联调时前面已经跑得很顺了。这其实把“契约测试”前置了谁违反了规约在Mock阶段就能暴露出来而不是等到联调时才发现两边藕断丝连的地方对不上。6. 写在最后的一点个人体会做软件这行越久越觉得“做减法”比“做加法”难得多。代码写出来容易删掉难功能做出来容易拒绝难。功能切片和API规约这套组合刚开始用的时候你可能觉得有点麻烦——多写了切片清单多写了规约文档好像拖慢了速度。但我自己的体会是这些前期投入几乎总能从后期的返工和联调成本里加倍赚回来。它不能保证你做一个惊艳的产品但能保证你做一个不失控的、可维护的、团队心里有底的产品。这两年我也越来越倾向于一个理念好的设计不是“我做了很多聪明的东西”而是“我让该存在的存在让不该存在的尽早止步”。需求边界挖得越清楚代码就越坦白架构就越诚实。希望这篇更新版的经验总结也能帮你把项目做得轻一点、稳一点、快一点。
返回列表