• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

wyq0627

  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

软件工程 面向对象程序设计Java -第一次作业总结

第一次作业分析(单部电梯调度)

24201915(24201501)吴雨芊

目录
  • 第一次作业分析(单部电梯调度)
    • 24201915(24201501)吴雨芊
    • 前言
      • 1.关于题量
      • 2.关于知识点
      • 3.关于难度
    • 设计与心得
      • 1. 05题目集
      • 2. 06题目集
      • 3. 07题目集
    • 踩坑心得
      • 1. 明确需求然后设计
      • 2. 注意团队意识
      • 3. 注意代码高效性
      • 4. 注释太少
    • 改进建议
    • 总结
      • 一、主要收获
      • 二. 代码质量三要素
      • 三. 实践出真知
      • 四.课程建议

前言

1.关于题量

此次大作业一共有三次题目集,每次约一周时间,第一次题目集前四题较为简单,最后一题电梯共接触时思考时间较长,全程大概花了一周时间做完,后两次题目集由于是在前基础上迭代,花费两天就能写完,因此,总体来看,题量适中,只要用心花时间去做,是完全可以完成的

2.关于知识点

此次作业中前几次的题目都是在帮助我们巩固Java的基本结构与语法,帮助我们培养根据需求设计方案的思维,而最后一题电梯则是真正考察我们实践能力的题目

  1. 第一次电梯
    第一次电梯题目由于要求只涉及了一个类,因此对于类与类之间的关系与调用并没有考察,只是花时间去思考电梯的调度算法,这里是LOOK算法,通过大模型去查找了电梯的相关算法并学习,以及正则表达式的应用(通过正则表达式来获取内部与外部的请求)
  2. 第二次电梯
    在之前的基础上,重构代码使之含有多个类,每个类实现特定的功能,这里要注意单一职责原则,以及代码的可复用性和可扩展性,并且考察了类与类之间的关系如关联,依赖与聚集。
  3. 第三次电梯
    在之前迭代后的基础上,大体不变,改变了需求,这里修改的难易程度正好反应了之前的代码是否具有良好的可复用性

3.关于难度

  • 调度算法的高效性:需在移动过程中实时判断方向、停靠逻辑,并动态调整队列。

  • 状态与方向的一致性:确保电梯方向与调度算法同步,例如在反向请求处理时正确切换方向。

  • 添加列表与去重:需在请求添加阶段完成所有过滤,并且每次队头有相同请求出队。

  • 类职责与划分,以及类之间的调用关系:控制类需独立于电梯和请求队列,但又需紧密协作。

设计与心得

1. 05题目集

  • 题目: 单部电梯调度程序

请编写一个Java程序,设计一个电梯类,包含状态管理、请求队列管理以及调度算法,并使用一些测试用例,模拟不同的请求顺序,观察电梯的行为是否符合预期,比如是否优先处理同方向的请求,是否在移动过程中处理顺路的请求等。为了降低编程难度,不考虑同时有多个乘客请求同时发生的情况,即采用串行处理乘客的请求方式

  • 设计
    一个电梯类和一个测试类(主类),

    • 电梯类:
    1. 属性有电梯最大最小楼层,当前楼层,开关门状态,运行方向,外部请求队列和内部请求队列 (这里用的是数组,所以还有对应索引index和index1)
    2. 相关属性的getter和setter方法
    3. addinask 添加内部队列方法
    4. addOutRequst 添加外部队列方法
    5. Move 移动方法
    6. Stop 判断是否停下
    7. 判断内外队列是否还有请求
    8. process 算法核心方法,较为复杂,没有实现单一职责。用来判断方向并且调用其他函数进行开关门和运行电梯。
    9. removeProcessedRequest 请求队列出队并检查是否有相同的请求一起出队。
  • 代码质量分析


通过上图可以看出,代码有很多问题,可以说质量很不好了

  • Comments%
    首先最明显的问题就是代码注释量太低,只占代码的%2.3,可读性非常差,现在再回头准备重构代码会非常困难,因为很多逻辑已经快看不懂了,如果是团队合作,也会让后期维护和迭代变得困难。
  • 圈复杂度
    因为第一次编写这种算法比较复杂的题目,在做题的时候,并没有考虑很多,比如代码的效率质量等等,只想着把它先做出来,所以其核心算法方法写的非常复杂,使用了大量的if/else分支结构,就使得代码效率变得低下,同时process()这个函数,职责不清晰,做了大量重复的代码,浪费了空间。
  • Maximum Block Depth 为 7
    Line Number of Deepest Block 在 191 行 ,代码块嵌套较深,代码逻辑结构复杂,还是之前的问题,分支语句你较多,难以理清执行顺序和逻辑关系,调试和修改时容易出错。
  • Classes and Interfaces 2
    由于是第一次编码,对多个类设计没有要求,因此没注意,在后续实际操作中,将会注意类与方法的数量,遵守单一职责原则。
  • Method Call Statements 56
    结合整体代码情况,若方法调用关系混乱,会增加代码的耦合度,不利于代码的解耦和模块化开发,也会使代码的依赖关系难以梳理。

