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.6 思想实验——一座可以盛下10万人的城市

你被赋予了一项几乎不可能的任务

想象这样一个场景:你被任命为一个全新城市的总建筑师。这座城市将容纳十万人居住。预算批准了,足够建造三个完整的城区,包括住宅、道路、供水、供电、学校、医院。

但城市规划要求的是五个城区。

你有30分钟来决定:哪两个城区被推迟?基础设施如何设计才能让城市在几十年内优雅地生长?而最关键的是,如果你的决策错了,十万人将不得不在一个设计糟糕的城市中生活几十年。

现在请你暂停阅读,闭上眼睛,试图在脑海中勾画出这座城市的规划草图。它看起来是什么样?你把主干道放在哪里?你把医院放在哪个城区?你为未来扩张预留了哪些接口?你在现在建造的基础设施中,是否已经为将来被推迟的第四、第五城区埋下了管道井?你是否在水厂的规划中预留了额外的处理能力,尽管现在只有三万人入住?

你可能会感到一种深沉的焦虑,一种“我知道我一定会犯错“的预感。这种焦虑不是你的个人特质问题。这种焦虑是所有有良心的架构师的共同症状,因为你面对的是一个“在所有约束下找到最不差的妥协方案“的问题。正如我们1.2节“没有银弹“的教训:复杂系统的设计没有最优解,只有经过充分论证的、在给定约束下可接受的解。

而我要告诉你的是:你刚才正在做的,就是汽车嵌入式软件架构师每天在做的事情。

映射:将城市翻译为软件系统

先建立这个思想实验中“城市“与“软件系统“之间的完整映射。这个映射不是比喻,它是同构的。城市建筑学与软件架构学面临的结构性问题是相同的。

城市要素软件系统对应项说明
城区(District)子系统/模块(Subsystem / SW-C Cluster)功能的内聚集合。在AUTOSAR中,一个“城区“可能是“诊断与通信区“、“传感器数据处理区”、“执行器控制区”、“电源管理区”。每个区内部高度内聚,区之间通过明确接口通信。
道路(Road)接口/总线(Interface / Bus)连接城区之间的通道。CAN总线=主干道(低速率、高容错),FlexRay=高速公路(高速率、时隙确定),Ethernet=城市快速路(超高带宽、复杂拓扑)。接口的宽度(数据带宽)、层次(物理层 vs 应用层)和方向(单向/双向)都必须被设计。
供水系统(Water Supply)数据流(Data Flow)信息在模块之间如何流动。哪个模块生产数据(水源),哪个模块消费数据(水龙头),数据在传输过程中是否经过中间节点处理(水厂)。NVM模块的读写路径、诊断数据的采集与上报路径:都是“供水系统“。
电网(Power Grid)计算资源/调度(Computing Resources / Scheduling)CPU时间、RAM空间、中断优先级,这些是软件世界的电网。一个城区停电=一个Task运行超限导致所有低优先级Task饿死。电力分配=Task的时间片划分和优先级分配。
消防站(Fire Station)监控机制(Monitoring / Watchdog / Safety Mechanisms)城市需要消防站快速响应火灾,软件需要看门狗定时器、内存保护单元(MPU)和端到端(E2E)通信保护。消防站的距离=监控机制的响应延迟,它必须在系统“烧毁“之前发现异常并采取行动。
建筑规范/建筑法(Building Code)编码规范/安全标准(MISRA C / ISO 26262 / CERT C)城市有建筑法,你必须在规划许可获批前证明你的建筑选址、结构设计、消防设计合规。软件有MISRA C和ISO 26262,你必须在功能发布前证明你的代码符合这些规则的约束。违反建筑规范的代价=一个法律上不可出售的建筑。违反MISRA C的代价=一个法律上不可认证的ECU。
规划审批(Planning Approval)ASPICE评估 / ISO 26262安全档案审查在施工之前,城市规划部门必须批准你的城区设计,检查你的道路宽度是否满足消防可达性、你的排水管道口径是否满足50年一遇的暴雨、你的绿地率是否达标。同理,在SOP之前,OEM的安全评估团队必须批准你的安全档案,检查你的HARA分析、你的安全概念、你的软件架构设计。
居民(Residents)利益攸关者(Stakeholders)驾驶员(需要安全可靠的功能)、维修技师(需要可读的诊断信息)、安全评估员(需要完整的追溯链)、接替你的未来开发者(需要可理解和可修改的代码)。不同居民有不同需求,你的城市(软件)必须同时为他们服务。

