Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

1.5 结构安全审查——建筑监理与软件审查

一栋楼和一台ECU的不同命运

请你做这样一个比较。

场景一:上海浦东,一栋即将竣工的住宅楼。 在第一位住户搬进去之前,这栋建筑已经经过了三层独立审查:第一层,设计审查:施工图纸在项目开工前由注册结构工程师审核并盖章。第二层,过程监理:从基坑开挖、钢筋绑扎、混凝土浇筑到幕墙安装的每一个阶段,都有独立监理工程师到场检查并在监理日志上签字。第三层,竣工验收:项目竣工后,由第三方质量监督站进行全面检查,包括混凝土强度回弹测试、沉降观测、消防系统联动测试。只有这三层审查全部通过,才能拿到《竣工验收备案表》。没有这张表,这栋楼不能出售,不能入住。

场景二:你的办公桌上,一台新开发的ESP(电子稳定程序)ECU。 这台ECU的软件包含大约50万行C代码,运行在AUTOSAR Classic Platform上,安全等级ASIL D(汽车安全完整性等级中最高的一级)。它需要实时处理来自多个传感器的输入(方向盘转角、横摆角速度、轮速),并在检测到车辆失控趋势时,在几十毫秒内对特定车轮施加制动。如果这个软件有缺陷,比如在特定条件下错误地触发制动,结果可能是一次致命的事故。

那么,谁在审查这台ECU的软件?谁在扮演建筑行业中“独立监理“的角色?

在最好的情况下:一个由功能安全经理领导的独立安全评估团队,依据ISO 26262进行安全审计。一个通过Automotive SPICE评估的开发过程,由独立评估师现场审核工作产品和访谈。一个由TÜV(德国技术监督协会)或类似认证机构出具的产品认证。一个包含代码评审、静态分析、单元测试和集成测试的验证体系。

在最差的情况下:项目快要延期了,安全档案被匆忙整理,“代码评审“变成了在Pull Request上点一个Approve按钮,静态分析工具跑完了但没人看报告上的MISRA违规项(因为“反正这些违规都有偏差记录”),功能安全经理的签名只是走一个形式。

建筑行业用一百年建立了强制性的独立审查制度,付出了无数生命代价。软件行业,尤其是汽车嵌入式软件行业,仍然在建立同等严格的审查文化的过程中。而你的责任,就是成为那个即使没有法律强制也会坚持专业审查的工程师。

核心洞察:建筑用一百年和无数生命代价才换来强制第三方审查,而软件因故障不可见、被商业保密遮蔽,至今仍在建立同等制度的路上。 在强制制度到来之前,专业道德要求我们每个人先成为那道“第二双眼睛“。

建筑为什么需要监理:一个经济学的解释

要理解代码审查的深层意义,我们必须先理解建筑监理制度是如何形成的。它的起源因为一个冷酷的结构性冲突。

在建筑项目中,利益冲突是结构性的、不可避免的:

  • 开发商的动机:最低成本、最短工期、最高利润。
  • 承包商的动机:用最少的材料和劳动力完成合同要求。
  • 建筑师的动机:设计出有价值的作品,但他们经常被开发商和承包商的预算决策架空。
  • 未来的住户的动机:一个安全的、耐久的、舒适的居住环境,但他们在施工阶段不在现场,没有发言权,甚至在购房时也无法判断结构隐蔽工程的真实性(你买房子时会凿开承重墙检查钢筋直径吗?显然不会)。

因此,独立第三方监理的存在,本质上是为了解决“信息不对称“(information asymmetry)问题:买方(住户)无法判断产品质量(结构安全),卖方(开发商/承包商)有动机在不可见的部分偷工减料。监理是买方(或社会公共利益)的代理人,其唯一的职责是替盲人看见看不见的东西。

