现代软件工程课程 个人博客作业2-结对编程项目总结
1. 作业地址
作业地址
2. PSP表格
| PSP 阶段 | 内容说明 | 预估时间(min) | 实际时间(min) |
|---|---|---|---|
| Planning(计划) | 估计这个任务需要多少时间 | 60 | 60 |
| Development(开发) | |||
| - Analysis(需求分析) | 了解并分析需求 | 30 | 20 |
| - Design Spec(生成设计文档) | 设计说明书 | 30 | 10 |
| - Design Review(设计复审) | 检查设计文档 | 10 | 0 |
| - Coding Standard(代码规范) | 代码规范说明 | 20 | 10 |
| - Design(具体设计) | 编写具体设计方案 | 120 | 60 |
| - Coding(具体编码) | 实现程序功能 | 240 | 360 |
| - Code Review(代码复审) | 检查、修改代码 | 120 | 180 |
| - Test(测试) | 测试并修复缺陷 | 120 | 180 |
| Postmortem(事后总结) | 总结经验、改进过程 | 30 | 60 |
| 合计 | 总计预估:780 | 总计实际:940 |
3. 如何利用这些方法对接口进行设计的
在本项目的结对编程实践中,我们以教科书中关于 Information Hiding、Interface Design 与 Loose Coupling 的原则为指导来设计接口与协作流程。首先,两名开发者共同阅读并讨论所依赖库的文档与源码,划定“公开 API”与“内部实现”边界,确保团队不依赖非约定的内部实现(信息隐藏)。
前端仅作为事件/消息的消费者,监听库端口并按契约解析接收到的 API 数据;后端则通过实现库预留的控制器抽象来完成业务逻辑,不直接改动库函数。若业务需求与库接口语义不完全一致,我们在后端继承官方控制器重写函数,将外部契约与库 API 之间的差异隔离开来,从而把实现细节封装在适配层(信息隐藏 + 低耦合)。
在接口设计上,我们坚持“最小暴露”“语义明确”的原则:每个接口都在文档中定义输入、输出、错误码和边界行为;结对时一人负责编写接口描述,另一人编写对应单元/契约测试,这些测试被纳入 CI 以保证接口变更可回滚并受控(接口设计)。结对编程使得一个人专注于实现、另一个人专注于接口契约和回归保证,从而在保障质量的同时最大限度降低模块间耦合。总体结果是前后端各自只关注自己契约内的职责,系统演化时能以最小代价进行替换或升级。
4. 描述重要模块接口的设计与实现过程
重要模块接口的设计与实现过程
在接口设计上,我们遵循“最小暴露、职责单一、契约明确”的原则。前端与后端的交互采用事件/消息驱动方式:前端通过监听后端暴露的端口接收消息,并在本地建立一份与后端数据模型一致的镜像(data model replica)。该做法保证了系统中过程结构的唯一性(single source of truth):前端的可视化与调试视图直接绑定到这份模型,从而提供了高效、低延迟的可视化接口,便于实时调试与状态展示,同时避免了对后端内部数据结构的直接依赖。
后端方面,我们对控制器基类(controller base class)的职责边界进行了逐项梳理,明确了各个回调与函数的触发时机。在此基础上实现的扩展遵循“在规定挂钩上实现业务逻辑,而不侵入库实现”的策略:所有业务行为均以重写/实现控制器规定接口的方式完成,必要时通过包装器/适配器进行输入输出格式转换,避免直接修改第三方库。针对能耗优化,我们在后端实现了一种“无任务则停机”(stop-when-idle)的策略:当检测到任务队列为空且满足安全条件时,系统进入低功耗或休眠状态,收到新任务再唤醒,从而降低空闲时的能耗消耗。
算法关键点与独到之处(不列源码)
本项目采用的算法以工程实践为导向,着重于系统可观测性与能耗控制而非单一指标的极致优化。核心要点包括:
- 基于前端镜像的实时诊断:利用前端同步的模型镜像做为轻量级观测点,使得后端在调试和性能诊断时能快速定位状态异常,减少调试回合时间。
- 控制器生命周期理解驱动的策略注入:通过对控制器基类中各函数调用时机的精确把握,我们在合适的生命周期点注入节能策略与度量采集,保证策略的正确性与低侵入性。
独到之处在于我们将“可视化镜像 + 控制器生命周期”的结合用于工程化问题(如调试流程和能耗优化)上,而不是把精力集中在构建一个复杂的、对所有场景都最优的算法模型上。
性能比较及原因分析
与那些从输入端就大量利用先验知识或针对特定任务做深度优化的算法相比,我们的方法在综合分析能力上表现相对逊色。主要原因包括:
- 算法模板化偏重工程适配:出于开发周期与可维护性的考虑,我们的算法设计更倾向于模板化和通用型实现,降低了对特定场景的深度适配能力。
- 输入端先验信息利用不足:当前实现主要依赖前端镜像提供的运行时观测信息,而在输入数据预处理与先验知识验证方面投入不足,导致在某些需要强先验的场景下效果不如专门优化的算法。
- 目标偏向系统级优化:我们优先解决的是调试效率和能耗问题,这在短期内能显著提高工程生产力,但会牺牲部分针对任务级别性能的最优性。
综上,目前算法在工程适应性、调试效率和能耗控制上具有明显优势,但在需要深入利用输入先验或做场景专门优化的基准测试中可能落后。我们认为这是可接受的权衡,并已规划后续改进。
后续改进方向(简要)
- 在输入端引入先验验证与特征工程以提高算法对数据的利用率;
- 将某些模板化模块替换为可配置的策略参数,使其在保持通用性的同时具备更好的场景适配能力;
5. 当需求发生变化时,如何重构代码
需求变化时的重构策略
当需求发生变化时,我们优先采用小步、可回退、低风险的重构策略:
- 在独立 feature 分支上实现变更,保持主干稳定;
- 将功能性改动局限在适配层或子类中,避免直接修改第三方库或公共基类,必要时通过新增继承的控制器类来扩展行为;
- 使用多继承/基类组合将控制器职责进行拆分——把通用行为放到基类中,把新需求对应的行为放到新的子类中,降低修改面;
- 通过简单的命令行参数(argv)暴露算法级开关与调优项,可以在运行时切换算法策略与参数,减少频繁的代码改动和部署成本;
- 任何结构性重构都遵循“先编写/更新测试 → 小规模重构 → 运行测试 → 提交 PR → 合并”的流程,确保每一步都有可验证的结果并可回滚.
这些做法保证我们能在需求演进时快速响应,同时将变更影响控制在最小范围内。
Pull Request / Merge 的流程与实践(在 Gitee/GitHub 上)
- 开发流程:前后端各自使用 feature branch 开发(例如
feature/frontend-x/feature/backend-y),完成后发起个人 pull request。 - 代码评审:每个 PR 都包含变更说明、测试截图或基准结果,并由另一位或多位结对成员/代码所有者进行审阅。
- 冲突处理:日常合并中遇到的冲突很少;少量冲突主要由前后端并行修改相邻文件或文档引起。我们通常使用 VSCode 的合并工具选择保留“前者”或“后者”并手动验证合并结果后提交。
PR / Merge 次数统计
在本项目迄今为止,个人提交的 pull request 约 4–5 次(小功能/bug 修复型 PR),以及 2 次较大规模的合并:
- 前后端分支合并入主分支(一次较大合并,包含前端视图与后端接口对齐);
- 后端算法调整的大规模合并(一次较大合并,包含算法参数调整与能耗策略注入)。
总体上合并问题较少,主要为少量冲突由工具和人工评审快速解决。
6. 程序的代码规范,设计规范。 你们两如何达成共识,采用了什么规范
我们在编程过程中遵循统一的代码与设计规范,并在结对时达成共识。主要规范包括:类名使用驼峰命名(CamelCase),函数与变量使用小写加下划线连接(snake_case),类内工具函数以_作为前缀区分内部方法。同时结合 AI 生成的注释与手动补充的文档,保证代码语义清晰。由于整体代码量较小,双方均能自觉遵守规范,无需额外的格式化工具。
异常处理方面,为了便于实验验证和源码调试,程序在 debug 阶段直接抛出异常,并通过日志(log)记录信息以定位问题。我们未设计独立的异常层次结构,而是使用 Python 内置的 ValueError、IndexError 等常见异常类型进行区分与处理。
由于项目体量有限,我们未启用 giteeScan 等自动化质量检测工具,而是通过代码审阅与调试日志来保证代码质量与可维护性。
7. **界面模块的详细设计过程。**你的程序有用户界面么?

