1. 为什么汽车设计必须把需求“拆开”而不是“写厚”
1.1 一个典型的需求失控现场
说实话,我在汽车电子行业这些年,见过太多“需求管理翻车”的项目。最典型的现象就是:产品经理拿着几百页的整车型谱需求去开评审会,软件团队只关心自己那部分控制策略,硬件团队盯着传感器接口,测试团队翻了两天文档也不知道验收标准到底该算谁的——最后需求评审会开成了“各部门免责会”,每个人回去都按自己的理解开发,等到系统集成阶段才发现,A团队理解的“自动跟车”是30km/h以上才启用,B团队做成了全速域。两边的“自动跟车”在需求文档里都是同一句话,谁能想到理解差这么多。
再看DOORS这个工具本身。IBM DOORS在汽车、航空、轨交这类安全相关行业里几乎是标配,原因很简单:它允许你建立形式化需求模块、维护条目级的追溯链接、做基线管理和变更控制,这些都是A-SPICE这类汽车过程标准明确要求的能力。但工具归工具,我见过很多项目上了DOORS以后,需求条目确实都录进去了,追溯链接也拉了,可整个库就是一个几百上千条需求的大杂烩,连系统需求、组件需求、测试用例都分不清。说白了,工具把人家的显式结构提供了,你却只拿来当Word用。
问题的根子往往不是工具,是拆法。DOORS需求管理实战这件事,真正值钱的能力是知道怎么把一个宏大的系统级需求,按模块逐层拆成能干活、能验证、能追溯的原子条目。这篇就想用一个自适应巡航系统(ACC)的设计案例,把模块化需求拆分从思路到落地完整走一遍。
1.2 模块化拆分到底解决了什么
先说个生活化的类比。一个餐厅后厨如果所有人都同时负责洗菜、切菜、炒菜、装盘,单子多起来必然乱;但如果切菜组只干切菜的活,炒菜组只接手切好的食材,谁做什么边界清楚、交接明确,整个出餐效率和质量就稳得多。汽车设计里跨专业、跨团队的复杂度远超餐厅,道理却一模一样。
模块化需求拆分解决的核心问题有四个:
追溯性。用户说“我要一辆安全的车”,这句话是没法直接拿去做设计的。把它拆成“车辆应能在本车道内识别前方静止目标”“车辆应在驾驶员无反应时进行紧急制动”等等之后,才能继续往下映射到子系统需求、组件需求,再到测试用例。DOORS里的链接矩阵就是在维护这条从用户到零部件的完整链条。
复用性。不要觉得拆分是只为一个项目服务的。主机厂通常是一个平台多款车型并行,平台化共享是大趋势。如果某个模块(比如雨刮控制、车窗防夹、ACC感知)的所有需求都是独立模块,新车型直接复用模块,不用再从几千条需求里手动捞一遍,这省下来的时间非常可观。
并行开发。汽车开发一定是供应商深度参与的,Tier1要看组件级需求,Tier2要看物料级要求。只有需求按模块拆清楚了,你才能把“感知模块需求包”发给雷达供应商,把“决策模块需求包”发给控制器团队,各干各的,互不阻塞。
变更影响分析。这是DOORS最值钱的功能之一,但前提是链接建得对。没有模块化拆分,一条需求的变更你根本不知道下游绑定了什么;拆清楚了,一条系统需求变了,DOORS会通过链接自动把受影响的组件需求、测试用例全部列出来,该改谁一目了然。
所以模块化拆分不是一个“更好看的文档组织方式”,它直接影响开发效率、质量成本和项目进度。不是说用了DOORS就自动有了好需求管理,而是你得先把“怎么拆”想明白,DOORS才能把你的结构化思路牢固地固化下来。
2. 模块化需求拆分的核心方法论:高内聚、低耦合与六步流程
2.1 第一原则:高内聚、低耦合,怎么落到需求上
模块化拆分的第一原则跟软件设计的“高内聚、低耦合”同源。听起来抽象,放到需求管理里其实很具体:
高内聚:一个模块里的需求条目,要围绕同一功能域或专业域。感知模块就是管“看”,决策模块就是管“想”,执行模块就是管“动”,诊断模块就是管“报故障”。每个模块内部的需求彼此关联紧密,共同支撑同一个职责。
低耦合:模块之间的依赖要少、要清晰,只通过明确定义的接口发生联系。感知模块输出的目标清单定义好格式、校验规则、更新频率,决策模块只管拿这个接口数据用,不允许感知模块去干预决策逻辑,也不允许决策模块要求感知模块“顺便把雨刷也控制一下”。一旦出现这种跨模块的额外约定,耦合就产生了。
我在实际项目中判断一个需求是否被拆好,会问三个问题:
- 这条需求的验证活动,能不能由一个团队独立完成?
- 如果这条需求变更,受影响的模块是不是明显小于全系统?
- 这条需求拆出去后,模块之间的接口有没有清晰到不需要电话沟通就能理解?
这三个问题只要有一个答案是“不能”,那就说明拆分边界还没划利索。ACC系统为什么要拆成感知、决策、执行、人机交互、失效处理这几个模块,不是随便分的,这些模块在物理上对应不同控制器或传感器、逻辑上对应不同专业领域、工程上对应不同供应商,是一个典型的“高内聚”划分方式。
2.2 一套可复制的六步拆分流程
基于ACCase,我把模块化需求拆分的完整流程总结成六步。这套流程我用了好几个项目,基本可以直接抄作业,只是模块命名和颗粒度要按实际系统调整。
第一步,从用户视角建立顶层需求。这一层不要谈技术实现,就写“用户要什么”。比如ACC的顶层需求可以写成:
- CR-001 当车辆在高速公路上行驶时,系统应能在驾驶员设定的目标车速下自动保持与前车的安全距离。
- CR-002 当系统检测到需要驾驶员接管时,应通过仪表盘和声音给出明确提示。
注意这套描述里没有提雷达、没有提控制策略、没有提刹车怎么执行。顶层需求就是站在“产品能做什么”的角度说话。
第二步,按功能域划分模块。分析一下实现顶层的这些功能,需要哪些专业域协作。ACC大致可以分出:
- 感知模块:检测前方目标车辆,输出目标相对距离、相对速度
- 决策模块:根据感知输入和驾驶员设定,计算期望车速/期望加速度
- 执行模块:将期望车速/加速度转换为发动机、制动系统的控制指令
- 人机交互模块:显示系统状态、发出接管请求、接收驾驶员开关操作
- 失效处理模块:系统自检、故障降级策略、安全状态输出
这五个模块就是后续所有拆分的“容器”,所有系统级需求最终必须归到某一个模块里。
第三步,给每个模块定义接口与边界。这是最容易被跳过、但最值得花时间的一步。接口定义要具体到信号级。比如感知模块输出的目标清单接口,至少包含:
- 数据项:目标ID、目标类型、相对距离(m)、相对速度(m/s)、目标存在置信度
- 更新周期:20ms
- 数据范围:相对距离0.5m~200m,超范围应有无效标志
- 错误处理:当传感器数据质量下降时,应给出“目标不可信”标志位
接口定义不清楚,模块边界就是摆设。供应商做感知模块的时候,根本不知道输出数据要被谁用、用什么格式,集成的时候才发现两边格式对不上——这种问题在设计阶段就埋雷了。
第四步,将顶层需求分解为原子化需求条目。这是真正的“拆分动作”,重点在上文提到的原子化:一条只表达一个需求。拿ACC系统需求举例:
- SR-ACC-PER-001 系统应能检测本车道前方最远200m内的同向行驶车辆目标。
- SR-ACC-PER-002 系统应在目标距离小于150m时,输出包含目标相对距离、相对速度的数据帧。
- SR-ACC-DEC-001 系统应在无前车时,按照驾驶员设定的目标车速执行定速巡航。
- SR-ACC-DEC-002 系统应在有前车且前车速度低于设定车速时,调节自身车速以保持驾驶员设定的安全时距。
- SR-ACC-ACT-001 系统应能输出期望减速度请求,范围0~3.5m/s²,控制周期20ms。
- SR-ACC-HMI-001 系统应在激活时点亮仪表盘上的ACC激活指示灯。
- SR-ACC-HMI-002 系统应在需要驾驶员接管时,发出持续至少2秒的声音报警。
- SR-ACC-FAIL-001 系统应在雷达传感器失效时,在500ms内退出ACC控制并提示驾驶员。
发现没有,每条需求的动词都是“应能”“应输出”“应在何时”这类可验证的表述,没有出现“设计一种算法算出减速值”“通过某个软件模块实现”等实现方案。让需求保持“什么必须发生”而不是“怎么发生”,是拆分中最难但最关键的纪律。
第五步,建立需求条目之间的链接关系。这条放在DOORS实操部分详细说,这里只强调原则:顶层需求到系统需求是1:N向下追溯,反之是N:1向上覆盖。每一条组件级需求都必须能追溯到某条系统需求,否则这条需求就是“孤儿需求”,多半是某个人自己发挥出来的。
第六步,检查完整性与验证覆盖。这个步骤建议测试团队也参加。对每条顶层需求,走一遍“它是靠哪些系统需求实现的?”;对每条系统需求,再走一遍“它靠哪些组件需求落地?”;最后拉着测试一起确认“每条需求有没有验证方法?”——是台架测试、实车测试、仿真验证还是评审检查,必须明确一种。没有验证方法的需求不算需求,只能算愿望。
2.3 什么时候该收手:拆分的颗粒度控制
很多新手容易掉进两个坑:一个是拆得太粗,“系统应能安全跟车”这种一句顶十句,看似简洁,实际上没法验证;另一个是拆得太细,把内部实现细节也拆出来,比如“系统应定义CAN报文ID为0x123用于传输目标距离”,然后越拆越多,管理成本爆炸,DOORS里一两千条需求根本没法看。
我的经验是拿三个标准来卡颗粒度:
- 可验证性:这条需求是否能通过一种明确的方法验证?能,就到这层;不能,继续拆。
- 单一归属:这条需求是否能明确归属到一个模块、一个团队?如果能被两个模块同时“认领”,说明还没拆到组件边界,需要继续拆。
- 不可再拆:把这条需求再拆下去,是不是就会变成设计细节?如果会变,说明这条需求已经是“原子需求”,收手。
ACC案例里,组件级需求我一般拆到“传感器要满足探测距离200m”这种程度就停了。再往下写“用77GHz毫米波雷达实现”“天线增益多少dB”,那就是设计输出,应该由供应商和硬件团队在详细设计阶段自己出,不应混进需求库里。
3. 在DOORS里把模块化需求从纸面变成可执行结构
3.1 第一步:把工程库按“层级+模块”立起来
很多团队拿到DOORS,第一件事就是新建一个Formal Module,然后把文档里的需求全录进去,一个模块装天下。这么干,工具的优势基本没用上,反而比Excel还难用。
我的建议是先在DOORS里建立一套“层级+模块”的结构。视觉上像一棵树:
- 第一层:用户需求模块(Customer Requirements),存放CR-001、CR-002这类原始用户需求。
- 第二层:系统需求模块(System Requirements),按功能域再分成ACC感知、ACC决策、ACC执行、ACC人机交互、ACC失效处理等若干个Formal Module。
- 第三层:组件需求模块(Component Requirements),对应每个物理组件,比如“毫米波雷达需求”“ACC控制器需求”“仪表显示需求”“制动执行器需求”。
这里有个关键选择:系统需求层,到底建一个大的“SR_ACC_系统需求”模块然后在属性里标注功能域,还是直接按功能域建多个小模块?两种项目我都用过,结论是:偏向第二种。因为DOORS支持模块之间做链接,你完全可以把感知模块的组件需求跟“SR_ACC_感知”模块建链接,逻辑结构清晰,过滤器也好做。如果全部塞进一个大模块,后面做视图、做覆盖率分析、做基线打包都会很痛苦。
每个Formal Module建立后,建议立刻加上一套必要属性。不要一上来就配二三十个属性,那会让数据录入变得极其繁琐,人员抵触情绪很大。我推荐从这几类起步:
| 属性名 | 示例值 | 用途 |
|---|---|---|
| ReqID | SR-ACC-PER-001 | 需求唯一编号 |
| ReqType | System / Component / Customer | 区分需求层级 |
| ModuleDomain | ACC / EPM / BCM | 模块归属标记 |
| Priority | High / Medium / Low | 优先级管理 |
| VerificationMethod | Test / Analysis / Inspection / Demo | 验证方法,评审必查项 |
| Status | Draft / Reviewed / Approved / Obsolete | 生命周期状态 |
这套属性先用起来,跑到第三个月你发现确实需要再增加某个属性(比如“目标车型”“安全等级”),再加也不迟。属性加多了管理成本是线性上升的,但收益不一定是。
3.2 第二步:视图过滤与状态管理,让需求库“能看”
建好模块、录好条目之后,下一步是让各种角色在同一个库里都能高效工作。原理上就是利用DOORS的View(视图)。比如:
- 对测试团队:创建“待验证需求视图”,过滤条件设置成
VerificationMethod = "Test"且Status = "Approved",他们只关注这几十条就够了。 - 对供应商:创建“雷达组件需求视图”,只显示组件需求模块里特定状态和优先级的条目。
- 对项目经理:创建“逾期未审批需求视图”,过滤
Status = "Draft"且“最后修改时间”超过两周的条目。
DOORS的过滤表达式支持基于属性的复杂逻辑,我常用的一个View过滤表达式长这样(以DOORS Classic为例):
if ((obj."Status" == "Draft") && (obj."Priority" == "High")) { display = true; } else { display = false; }这个用DXL脚本控制。打标签时只要在必要的地方用就行,大部分日常过滤用系统自带的视图向导就够,不需要阶段性地写大段DXL。视图的核心逻辑是“让每个角色只看自己需要的那部分”,能够大幅减少“需求库太大没人愿意翻”的问题。
3.3 第三步:用链接把追溯链真正打通
这部分是DOORS的灵魂,也是模块化需求拆分落地的关键。很多团队建了链接但维护得很随意,我要先泼一盆冷水:链接建错比不建更危险,因为你会迷信它。
ACC案例里的链接应该这样建:
- 在“SR_ACC_决策”模块里,找到组件需求
CTR-ACC-DEC-002 系统应在前车速度低于设定车速时,调节自身车速以保持安全时距,右键建立到“SR_ACC_DEC_组件需求”模块里的对应组件需求的链接。 - 反向也建一层:从“SR_ACC_系统需求”里的系统需求,向上链接到“CR_ACC_用户需求”里的CR-001。
这样当你打开任何一条需求,都可以查看“向上追溯”(这条需求由谁派生)和“向下追溯”(这条需求被谁实现),也就是DOORS里的Traceability视图。
ACC这个例子里有一条让我印象深刻的链接链:
- 用户需求CR-001(自动保持与前车安全距离)
- 向下链接到系统需求SR-ACC-DEC-002(有前车时调节车速保持安全时距)
- 再向下链接到组件需求CTR-ACC-DEC-002(模块输出目标距离/速度信息)
- 再进一步链接到测试用例TC-ACC-004(在仿真场景中验证本车跟随前车加减速)
整个链条串起来,任何一个环节的政策变了,比如“安全时距从1.2秒改成2.0秒”,DOORS会在链接两端标出Suspect(可疑),提醒你“下游实现和上游测试都可能要改”。这个功能如果不依赖链接,靠人工一个大表全局去比对,至少要半天到一天。
还有一个实操小技巧:不要只建“肯定有”的链接,还要定期查“应该有没有”。我每周会用DOORS的“创建链接分析矩阵”功能,拉一张“系统需求×组件需求”的矩阵视图,看有没有系统需求没有对应的组件需求、或者组件需求没有向上追溯到任何系统需求。那些没链接的,要么是漏拆了,要么是某个人自己“加戏”了。
3.4 第四步:基线管理与变更控制,让拆分成果可追溯
模块化拆分做完、链接打通、需求评审通过之后,下一步是把这个状态“冻结”起来。DOORS里的Baseline(基线)就是干这个的。
我的习惯是在关键里程碑建立基线,比如“设计冻结”“SOP前冻结”。基线一旦建立,这个时刻点的需求内容、属性、链接关系就被固定了,后面任何人修改原始内容,DOORS都会把这条变成Suspect。要真改,必须走变更流程,而不能悄悄把手改掉。
ACC项目里曾经发生过一件事:一位硬件工程师在软件冻结后,悄悄把一个执行器需求从“制动减速度最大3.5m/s²”改成了“制动减速度最大4.5m/s²”,因为他觉得供应商给的执行器能支持到4.5。这个修改没有走任何变更评审,导致制动系统的台架测试用例还在按3.5验收,两边差出一个奇奇怪怪的藕断丝连。后来就是因为基线对比,这个差异被逮到了,否则等到实车测试才发现,返工成本要大得多。
所以基线背景下,变更流程的基本路径是:
- 需求变更请求提出(描述变更原因、影响范围)
- 相关方评审(重点看DOORS链接矩阵中受影响的模块和测试用例)
- 批准后在DOORS中通过Change Set(或直接编辑但标记Obsolete)实施修改
- 重新建立基线,并通知下游确认
这个过程听起来繁琐,但汽车开发的现实就是:不经评审的需求变更,十有八九会在某个集成节点爆发成事故——不是技术事故,而是管理事故。
4. 实战中绕不开的坑与排查实录
4.1 高频问题速查表
我把实际支持过的团队里最常遇到的问题整理成了表格。这些问题都不是“能不能用DOORS”层面的,而是“怎么用好DOORS”层面的:
| 问题 | 根因分析 | 处理思路 |
|---|---|---|
| 导入Excel/Word后格式全乱 | 源文档使用了合并单元格、多级编号、上下标 | 先清洗源文档为“一单元格一条需求”,再导入;不要直接导入刊例文档 |
| 需求编号重复或缺失 | 手工录入时没有依赖ReqID属性 | 建立“ReqID唯一性”的约束或导入后跑一次DXL重复性检测 |
| 链接链断裂,下游需求显示“无来源” | 删除或重命名模块/条目时没有检查链接 | 删除前先在“向下追溯/向上追溯”视图里看影响范围;优先标记Obsolete而不是直接删 |
| 基线前需求被悄悄修改 | 流程上没有强制走变更 | 为每个模块设置“修改需审批”权限,至少设置“写”权限仅给需求负责人 |
| 过滤视图不生效 | View表达式引用属性名与模块属性名不一致 | 统一大小写和空格,比如统一“Status”而非“status”或“状态” |
| DOORS响应越来越慢 | 一个模块塞了太多条目,且视图全量加载 | 将模块按功能域拆小;常用视图只加载必要属性列 |
这些坑里,最让我感慨的是“删除条目”这一个动作。只要删了带链接的需求,链子就断了,而且DOORS不会自动提醒你“下游还有20条测试用例等着你”。所以我们的团队纪律是:需求只做状态迁移,不轻易删除。真的不需要了,把Status改成Obsolete,链接还在,追溯还在,追溯关系还在,后续分析还能看到“这里曾经有过一条需求,后来被废弃了”。直接删掉,等于抹去了历史决策痕迹,万一后面有人问“为什么这个功能不做了”,你都找不到证据。
4.2 用两个小脚本提升排查效率
DOORS的DXL脚本能排查很多问题,下面这两个我觉得最实用。
排查一:找出没有验证方法的需求
int i for i in Module "SR_ACC_系统需求" do { if (null obj."VerificationMethod") { print "缺少验证方法: " current Module(obj).ObjectText "\n" } }这个脚本虽然简单,但能在评审前把“不干活的需求”全部揪出来。没有验证方法的需求,基本等同于不可验证需求,必须打回去重写。
排查二:查找没有向上追溯的组件需求
Module m = Module("CTR_ACC_组件需求") LinkModule lm = linkModule("Traceability") for i in m do { if (noLinks(obj)) { print "无追溯链接的需求ID: " obj."ReqID" "\n" } }因为牵扯到具体的LinkModule命名,实际使用时需要按你的工程调整模块名和链接模块名,但思路是通用的。这类脚本可以每两周跑一次,作为需求健康度检查的一部分。
4.3 一套活了很久的“需求健康度”检查习惯
这里分享一个我在多项目里反复使用、成本低效果好的习惯——每周五下午花15分钟,拉一份“需求健康度checklist”:
- 新增需求有没有都在本周内分配了ReqID和VerificationMethod?
- 有没有Status为“Draft”超过两周的高优先级需求?
- 有没有没有向上追溯的组件需求?
- 上周基线之后,有没有非授权修改?
前两项直接在DOORS里用视图过滤就能完成,后两项用DXL脚本或链接分析矩阵。这15分钟看起来是额外成本,实际上它是提早发现需求失控苗头的雷达。很多项目前一两个月觉得“没必要做”,等到强烈感受到“乱”的时候,往往已经积压了上百条需要补链接、补验证方法的需求。
还有个细节:需求会议的节奏,最好跟需求拆分的节奏绑定。模块边界划分和接口定义阶段,建议每两周开一次跨模块需求评审会,只聊接口不聊实现;等链接和属性都稳定了,再改成月度评审,配合基线和变更流程。不要每周都拉所有人一起在DOORS里一行一行过需求,那样大家很快就对工具失去耐心了。
4.4 工具之外,这几点比DOORS本身更值得投入
做完ACC这个典型案例,我想说一个可能不少人会忽略的结论:DOORS再好用,也不可能把一个乱七八糟的“我认为”变成“已验证的需求结构”。模块化需求拆分真正的功夫,有一半花在DOORS之外。
首先是写需求的能力。我在评审中见过太多“系统应能自动适应不同路况”这类需求,听起来很高级,事实上没有任何可验证性——什么叫“适应”?不同路况有哪些?适应到什么程度算合格?拆分成“系统在雨雪路面应能将最大减速度限制在2.5m/s²以内”,虽然不浪漫,但至少能测。团队如果没有统一的写作规范,DOORS里录多少条都是数字堆积。
其次是模块边界的讨论。ACC这个案例里,真正让团队花时间的不是录入DOORS,而是在白板上争论“失效处理到底应该属于决策模块还是独立模块”。这类争论是有价值的,它实际上是在逼着各专业负责人把系统级的问题在纸面上想清楚。DOORS只是把讨论的结果固化成结构化数据,让争论不会在下个月重新发生一遍。
最后是属性与视图的设计。属性不是字段游戏,属性设计本质上是“这个项目怎么管”的映射。想管验证,就要有VerificationMethod属性;想管审批,就要有Status和Approver属性;想管供应商合同,就要有Supplier属性。什么时候加属性、什么时候不加,取决于你在哪个阶段需要什么样的管理视角,而不是为了把DOORS界面填满。
我在ACC项目里最满意的一个决策,是组件需求模块直接按物理供应商切分,每个供应商只看到自己要交付的那个模块。DOORS的视图技术让这变得很容易,但前提是模块划分当初就是朝这个方向设计的。模块化拆分,本质上是把复杂的系统工程拆成若干个可独立管理、可独立验证、可独立供应商交付的小单元——这个思路放在ACC上是这样,放在其他任何整车系统上,同样成立。