现在把这个框架投射到汽车嵌入式软件上:

  • OEM(整车厂):相当于开发商,它希望用最低成本、最短开发周期获得合格的ECU软件。
  • Tier-1供应商:相当于承包商,它必须在合同预算内交付软件,如果超支,它亏损。
  • 软件开发团队:相当于建筑师/施工队,他们设计并实现软件,但受制于外在的进度和预算压力。
  • 车辆的用户(驾驶员/乘客):相当于住户,他们无法判断自己正在驾驶的汽车里运行的软件是否经过了充分的安全审查。

谁在汽车行业中扮演“监理“的角色?

答案是:一个分布式的审查网络,没有单一的监理角色,而是多个角色各自负责一个维度的审查:

  • 功能安全经理(FSM,Functional Safety Manager):负责确认安全档案(Safety Case)的完整性,从危险分析(HARA)到安全目标到安全需求到软件安全需求到实现到测试,每一条链路都经过验证。
  • ASPICE评估师:负责评估开发过程的规范性,评估“开发这个产品的过程是否具有可重复性和可管理性“。
  • TÜV/认证机构:负责产品层面的合规性认证,独立的、商业化的第三方。
  • 代码评审者(你的同事):负责逐行确认代码的正确性和安全性。
  • 静态分析工具:负责自动化的规则合规检查。

这套体系在纸面上看起来完善,但在实践中有一个根本缺陷。建筑监理对“偷工减料“的检查是不可通融的,监理日志上的一个签字意味着一根钢筋的直径、一条焊缝的长度确实达到了设计要求。如果监理签字后发现不合格,监理工程师承担法律责任。而在汽车软件审查体系中,“代码评审通过”、“静态分析报告已评估”、“安全论证已完成“这些结论的有效性,高度依赖于执行这些审查的工程师的主观专业判断,而他们可能与开发者处于同一个汇报链条中。

这就是为什么我们在前面(1.3节)讨论ASPICE时强调:过程能力评估机制本身也需要独立性。被独立于产品线之外的评估师按照统一标准评估。

核心洞察:汽车嵌入式软件的“监理“危机不在于没有审查机制,而在于机制被“形式化“。 ASPICE、ISO 26262、TÜV认证都是审查机制,但常因进度压力和成本控制而变成:签了字但没审查,出了报告但没分析,过了评审但没思考。每一次这样的妥协,都是在对住宅楼的钢筋检查中“大致看一眼就行“。你不必因为别人都这么干就这么干,你的专业判断是你唯一不被取代的价值。

软件不可见性的衍生后果

在1.2节中,我们讨论了软件的不可见性,它没有物理实体,因此故障难以察觉。但这里我要展开一个更具体的后果:软件不可见性使经验积累的代价更高。

当一个建筑结构工程师犯了一个错误:比如低估了风荷载,最坏的情况是建筑倒塌,所有幸存结构工程师都会学到“这个设计参数不能小看“。这个教训是即时的、公开的、被写入新版本的建筑规范中的。日本建筑规范中关于抗震设计的每一次修订,背后都是阪神、新潟、熊本的倒塌建筑和失去的生命。每一次物理塌陷,都让整个行业变得更安全。

当一个汽车软件工程师犯了一个错误:比如在一个CAN中断处理函数中留下一个竞态条件,这个错误可能:

  1. 三年后才在特定车辆、特定驾驶模式、特定季节下触发。
  2. 触发时表现为“偶发性ESP误触发“,无法复现。
  3. 触发后,即使导致了事故,调查可能要花数月才能追溯到那个竞态条件。
  4. 调查报告可能因为商业机密的约束而不公开,只有内部团队知道。
  5. 行业内的其他软件工程师永远不知道这个案例,因此同样的问题可能在另一家公司重演。

软件的不可见性意味着:行业从失败中学习的效率,远低于建筑行业。 建筑行业有公开的事故调查报告、修订的设计规范、强制性的执业资格考试。软件行业:尤其是汽车嵌入式软件行业,的失败案例大多被包裹在保密协议、法律和解和“供应商责任归属于合同条款“的迷宫里。

