《现代软件工程》课程的结对编程实践
ZGCA《现代软件工程》课程 个人博客作业 #2
1. 项目概况
-
项目名称:电梯调度系统与可视化界面
1.1 团队成员:
-
zy:负责算法分析设计、核心调度算法设计与实现、后期性能优化、调试;
-
myg:负责整体框架搭建、通信模块实现、前端可视化界面实现、算法分析设计;
开发模式:结对编程 (Pair Programming)
1.2 项目目标:
构建一个支持图形化与无头模式的电梯调度系统。 系统应支持多电梯、多楼层的调度需求,提供高效的乘客等待与运行时间优化算法,并通过前端界面实现实时状态展示与交互。
2. PSP 表格
| PSP 阶段 | 预估时间(分钟) | 实际记录(分钟) |
|---|---|---|
| 计划:明确需求和其他因素,估计以下的各个任务需要多少时间 | 45 | 45 |
| 开发(包括下面 8 项子任务) | 460 | 940 |
| ・ 需求分析(包括学习新技术、新工具的时间) | 30 | 30 |
| ・ 生成设计文档(整体框架的设计、各模块的接口、用时序图、快速原型等方法) | 30 | 30 |
| ・ 设计复审(和同事审核设计文档,或者自己复审) | 30 | 30 |
| ・ 代码规范(为目前的开发制定或选择合适的规范) | 20 | 20 |
| ・ 具体设计(用伪代码、流程图等方法来设计具体模块) | 20 | 20 |
| ・ 具体编码 | 240 | 540 |
| ・ 代码复审 | 60 | 60 |
| ・ 测试(自测测试、修改代码、提交修改) | 60 | 240 |
| 报告 | 130 | 130 |
| ・ 测试报告(发现了多少 bug,修复了多少) | 60 | 60 |
| ・ 计算工作量(多少行代码、多少次提交、多少测试用例、其他工作量) | 10 | 10 |
| ・ 事后总结,并提出改进计划(包括写文档、博客的时间) | 60 | 60 |
| 总共花费的时间(分钟) | 635 | 1115 |
(由于是结对编程,所以此表为两人合作的总情况,与小组成员相同)
3. 项目实现简述
3.1 项目结构
系统分为前端和后端两大部分,项目结构和相应功能介绍如下:
elevator_scheduler/
├── backend/
│ ├── comm/ # 通信模块
│ │ ├── __init__.py
│ │ └── websocket_broadcastor.py # WebSocket 广播器
│ │
│ ├── controller/ # 控制器模块
│ │ ├── __init__.py
│ │ ├── controller_with_comm.py # 继承自仿真环境提供的基础控制器类,并组合了通信模块子类,以实现方便地与前端交互
│ │ └── *.py # 各种调度算法实现,继承自 controller_with_comm.py,并实现必要的事件函数
│ │
│ ├── scene/ # 场景模块
│ │ ├── __init__.py
│ │ └── scene_manager.py # 场景管理器,负责维护当前场景状态,与后端 type 统一接口方便快捷地使用 json 交换场景数据
│ │
│ └── start.py # 后端服务器逻辑,负责初始化控制器和通信模块,并根据传入参数控制算法后端的运行行为
│
├── frontend/
│ ├── public/ # 静态资源\
│ ├── src/ # 前端源代码,使用 React 框架编写,Vite 进行构建,shadcn+tailwindcss 进行样式设计
│ │ ├── assets/ # 前端静态资源
│ │ ├── body/ # 页面主体部分
│ │ ├── components/ # 各种组件
│ │ ├── css/ # 样式文件
│ │ ├── lib/ # 样式工具函数
│ │ ├── App.tsx # 主应用组件,包含整体布局和各个子组件的组合
│ │ ├── main.tsx # 入口文件,渲染主应用组件
│ │ └── contexts_and_type.ts # 关键的上下文和类型定义文件
│ └── ... # 其他前端配置文件
│
├── start.sh # 启动脚本,包括环境配置、依赖安装和前后端启动命令
├── start_no_gui.sh # 无头模式启动脚本,仅启动后端算法服务,以供其他组交叉接入
└── README.md
3.2 前端
前端主要由我通过 React 框架实现,负责电梯调度系统的可视化界面。 实现了以下功能:
- 整体响应式布局:使用
react-grid-layout实现:- 设计了 可视化现实、数据实时统计、运行结果统计信息和日志显示 共三个板块;
- 每个板块可以自由拖拽和调整大小,在有部分信息被遮挡的特殊情况下可以自行调整布局以获得更好的视觉体验;
- 两种通信方式:
- HTTP 轮询:一开始按邹老师的要求以为这里 MVC 结构中的 Controller 是指仿真环境后端而不是算法后端,因此采用 HTTP 轮询的方式定时直接从仿真环境后端获取电梯状态数据,故实现了这一方法;
- WebSocket:构建一个建立连接后可以随时由客户端或服务器发送信息的双向通信通道:
- 前端通过 WebSocket 向后端发送信号通知服务端运行等,并实时接收后端算法服务发送的场景状态更新消息;
- 通过回调函数更新电梯状态显示和统计信息的 State;
- 电梯状态可视化:通过
react-spring和原生 html5 元素实现:- 能实现包括楼层(上下行乘客队列、队列方向标识)、电梯轿厢(编号、位置、内部乘客)、乘客(位置、编号)等信息的实时展示;
- 实现了淡入淡出动画和平滑的移动动画效果;
- 响应式地根据服务端返回的当前场景状态信息和前端窗口大小调整布局;
- 场景状态管理:
contexts_and_type.ts中定义场景状态的 TypeScript 类型,确保前后端数据格式一致- 通过 WebSocket 接收的 JSON 数据按定义的类型进行解析并更新
contexts_and_type.ts中定义的场景状态 Context; - 电梯状态可视化组件和统计信息组件订阅该 Context,实现数据驱动的 UI 更新;
- 其他统计信息与日志显示组件也同理,通过 Context 或 State 等 Hook 进行数据管理和更新;
前端开发过程中也遇到了许多挑战。由于之前使用过但不够熟悉 React 框架,初期在组件设计和状态管理上花费了较多时间。 另外,WebSocket 的双向通信机制也需要一定的学习成本。 通过查阅文档和参考示例代码,逐步掌握了这些技术,并成功实现了预期功能。后来也发现其实很多地方可以引入 AI 辅助编程工具来提升开发效率,完全手工得搓出前端虽然可以达到个人预期的前端视觉效果,但耗时较多,部分不影响前端视觉的代码结构部分其实可以多多借助 AI 工具来提升效率。
技术优势:
- 功能解耦:将通信功能独立封装,后续业务类专注于算法实现
- 性能优越:WebSocket 相比传统 HTTP 轮询具有更低的延迟和更高的实时性、稳定性,并可实现双向通信
- 易于扩展:模块化设计便于后续功能扩展和维护
3.3 后端
后端的通信模块、控制器基类、场景管理器等也由我实现,主要需要考虑与前端的接口商定和通信细节,使用 python 编写,实现了以下功能:
- 通信模块:
- 广播器基类:
- 使用
websockets库实现 WebSocket 服务器,负责与前端建立连接并进行双向通信; - 实现连接管理功能,维护所有活跃的客户端连接和断开;
- 实现消息处理的回调函数注册功能,以便在接收到前端消息时触发相应的处理逻辑;
- 提供广播功能,将指定消息类型和消息内容发送给所有连接的客户端;
- 使用
- 广播器子类:
- 继承自广播器基类,专门用于电梯调度系统;
- 能够将场景状态更新消息发送给所有连接的前端客户端;
- 能够将一些调试信息和错误消息发送给前端客户端;
- 能够阻塞后端,并等待前端客户端的确认消息;
- 广播器基类:
- 场景管理器:
- 负责维护当前场景状态,包括电梯位置、乘客队列等信息;
- 提供接口以便控制器方便快捷地与前端交换场景数据,使用 JSON 格式进行数据交换;
- 控制器基类:
- 继承自仿真环境提供的基础控制器类,并组合了通信模块子类,以实现方便地与前端交互;
- 作为中间层,封装了与通信模块的交互逻辑,在创建控制器实例时初始化通信模块和场景管理器;
- 在必要的事件函数中调用场景管理器的方法更新场景状态,并通过通信模块将更新后的状态发送给前端;
后端的算法实现主要由组内另一位成员 zy 负责,但在算法设计阶段以及调试阶段我也积极参与。在设计调度算法时,我提供了一些思路和建议,例如通过初版前端测试发现同伴已完成的初版算法存在一些缺陷时,我对其打算在基础公交车调度算法上进行改进的思路给予了肯定并提出了诸如空载跳过、无目标折返等优化的详细实现方法。在调试阶段,面对无法上下乘客等问题,我通过分析模拟器的整个数据模型和类方法定义,以及仿真循环的结构,理解了需要在电梯停止时同时考虑轿厢内乘客和即将进入的乘客统筹安排目的地,因为事件循环在执行了电梯到达停止的回调后才会安排乘客进入,而乘客又是根据电梯调度后产生的方向来决定是否进入电梯的,所以需要提前安排好电梯方向否则会出现无法上下乘客的问题。 通过这些分析和调试,我们合作最终实现了一个较为完善的电梯调度系统。
算法以经典的扫描算法(SCAN) 为基石,集成多种优化策略:
- 满载跳过:电梯满载时跳过沿途请求
- 空站跳过:无乘客上下时跳过停靠
- 智能转向:当前方向无任务时自动转向(双向无任务则原地待命)
- 意图导向:基于距离和乘客目标楼层智能决策运行方向
- 负载均衡:优先调度能耗更低的主力机
- 。。。
zy 同学也对算法进行了多轮的全面调试和优化,最终实现了较为理想的调度效果。
详情可见组员博客
4. 结对编程实践总结
4.1 软件工程思想应用
-
高内聚低耦合:在设计系统时,我们注重模块的高内聚和低耦合,确保各个组件职责明确,便于维护和扩展。
-
前后端分离:采用前后端分离的架构设计,前端负责用户交互和可视化展示,后端处理核心调度逻辑和数据管理,提高了系统的灵活性和可维护性。
也依据这些原则,使得我们组开发的前端界面和后端算法服务可以较为方便地集成到其他组的后端服务或前端界面中。
4.2 项目管理
未线下见面一同编程时,全程使用 Git 进行版本控制,确保代码的有序管理和协同开发。通过本地分支管理和合并请求,共同维护一个远端主分支,保证代码质量和团队协作的高效性。
线下采用结对编程模式,实时交流和协作,提升了代码质量和开发效率。
4.3 导向变化与需求变化
在我们各自完成初版通信前端和后端算法进行整合并调试后,发现了许多问题,我们的导向也从一开始的效率优先,变成了稳定性优先,然后再考虑在稳定的基础上提升效率。为此我们完全放弃了之前的算法实现,但是保留了项目结构和后端基类及接口设计,没有影响到前端和通信模块。为了保证改动能满足需求,我们编写了大量的测试用例,尽可能覆盖了各种边界情况和异常情况,确保每次修改后都能通过所有测试用例,从而保证代码的正确性和稳定性。而后来加入新的需求后,我们也得益于之前良好的模块化设计,能够较为方便地进行功能扩展和修改。
4.4 代码与设计规范
我们之间没有明确的代码规范,但是在编码过程中遵循了一些基本的编码规范,比如 PEP8 和 TypeScript 的基本命名规范、注释规范、代码结构规范等。
5. 结对编程体验

结对编程的体验非常好。通过实时交流和协作,我们能够更快地发现和解决问题,提高代码质量和开发效率。在编码过程中,我们可以互相学习和借鉴对方的经验和技巧,提升自己的编程能力。同时,结对编程也促进了团队合作和沟通,增强了团队凝聚力和协作能力。