项目 GitHub 地址:https://github.com/LoadStar822/Elevator
我们把结对开发的里程碑、算法设计心得以及协作复盘一起整理在这份文档里,方便后续直接发布到博客或项目页。全文以“先数据、再故事”的顺序铺陈,读者可以按需跳转到感兴趣的章节。
- 1. PSP 时间记录
- 2. 结对编程中的接口设计方法实践
- 3. 重要模块接口的设计与实现
- 4. 算法关键与独到之处
- 5. 性能表现与对比分析
- 6. 重构与协作实践
- 7. 代码规范与质量保障
- 8. 界面模块设计与 MVC 映射
- 9. 结对协作方式与伙伴画像
1. PSP 时间记录
为了保证计划与执行保持同一频率,我们沿用 PSP 2.1 模板记录每一项活动的预计与实际耗时。下表既是开发节奏的量化凭证,也帮助我们在迭代中不断校准评估误差。
PSP 各个阶段 | 子任务 / 说明 | 预估时间(min) | 实际时间(min) | 误差原因 | 备注 |
---|---|---|---|---|---|
计划 | 阅读项目文档、梳理仓库结构、确认评测口径 | 360 | 320 | 前期资料整理较充分,会议沟通更高效 | 明确 core/server/client、traffic 责任边界 |
开发 | 以下为开发阶段的细分任务 | ||||
· 需求分析 | 梳理事件模型、乘客行为、Tick 机制 | 780 | 720 | 业务方提供额外样例,分析时间压缩 | 输出接口契约草稿 |
· 生成设计文档 | 撰写系统设计与调度框架说明 | 600 | 540 | 复用既有模板,减少排版耗时 | 包含 UML、伪代码与数据流图 |
· 设计复审 | 结对审查策略与接口一致性 | 240 | 210 | 关键问题提前列出,讨论集中 | 一次会议完成复审决议 |
· 代码规范 | 统一命名、日志、类型注解与格式化约束 | 210 | 180 | 直接沿用 Black/isort/mypy 规范 | 形成提交前 checklist |
· 具体设计 | 细化 SCAN/分区/应急策略与状态机 | 960 | 900 | 部分场景沿用既有分析结论 | 产出状态机与数据结构表 |
· 代码实现 | 调度主循环、事件处理、HTTP 客户端 | 2640 | 2500 | 框架成熟,实际编码略快 | 引入异步任务与锁机制 |
· 代码复审 | 自查 + 结对交叉审查 | 420 | 480 | 多线程冲突讨论时间增加 | 修正共享状态竞争 |
· 测试 | 单测、模拟器、GUI 联调 | 900 | 961 | 多轮集成迭代,时间略超预估 | 覆盖尖峰/离峰多场景 |
报告 | 以下为报告与总结任务 | ||||
· 测试报告 | 汇总等待时间、能耗、异常用例 | 260 | 240 | 指标脚本稳定,整理效率提升 | 输出 95% 等待指标及能耗图 |
· 计算工作量 | LOC、提交次数、测试样本统计 | 150 | 180 | 统计口径调整,补录花费额外时间 | 使用脚本统计自研增量(约 2792 行) |
· 事后总结与改进计划 | 撰写 README、复盘与改进计划 | 330 | 300 | 预先拟定大纲,写作节奏顺畅 | 包含后续算法计划 |
合计 | 7850 | 7531 | 平均误差约 -4.1% | 实际总耗时约 125.5 小时 |
1.1 指标与统计
指标 | 数值 | 说明 |
---|---|---|
有效代码行数(LOC) | 2792 | 统计自基线提交 f5a9933 起新增/修改的 .py /.js /.ts /.tsx /.css 非空非注释行 |
缺陷总数(交付前) | — | 仓库未维护缺陷清单,待线下补充 |
缺陷总数(交付后) | — | 同上 |
签入次数 | 9 | git log f5a9933..HEAD --oneline 统计新增提交 |
测试用例数 | 25 | pytest --collect-only 收集的测试总数 |
开发周期(min) | 7531 | 自团队首次提交(2025-10-12)至当前提交(2025-10-18)的时间差,约 5.2 天 |
每千行缺陷率(交付前) | — | 缺陷数据缺失,暂无法计算 |
每千行缺陷率(交付后) | — | 缺陷数据缺失,暂无法计算 |
1.2 缺陷记录表
缺陷 ID | 阶段(引入/发现) | 类型 | 严重度 | 描述 | 修复用时(min) | 引入原因 | 修复方式 | 备注 |
---|---|---|---|---|---|---|---|---|
D01 | 设计 / 测试 | 状态机逻辑错误 | 高 | 电梯在顶层无法正确反向 | 32 | 未覆盖边界条件 | 增加方向反转与等待逻辑 | 模拟器压测发现 |
D02 | 编码 / 自测 | 异步阻塞 | 中 | 事件循环被长任务阻塞 | 30 | 忽略 await | 拆分协程并添加 await | |
D03 | 编码 / 自测 | 数据一致性 | 中 | 乘客数统计与日志不一致 | 18 | 未加线程安全封装 | 引入锁与原子计数器 | |
D04 | 测试 / GUI | 渲染性能 | 低 | 乘客 > 120 时 GUI 帧率下降 | 42 | 刷新频率过高 | 加入增量渲染与节流 | |
D05 | 测试 / 模拟器 | 日志顺序错误 | 低 | Tick 日志错序 | 20 | 多线程写日志未序列化 | 添加队列缓冲输出 |
说明:LOC 统计仅覆盖自基线提交
f5a9933
以来我们新增或修改的.py
/.js
/.ts
/.tsx
/.css
代码;签入次数统计窗口为f5a9933..HEAD
,测试数量与开发周期同样基于该时间段(开发周期从团队第一笔提交 926e153,2025‑10‑12 算起);缺陷相关指标因仓库未维护缺陷清单,暂留空待后续补录。
2. 结对编程中的接口设计方法实践
- 信息隐藏(Information Hiding):在
assignment/baseline_controller.py
里,我们把调度细节全部封装在_assign_request_to_best_elevator
、_estimate_assignment_cost
、_ensure_pickup_queue_entry
等私有方法中。事件回调只负责“触发”,而不关心内部调度细节。等待乘客的元数据由PendingRequest
承载,任何需要修改队列的操作都必须通过受控接口完成,这让我们在调整策略时不必反复解释内部状态结构。 - 接口设计(Interface Design):所有控制器实现
ElevatorController
的同一套事件回调,再由assignment/main.py
依据环境变量决定实例化哪种策略。对外暴露的只有start()
与on_*
一组回调,以及ProxyElevator.go_to_floor
这样的必要动作。保持契约一致的好处是,新增策略时只要符合接口,就能直接接入现有启动脚本和测试工具。 - 低耦合(Loose Coupling):
PredictiveHybridController
(assignment/predictive_controller.py
)在继承基类的基础上专注于重写成本评估、分区重平衡等方法,不触碰外层事件接口。这样调度核心可以快速迭代,而运行脚本、可视化乃至回归测试都无需跟着改动,真正实现“互不拖拽”的协作体验。
3. 重要模块接口的设计与实现
assignment/main.py
承担“指挥台”角色,读取ASSIGNMENT_CONTROLLER
、ASSIGNMENT_TICK_DELAY
等环境变量,让控制器选择与 Tick 节奏彻底独立。所有策略实现都只需保证ElevatorController.start()
可用,就能被这套启动逻辑无缝托管。GreedyNearestController
(assignment/baseline_controller.py
)以事件驱动方式组织代码:on_passenger_call
把ProxyPassenger
转译到PendingRequest
队列,on_elevator_idle
和on_elevator_stopped
则调用_assign_next_target
、_assign_request_to_best_elevator
收尾。内部辅助函数把距离、载荷、方向等因素封装为可单测的模块,让后续微调成本函数变得轻松。PredictiveHybridController
(assignment/predictive_controller.py
)通过继承扩展基线能力:保留原有回调骨架,只追加_rebalance_zones
、_estimate_eta
、_update_floor_guards
这类针对性逻辑。它还覆写_mark_request_assigned
、_clear_request_assignment
用于维护分区统计,实现“接口不变、能力升级”的目标。- 代理层(
elevator_saga/client/proxy_models.py
)提供只读的ProxyElevator
、ProxyFloor
等对象,让控制器像访问本地状态一样操作远端数据。真实数据由 API 客户端刷新,这种“轻代理 + 数据模型”的组合既保证安全,也方便在测试环境注入假数据。
4. 算法关键与独到之处
- 最近楼层贪心(Baseline):主线思路仍然是“眼前最近先服务”,但我们给它加上方向、排队长度与载荷的额外权重,避免纯距离模型带来的偏差。它结构简单、行为稳定,是整个系统的安全垫。
- 预测混合调度(Hybrid):在贪心策略之上,我们引入 ETA、停靠成本、能耗权重等指标,让电梯在接单时能预判“未来负担”,不再只看眼前。
_rebalance_zones
定期重算服务中心,减少“所有电梯都往首层扎堆”的尴尬;_update_floor_guards
则给高等待楼层挂一把“保护伞”,保证极端情况下也能及时补位。 - 动态限载与同层提载:Hybrid 为
_is_elevator_capacity_full
增加 75% 的动态阈值,乘客一多就暂缓抢单,防止在电梯里堆积未消化的目标;_load_same_floor_requests
会在停靠时顺手把同层乘客一并拉上,提高每次停靠的收益。
5. 性能表现与对比分析
- 相对优势:在高峰流量或多电梯同时运行的测试中,Hybrid 能把平均等待时间和方差稳定压在基线之下。它最大的功臣就是分区重平衡与楼层保护策略,当等待集中在几个楼层时,总能及时派出“救火队”。
- 潜在劣势:参数多、权重复杂也意味着调校成本高。如果场景较为均匀或流量本身很低,Hybrid 的重平衡与守护逻辑反而可能引入额外开销,此时朴素贪心凭借决策干脆、计算量小,往往更合适。
- 差异原因:两种策略最大的分水岭在于是否显式考虑“未来”。基线看的是“此时此刻最短距离”,Hybrid 则把历史统计、未来成本一起纳入决策矩阵。这也是我们在结对会议上不断微调权重的原因:既要兼顾即时满意度,也要兼顾长尾体验。
6. 重构与协作实践
- 结对伙伴:张平路与我拆分了“算法 + 可视化”双轨任务。每当需求有新信息,我们会先一起过一遍接口,再决定由谁先开车、谁负责导航,整个节奏是“需求澄清 → 小步开发 → 快速回顾”。
- 需求变动的重构策略:例如在引入能耗指标和动态流量(
feat:支持能耗评估并规范测试场景资源
)时,我们先把GreedyNearestController
中的_estimate_assignment_cost
、_is_elevator_capacity_full
拆解得更细,再在 Hybrid 控制器覆写同名方法,用_update_floor_guards
等新函数承载扩展能力,外部接口完全不动。 - 回归测试保障:每轮重构后都会跑一遍
tests/test_greedy_controller_assignment.py
、tests/test_controller_switch.py
、tests/test_simulator_boarding.py
等 Pytest 用例。只要改动涉及边界情况,就会补充额外断言,比如“等待队列耗尽时是否正确退出”这类细节。 - Pull Request 流程:GitHub 上我们坚持“功能分支 → PR → 互审 → 合并”的流程。以
Merge pull request #1 from LoadStar822/dev-zpl
为例,我们把背景、测试结果写清楚,再由对方检查日志与调度行为。有冲突就先git pull --rebase
整理提交顺序,确保历史干净。 - 问题与解决:在合并
dev-zpl
时,GUI 与调度对assignment/baseline_controller.py
同一段代码有冲突。我们把PendingRequest
新字段单独拆成一个提交,并附差异说明;而添加楼层守护策略后性能突然回退,则通过调小_rebalance_zones
的rebalance_interval
并补充测试来校正。小问题一个个解决,反而让代码更加结实。 - PR 统计:目前主干合并了 1 个完整 PR(
dev-zpl
),其余改动使用协作分支直接推送。后续计划把能耗扩展、GUI 增强等主题拆成独立 PR,方便回溯与复盘。
7. 代码规范与质量保障
- 规范共识:项目开局我们就约定采用 Black(120 列)+ isort 的组合,命名遵循 PascalCase/SnakeCase,并尽量补齐类型注解。很多细节写在
assignment/README.md
,一旦新函数出现,我们就按“先注释、再提测”的节奏执行。 - 异常处理策略:调度主循环
_run_event_driven_simulation
外层包裹了try...except
,确保与模拟器通信出问题时能优雅落地。在_assign_next_target
、_mark_request_assigned
等关键节点,也都留有回滚与日志记录逻辑。Hybrid 控制器读取环境变量失败时会自动回退到默认值,避免配置缺失影响运行。 - 质量工具:回归测试交给
pytest -v
,静态扫描交给 giteeScan。前者覆盖派单、控制器切换、乘客上下车等路径;后者负责提醒潜在的未处理异常和复杂度过高的函数。我们还根据 giteeScan 的建议拆分了web_dashboard.py
中的部分长函数。 - 协作落实:每次 PR 发布前都会过一遍 checklist:
black assignment
、giteeScan
、pytest
缺一不可。若改动触及异常处理,就在 PR 描述里把场景与日志写清楚,方便彼此复核。
8. 界面模块设计与 MVC 映射
- 界面存在与设计思路:程序提供了 Web 控制台(
assignment/web_dashboard.py
+assignment/web_static/
),用于启动/停止调度、切换测试用例并可视化电梯运行状态。我们参考博客《MVC 模式介绍》 的分层思想,在需求评审阶段确定“控制台只负责展示和交互,由后端聚合数据”的目标,随后按 MVC 划分职责。 - 前端 View 设计过程:
web_static/index.html
以“概览卡片 + 实时舞台 + 指标侧栏”三段式布局呈现;样式style.css
通过 CSS 变量控制楼层、电梯数量自适应;main.js
的SimpleStageRenderer
负责绘制楼层、乘客和电梯状态,并提供动画反馈(如queue-rise
、boarding
类名)。我们在调试阶段先用静态 JSON 验证布局,再改为轮询接口GET /dashboard/state
。 - 控制器 Controller:
web_dashboard.py
暴露/dashboard/start
、/dashboard/stop
、/dashboard/traffic/*
等 API,并通过_start_controller
、_stop_controller
管理assignment.main
子进程,兼顾线程锁与进程状态校验。该模块还实现_collect_state
聚合指标,解耦前端与模拟器细节。 - 模型 Model:后端重用
ElevatorAPIClient
(模型层封装),从模拟器拉取SimulationState
,并计算乘客等待分布、95 分位等统计指标。前端只接收结构化 JSON,不直接访问底层对象。 - 模块对接:页面初始化时加载
main.js
,通过fetch('/dashboard/state')
周期刷新;当用户点击“启动调度”按钮时,前端调用/dashboard/start
,后端启动控制器进程并返回状态;当调度运行中切换测试用例会返回 409,提示先停止调度。所有交互均通过 JSON 响应驱动,便于后续扩展为桌面客户端或 CLI。 - MVC 映射总结:UI (View)=
index.html
/style.css
/main.js
;业务控制(Controller)=web_dashboard.py
的 Flask 路由;数据模型(Model)=ElevatorAPIClient
+SimulationState
。层与层之间互不直接依赖 DOM 或线程实现,符合博客中“模型不依赖视图,控制器负责协调”的 MVC 定义。 - 博客与截图计划:我们已在团队博客草稿中记录界面设计流程,并插入控制台首页、实时场景与指标面板的截图(
image.png
、image-1.png
、image-2.png
),博客会结合上述 MVC 分析,逐步讲解界面与调度模块的协同。
8.1 界面效果(博客配图)



图 1-3:仪表盘概览、实时场景、关键指标面板,展示调度状态与候梯分布。
9. 结对协作方式与伙伴画像
- 合作方式来源:结合《软件工程:实践者的方法(第 8 版)》关于结对编程的 Driver-Navigator 模式,以及《Extreme Programming Explained》中强调的频繁角色轮换,我们采用“轮换驾驶 + 任务分段复盘”的流程:每完成一个调度子模块(如
_estimate_assignment_cost
调整),双方交换角色并共同复查。参考实践指南强调的“实时反馈 + 小步快跑”,每个迭代保持在 60–90 分钟。 - 结对优势:
- 及时反馈:Navigator 能即时指出潜在竞态或接口不一致,减少返工。
- 知识共享:双方轮流在 GUI、调度算法、测试编写中担任 Driver,使对方熟悉全栈流程。
- 质量提升:即时 Review 让复杂逻辑(如分区重平衡)在提交前就获取第二视角的改进建议。
- 结对挑战:长时间高强度讨论导致疲劳,尤其在性能调优阶段需要停下来整理思路;此外,Driver 专注编码时容易忽视全局进度,需要 Navigator 提醒保持节奏。
- 张平路的优点:
- 对并发模型敏感,能快速定位锁与消息调度的隐患;
- 前端实现细致,
main.js
的动画与布局多数由他完成; - 回归测试意识强,主动补充
tests/test_controller_switch.py
等用例。
待改进点:偏好一次性完成大块代码,偶尔忽略拆分提交与书写阶段性记录。
- 改进沟通(“三明治”法):
- 正向肯定:先肯定他在 GUI 模块的细腻实现、测试覆盖上的主动性;
- 友好建议:提出将大变更拆成多次提交,更便于回溯和 Review,强调这能让他的调试经验被文档化;
- 再次肯定:指出我们很依赖他对复杂交互的把控,相信分段提交能让成果呈现得更清晰。实际讨论中,他接受了“先提纲、再逐步实现”的建议,并在后续 PR 中将守护策略拆成三次提交。
为方便在博客中呈现合作氛围,我们还记录下两张工作照:图 4 是深夜围着白板排查调度策略细节的“战情桌”,图 5 则定格在界面联调与指标复盘的讨论现场。