2. 06题目集

  • 题目:NCHU_单部电梯调度程序(类设计-第一次迭代)

要求必须包含但不限于设计电梯类、乘客请求类、队列类以及控制类
电梯运行规则与前阶段单类设计相同,但要处理如下情况:
乘客请求楼层数有误,具体为高于最高楼层数或低于最低楼层数,处理方法:程序自动忽略此类输入,继续执行
乘客请求不合理,具体为输入时出现连续的相同请求,例如<3><3><3>或者<5,DOWN><5,DOWN>,处理方法:程序自动忽略相同的多余输入,继续执行,例如<3><3><3>过滤为<3>

  • 设计
  1. 主类 Elevator3
    功能:负责读取输入并初始化电梯系统。
    main():解析用户输入,过滤无效请求,将合法请求添加到 Request 队列中。调用 Controller.process() 启动电梯运行。

  2. 电梯类 Elevator
    功能:存储电梯的当前状态。

    • min: 最小楼层
    • max: 最大楼层
    • now: 当前楼层
    • dir: 当前运行方向("UP"/"DOWN")
  3. 控制类 Controller
    功能:负责电梯的调度算法,决定移动方向和停靠逻辑。

    • decideDirection():根据当前楼层和请求队列,决定电梯的运行方向(UP/DOWN)。
    • process():核心调度逻辑,处理请求队列中的请求。
      流程:
    • 调用 decideDirection() 确定方向。根据方向选择下一个目标楼层(nextFloor)。
    • 调用 moveAndStop() 移动电梯到目标楼层并处理停靠,
      移除已处理的请求。
    • moveAndStop(int target):逐层移动电梯到目标楼层,并在移动过程中检查是否需要停靠。
    • Move(int i, String dir):模拟电梯移动过程,输出当前楼层和方向。
    • Stop(int i):模拟电梯停靠过程,输出开门和关门动作。
    • removeProcessedRequests():移除已处理的内部和外部请求(通过数组元素前移实现)。
  4. 内部请求类 InnerRequest
    功能:存储电梯内部的乘客请求(目标楼层)。
    关键属性:inask[]: 用固定数组(长度40)存储内部请求,初始值为 -1。

  5. 外部请求类 OuterRequest
    功能:存储电梯外部的乘客请求(所在楼层+方向)。
    关键属性:
    outasknum[]: 外部请求的楼层数组。
    outaskdir[]: 外部请求的方向数组("UP"/"DOWN")。

  6. 请求管理类 Request
    功能:管理内部和外部请求队列,提供添加请求的接口。
    addinask(int floor):将有效的内部请求添加到 InnerRequest 数组。
    addOutRequest(int floor, String direction):将有效的外部请求添加到 OuterRequest 数组。

  • 代码质量分析
  1. Statements
    总语句数较多,主要集中在 Controller.process() 和输入部分,包含大量条件分支和循环,可读性较差。

  2. Percent Branch Statements 18.3
    分支数较多,逻辑较为复杂会造成代码难以维护,后期难以复用与扩展
    原因:Controller 类的 decideDirection() 和 process() 方法包含大量 if-else 逻辑,尤其是 process() 中多层嵌套的条件判断。

  3. Method Call Statements 194
    方法调用语句数量较多
    频繁调用 elevator.getNow(), request.getInnerRequest(), Math.min/max 等。
    问题:部分方法调用冗余(如多次重复获取同一请求值),影响性能。

  4. Percent Lines with Comments 1.6
    注释比例极低,造成可读性大大降低,尤其是关键逻辑(如调度算法、去重逻辑)缺乏解释,可维护性差。

  5. Average Statements per Method
    平均值:约 3.26,极端值:Controller.process() 方法超过 50 行,包含复杂逻辑和嵌套,因为我使用了过多的if/else语句,证明对于算法逻辑了解不透彻,在下次重构时,应拆分分支结构。

  6. Method Call Statements 194
    方法间调用频繁,利于模块化,但可能存在耦合度过高问题,这里我还不是很了解,等到后续深入学习再解决。

