第一次Blog作业

目录
一.前言
二.设计与分析
三.踩坑心得
四.改进建议
五.总结

一.前言
分析5-7题目集的难度,题量与知识点:

<一>5题集中的验证码和QQ号校验
涉及:字符串的处理字符串长度的获取、字符类型的判断、字符的 ASCII 码范围判断等知识。
难度:单个功能需求,入门难度。

<二>6题集中的点线类设计和雨刷速度控制
涉及:私有属性的定义、构造方法、属性的 getter 和 setter 方法、类之间的交互、方法的调用以及根据不同操作改变雨刷速度的逻辑等知识。
难度:中等难度,主要在于合理设计各个类,遵循单一职责原则,处理好类之间的交互和逻辑关系。

<三>7题集中的佣金和蒙特卡洛求圆周率
涉及:随机数的生成、几何图形的判断、圆周率的估算以及与标准圆周率的比较等知识。
难度:难度中等,重点在于方法的逻辑与原理。
接下来将重点对三次电梯调度问题源码进行分析。

二.设计与分析

<一>第一次电梯调度:
虽然说第一次电梯调度没有很成功地通过测试点,但是给了我一个很好的教训。
当看到这样一个大工程的作业时候,心里总是没有很多信心,看着详细运行过程的说明,我的关注点都在内部外部队列的设计上,没有对出队的逻辑搞得很懂就开始写代码。由于第一次没有给出参考的类设计,所以就按照别的同学的思路和老师的提示做了类的设计。
我一开始构思这个电梯调度系统,因为电梯本身肯定得是一个独立的实体,所以创建了Elevator类。在这个类里,我把当前楼层currentFloor、运行方向direction、运行状态state,还有最大楼层maxFloor、最小楼层minFloor这些属性都写了进去。只要能实时记录这些信息,就能准确模拟电梯在不同时刻的状态,方便后续的调度和控制。
接着,我开始考虑请求管理的问题。毕竟电梯运行,靠的就是接收各种请求。请求来源不一样,有内部请求,还有外部请求,外部请求还得分向上和向下两种情况。所以,设计了RequestQueue类,专门用来管理这些请求。
为了方便处理,我用Queue接口来构建请求队列。对于内部请求,用普通的LinkedList队列,先进先出的特性(这里是我第一次的理解误区,其实不是这样子的)刚好能满足处理顺序。而对于外部请求,我错误地地决定用PriorityQueue,还分别按照楼层升序(向上请求队列upRequests)和降序(向下请求队列downRequests)进行排序。我当时的想法是,电梯按楼层顺序一个个处理请求应该会很高效吧。至少在我脑海里,电梯把每个请求都安排得明明白白。
对于调度核心,我创建了Controller类。在processRequests方法里,我规划了一个处理顺序:先一股脑儿把向上的请求处理完,再去处理向下的请求。我觉得这样能减少电梯来回折返,提高运行效率。(这里也是我自以为是的想法)为了确定电梯该去哪个楼层,我又写了getNextFloor方法。这个方法会根据电梯当前的运行方向,优先处理同方向的外部请求,要是没有合适的外部请求,再去处理内部请求(其实正确的应该是优先处理同方向且距离近的)。
而且,处理内部请求时,还会根据电梯运行方向过滤 —— 向上时只考虑比当前楼层高的请求,向下时只考虑比当前楼层低的请求。我当时觉得这套逻辑已经很完善了,把各种情况都考虑到了。
然而,当我开始测试代码、模拟真实场景时,问题逐渐暴露出来。如下图所示:

  **我这才意识到,在处理前先行对指令进行优先级排序,是个错误的决定。原本我以为按楼层优先级排序,能让电梯高效运行,但实际运行时,却出现了很多不合理的情况。比如说,当电梯正在向上运行,按照预先排好的优先级队列,它可能会直接略过中途楼层的请求,直奔远处楼层的请求。这也完全违背了题目本身正确的逻辑和初衷。**

** 我又仔细重新看老师提供的文件,发现预先的排序完全是错误的逻辑。不是一开始就把请求顺序定死,这样反而限制了电梯调度的灵活性。**
** 后续,我得重新规划请求队列的管理方式,每次处理都必须是队列中剩余的第一个指令,这才是真正的题意和正确的逻辑。**

