题目集5~7 单部电梯调度——三周血泪实录

作者:曾建东 | 南昌航空大学 软件工程大一
时间:2025‑04‑20


1. 前言|先吐个槽

还记得第一次看到“单部电梯调度”这行标题的时候,我心里是窃喜的:

“电梯不就是 while 循环 + if 判断方向?分分钟写完。”

结果真正动手才发现——真正难的不是能跑,而是跑得对、跑得快、还能持续改。

三周、三张类图、三次 PTA 红叉、无数次 IDEA 运行……电梯把我从“写得动”打到“会怀疑人生”再到“勉强看懂自己写的代码”。

下面这张表是我给自己做的小统计▼

版本 题目编号 新增要求 文件/类 数 代码行数¹ PTA 首提交得分 心情
V1 5‑5 跑通最基本的单电梯 2 320 70/100 🙂 “原来如此”
V2 6‑3 SRP 拆类 6 450 50/100 😵 “Bug 炸裂”
V3 7‑3 Passenger + 新输入格式 7 530 80/100 😌 “总算像样”

¹ 行数是 SourceMonitor 报表里的 Lines,不含空行和注释。

为了把这一路的坑、血和泪都留个纪念,也为了期末 Blog 不再被老师挑“空洞”,我决定老老实实写一篇超 3000 字的复盘

如果你正卡在 V1、V2 的重构里,不妨看看我的踩坑清单,也许能少走几步弯路——当然,也可能笑出声。


2 设计与分析|我的三版类设计到底改了什么?

有一说一, 这部分还是好设计的,那个调度算法是真阴间

2.1 V1(题目集 5):单类 —— 能跑但不好改

位置
dianti dt1.java
currentFloor
direction (enum)
两条数组队列 dianti_run() (269 行,复杂度 7,最深嵌套 6)
should_setDirection()
is_need_stop() 纯过程式写法,SRP 失守

总结:V1 的问题不是“写不出来”,而是“改一行炸一片”。第一次提交后我就意识到:再加新需求一定会崩。


2.2 V2(题目集 6):第一次拆类,把职责拉出来透气

为什么需要它 跟谁交互 设计取舍
Elevator 单纯保存电梯物理状态 Controller 只提供 getter/setter,不做逻辑
Controller 大脑:决策 + 调度 ElevatorRequestQueue 仍然比较胖,复杂度 5,但比原来好
RequestQueue 管两个链表 Controller LinkedList 而不是数组,弹性好
InternalQuest / ExternalQuest 把“请求”升级为对象 RequestQueue 方便以后加字段,如时间戳

2.3 V3(题目集 7)

角度 说明
架构设计 新增 Passenger 类:外部请求改为“源楼层 + 目的楼层”,停靠后自动将目的楼层加入内部队列,代码更贴近现实。
调度算法 流程:① 选取最近的同向请求 → ② 停靠源楼层接客 → ③ 将目的楼层加入内部队列 → ④ 继续运行。核心逻辑简化为 15 行以内。
性能指标 行数 530,最大圈复杂度 4,平均方法长度 9 行;PTA 用时缩短到 128 ms,功能更丰富反而更高效。

2.4 最终版本代码的 SourceMonitor 报表解读

下面是项目最新版在 SourceMonitor 中的统计截图:

字段 数值 解读
Lines 482 文件行数接近 500,说明 Main.java 已经承载了较多逻辑,需要考虑拆分。
Statements 216 业务语句较多,但与文件行数对比,空行与注释占比极低,代码密度较大。
Percent Branch Statements 17.6 % 分支占比接近 20 %,整体逻辑尚可,但过深的分支会影响可读性。
Method Call Statements 95 方法调用占比高,说明跨类调用频繁,可关注接口设计是否合理。
Percent Lines with Comments 0.4 % 注释极少,接近空缺,后期维护时几乎没有说明,需要大幅补充注释。
Classes and Interfaces 12 文件中包含 12 个类或接口,职责高度聚集,已超常规单文件管理范围。
Methods per Class 3.50 平均每类 3–4 个方法,单个文件内方法数量合理,但类数量过多建议分包。
Average Statements per Method 3.45 每方法平均 3–4 行语句,方法本身较短,但多方法堆叠导致文件过长。
Name of Most Complex Method Controller.recept_twoList() 最复杂的方法负责读取并解析输入,逻辑集中,建议提取子方法简化。
Maximum Complexity 20 圈复杂度已超过 10 警戒线,位于 recept_twoList(),最急需拆分或优化。
Maximum Block Depth 8 嵌套深度高达 8 层,阅读与测试压力都很大,应考虑早返回和函数拆分。
Average Block Depth 2.39 平均嵌套深度合格,但高峰深度过高,说明少数方法极度临界。