概览
本项目有用户界面(基于 Vue 构建),界面通过监听后端 API 的端点来维持与后端一致的数据镜像(model replica),并将状态可视化与调试操作暴露给用户。前端是独立的视图层(V),控制器(C)被实现为后端的控制器类(我们修改/重写的部分),模型(M)来源于后端的数据定义;前端在运行时复制后端模型用于显示,从而实现了清晰的职责分离与 MVC 思想。
我们监听/对接的后端 API
POST ${this.baseUrl}/api/client/register— 客户端注册(GUI 作为一个 client 注册到模拟服务器)GET ${this.baseUrl}/api/state— 获取当前仿真状态(全局/系统状态快照)POST ${this.baseUrl}/api/step— 单步推进仿真(或提交控制指令)GET ${this.baseUrl}/api/traffic/info— 获取流量/任务信息用于可视化
界面模块的详细设计过程
1. 需求与约定确认
- 与后端结对阅读后端数据定义(types / schema / API 文档),把“哪些字段是公开契约、哪些是内部细节”先划分清楚。
- 确定前端只读取公开契约字段,不依赖任何未导出的内部状态或实现细节。
2. 前端模型镜像(Model Replica)
- 在前端建立一套与后端数据定义一致的数据模型(TypeScript 接口/类型),启动时通过
register获取 clientId,并周期性或事件驱动地拉取state与traffic/info,把返回数据写入本地模型镜像。 - 所有 UI 组件都从这套镜像读取数据——保证整个界面以“单一事实来源(single source of truth)”为根基。
3. View(视图)设计
- 使用 Vue 的组件化进行页面拆分:
- 全局状态面板(显示 currentTick、clientId、系统总体状态)
- 电梯/资源可视化组件(按后端模型渲染每个单元)
- 任务/流量信息面板(来自
/api/traffic/info)
- 组件通过 props + Vuex(或 composition API 的集中 store)读取镜像,渲染时尽量做轻量化计算,复杂聚合在 controller/后端预处理后返回。
4. Controller(控制器)对接与职责
- 控制器的实现位于后端(我们重写/扩展的部分),其主要职责:接收来自前端的控制命令(例如单步/参数切换)、在合适时机触发仿真推进、收集能耗/度量并把状态写回
/api/state。 - 前端通过
POST /api/step触发控制动作;控制器在内部处理具体逻辑后,更新后端状态,随后前端拉取state得到最新模型镜像并渲染。 - 我们在控制器层只实现业务逻辑(hook/override),不修改库的内部实现,保证低耦合和可回退性。
5. 数据一致性与同步策略
- 采用“事件驱动 + 拉取回填”的策略:控制命令先发给后端(确保命令被中央接受),后端更新状态;前端通过短轮询或 WebSocket(若支持)获取最新状态并更新镜像。
- 在并发或网络抖动情况下,前端对比
currentTick来决定是否丢弃过期信息或请求重试。
6. 调试与可视化
- 因为前端保存了后端模型镜像,调试时可以直接在 UI 上看到某个 tick 的全量状态,有利于快速定位问题。
- 在 debug 模式下,前端会显示原始 API 返回(raw JSON)与处理后模型(parsed)以便验证解析/映射是否正确。
异常与错误处理(界面层)
- 前端对网络/请求错误有简单的展示(提示条或弹窗),并在控制台/日志里记录详细信息以便回溯。
- 对于解析失败或契约不符的情况,前端会在 UI 明显位置显示“数据格式错误”并记录原始 payload,这有助于开发时快速发现后端契约漂移。
- 运行时异常在开发/Debug 模式下会被抛出以便定位;生产模式则以用户友好的错误提示替代详细堆栈信息。
性能与可扩展性考虑
- 渲染优化:采用按需渲染、虚拟列表等减少 DOM 开销;只在模型发生实际变更时触发重绘
- 可配置参数:通过命令行 argv 在后端暴露算法级开关,前端控制面板提供切换入口,便于实验或对不同策略。
- 适配层:当后端接口变更或需要切换底层实现时,前端只需更新一处数据适配器,而不是全站点修改。
MVC 映射说明
- Model(模型):后端是权威模型定义与状态存储;前端维护一份只读镜像用于显示。
- View(视图):Vue 组件负责所有呈现逻辑(可视化、电梯面板、流量表格等)。
- Controller(控制器):后端控制器是真正处理业务/调度的地方;前端仅作为命令发起者/事件消费者而非业务执行者。
8. 描述结对的过程, 提供非摆拍的两人在讨论的结对照片