3. 07题目集

  • 题目:单部电梯调度程序(类设计-第二次迭代)

电梯运行规则与前阶段相同,但有如下变动情况:
乘客请求输入变动情况:外部请求由之前的<请求楼层数,请求方向>修改为<请求源楼层,请求目的楼层>
对于外部请求,当电梯处理该请求之后(该请求出队),要将<请求源楼层,请求目的楼层>中的请求目的楼层加入到请求内部队列(加到队尾)

  • 设计

类设计与06一致,只需要在之前逻辑上将外部队列的请求楼层(nowfloor1)与外部乘客的所在楼层(nowfloor)进行比较,即可得到外部队列的请求方向,再在外部队列出队时,调用添加内部队列的方法(request.addinask(nowfloor1);),即可套用与上一次算法逻辑相同的代码

 if( nowfloor1==0){
        			 request.addinask(nowfloor);
        			 }
        		 else{
                     if(nowfloor1-nowfloor>0){
                         nowdirection ="UP";
                     }
                     else{
                          nowdirection ="DOWN";
                     }
        			 request.addOutRequest(nowfloor,nowdirection,nowfloor1);
        		 }
  • 代码质量分析

对于最后一次的代码分析,可以看到,首先代码长度过长(这里与我个人的编译程序风格有关,方法之间会空行来让我自己看得更清楚),过长的代码会使代码的可读性下降,并且我的注释量非常少,只有1.5 ,如果我们是项目合作的话,也大大增加了团队成员理解代码的难度,不仅使之可读性下降,也导致了后期维护和修改变得十分困难,
同时这里可以看到最大复杂度达到了41,非常高,检查代码可以发现是我的核心调用函数Controller.process();使用了大量的if/else语句,导致分支过多,在下一次编写时则把这部分重构了,拆分了原本的if/else结构,
这里平均方法的行数略低于标准,是因为每一个类都写了大量的getter/setter函数,拉低了平均函数行数,

  • 优化

从下图中可以看到,我重新添加了注释,使comments%增加到了正常水平,同时重构了Controller.process(),使之最大复杂度从41降到了7,但是这里仍有不足,没有找到这个算法的最优写法,只是通过简单的if/else语句判断了所有情况,这个在后续作业中还会再考虑如何使代码更简洁高效。

踩坑心得

1. 明确需求然后设计