<二>第二次电梯调度:
这次调度要求合理地类设计,同时还要求优化对输入错误指令和重复指令的处理方法。
以下是SourceMontor生成的报表分析与类图解释:

  **类图**采用了模块化设计,将不同的功能封装在不同的类中,如 Controller 负责调度,RequestQueue 负责请求管理,Elevator 负责电梯状态管理。这种设计使得代码结构清晰,易于维护和扩展。例如,如果需要添加新的调度策略,只需要修改 Controller 类的代码;如果需要增加新的请求类型,只需要修改 RequestQueue 类。
  枚举类 State 和 Direction 的使用使得代码更加清晰和易读。通过枚举类,可以明确地定义电梯的状态和运行方向,避免了使用常量可能带来的混淆。例如,使用 State.MOVING 和 Direction.UP 比使用整数常量更具可读性。
  代码实现了数据和行为的封装。每个类都有自己的属性和方法,通过对象之间的交互来完成整个系统的功能。这种封装性提高了代码的安全性和可维护性。

报表分析

代码行数,共 324 行。
语句数量,有 153 条。
分支语句占比,为 13.7% 。
方法调用次数,总计 54 次。
注释占比,达 9.9% ,注释还是挺丰富的。
类的数量,是 7 个 。
平均每个类的方法数,为 4.14 。
平均每个方法的语句数,是 4.83 。
最大复杂度,值为 12 ,存在较复杂的方法,可能需要进行重构优化。
复杂度分析

从表格看,Main.main() 方法复杂度和语句数都较高,是代码中的重点关注对象,可能需要优化拆分以降低复杂度。
从柱状图中可以看到,在深度为 0 - 2 的区间内,语句数量较多,大部分方法的深度集中在较低层次,代码结构相对较为扁平,深度较大的方法相对较少。
从雷达图来看,代码质量存在多处可优化点。注释占比或许未达理想状态,不利于代码理解与维护;平均每个类的方法数以及平均每个方法的语句数均超出合适范围,表明类职责划分不够清晰,方法内功能过度聚合。最大复杂度、平均复杂度数值较高,且最大深度、平均深度也超出理想水平,反映出代码逻辑和结构复杂,嵌套与调用关系不够简洁。
后续我需要从增加注释、合理拆分重构类与方法、优化整体结构等,逐步提升代码质量。

<三>第三次电梯调度:
第二次迭代给出了新的修改要求:
新增乘客类:创建Passenger类,专门管乘客相关信息。
简化请求逻辑:不再用单独的乘客请求类,外部请求格式从 “<请求楼层数,请求方向>” 改成 “< 请求源楼层,请求目的楼层 >” ,更清楚乘客从哪到哪。
遵循设计原则:设计电梯类、乘客类、队列类和控制类时,每个类只干一件事,让代码更清晰好维护。
修改请求处理:电梯处理完外部请求后,把请求里的目的楼层作为内部请求,加到内部请求队列末尾,继续送乘客到目的地 。电梯其他运行规则和之前一样。
以下是SourceMontor生成的报表分析与类图解释:

报表分析

代码行数,共 486 行,代码规模有所增加。
语句数量,有 212 条,逻辑指令相对较多。
分支语句占比,为 34.0% ,比之前大幅提高,代码逻辑复杂程度显著上升。
方法调用次数,总计 123 次,模块间交互更为频繁。
注释占比,达 6.6% ,相较于之前有所降低,注释丰富度欠佳。
类的数量,是 5 个 ,类的数量减少。
平均每个类的方法数,为 4.40 ,类的功能丰富度稍有变化。
平均每个方法的语句数,是 8.91 ,方法代码量增多,复杂度可能提升。
最大复杂度,值为 38 ,远高于之前,存在极复杂方法,急需重构优化以降低维护难度。
复杂度分析

从表格看,Controller.determineDirection() 方法复杂度高达 38,语句数为 55 ,可能导致逻辑理解困难,需要对该方法优化重构,提升代码可读性与可维护性。
从柱状图中能够发现,代码中有逻辑存在嵌套过深,if-else语句用的太多了。
从雷达图上看,代码在逻辑和结构上较为复杂,方法间的嵌套与调用关系需要进一步简化。
所有需要增加注释量,尤其在复杂方法那里,对类和方法需要合理拆分,确保类职责单一,然后优化方法间的嵌套。