这就是为什么静态分析和代码评审对你个人而言如此重要:因为它们是你个人从不可见的世界中积累可见经验的少数途径之一。每一次你通过代码评审发现了一个潜在的竞态条件,每一次你通过静态分析报告理解了一个看似无害但其实危险的类型转换,你都在为自己建立一个无法从外部事故报告中学到的“失败模式数据库“。

静态分析:软件的结构力学计算

用一个建筑类比来理解静态分析(Static Analysis)的本质。

在建筑设计中,结构力学的核心任务是验证:在给定设计载荷(恒载、活载、风载、地震作用)下,结构的每一个构件的应力不超过其承载能力,结构的整体变形不超过使用要求。结构工程师使用有限元软件对整栋建筑建模,输入载荷,运行分析,检查输出。任何超出设计范围的应力(比如某个柱子的轴向力超过了混凝土的抗压强度)都会被标记为错误,必须在施工前修正设计。

软件静态分析做的事情,在概念上完全一致。

在代码中,静态分析工具将你的源代码建模为一个抽象的程序模型(control-flow graph, data-flow graph, call graph),然后在这个模型上运行规则检查:

  • MISRA C Rule 17.2:函数不应该是递归的(因为在汽车嵌入式系统中,递归的调用深度不可预测,可能stack overflow,这相当于“结构的不确定性“)。
  • MISRA C Rule 10.1:操作数不应是不适当的类型(将一个有符号整数赋值给一个无符号整数,这相当于“荷载类型不匹配“,你把风荷载错误地当作恒载输入了)。
  • ISO 26262的软件安全要求:所有安全关键路径上的函数必须满足MC/DC覆盖率(Modified Condition/Decision Coverage,修正条件/判定覆盖),这相当于“所有承重构件的应力分析必须覆盖所有荷载组合工况“。

但这里有一个根本性的区别:建筑结构分析是完备的。给定精确的数学模型(有限元模型)和精确的物理定律(弹性力学),你可以在设计载荷下充分验证结构的安全性。而代码的静态分析是近似的不完备的,因为停机问题(1.2节)告诉我们,不存在算法能够判断所有可能的程序行为。

正因为如此,在建筑中,结构分析可以以极高的置信度证明安全性:你可以说“在规范规定的设计工况下,这栋楼的倒塌概率低于10的负六次方“。

在软件中,静态分析总能发现某类错误,但绝对无法发现所有错误。MISRA C规则17.2能防止递归引起的堆栈溢出,但不能防止一个逻辑正确的递归函数在执行了数百万次后耗尽系统资源。静态分析能发现“一个指针可能为NULL就被解引用“,但不能发现“这个指针指向的数值在本函数运行期间被另一个中断改变了,产生了一个数据竞态(data race)“。

也就是说:静态分析能提升证据的广度(我可以证明我的代码不违反MISRA C:2012的全部143条规则),但不能替代证据的深度(我针对性地验证了每个具体安全需求)。

这就是为什么汽车嵌入式软件开发需要静态分析 + 单元测试 + 集成测试 + 硬件在环(HIL)测试 + 实车测试的多层次验证体系,每一层覆盖不同层面的不确定性。没有一个单一工具能够给整个系统打上“安全“的标签。

核心洞察:静态分析永远不能说“代码是安全的“,只能说“我排除了这些已知类别的缺陷“。 软件静态分析在概念上等同于建筑的结构力学计算,但它的完备性受制于停机问题:软件的语义属性不可判定。因此在汽车嵌入式软件中,你只能说“静态分析通过了,所以我排除了这243类已知的浅层缺陷,但深层语义的正确性仍需要大量的针对性测试和人工推理来证明“。这是对1.2节“没有银弹“的一次工程实践性回应。

代码评审:人和人之间的信任机制

如果说静态分析是结构力学计算,是一个自动化的、规则驱动的、数学模型化的验证,那么代码评审就是结构监理工程师的“现场检查“。它不能也不应该被自动化替代。