改进思路

  1. Controller.recept_twoList() 中的输入解析分解成多个私有方法,如 parseExternal()parseInternal()
  2. 对过深的 if-else 结构使用早期返回 (return),减少嵌套层级;
  3. 增加注释,目标达到 10 % 以上,让后续阅读更轻松;
  4. 考虑将 Main.java 按包或功能拆分成多个文件,降低单文件复杂度。

3. 采坑清单|从 0 分到 100 分的“死因检讨”. 采坑清单|从 0 分到 100 分的“死因检讨”

坑号 报错关键词 真相 补救 花费时间
01 非零返回 输入流错位 nextLine() 提前消费换行 15 min
02 运行超时 循环里狂 println 限制打印频率 2 h
03 数组越界 自写队列指针乱跳 LinkedList 一晚上
04 死循环 重复请求未过滤 记录 lastFloor+lastDir 30 min
05 目的楼层丢失 没把外部票拆两次 Passenger 入内部队列 40 min

血泪教训:打印永远别写在最深的循环里;队列逻辑一旦自实现,十有八九踩坑。


4 改进与展望(简版)

下面列的是我能想到、又觉得自己真有希望做到的几件事,用最直白的话写:

  1. 把“选下一层”的规则单独写成一个方法
    以后如果想换别的算法(比如先到最近楼层、或者先到人最多的楼层),只改这一个地方,不用满文件找判断语句。

  2. 换掉死循环
    现在电梯一直 while(true) 轮着看队列。

  3. 加日志文件  
    再也不用到处 println。调试时开 DEBUG,正式跑只保留关键信息,出问题再翻日志。

  4. 补测试  
    现在覆盖率大概 40 %。目标是把常见情况和边界情况都写成 JUnit 用例,让覆盖率先到 70 %。有了测试,重构时心里更踏实。

  5. 友好的错误提示  
    以前读到非法输入直接 continue,用户不知道哪错了。想改成提示“楼层超范围”或“格式错误”之类的信息,便于定位。

先把这 5 件事做好,再说更多花哨的功能。

5 写在最后|几个很朴素的想法 写在最后|几个很朴素的想法

这一段不讲术语,只说直白的感受。

5.1 这三周我真正记住的

  • 拆分真的能救命。V1 全写在一个类里,后来想加一行都怕崩;分开之后虽然文件多了,但心里没那么慌。
  • 打印≠日志。一开始用 System.out.println 满屏飞,PTA 直接超时;学会分类输出后才发现“少说话”能提速。
  • 测试不是走过场。写一两个样例远远不够,自己随便造 20 组数据才能找到边界 Bug。
  • 数字会说话。SourceMonitor 的红条、PTA 的耗时,一眼就能看出问题在哪,比“感觉代码太乱”要准确得多。

5.2 目前最头疼的

  • 注释当初写太少了,现在会头看代码有些方法得想一下才能想起来是干嘛的
  • Jacoco 只有 40 多 %,说明还有很多路径没跑到;

5.3 给课程组的一点小建议

  1. 测试点分层:能不能先跑基础用例、再跑高难度用例?而且把测试点分开来.这样定位问题更快。
  2. 示例代码分享:如果能在课堂或邮件里提供一份官方参考实现,大家就可以边看边学,明白出题的思路。
  3. 实时答疑时间:作业发布后隔几天安排一次线上答疑,针对同学的具体问题进行讲解,能大大减少走弯路的时间。

5.4 接下来的小目标

  • 深入学习java程序设计实现
  • 学习一波计算机互联网原理
  • 每月写一篇博客记录踩坑,逼自己输出。

尾声:电梯项目暂时告一段落,代码还有很多不完美,但能清楚地看到自己从“能跑”到“敢改”的变化——这就够了。

posted @ 2025-04-20 11:50  AAA卤味东哥  阅读(46)  评论(0)    收藏  举报