三.踩坑心得
在第一次电梯贸然开始导致失败以后,我认真重新思考了出队逻辑,之前我采用了提前对指令进行优先级排序的方式是典型地理解题意不充分,常常出现忽略中楼层请求或者调度不合理。如下图:

于是,我明确了新的调度策略:将内外部指令分别存储在内外部队列中,每次电梯运行时,仅查看内外部队列的第一个指令,优先执行与电梯当前运行方向相同且距离最近的指令,待该指令执行完毕后,将其从队列中移除。
我首先将注意力集中在了RequestQueue类上。在之前的设计中,为了实现指令的预先排序,我选用了PriorityQueue来管理外部请求队列。其中,向上请求队列按照楼层升序排序,向下请求队列按照楼层降序排序。但不符合题意逻辑。

于是我决定将upRequests和downRequests这两个队列的数据结构统一替换为LinkedList,不再进行预先排序。

在确定了到该目标楼层后,需要将相应的指令从队列中移除,以保证队列中第一个的是正确的有效请求。我在removeRequests方法中对这一过程进行了详细处理。对于内部请求,我使用remove方法将当前楼层对应的请求从队列中移除;对于外部请求,我使用removeIf方法,根据电梯的运行方向,精准移除当前楼层的同方向请求。这样可以确保指令处理的准确性和队列的有效性,避免出现重复处理或处理错误的情况。
以下是我重新思考的过程,尤其是对于<3><3,UP><3,DOWN>,这些同层情况的出队和变向也重新进行了思考,否则特别容易在出队逻辑上犯错:(一些思考的文档)

第三次:
之后输入格式发生改变之后,需要从 **queue.getInternalRequests() 和 queue.getExternalRequests() 里获取队首的乘客请求对象 inPassenger 和 outPassenger
这一步就容易踩坑,要是没考虑到队列为空的情况,直接去取队首元素,程序就会报错。一开始没处理好这种边界情况,调试了好久才发现问题。
心得就是写代码时一定要对输入输出的边界条件多做检查,不能想当然觉得队列里肯定有元素。
要是只有内部请求队列有元素,那就直接把内部请求队列的队首元素出队。看似简单,但要是在其他地方对队列操作逻辑有改动,没同步更新这里的代码,也会出问题。这让我意识到代码修改时要全局考虑,不能只改一处而忽略其他相关部分。
总之,队列出队逻辑虽然看似不复杂,但在实际编写中,各种边界情况、逻辑判断和对象操作都容易出错,要多测试多检查。

四.改进建议

  1. 职责单一性增强:部分类的职责太重。比如 Controller 类中,determineDirection 方法逻辑过于复杂,承担了过多判断电梯运行方向的任务,可考虑将其中一些if-else语句抽取成独立的方法。
  2. 有的方法复杂度太高,如 Controller.determineDirection() ,可以通过减少方法的嵌套、提取重复代码降低复杂度。重复的条件判断可提取成独立方法,减少嵌套次数。
  3. 处理队列操作时,要充分考虑队列为空等边界情况
    4.复杂大方法要多写注释,不然到现在再看真的有点地方一头雾水。

五.总结
通过这几个题目集,让初学java 的我得到了很大的成长:
1. 学会了看代码好坏:
知道了能从好多方面看代码怎么样,像代码有多少行、有多少条语句、分支语句占多少比例,还有方法复不复杂。这些能帮我判断自己写的代码好不好,以后写的时候心里就有数了。
2.类和方法设计:
明白类和类之间有关系,而且一个类最好就干一件事,方法也别写得太复杂,不然自己都绕晕。设计的时候得把职责分清楚,这样代码才好改、好维护。
3. 优化代码:
知道了代码能从好多地方优化。比如方法太复杂就得拆开,注释得写多点让人能看懂,名字得取明白,还有一些特殊情况得考虑周全。
4. 电梯程序逻辑:
搞懂了电梯调度程序如何运行的,像请求如何处理,电梯如何决定往上走还是往下走,写这种程序得想非常多情况,是一个很有意思的挑战。
5.要学会拆分方法然后减小复杂度,嵌套太多不是好事

posted @ 2025-04-19 18:13  24201839-薛文韬  阅读(22)  评论(0)    收藏  举报