深入理解代码评审到底在做什么。

很多人(包括刚入行的工程师,也包括很多项目经理)对代码评审有一个根本性的误解:他们认为代码评审的目的是“找bug“。如果你也这么认为,那么你就会自然地得出结论:“如果静态分析工具已经找出了所有规则违反,如果测试已经覆盖了所有需求:那为什么还需要代码评审?”

代码评审的真正目的不是找bug:或者更准确地说,找bug只是它的一个副产品。代码评审的三个更根本的目的是:

第一,建立“第二双眼睛“的信任机制。 建筑监理到场检查钢筋绑扎,因为他在用“第二双眼睛“确认设计师和施工方的判断是正确的。同一个道理:代码评审的核心价值不在于评审者发现了原创作者没发现的bug,而在于两个独立的专业判断在同一个结论上达成一致。当两个人独立阅读同一段代码并独立得出结论说“这段代码实现了需求R_ESP_001且没有引入副作用“时,其置信度远超一个人的单次判断,因为独立判断降低了共同盲区的概率。

第二,传递隐性知识。 需求文档写不出所有细节。测试用例捕捉不了所有边界条件。但当你和另一位资深工程师一起坐在屏幕前逐行阅读扭矩控制函数时,对方可能会说:“等等,第47行这个移位操作:你确认目标平台的位移操作符在处理超过寄存器宽度的位移量时行为是确定的吗?在某些ARM Cortex-R上这是实现定义的(implementation-defined behavior)。“这句话不在任何需求文档或测试用例中,它是工程师头脑中的隐性知识。代码评审是这类隐性知识从一个人的大脑迁移到另一个人大脑的最主要通道。

第三,形成“人“的追溯链。 当你在一份多线程代码的评审评论中写下:“这个共享变量在中断上下文中被修改,但没有volatile修饰,这可能导致编译器优化掉主线程中对它的重读,产生数据竞态。“你创造了一条记录,这条记录在六年后车辆召回调查中可以回溯。调查员会说:“这里有一个代码评审评论指出了潜在的数据竞态,当时被修改了吗?修改后的代码被重新评审了吗?重新评审的记录在哪里?“记录的可追溯性是建筑监理日志在软件领域的等价物。

核心洞察:代码评审是在建造一个“人类判断网络“,两个独立视角的一致声称,是比任何自动化检查都更深厚的安全证据。 好的评审不仅仅是找出一个变量命名错误然后让作者改掉,好的评审是让两个人共同声称:“我们已经从两个独立的角度审视了这段代码,我们一致同意:它实现了它应该实现的功能,且没有引入它不应该引入的风险。”

审查的未来:当人机协作成为标准

先前瞻一下。当AI编码助手(如GitHub Copilot、Cursor、等)越来越深入地参与汽车嵌入式软件的开发时,审查机制会发生什么变化?

目前,AI辅助编程在汽车嵌入式领域仍处于非常早期的阶段,这因为汽车嵌入式对代码的要求与通用软件开发有本质不同:

  • 确定性要求:AI生成的代码往往“大多数时候正确但偶尔有微妙错误“。对互联网APP,99%正确已经非常优秀。对汽车ESP制动控制,99%正确 = 1%的事故率 = 完全不可接受。
  • 可追溯性要求:ISO 26262要求从安全需求到代码行的完整追溯。你如何为一段AI生成的代码建立“这条if-else对应了哪条安全需求“的论证?
  • 软件需求明确性要求:AI擅长的是“我将从互联网上数十亿行代码中提取模式“,但汽车嵌入式软件的许多代码(设备驱动、特定ECU的HAL实现)在互联网上根本没有训练样本。

