一、前言
经过大一上学习了C语言后,我熟悉掌握了一些面向过程的编程思路与方法。而在进入Java语言的学习后,且连续三次迭代的电梯大题磨练下,我开始由面向过程的思维转向面向对象的思维。三次题目集的知识点包括属性的私有化,对电梯运行过程的封装和模块化,类的单一职责,以及边界测试等知识与要求。题目一周一次迭代,题量较为适中。而难度随学习进度逐渐升级,难度曲线合理,但作为第一次写大作业,在写第一道电梯时对读题理解等都有较高的要求,需要花费一定的时间。
下面我将分三次大作业,用SourceMonitor的生成报表内容以及PowerDesigner的相应类图等方式来讲解,分析此次题目和我的代码。同时也包含我本人的一些解释和心得,虽然算不上什么秘辛,但或许也能供自己和大家参考。
二、设计与分析
第一次电梯作业
第一次作业的要求是设计一个电梯类,其包含最大最小楼层、当前楼层、运行状态、方向、内部及外部(区分上下行)请求队列。电梯停在默认楼层,响应请求时先处理同方向请求,再处理反方向请求,按楼层顺序逐层检查请求并开关门;设计的Java程序要实现状态管理、请求处理和调度算法,通过键盘模拟输入,测试电梯是否按规则运行。
类设计:
本次题目为第一次电梯的编写,所以需要构建整体的算法和框架。其中核心的实现类为电梯类即为Lift,本次作业代码编写没有完全遵循类单一职责原则,该Lift类中属性为题目要求的层数、状态、内外两个队列等,而方法则包括了队列操作和电梯运行逻辑等操作。此外,由于本次题目中未使用集成框架,我额外编写了Node类,和List类来模拟链表、队列的一些功能,从而人工实现了队列的一些基础操作。最终程序的实现是通过遍历电梯对象中的两个传入的队列,与电梯当前状态和方向相比较,不断得到下一个该去的楼层,当到达后目标后去掉同层所有请求,重复操作,直到两个队列清空为止。
代码分析:
由代码的静态度分析来看:
(1)代码规模:代码有262行,178条语句,语句密度≈ 0.67(178÷262),代码的规模较为适中。
(2)类分析:总共四个类,每个类平均有5、6个方法,每个方法平均7条语句说明日常逻辑较为简洁。
(3)代码注释:注释行占比为%5,注释占比相对较低,在一些关键行和关键分支时的注释可能不够,代码的可读性和理解性不高。
(4)代码结构:最大块深度达到 5 层,而区间分布显示有近六成语句处于深度 2~3 的嵌套,说明有一定部分的代码逻辑较为复杂,使得代码的可读性、可维护性、可调式性都不够好。
(5)代码复杂性:而代码最复杂的方法Lift.target();其圈复杂度达到了28,过于复杂,可能造成难以理解和调试等问题。
(6)分支与方法调用:分支语句占比:25.3 %,逻辑复杂度中等偏上。方法调用数:74 次,约占总语句的 41.6 %,调用频率合理;但在target方法中调用链过长,性能与测试性方面存在问题。
第二次电梯作业
第二次作业的要求是对之前电梯调度程序进行迭代性设计,目的为解决电梯类职责过多的问题,类设计要求遵循单一职责原则(SRP),要求必须包含但不限于设计电梯类、乘客请求类、队列类以及控制类,此外还要求处理如下情况:
乘客请求楼层数有误,具体为高于最高楼层数或低于最低楼层数,处理方法:程序自动忽略此类输入,继续执行;
乘客请求不合理,具体为输入时出现连续的相同请求,例如<3><3><3>或者<5,DOWN><5,DOWN>,处理方法:程序自动忽略相同的多余输入,继续执行,例如<3><3><3>过滤为<3>。
类设计:
第二次的电梯题目在第一题的基础上要求实现四个类,即类图中的Lift电梯类,Request乘客请求类,Controller控制类,以及RequestQueue队列类。本次代码使用了LinkedList来存储队列信息,代替了第一次作业中的人工实现的队列类,此外将之前第一次作业中的电梯运行的相关逻辑转移重构到了控制类,将电梯类中的队列及其相关操作转移重构到了队列类,每一个用户的请求都是一个乘客请求类,这些类的改变使得类的设计基本遵循了类单一职责的原则,贴合题目以及实际的要求。之后在电梯类中按照题目要求引入了一个判断数据是否有效的boolean类型方法,从而实现在题目要求所给的异常数据处理,而其他基本算法相比第一次变化不大。
代码分析:
由代码的静态分析来看:
(1) 代码规模:总行数325行、语句194条,规模仍属中等偏上。语句密度≈ 0.60(194÷325),与一般Java项目相当,说明一行通常只承载一条逻辑语句,可读性尚可。
(2) 类分析:共5个类,平均每类≈6.8个方法。方法平均语句数≈4.2,日常业务代码较为精简。不过Controller类内聚度偏低:单独占了10个以上方法,其中gettarget方法异常庞大,后期调试压力可能会集中在这一个类上。
(3) 代码注释:注释行占比仅2.8%,占比较低。关键分支、复杂算法(如调度策略)基本缺少中文或英文说明,使得阅读成本高。
(4) 代码结构:最大块深度 5 层;26行代码处达到最深嵌套。近37%语句处于3层以上嵌套,阅读与调试都会受影响。
(5) 代码复杂性:平均圈复杂度 2.85,整体尚可。最复杂方法Controller.gettarget():复杂度为33、语句数34、块深度为5、调用96次,过于复杂,需要继续拆分。
(6) 分支与方法调用:分支语句占比22.2%,逻辑复杂度中等;主要集中在changedirection()、checkrequest()与gettarget()。方法调用169次,占语句总数≈87%,调用次数过多来驱动程序导致影响后续测试与调试,同时也让代码的性能降低。
第三次电梯作业
第三次作业的要求是对之前电梯调度程序再次进行迭代性设计,加入乘客类(Passenger),取消乘客请求类,类设计要求遵循单一职责原则(SRP),要求必须包含但不限于设计电梯类、乘客类、队列类以及控制类,而电梯运行规则与前阶段相同,但有如下变动情况:
乘客请求输入变动情况:外部请求由之前的<请求楼层数,请求方向>修改为<请求源楼层,请求目的楼层>
对于外部请求,当电梯处理该请求之后(该请求出队),要将<请求源楼层,请求目的楼层>中的请求目的楼层加入到请求内部队列(加到队尾)。
类设计:
第三次电梯题目将之前的乘客请求类改为了Passenger乘客类,该类属性相比之前请求类的层数、方向改为了当前楼层和目标楼层。由于乘客类的引入和其属性的变化,导致之前直接通过比较乘客请求类里的方向属性和电梯层数、当前方向的方法来决定电梯运行逻辑失效,于是我在乘客类添加了一个Direction()方法:即通过判断乘客的当前楼层和目标楼层来得出方向,从而可以在稍加修改其他类的基础上继续套用之前的算法。于是本次类的设计除了乘客类的加入和部分方法内容修改外,基本的设计与第二次相似。
代码分析:
由代码的静态分析来看:
(1) 代码规模:总行数340行、语句203条,规模属中等偏上。语句密度≈ 0.60(203÷340),说明一行通常只承载一条逻辑语句,可读性尚可。
(2) 类分析:共5个类,平均每类≈7个方法。方法平均语句数≈4.31,业务逻辑总体简洁。不过Controller类依旧承担过多职责。
(3) 代码注释:注释行占比仅 2.1 %,关键分支、调度算法与边界处理几乎无说明,新成员理解难度高。
(4) 代码结构:最大块深度 5 层(出现在第 26 行)。约 37 % 语句位于 3 层及以上嵌套,可读性、可调试性和可维护性受到明显影响。
(5) 代码复杂性:平均圈复杂度 2.89,整体尚可。最复杂方法 Controller.getTarget():复杂度 33、语句 34、块深度 5、被调用 102 次,该方法过于复杂。
(6) 分支与方法调用:分支语句占比 22.7 %,逻辑复杂度中等;主要集中在changedirection()、checkrequest()、getTarget() 等。方法调用175次,占语句总数≈86%,既影响性能,也增加测试难度。
三、采坑心得:
第一次电梯作业
第一次作业由于是最开始接触全新的电梯大题,因此也是花费最长时间的一次大作业。当总结起来花费时间的原因时,我发现很大一部分在于读题审题的问题。面对这样一个相当长度的题目以及老师发的各种讲解,我一开始是没有耐心去细致分析的题目的,就连一开始题目提到的end不区分大小写都没有看到。
反而我当时抱着侥幸心理,想着先写点代码也许就能过测试点。而我最初写的代码完全忽视了方向优先策略和LOOK算法。
直到老师开放讨论区,并发布新的测试样例,此时我才发现自己的代码完全不符合规律,只是表面上巧合与题目的样例相同,此时我才静下心来分析电梯运行的过程,查阅老师发的各种资料,最终不断在样例的检验下调试出了获取下一个目标楼层(例如:电梯此时方向为UP,那么它会优先选择它上面的楼层,但如果电梯层数<外部层数<内部层数,而外部为DOWN方向,那他会先去内部请求对应的层数。其他等等规律都是通过样例测试去发现,而DOWN规律正好与UP规律相反)和移动电梯(移动电梯前往目标楼层直到队列清空电梯状态变为Stopped)等方法(虽然这些方法都比较复杂,是纯粹的堆砌if-else结构…)。此外,还有一个点是我一开始没有注意到的,那就是电梯如果到达某一楼层后,要将内外部两个队列头部的同层请求都清空。解决各类问题后,最终得以通过题目。
第二次电梯作业
第二次重构代码的过程较为顺利,因为算法没什么问题,加入一些检测遍历数据范围的方法完成对异常数据的处理后,题目便通过了测试点。但这次编写代码我一开始使用ArrayList来操作队列,IDEA中可以编译但提交到pta显示编译错误,于是我发现ArrayList本身不支持队列操作的一些方法,只是IDEA而外兼容提供,于是改为LinkedList操作队列。
第三次电梯作业
第三次作业的修改也较为顺利,在加入Passenger.Direction()方法通过乘客当前楼层和目标楼层来判断其方向后,将所有代码中涉及到对之前的请求方向getter调用全部改为使用Direction方法,修改后就通过了测试点。
四、改进建议:
通过本次一个周期的大作业,我发现了许多问题。我的注释占比太少,会影响代码的理解和可读性,往后编写代码需要在关键分支点加入更多注释来说明,只有不论对他人和自己来说都更方便阅读。
而我的代码中在三次作业中都存在一些方法过于复杂,说明我目前依然只是停留在通过题目的目标,忽视代码最关键的一些可维护性和健壮性。而通过静态分析我也发现我代码一些方法调用次数过多,往后也需要通过各种方法来改进其耦合度。
此次大作业也让我发现自己对于Powerdesigner,SourceMonitor,编译器等工具运用的还不够熟练,我需要在往后的代码编写过程中更全面的运用这些工具,使我的代码编写能及时解决问题而不是将问题堆积在一起难以解决,也能提高我编写代码的效率。
五、总结:
完成本次大作业后,我收获颇丰。其中最受到启发的就是读题的重要性,一个好的读题可以让后续代码的编写一劳永逸,读题决定了我们的方向,如果一开始的方向就走错的话,那么后续的修正就需要花上更多的时间。此外,我也学习到了Java相比C语言不一样的思想,包括其代码的封装性和面向对象的思维方式。而在编写过程中,我也发现了Java中很多好用的类和包,这促使我之后更进一步的学习。而此次大作业相比之前的pta作业还有一点不一样的就是题目的迭代,这让我明白了代码最开始编写就要考虑到其可扩展性和健壮性,这样可以让后续的编程更舒服。
而对于我们当前学习的Java课程,我认为整体的课程节奏较为良好,老师授课效果也不错,但我认为Java实验系统的缺陷较多,禁用复制粘贴的功能也不是很有意义,其正向收益不高、提高了时间成本,本可以让学生把这些机械敲代码的时间节省下来去做其他更有意义的事。此外我认为pta中的讨论区可以常态开放,通过讨论和阅读他人发的问题,都可以很好激发灵感,促进学生相互间的进步。
希望在往后的学习中我编写程序能越来越得心应手,面向对象的课程能越来越好!
来源链接:https://www.cnblogs.com/wxzdd/p/18827494
如有侵犯您的版权,请及时联系3500663466#qq.com(#换@),我们将第一时间删除本站数据。
暂无评论内容