9. 采用了哪种合作方式
在项目开发的关键模块中,我们采用了“驾驶员 / 领航员”模式的结对编程。我担任领航员的角色,主要负责查阅源码、分析问题、提供实现建议,同时监督驾驶员的操作是否符合设计契约。在结对过程中,我的观察和反馈能够及时发现潜在问题,驾驶员则即时进行修改和实现。
这种模式的体验是非常高效的:它不仅减少了错误发生率,还促进了知识共享和技能互补,使我们在短时间内完成了复杂模块的设计与调试。不过,它也要求双方保持持续沟通与同步,如果思路停滞或沟通不顺,可能会暂时影响效率。总体而言,作为领航员的体验让我更深入地理解了系统设计和实现逻辑,也提高了代码审查和问题分析能力。
伙伴的优点与缺点
优点
- 技术能力强,能够快速实现后端逻辑和接口。
- 责任心强,注重接口契约和数据一致性。
- 沟通顺畅,能够及时讨论需求变化和接口设计。
缺点
- 目前还处于项目初期,没有明显缺点,合作过程中整体平稳。
改进方式
由于目前没有明显缺点,因此我们更多是保持良好沟通与协作,并在日常开发中相互支持,共同提升开发效率和代码质量。
10. 在你实现完程序之后,在PSP表格记录下你在开发各个步骤上实际花费的时间。并说明差异的原因。
- Coding(具体编码)
实际时间比预估多 120 分钟,主要原因是遇到接口对接问题和部分逻辑调试复杂,尤其是前后端交互和模型镜像同步部分需要多次测试和修改。 - Code Review(代码复审)
实际花费 180 分钟,比预估多 60 分钟,因为在复审过程中发现一些潜在 bug 和不符合规范的代码,需要额外修改。 - Test(测试)
实际用时比预估多 60 分钟,主要是为了验证前后端数据同步、接口契约以及能耗调优策略,确保系统运行稳定。 - Postmortem(事后总结)
实际用时比预估多 30 分钟,是因为在总结阶段结合开发过程记录了详细经验和改进措施,为下一轮迭代做准备。
总体来看,编码与测试阶段的不确定性和调试复杂性导致总实际时间(940 分钟)比总预估时间(780 分钟)增加了约 20%。
11. 其它收获
在这个项目中,我对前后端分离的开发模式有了更深入的理解。通过独立实现前端界面和后端控制器,我认识到如何让前端既美观又高效地展示数据,以及如何通过接口契约保证数据一致性。同时,我学会了利用 AI 工具辅助需求分析和文档生成,例如用 AI 快速整理接口设计或生成初步方案,这显著提高了效率,但前提是我必须理解技术背景,否则 AI 输出难以直接落地。
在技术攻克方面,我通过阅读源码、分析数据模型和接口逻辑,解决了前后端同步和模型镜像维护的难点,掌握了如何在调试阶段快速定位问题并优化流程。阅读相关资料和博客让我了解了 MVC 模式的实际应用,以及结对编程中的驾驶员/领航员模式如何提升代码质量和协作效率。整个过程不仅提高了我的开发能力,也让我对系统设计、算法调优和团队协作有了更全面的认识。
