OO第二单元电梯作业总结
一、设计策略
具体的模块分析见<三>,本部分仅给出我的设计方案与设计时的有关思考。
在初步审定需求时,凭借上课获得的生产者-消费者模式的有关知识,决定了使用如下几个线程:主线程,用于启动调度线程;调度线程,用于调度,并在其中启动读入需求线程及电梯线程;获取请求线程负责与课程组下发的jar包提供的需求获取等方法对接;电梯线程则负责电梯的运行(包括捎带)及输出。调度线程,获取请求线程分别承担消费者和生产者模式,共同持有Tray类的实例作为“传送带”。而调度器和电梯线程之间的通信主要靠类似观察者模式中的监视器实例来负责电梯线程运作过程中的 wait 和 notify 。
- 第一次作业,程序运作过程如下:由GetRequest类获取请求,将请求压入传送带的待处理队列,并唤醒tray的弹出请求方法,给调度器传递消息(通过调度线程类的方法调用),调度器将请求加入电梯线程的等待list,并通过Monitor实例唤醒电梯,于是电梯开始运作。这样的模式不断地运行,电梯在开关门时会实践捎带策略。第一次作业中我使用LOOK算法,电梯停滞的时候随时等待主请求的进入。当主请求进入时,马上去接主请求并在路上不接其他人,接到主请求也就确定了主运动方向,只有出发楼层为当前楼层且方向与当前的方向相同的时候才会捎带该请求。到这一趟需要到达的最边缘楼层时就接下一个主请求。在开关门的时候都实践捎带的策略。
- 第二次作业在第一次的基础上增加了电梯数量,扩充了电梯课停靠楼层,我的程序架构完全没变,只在原有基础上稍作了扩充并且在选择电梯的时候稍微判断了一下运行方向和当前楼层等信息。第二次电梯的限定人数通过调度器处调用相关方法来判断电梯内人数及电梯等待队列人数,两者综合不超过最大人数。即电梯线程中不需要考虑超载问题。
- 第三次作业由于性能指标多增加且只增加了每个乘客的等待时间,于是我不得不重新写了我的捎带策略,即一方面,首先保证如果出发楼层为当前楼层,则在不超载的情况下必然先进来,不管是否同向,另一方面,电梯的运行方向达到的最大楼层由电梯内的正在执行请求的最大或最小楼层决定。调度方面其实算法反而简化了,由于动态过程相当难以预测与分类,于是我采用了只看waitList多少。于此同时,电梯的超载控制转交给电梯自己来完成。即可能电梯满载时waitList是有剩余的(第二次作业则不会出现此情况,为较大的修改)。同时因为电梯读入结束时如何停止电梯线程的问题,我增加了Dispatcher类专门负责电梯换乘和电梯运作的停止信号的产生,此处的与其他若干线程的耦合度相当高。
二、可扩展性
第三次作业中,我更改了捎带策略,发现比之前的策略性能好了不少,也不需要在调度器中考虑waitList的数量了。因此第三次作业的性能上的扩展性空间并不少。同时,我的可停靠楼层使用的是一维数组,其实可以支持可变情况,因此相关扩展性较好,电梯的动态加入也只需要调用调度器中的一个并不复杂的方法。因此我认为我第三次作业的可扩展性还是较好的。
开闭原则:电梯类做的仍然不是很好,有待改进。
单一职责原则:贯彻地较好。
里氏替换原则、接口隔离原则:本次迭代开发没有使用继承,今后如有进一步扩展需求可以考虑抽象出借口等。
三、基于度量的程序结构分析
首先解释若干参数:
ev(G)基本复杂度,是用来衡量程序非结构化程度的,非结构成分降低了程序的质量,增加了代码的维护难度,使程序难于理解。因此,基本复杂度高意味着非结构化程度高,难以模块化和维护。实际上,消除了一个错误有时会引起其他的错误。
Iv(G)模块设计复杂度,是用来衡量模块判定结构,即模块和其他模块的调用关系。软件模块设计复杂度高意味模块耦合度高,这将导致模块难于隔离、维护和复用。模块设计复杂度是从模块流程图中移去那些不包含调用子模块的判定和循环结构后得出的圈复杂度,因此模块设计复杂度不能大于圈复杂度,通常是远小于圈复杂度。
v(G)圈复杂度,是用来衡量一个模块判定结构的复杂程度,数量上表现为独立路径的条数,即合理的预防错误所需测试的最少路径条数,圈复杂度大说明程序代码可能质量低且难于测试和维护,经验表明,程序的可能错误和高的圈复杂度有着很大关系。
下面先给出三次作业的类图和和相关代码的度量统计,然后一并分析。
第一次作业
UML类图:
代码度量分析
method | ev(G) | iv(G) | v(G) |
---|---|---|---|
ElevatorThread.addWait(PersonRequest) | 1.0 | 1.0 | 1.0 |
ElevatorThread.chooseMain() | 2.0 | 5.0 | 10.0 |
ElevatorThread.delay200() | 1.0 | 2.0 | 2.0 |
ElevatorThread.elevatorRun() | 4.0 | 8.0 | 9.0 |
ElevatorThread.ElevatorThread(Tray,Object) | 1.0 | 1.0 | 1.0 |
ElevatorThread.letMainIn() | 1.0 | 4.0 | 6.0 |
ElevatorThread.moveDown() | 1.0 | 2.0 | 2.0 |
ElevatorThread.moveUp() | 1.0 | 2.0 | 2.0 |
ElevatorThread.OpenAndClose() | 1.0 | 13.0 | 19.0 |
ElevatorThread.outDoor() | 1.0 | 5.0 | 5.0 |
ElevatorThread.run() | 1.0 | 3.0 | 3.0 |
ElevatorThread.setBound() | 1.0 | 3.0 | 7.0 |
ElevatorThread.taskAdd(PersonRequest) | 1.0 | 2.0 | 2.0 |
ElevatorThread.waitListEmpty() | 1.0 | 1.0 | 1.0 |
GetRequestThread.GetRequestThread(Tray) | 1.0 | 1.0 | 1.0 |
GetRequestThread.run() | 1.0 | 3.0 | 3.0 |
Main.main(String[]) | 1.0 | 1.0 | 1.0 |
ScheduleThread.run() | 3.0 | 3.0 | 3.0 |
ScheduleThread.ScheduleThread(Tray,Object) | 1.0 | 1.0 | 1.0 |
Tray.getTrayRequest() | 3.0 | 3.0 | 4.0 |
Tray.isEmptyAndRequestEnd() | 1.0 | 2.0 | 2.0 |
Tray.noWaitRequest() | 1.0 | 1.0 | 1.0 |
Tray.queueExpand(PersonRequest) | 1.0 | 1.0 | 1.0 |
Tray.requestEnd() | 1.0 | 1.0 | 1.0 |
Tray.setEnd() | 1.0 | 1.0 | 1.0 |
Tray.Tray(Object) | 1.0 | 1.0 | 1.0 |
Total | 156.0 | 190.0 | 243.0 |
Average | 1.3448275862068966 | 1.6379310344827587 | 2.0948275862068964 |
第二次作业
UML类图:
代码度量分析
method | ev(G) | iv(G) | v(G) |
---|---|---|---|
ElevatorThread.addWait(PersonRequest) | 1.0 | 1.0 | 1.0 |
ElevatorThread.chooseDir() | 1.0 | 3.0 | 3.0 |
ElevatorThread.chooseFromTask() | 1.0 | 3.0 | 3.0 |
ElevatorThread.chooseFromWait() | 4.0 | 5.0 | 13.0 |
ElevatorThread.CloseDoor() | 4.0 | 5.0 | 6.0 |
ElevatorThread.delay(int) | 1.0 | 2.0 | 2.0 |
ElevatorThread.elevatorRun() | 4.0 | 17.0 | 18.0 |
ElevatorThread.ElevatorThread(Tray,Object,ElevId) | 1.0 | 1.0 | 1.0 |
ElevatorThread.getCompFloor() | 2.0 | 1.0 | 2.0 |
ElevatorThread.getElevId() | 1.0 | 1.0 | 1.0 |
ElevatorThread.getTo() | 2.0 | 1.0 | 2.0 |
ElevatorThread.moveDown() | 1.0 | 2.0 | 3.0 |
ElevatorThread.moveUp() | 1.0 | 2.0 | 3.0 |
ElevatorThread.OpenAndClose() | 1.0 | 3.0 | 3.0 |
ElevatorThread.openDoor() | 3.0 | 5.0 | 6.0 |
ElevatorThread.outDoor() | 1.0 | 5.0 | 5.0 |
ElevatorThread.run() | 1.0 | 3.0 | 3.0 |
ElevatorThread.sum() | 1.0 | 1.0 | 1.0 |
ElevatorThread.taskAdd(PersonRequest) | 1.0 | 2.0 | 2.0 |
ElevatorThread.taskFull() | 1.0 | 1.0 | 1.0 |
ElevatorThread.taskListEmpty() | 1.0 | 1.0 | 1.0 |
ElevatorThread.temp() | 1.0 | 2.0 | 2.0 |
ElevatorThread.toString() | 1.0 | 1.0 | 1.0 |
ElevatorThread.waitListEmpty() | 1.0 | 1.0 | 1.0 |
GetRequestThread.getElevatorNum() | 1.0 | 1.0 | 1.0 |
GetRequestThread.GetRequestThread(Tray,Object) | 1.0 | 1.0 | 1.0 |
GetRequestThread.run() | 1.0 | 3.0 | 3.0 |
Main.main(String[]) | 1.0 | 1.0 | 1.0 |
MainTest.run() | 1.0 | 3.0 | 3.0 |
ScheduleThread.ElevNotify(ElevatorThread) | 2.0 | 2.0 | 7.0 |
ScheduleThread.more(int) | 3.0 | 7.0 | 8.0 |
ScheduleThread.one() | 3.0 | 3.0 | 3.0 |
ScheduleThread.run() | 3.0 | 5.0 | 5.0 |
ScheduleThread.ScheduleThread(Tray) | 1.0 | 1.0 | 1.0 |
Tray.getTrayRequest() | 3.0 | 3.0 | 4.0 |
Tray.isEmptyAndRequestEnd() | 1.0 | 2.0 | 2.0 |
Tray.noWaitRequest() | 1.0 | 1.0 | 1.0 |
Tray.queueExpand(PersonRequest) | 1.0 | 1.0 | 1.0 |
Tray.requestEnd() | 1.0 | 1.0 | 1.0 |
Tray.setEnd() | 1.0 | 1.0 | 1.0 |
Tray.Tray() | 1.0 | 1.0 | 1.0 |
Total | 186.0 | 226.0 | 282.0 |
Average | 1.4090909090909092 | 1.7121212121212122 | 2.1363636363636362 |
第三次作业
UML类图:
代码度量分析
method | ev(G) | iv(G) | v(G) |
---|---|---|---|
AbleList.getAbleList(ElevType) | 5.0 | 2.0 | 5.0 |
Dispatcher.canReach(ElevType,int) | 5.0 | 2.0 | 5.0 |
Dispatcher.catchOutPerson(PersonRequest,int) | 1.0 | 3.0 | 3.0 |
Dispatcher.chooseElev(PersonRequest) | 2.0 | 8.0 | 9.0 |
Dispatcher.dealTransfer(PersonRequest) | 2.0 | 2.0 | 3.0 |
Dispatcher.Dispatcher(Tray) | 1.0 | 1.0 | 1.0 |
Dispatcher.indexOfFloor(int) | 1.0 | 1.0 | 2.0 |
Dispatcher.judgeEnd() | 1.0 | 2.0 | 2.0 |
Dispatcher.noChoiceBut(PersonRequest,int,int) | 19.0 | 19.0 | 31.0 |
Dispatcher.putRequest(PersonRequest) | 1.0 | 1.0 | 1.0 |
Dispatcher.trans(int) | 2.0 | 1.0 | 2.0 |
ElevatorThread.addWait(PersonRequest) | 1.0 | 1.0 | 1.0 |
ElevatorThread.chooseDir() | 1.0 | 3.0 | 3.0 |
ElevatorThread.chooseFromTask() | 1.0 | 1.0 | 1.0 |
ElevatorThread.chooseFromWait() | 4.0 | 5.0 | 13.0 |
ElevatorThread.CloseDoor() | 4.0 | 5.0 | 6.0 |
ElevatorThread.delay() | 1.0 | 2.0 | 2.0 |
ElevatorThread.elevatorRun() | 4.0 | 17.0 | 18.0 |
ElevatorThread.ElevatorThread(Tray,Object,String,ElevType,Dispatcher) | 1.0 | 1.0 | 1.0 |
ElevatorThread.getAble() | 1.0 | 1.0 | 1.0 |
ElevatorThread.getCompFloor() | 2.0 | 1.0 | 2.0 |
ElevatorThread.getElevId() | 1.0 | 1.0 | 1.0 |
ElevatorThread.getElevState() | 1.0 | 1.0 | 1.0 |
ElevatorThread.getElevType() | 1.0 | 1.0 | 1.0 |
ElevatorThread.getTo() | 2.0 | 1.0 | 2.0 |
ElevatorThread.moveDown() | 1.0 | 2.0 | 3.0 |
ElevatorThread.moveUp() | 1.0 | 2.0 | 3.0 |
ElevatorThread.OpenAndClose() | 1.0 | 3.0 | 3.0 |
ElevatorThread.openDoor() | 3.0 | 5.0 | 6.0 |
ElevatorThread.outDoor() | 1.0 | 5.0 | 5.0 |
ElevatorThread.run() | 1.0 | 3.0 | 3.0 |
ElevatorThread.setHiLoEst() | 1.0 | 2.0 | 7.0 |
ElevatorThread.setMaxNum(ElevType) | 4.0 | 2.0 | 4.0 |
ElevatorThread.setSpeed(ElevType) | 4.0 | 2.0 | 4.0 |
ElevatorThread.sum() | 1.0 | 1.0 | 1.0 |
ElevatorThread.taskAdd(PersonRequest) | 1.0 | 1.0 | 1.0 |
ElevatorThread.taskFull() | 1.0 | 1.0 | 1.0 |
ElevatorThread.taskListEmpty() | 1.0 | 1.0 | 1.0 |
ElevatorThread.temp() | 1.0 | 2.0 | 2.0 |
ElevatorThread.toString() | 1.0 | 1.0 | 1.0 |
ElevatorThread.waitListEmpty() | 1.0 | 1.0 | 1.0 |
ElevId.getId(String) | 8.0 | 2.0 | 8.0 |
ElevType.getType(String) | 5.0 | 2.0 | 5.0 |
GetRequestThread.GetRequestThread(Tray,Dispatcher) | 1.0 | 1.0 | 1.0 |
GetRequestThread.run() | 3.0 | 6.0 | 6.0 |
Main.main(String[]) | 1.0 | 1.0 | 1.0 |
ScheduleThread.allElevNotify() | 1.0 | 2.0 | 2.0 |
ScheduleThread.ElevNotify(ElevatorThread) | 1.0 | 7.0 | 7.0 |
ScheduleThread.newElev(ElevatorRequest) | 1.0 | 3.0 | 3.0 |
ScheduleThread.run() | 3.0 | 6.0 | 6.0 |
ScheduleThread.ScheduleThread(Tray) | 1.0 | 1.0 | 1.0 |
ScheduleThread.sortElevForReq(Pair |
2.0 | 8.0 | 11.0 |
ScheduleThread.typeListAdd(ElevatorThread) | 2.0 | 2.0 | 5.0 |
Tray.getTrayRequest() | 3.0 | 3.0 | 4.0 |
Tray.isEmpty() | 1.0 | 1.0 | 1.0 |
Tray.isEmptyAndRequestEnd() | 1.0 | 2.0 | 2.0 |
Tray.isNoInputReq() | 1.0 | 1.0 | 1.0 |
Tray.noWaitRequest() | 1.0 | 1.0 | 1.0 |
Tray.print() | 1.0 | 1.0 | 1.0 |
Tray.queueExpand(Request) | 1.0 | 1.0 | 1.0 |
Tray.setEnd() | 1.0 | 2.0 | 2.0 |
Tray.setNoInputReq() | 1.0 | 1.0 | 1.0 |
Tray.Tray() | 1.0 | 1.0 | 1.0 |
Total | 268.0 | 309.0 | 406.0 |
Average | 1.6242424242424243 | 1.8727272727272728 | 2.4606060606060605 |
这三次作业,我的架构除了第三次性能方面的调整做了较大改动,前两次的架构几乎完全一样。其中每个类的规模都不是很大,各个类(线程)之间的功能分的很清楚。但是电梯类的开关门的方法与其他若干方法的耦合度确实难以避免地高,该方法本身的规模也很大,原因在于捎带策略的复杂。而后来改变捎带策略后这一点有所好转。
不过也可以清晰地看到,电梯类一直最庞大,最臃肿。
四、关于bug的分析
- 第一次公测时一开始有RTLE,后来发现我最早的电梯结束信号给的不对,有很多情况下是无法在合适的时候设置输入请求的结束标志,也有些时候设置了却无法唤醒等待中的电梯线程。后来虽然莫名其妙地在几个地方加了若干代码就OK了,但是我还是选择重新设计了相关部分。经过分析,发现原来的问题在于--我的设计中线程的运作分为三个信息传递的层次,它们处在链条的不同位置,读入请求、调度、电梯运行三个线程,但一开始我没有做好功能分开,跨层次地调用了set一类的方法导致随机死锁。后来我遵序不宜跨层次交互信号为原则--即阻塞等操作只应该在相邻的信息传递层次之间实行,成功地避免了死锁并让代码变得非常清晰。强测四平八稳,性能不是很好,有几个点性能分为0。
- 第二次公测和强测都没有出bug,但是互测时被找到了一个bug,在特殊情况下捎带策略会出问题————主请求正好完成的时候同层有捎带会不继续运行。后来经过简单地修改即修正了bug,是笔者测试不足造成。
- 第三次强测非常幸运的没有出bug,而互测测出了两个非常麻烦的bug,第一个是我没有读清楚指导书,以为新增电梯的ID只有X1, X2, X3,而实际上是Xi(i为正整数)。修复的时候也非常麻烦,因为为了代码清晰简洁我在相关部分大量使用了枚举类,修改了40多行,实在是不应该的问题。另一个问题也非常致命————我在设计时因为把电梯结束运行的关键标志的设立交给了Dispatcher类,此时如果最后一条指令为新增电梯指令,电梯线程会停不下来。修正的时候发现如果我像前两次作业中将GetRequest中的run方法的最后加上结束标志,有可能会造成如果当时恰巧有最后一条是需要换乘的指令(需要放置结束标志设立)因为线程运行顺序诸多原因只载换乘前半段就停止运行。只能使用sleep方法,让别的线程,先运行一会。
五、测试策略
惭愧的是,我几乎没有hack到别人。
但经过对大家hack情况的分析,发现由于本次互测数据要求普遍比较高,有些专攻边界的测试不被允许。大家的hack方向主要有:
- 高并发时电梯是否正常运转(不超载不漏载)
- 捎带策略的特殊情况的考验
- 很少但有特殊关系的指令(仅一条需要的指令且需要换乘或者其他)或某些特殊指令结尾(如第三次的以新增电梯指令结尾)
本单元的hack数据点主要针对多线程协作,而非第一单元的以各种边界情况为主。
六、心得体会
本单元我的面向对象思想相较于第一单元有不小的提升。主要是多线程的协作使得任何不清晰地设计都会造成并不直观反映问题的bug,甚至是随机出现,难以复现的bug。我也收获了一个重要的设计的细节上的原则————当多层次交互的时候尽量将可以互相改变关键字段属性的方法局限于相邻通信层次。
第三次互测出现的两个bug提醒我充分覆盖性测试的重要性。还有就是如果设计时如果有任何想到的可能会出问题的情景,一定要及时记录下来否则等别人发现已经有些晚了。