当你建立了这个映射,我们1.3节讨论的瀑布/敏捷/ASPICE、1.4节讨论的柯布西耶/形式追随功能、1.5节讨论的结构安全审查,所有这些概念都可以在这座想象的城市中找到物理对应的位置和坐标。它们不再是抽象的管理学术语,它们是城市的道路、建筑、管道、消防站和建筑法。

核心洞察:城市是理解软件架构的最佳类比,因为两者面对完全相同的六类约束。 物理空间有限(嵌入式系统的RAM/ROM有限)、基础设施必须预留未来扩容能力(架构的扩展性)、不同区域有不同用途(模块的单一职责)、设计错误的受害者是百姓而直接受益者是开发商——软件架构面临的有限资源、不可预见的未来需求、多样化的利益攸关者、严格的合规标准、不可逆的某些架构决策,和城市设计完全相同。

第一道难题:三个城区的预算,五个城区的需求

这是你面对的第一个架构决策:在资源约束下,哪些功能优先交付?

汽车嵌入式软件项目面对的是完全相同的处境。ECU的ROM和RAM是固定的,CPU周期是有限的,CAN总线的带宽是饱和的,但你必须在给定的硬件平台上实现所有功能需求。“全部实现“往往在物理上不可能。必须做优先级管理。

现在列举五个城区:

  1. 动力与控制区(Powertrain & Vehicle Dynamics Control):发动机控制、变速器策略、扭矩矢量分配、ESC(电子稳定控制)。这个城区的居民对“车辆能正常驾驶“这个基本需求负责。
  2. 诊断与通信区(Diagnostics & Communication):UDS诊断服务、DTC管理、CAN/LIN/FlexRay/Ethernet通信栈、网络管理。这个城区负责车辆与外部世界(诊断仪、OEM服务器)的对话。
  3. 车身与舒适区(Body & Comfort):车窗控制、座椅调节、空调、灯光。
  4. 安全与监控区(Safety & Monitoring):看门狗管理、内存保护、端到端保护、功能安全监控。
  5. OTA与远程服务区(OTA & Telematics):远程软件升级、远程诊断、云端数据交换。

你现在有预算建造三个。你选择哪三个?

答案直观但并不简单:动力与控制区必须建,因为车要能动。安全与监控区必须建,因为ISO 26262的要求不可协商。诊断与通信区必须建,因为不满足UDS法规要求,车辆不能取得型式认证,不能上市销售。(关于UDS诊断协议的详细解读,参见《UDS深度解析》第2章:“法规驱动的诊断需求:OBD与排放”。)

那么车身与舒适区和OTA与远程服务区被推迟,这意味着:

  • 在第一批车辆中,车窗控制可能只有一个基础功能版本:“手动升降“变成了“基础版的电动升降”(也许只支持驾驶员侧),后排空调可能是手动而非自动分区控制。
  • OTA功能可能只在最低限度可用,能够接收诊断仪通过OBD-II接口的软件更新,但不能通过蜂窝网络远程接收更新。

但关键问题是,你在现在的基础设施设计中,是否为这两个被推迟的城区预留了扩展接口?

这就是柯布西耶“底层架空“理念(1.4节)的实际应用:你为未来建造的柱子(基础设施、接口、通信机制),应该是现在铺设的,不是未来征用的。如果你不在现在的城区设计中预留CAN总线上OTA消息的ID段,两年后你想加入OTA功能时将面临整个通信矩阵的重新规划。这相当于在城市已经建满之后,你才想起来应该预留一条地铁线路,你唯一的选项是拆迁已有建筑,代价巨大。

核心洞察:好的架构不是“做了所有需求“,而是“知道哪些现在做、哪些可以推后,并为推后的需求预埋基础设施“。 在汽车嵌入式软件中,这意味着通信矩阵预留扩展ID段、Task调度周期留有安全裕度、ROM规划为未来功能模块预留空间。这些“多余“的设计在项目开始时看起来“浪费“,但两年后当新需求出现而不需要重写整个系统时,你对自己的先见之明会深感庆幸。

第二道难题:道路系统设计——接口 = 一切的基石