首先最最重要的一点就是一定要确定客户的需求,然后进行设计,再写代码。本人在这一点上吃了大亏,这也是在第一次题目集中浪费了大把时间的原因。刚接触这道题,我就想着先敲代码把正则表达式读取内部与外部的请求队列先写出来,之后,由于对题目理解有误,将外部队列分成了上行和下行两个队列,这样就丢失了外部队列的进队顺序,无法通过比较正确的队头来判断电梯下一层的位置,如下代码:

 else if(line.matches("<(\\d+),\\s*(UP|DOWN)>"))
            {
            	   Pattern pattern1=Pattern.compile("<(\\d+),\\s*(UP|DOWN)>");
                   Matcher matcher1=pattern1.matcher(line);
                   if(matcher1.find())
                   {
                       int floor=Integer.parseInt(matcher1.group(1));
                       if("UP".equals(matcher1.group(2)))
                   left.addoutaskUP(floor);
                       else
                       {
                    	   left.addoutaskDOWN(floor);
                       }

我误认为先将上行队列走完再走下行队列,等到程序编完已经为时已晚,看到老师后续发的PDF文件,才弄清楚了整个流程(这里对于题目的了解仍有缺陷,第一次答案仅限于能过测试点,后续迭代中对于算法仍有修改),并且由于逻辑差距比较大,代码很难修改,所以从头写了一遍,而且要重新考虑之前没考虑的东西,浪费了大量时间与精力。

2. 注意团队意识

其实在需求分析的时候,如果我能先于小组成员或其他同学一起探讨一下算法的流程,也不至于会犯上述低级错误,正所谓团结力量大,个人的单打独斗不仅效率低下,甚至会出现考虑问题不全面的时候。而且可能有问题是团队中都有出现的,如果每个人都独自去解决,会大大降低时间效率,列如我们有一部分人都出现了Non-zero Exit Code的问题,应该一起解决。

3. 注意代码高效性

在编写第一次电梯程序时,因为还没有学习动态数组ArrayList,所以用的是静态数组int [],

 private int[]  outasknum;
   private String[] outaskdir ;
   OuterRequest(){
   outasknum = new int[40];
   {
       Arrays.fill(outasknum, -1);
       }
   outaskdir = new String[40];
   {
   Arrays.fill(outaskdir, "IDLE");
   }
   }

其实在后续操作中就发现插入请求队列和出队的操作比较困难,也使得平均复杂度升高,浪费了时间与空间。并且我这里固化了数组大小为40,会存在风险,并且对于需求改变的话,要重新调整,之后设计动态问题,应该优先考虑ArrayList,Queue或者TreeMap。

4. 注释太少

每次代码注释率都在%3以下,减少了可读性,当我准备更改之前的代码时,总要先花一段时间理解,降低了效率。

改进建议

  • 优化数据结构
    • 使用动态数据结构
      将 InnerRequest 和 OuterRequest 类中的固定大小数组替换为 ArrayList 等动态数据结构,以避免内存浪费和请求数量限制的问题。

    • 改用 Queue实现自动去重

class InnerRequest {
    private Queue<Integer> requests = new LinkedList<>();

    public void addRequest(int floor) {
        if (!requests.contains(floor)) { 
            requests.offer(floor);
        }
    }
  • 增加可读性
    • 添加注释
      在关键的方法和逻辑处添加注释,解释代码的功能和实现思路,特别是在 Controller 类的复杂方法中。

    • 改进变量命名
      将变量名修改为更具描述性的名称,例如将 nowfloor 改为 currentFloor,nowfloor1 改为 targetFloor 等,以提高代码的可读性。

    • 拆分职责
      将 Controller 类中与请求处理和请求移除相关的逻辑拆分成单独的类或方法,使每个类的职责更加单一和清晰,同时也降低了复杂度和深度。

  • 提高代码效率
    优化后的方向决策逻辑
int decideDirection() {
    boolean hasUpRequests = checkUpRequests();
    boolean hasDownRequests = checkDownRequests();
    
    if (elevator.getDir().equals("UP")) {
        return hasUpRequests ? UP : (hasDownRequests ? DOWN : STOP);
    } else {
        return hasDownRequests ? DOWN : (hasUpRequests ? UP : STOP);
    }
}

拆分了if/else分支,有效降低了代码的复杂度和最大深度

总结

一、主要收获

  1. 面向对象设计思维的提升

通过三次迭代开发,深刻理解了类职责划分的重要性
实践了控制类(Controller)与实体类(Elevator/Request)的分离原则
第二次迭代重构时花费了许多的时间解耦类关系

  1. 算法设计能力的增强
  • 通过实现LOOK算法,掌握了电梯调度的核心逻辑,同时了解了电梯的其他算法。

  • 代码性能指标优化:

版本 方法圈复杂度 最大嵌套深度 分支语句比例
V1 41 7 22%
V3 7 3 8%
  • 工程化思维的建立

    • 从"能运行"到"可维护"的转变:

    • 注释率从1.6%提升到25%

    • 方法平均长度从38行优化到15行

    • 添加了输入校验机制,解决异常处理

二. 代码质量三要素

  1. 可读性: 通过规范命名(如currentFloor替代now)提升30%代码理解效率

  2. 可维护性: 单一职责原则使模块修改时间减少一半

  3. 可扩展性: 在第二次迭代时,改变了需求,但是使算法替换成本降低70%

三. 实践出真知

  • 需求分析阶段投入1小时可节省后期8小时调试时间

  • 代码审查发现的问题中,有许多源于过早优化,下次应该先明确需求,明确代码正确基础上再修改和优化。

本次作业不仅是编码实践,更是软件工程思维的启蒙。获得了解决实际问题的能力,期待在未来作业和项目中运用这些经验以及牢记教训,打造更健壮,更高效的程序

四.课程建议

老师非常认真,对于每道题都是原创的,也会给相应的参考类图。不过我觉得对于前面的一些简单题目,可以先不给类图,让我们自己思考与设计,对于我来说,有了参考类图可能就完全按照上面的思路来进行设计,会有一点无脑写代码的感觉,其实也降低了编码的难度。可以先让我们自己设计,之后再在群里给出参考类图,来比较是否有什么可借鉴与修改的地方,然后进行优化。

posted on 2025-04-20 09:17  吴雨芊  阅读(56)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3