但AI在审查侧(而不是生成侧)可能带来实质性提升:

  • 智能静态分析增强:传统静态分析是基于规则的(“检查是否违反了MISRA C 17.2”)。AI增强的静态分析可能识别更细微的模式:例如:“这段代码的函数调用序列与去年同一个团队写的一段代码相似,那段代码后来发现了一个竞态条件,建议检查第47行的锁获取顺序。”
  • 自动化的代码评审辅助:AI可以生成一份“代码变更摘要“给评审者:“这个提交修改了扭矩控制函数中的PID参数,新增了一个安全检查,删除了一个废弃变量的赋值。建议评审者重点关注安全检查的逻辑完备性,特别是第128行的边界条件处理。“这不会替代评审者,但会大幅提升评审者的效率,因为评审者不需要花30分钟来“读懂“代码变更,而可以直接进入“分析逻辑安全性“的深层工作。
  • 跨项目的模式挖掘:当一个团队中的多份代码评审记录被AI分析,可能识别出“这个团队在竞态条件处理上有一个系统性盲区,过去一年中出现了5次类似的竞态条件,每次都在代码评审中才被发现。建议该团队一次多线程编程的专项培训。“

但无论技术如何进步,核心事实不变:软件审查的最后一公里永远是人。 一个人类工程师用他的专业判断、他对“这个功能在真实世界中可能怎样失效“的想象力、以及他的道德勇气去决定“我认为这个系统是安全的“,这个环节不能被自动化。因为自动化不承担责任。让一个算法决定“这个刹车系统可以发布“。当事故发生后,谁来承担责任?算法没有生命,没有执业执照,不能被起诉,但一个经过严格审查才签字的工程师,要为他签名的内容付出职业声誉的代价。

这也是为什么“独立审查“这个概念最终是一个非常人性化的概念,它依赖的是信任和责任在人类之间的流动,而不是数据在算法之间的流动。


本篇小结

  • 建筑行业的强制性独立监理制度源于“信息不对称“:买方(住户)无法判断结构安全性,因此需要第三方代理人代替买方独立检查。汽车嵌入式软件面临同样的结构性问题:驾驶员无法判断他正在驾驶的汽车的软件是否经过了安全审查。
  • 软件的不可见性意味着行业从失败中学习的效率远低于建筑行业,建筑倒塌是即时的、公开的、被写入新规范的;软件故障是延迟的、统计的、常被法律和商业保密遮蔽的。这使个体的专业审慎(代码评审、静态分析)比在任何其他工程学科中都更加重要。
  • 静态分析在概念上等同于建筑的结构力学计算,它是规则驱动的、可自动化的、针对已知错误类别的“结构安全性检查“。但停机问题保证静态分析永远是近似的、不完备的,它排除已知的浅层缺陷,但不能替代针对深层语义的针对性测试和人工推理。
  • 代码评审的本质建立“第二双眼睛“的独立验证机制(降低共同盲区概率)、传递隐性知识(超越文档和测试用例)、形成可追溯的人类判断记录(当事故调查开始时的回溯链条)。
  • AI的未来角色可能在审查侧(而非生成侧)带来最大价值,智能模式识别、自动化代码变更摘要、跨项目的系统性缺陷挖掘,但“最后一公里“的最终安全判断永远不能也不应该被自动化替代,因为只有人类承担职业责任。
  • 成为一名愿意“仔细看“的审查者是你对行业的贡献,因为你是那道“第二双眼睛“,而在这个看不见的软件世界里,多一双眼睛往往就是生与死的区别。

【下集预告】 至此,我们已经讨论了软件工程的历史(1.2)、方法论的比较(1.3)、架构的设计哲学(1.4)以及独立审查的重要性(1.5)。现在,用一个统一的思想实验把所有线索编织在一起:如果你是一座容纳十万人城市的总建筑师,你只有三片区域的预算却需要建五片:你会如何决策?你将如何设计基础设施以保证城市可以在未来几十年间优雅地生长?这座“城市“就是你的汽车嵌入式软件系统,而你是那个需要同时考虑美学、结构、安全、成本和人的首席架构师。这就是1.6节,《思想实验——一座10万人的城市》。