现在设想你已经选定了前三个城区(动力控制、安全监控、诊断通信)。下一个问题是,这些城区之间的道路系统应该如何设计?

在建筑学中,道路网有三种基本形式:网格状(Grid,如纽约曼哈顿)、放射状(Radial,如巴黎)、有机生长型(Organic,如中世纪欧洲古城)。三种形式各有优劣:网格状效率高但缺乏层次感,放射状中心带宽高但外围分散后效率低,有机型灵活性高但导航困难。

在软件架构中,系统的连接拓扑同样有三种基本模式:

直接连接(点对点):每个模块知道所有它需要通信的模块,这相当于放射性系统,每一个住在城区A的人都必须对“我想去城区B“知道一条专门的道路。问题:每增加一个模块,链接数以O(n²)增长。适用于小型系统(<5个模块),不适用于大型系统(当你有50个SW-C时,直接连接的通信矩阵将是一个不可维护的噩梦)。

总线型连接(星型/广播):所有模块通过一个共享通信通道(CAN总线)通信,这相当于曼哈顿的网格状道路。每个模块发送消息到总线上,感兴趣的模块接收。问题:共享通道的带宽是固定的(CAN总线500kbps/1Mbps)。当城市规模增长超过道路承载能力时,拥堵发生,即CAN总线负载超过设计上限。

分层路由连接(软件总线/中间件):引入一个中央路由层(如AUTOSAR的RTE,或者更传统的RTOS的消息队列),这相当于城市设置了“主干道 + 次干道 + 支路“的层次结构。模块不直接彼此通信,而是通过预定义的路由规则(端口、接口、连接器)间接通信。问题:引入了额外的延迟(消息通过中间件传递需要上下文切换和内存拷贝),在硬实时系统中,这个延迟可能不可接受。

在真实AUTOSAR项目中,你通常看到的是一种混合拓扑:分层路由(RTE)作为骨干,部分性能关键的路径使用直接连接(中断 + 共享内存),总线型(CAN)作为对外通信的通道。

这正是城市规划中“综合交通体系“的软件映射:步行、自行车、公共交通、私家车,不同出行需求(不同通信需求)使用不同的交通方式(不同的通信机制)。

核心洞察:你的接口设计决定了系统五年后的可扩展性——好的接口让数据流“可预测“和“可验证“。 如果你今天的模块之间通过“全局变量 + 不定时查询“方式通信,五年后任何人都无法理解数据和信号在系统中的实际流向。在汽车嵌入式系统中,“可验证“意味着你可以从接口定义中追踪数据的端到端保护,这直接服务于ISO 26262的安全论证。

第三道难题:丑陋但坚固的楼——什么时候不要重构

现在把镜头拉近到具体的建筑。在你规划的第三个城区中,有一栋“楼“:这是一个有200行的函数,里面有14层if-else嵌套。它控制着ESC在一个特殊驾驶工况(对开路面制动,即split-μ braking)下的制动压力分配。它已经经过6000小时的实车测试,在所有已知场景下工作正常。其WCET(最坏情况执行时间)距预算仅差0.3ms,你不能增加哪怕一层额外的函数调用,否则它就会超限,触发Task超时。

现在有一个新的工程师加入团队。他看了一眼函数说:“天哪,这太丑了。让我把它重构一下,拆成10个每行不超过20行的小函数,遵循单一职责原则,应用策略模式消除这个if-else嵌套。”

你应该让他重构吗?

答案是:不应该,除非他可以同时保证重构后的代码:(1)与重构前在6000个测试场景下输出完全一致(回归测试),(2)WCET不超过(甚至更优)重构前,(3)重构本身有明确的业务需求驱动,而不仅仅是“让它变漂亮“。

我不是在为丑陋代码辩护。我在为工程判断辩护。柯布西耶的Villa Savoye(1.4节)是建筑史上最优雅的设计之一,但它漏水,因为它没有考虑到现实世界的冻融循环。同理,一个被优雅地重构分解成10个函数的代码,如果因为函数调用上下文切换导致WCET增加0.5ms,破坏了Task的实时性,它就是软件版的Villa Savoye。它看起来完美,但它不能运行在真实约束下。

更具体地说:“丑陋“从来不是一个有效的工程需求。“安全”、“可维护”、“可测试”、“在规定时间内完成计算”:这些才是工程需求。如果你要把“易于修改“也加入需求,那么你的重构必须直接绑定一个“因这个重构而受益“的下一个修改任务。

在建筑术语中,这叫“改建许可“(alteration permit)。你必须在动工之前说明你为什么要拆除这面墙、你的改建方案满足现行建筑规范中的所有要求、你的改建设计不会影响建筑的结构整体性。如果一个工程师想“重构“一段已经过充分验证的代码,他应该先提交一份类似的“重构许可“,描述重构的原因(具体哪个业务需求驱动了重构)、重构的风险(如何保证不引入回归)、重构的验证方案(如何测试重构后的代码)。

核心洞察:重构的唯一正当理由是“可预见的未来有明确修改需求,而当前结构会使该修改困难、不安全或不可能“。 如果你不确定下一个需求是什么,就不要重构——“明天的可维护性“是一个模糊的假设:你是在为谁维护?为什么?维护的具体内容是什么?回答不了这些问题,你的重构可能只是在做一个美学项目,而美学项目在安全关键软件中没有优先权。

居民的视角:城市是用来生活的

柯布西耶从Villa Savoye到马赛公寓的成长(1.4节)告诉我们一个终极真理:城市(软件)不是为建筑师自己建造的,是为居民建造的。

现在看看这座软件城市中不同的“居民“:

居民甲:驾驶员。 她不关心你的依赖图有多优雅。她关心的是“在高速公路上急刹车时,刹车系统会不会意外地在某一个轮子上施加制动力而导致车辆甩尾“,即你的ESC系统在split-μ工况下的安全性。如果她因为一个竞态条件而遭受事故,你精美的架构图不会给她任何安慰。

居民乙:维修技师。 他在车间的诊断仪上看到一个奇怪的DTC(诊断故障码),“C1243: ABS泵电机转速信号丢失”。他需要知道这个DTC意味着什么、如何定位、如何修复。如果你的诊断模块中这个DTC的debounce逻辑被塞进了一个不相关的CAN通信模块里,他将永远找不到根源。这就是“模块职责模糊“的直接代价。

居民丙:安全评估员。 他在审查你提交的安全档案。他从安全目标SG_01(“防止非预期制动”)出发,试图追踪到实现代码。在软件架构设计文档中,他看到“制动扭矩计算位于SWC_BrakeTorqueCalc中“,继续追踪到详细设计文档,发现了这个SW-C的runnable定义和内部函数划分。继续追踪到代码,他需要看到代码中“在踩刹车踏板时,系统不应在无驾驶员意图的情况下施加减速度“这个逻辑的显式表达。如果这条追溯链的任何一个环节断裂,如果架构文档中的“制动扭矩计算“和代码中的函数名称对不上,他的评估就是“不通过“。

居民丁:接替你的未来开发者(也许是三年后的你自己)。 她在凌晨三点被叫醒,因为生产现场发现了一个紧急问题:在极寒条件(-40°C)下,某个ECU启动后看门狗超时的概率突然增加了。她被找来定位根因,因为你是三年前写这段代码的人,而你正在度假。她打开你的代码。如果你当年在设计时采用了清晰的命名、明确的注释(“这个延时是为了等待外部SBC芯片的稳定时间:如果低于200ms,SBC可能未达到稳定,如果高于500ms,看门狗可能超时”)、并且模块划分清晰让她能在15分钟内定位到相关的初始化代码,她将能够快速判断问题。如果你的代码是“优雅的“但所有人都读不懂,她将必须花数天从头逆向推导你的逻辑。

你的架构最终不是被你或你的架构委员会评判的,是被这四类居民在使用中评判的。

核心洞察:评判软件架构好坏的标准,是五年后系统不在你控制范围内时,它是否仍在服务驾驶员、维修技师和接班工程师。 五年后,没有人会在意你画得漂漂亮亮的UML图,但所有人都会在意你留下的承重墙是否牢固、道路规划是否合理、消防站(安全机制)是否能在火灾(故障)发生时及时响应。

尾声:从小世界到广大的大地

回过头看,这场思想实验不是关于“如何设计软件“的技术教程,这整本书接下来的章节才是。这场思想实验是关于心态。

你的ECU代码在每次启动时,会运行几十万行的C代码。你写进了多少种潜在故障模式?你为这些故障模式预留了多少个“消防站“?你设计的“道路系统“能否被十年后一个完全陌生的工程师快速理解?你的“建筑规范“(MISRA C违规偏差记录)是怎样撰写的,是认真记录了风险和缓解措施,还是复制粘贴了一个模板?

正如1.2节中布鲁克斯告诉我们的:建筑要防的是坍塌,软件要防的也是坍塌。 你不是在建造一座房子,你是在建造一座容纳十万人、运行数十年的城市。

而城市设计最颠覆人心的洞见在于:你的设计的影响,在你离场之后依然延续。 一栋今天建成的建筑,它的结构寿命预期为50-100年,它的建筑师的职业巅峰期早已过去,建筑依然在使用。一个今天写入ECU的软件,它的运行寿命可能是15年(车辆的预期使用年限)乘以数百万辆,你可能早已调离项目、转行、退休,你的代码依然在全世界数百万上千万辆车中每天运行。

这就是你和城市设计师共享的命运:你的决策影响的时间跨度,超出了你在场的时间。 你不能征求未来驾驶员的意见,你不能测试未来维修技师的知识水平,你不能预测未来安全规范的修订内容。你唯一的锚点是:在当下的有限信息、有限资源、有限时间中,做出经过充分论证的、负责任的、经得起时间考验的设计决策。

梁思成,新中国最伟大的建筑学家,在《中国建筑史》的开篇写过这样一段话。他说中国古代建筑以木材为主,几乎每过一代就要重建。但这些不断重建的建筑,始终遵循同一套结构逻辑,榫卯体系。千年来,材料朽烂了,建筑形态随朝代更迭了,但结构的逻辑传承下来了。

你正在书写的代码可能在五年后被重构、被重写,但你对“为什么这个接口要这么设计“、“为什么这面‘承重墙’不能移动”、“为什么这条追溯链条必须完整“的理解,这个结构性的思维方式,会穿越你的代码、穿越你的项目、穿越你的职业生命,依然活下去。

这就是我从加米施的雪到马赛公寓的屋顶带你来这里的最终原因。这不是一本关于“怎么写代码“的书,这是关于“如何建造一座不会倒塌的城市“的书。

而这座城市:从这本书第一行读到这里的你,已经开始了你的建设。


本篇小结

  • 城市设计是理解软件架构的最佳类比,因为两者面对的是完全相同的一组约束:有限资源、不可预见的未来需求、多样化的利益攸关者、不可忽视的合规标准、以及部分不可逆的架构决策。
  • 资源约束下的优先级管理是架构师的第一道关口。你不能满足所有需求,但你必须为被推迟的需求预埋基础设施(通信ID段、CPU裕度、ROM空间),否则未来的扩展将面临“在城市建满之后再修地铁“式的代价。
  • **接口设计(道路系统)**决定了系统的可扩展性和可验证性。你的模块之间的通信拓扑(直接连接 vs 总线 vs 分层路由)的选择,将直接决定五年后一个陌生工程师能否理解这个系统的数据流。
  • “丑“不是重构的理由,重构的唯一正当理由是“可预见的未来有明确的修改需求,而当前结构会使该修改困难、不安全或不可能”。已经经过充分测试的代码,在实时性和安全性约束下可能无法承受重构,这就是现实世界的冻融循环,Villa Savoye漏水的教训。
  • 最终判断标准来自“居民“:驾驶员(安全性)、维修技师(可诊断性)、安全评估员(可追溯性)、接班工程师(可维护性)。你的架构不是被架构评审的分数判定的,是被这些“居民“在五年、十年后的实际使用体验判定的。
  • 你的代码比你的职业生涯活得更久,这一认知不应是负担,而应是力量的来源。因为你对接口设计、模块划分、安全论证和追溯链路的每一次严谨判断,都在为一座比你活得更长久的城市贡献一块经得起时间考验的基石。

【下集预告】:我们从沙砾走到了地基:汉谟拉比法典的威慑力、1968年的软件危机、三种方法论的取舍、柯布西耶“形式追随功能“、独立审查的第二双眼睛,最后用一座十万人的城市把这一切织成一条主线。现在,地基已经打好,是时候开始砌墙了。下一章我们进入建造法则:从一块砖(模块)到门窗(接口),从梁的使命(单一职责)到市政供水(依赖注入)。真正的施工,从现在开始。