BUAA_OO_Unit2_Summary

BUAA_OO_Unit2_Summary

目录

  • 程序结构分析

    • 第一次作业

    • 第二次作业

    • 第三次作业

  • 度量分析

    • 规模分析

    • 复杂度分析

    • 可拓展性分析

    • UML

  • Bug分析与测试策略

    • Bug分析

    • 测试策略

  • 心得体会

一、程序结构分析

第一次作业

  • 设计要求

    模拟多线程实时电梯系统,五个楼座分别有且只有一部纵向电梯,处理同一楼座的乘客请求。

  • 同步块设置与锁的选择

    设置共享队列总请求队列waitQueue与各楼层请求队列processingQueue。对于代码中涉及到共享对象的部分,选择使用 synchronized 加锁。当然还有一种奇怪的判断方式在于,有的地方感觉不必加锁,但是跑起来会报错,干脆一同加上。

  • 调度器设计

    在第一次作业中似乎没有什么调度可言,由于每个楼座只有一部电梯,我们只需要把对应的请求放入到相应的楼座内,剩下的交给电梯自己取需求即可。

  • 调度策略

    电梯调度策略采用look策略,概括而言就是能捎带尽量捎带,性能上也还尚可,就未作进一步优化。

第二次作业

  • 设计要求

    在第一次作业的基础上,增加了横向电梯设置以及新增电梯设置。

  • 同步块设置与锁的选择

    同步块设置和第一次作业没什么本质差别,增加了各楼层的共享队列floorQueue。涉及共享对象的部分同样采用synchronized 加锁。由于并未进行重构,所以整体和第一次作业差别不大。

  • 调度器设计

    在第二次作业中,由于每个楼座中不止存在一个电梯,所以作业一中的分配策略就无法应对了。在编写调度之前也同其他同学进行了讨论,有的同学采用了算法分配,有的采用自由竞争,而我由于看到了指导书中提到 “在电梯的调度上,采用一种较为均衡的调度方式” ,于是采用算法分配。

    在请求进入后先由调度器分配到对应的楼座/楼层队列中,然后在队列中进行二次分配,假如A 座 7 层有5个乘客,3部电梯,那么第1个乘客分配给第1部电梯,第2个乘客分配给第2部电梯,第3个乘客分配给第3部电梯,第4个乘客分配给第1部电梯,第5个乘客分配给第2部电梯。

    这样的设计最大的优势是设计简单,不需要进行较多的代码编写与重构设计,只需要依靠上次作业的架构并简单添加即可。劣势则在于简单的分配策略并不能保证性能的要求,可能会在性能分上有所损失。

  • 调度策略

    纵向电梯依旧采用look策略,不再赘述。而横向电梯的设计策略比较费脑筋,在直观感觉上,哪怕是始终朝着同一个方向移动,由于是环形电梯且一共只有五个楼座,感觉性能上也不会损失太多。但是不设计调度策略总归感觉不太好,于是开始coding......设计出来的策略如下,在电梯内有乘客时,向乘客方向移动,同时实现同方向捎带;没有乘客时则向最近的一个方向运行。

第三次作业

  • 设计要求

    在前两次作业基础上,增加换乘请求。同时横向电梯选择性停靠,并不一定能够到达所有楼座。

  • 同步块设置与锁的选择

    同上

  • 调度器设计

    调度器设计整体与第二次作业相同,先由调度器分配到楼层/楼座中后再分配到各个电梯。但是由于设计要求的变化也做出了一定改变。

    首先是对于横向电梯,由于并不一定能到达每个楼座,所以也就不能简单的采用平均分配策略,因为你分配的电梯可能根本送不到对应楼层。这时就需要进行特判,将请求分配给能够送到的电梯中。

    但是这样又产生了另一个问题,平均分配策略失效了,因为存在特判,也就并不能保证同一楼层的电梯分配乘客数量的均衡。这时本人采用了比较分配的方法,再满足送达条件的情况下,优先将请求分配给请求数量较少的电梯,从而达到分配的相对均衡。

  • 转乘设计

    第三次作业中最核心的问题就在于转乘问题,本人在设计之初试图继续沿用以往的设计框架进行构造,但是发现实在无法实现,于是重写了PersonRequest类,增加了多个属性,如transfer属性用以标志是否需要转乘,transfering属性用以标志是否正在转乘。

    一个需要转乘的乘客路径大致可分为三段,初始楼座上下运行,转乘电梯横向运行,目标楼座上下运行。因此可以进行设计,先在初始楼座运行到转乘楼层,在由转乘电梯运送到目标楼座,最后在目标楼座内运送到目标楼层即可。

    这是宏观的设计,具体可能还有一些特殊的请求,比如转乘楼层即是目标楼层,那么本质上只有两段路要走等等,特殊处理即可。

二、度量分析

规模分析

 

 

可以看到,代码行数达到了一千二百行,可以说是一个较为庞大的数字了。主要行数贡献的类为RequestQueue类,该类中定义了横向,纵向调度策略,由于判定较为复杂从而导致了行数较多。其次是Elevator类和Conveyor类,代表了纵向电梯和横向电梯,其中的开关门判断,进人出人也使用了较多行数。

复杂度分析

classOCavgOCmaxWMC
Channel 4.2 11.0 21.0
Conveyor 2.9444444444444446 10.0 53.0
Elevator 2.6956521739130435 12.0 62.0
Input 4.125 10.0 33.0
MainClass 8.0 8.0 8.0
MyRequest 1.0454545454545454 2.0 23.0
OutputThread 1.0 1.0 3.0
RequestQueue 3.347826086956522 14.0 77.0
Total     280.0
Average 2.7184466019417477 8.5 35.0
MethodCogCev(G)iv(G)v(G)
RequestQueue.setTransferEnd(boolean) 0.0 1.0 1.0 1.0
RequestQueue.setReqNumber(int) 0.0 1.0 1.0 1.0
RequestQueue.setPersonRequests(ArrayList) 0.0 1.0 1.0 1.0
RequestQueue.setMark(int) 0.0 1.0 1.0 1.0
RequestQueue.setInputEnd(boolean) 0.0 1.0 1.0 1.0
RequestQueue.setEleNumber(int) 0.0 1.0 1.0 1.0
RequestQueue.RequestQueue() 1.0 1.0 2.0 2.0
RequestQueue.noty() 0.0 1.0 1.0 1.0
RequestQueue.isTransferEnd() 0.0 1.0 1.0 1.0
RequestQueue.isInputEnd() 0.0 1.0 1.0 1.0
RequestQueue.isEmpty() 0.0 1.0 1.0 1.0
RequestQueue.getReqNumber() 0.0 1.0 1.0 1.0
RequestQueue.getPersonRequests() 0.0 1.0 1.0 1.0
RequestQueue.getOneRequest() 6.0 2.0 4.0 6.0
RequestQueue.getMin() 3.0 1.0 2.0 3.0
RequestQueue.getMark() 0.0 1.0 1.0 1.0
RequestQueue.getEleRequests(int, int, int, boolean) 42.0 1.0 23.0 24.0
RequestQueue.getEleNumber() 0.0 1.0 1.0 1.0
RequestQueue.getConRequests(int, int, int, boolean) 42.0 1.0 23.0 24.0
RequestQueue.eleElse(int, int, boolean) 30.0 1.0 18.0 18.0
RequestQueue.dis(MyRequest) 0.0 1.0 1.0 1.0
RequestQueue.conElse(int, int, boolean) 30.0 1.0 18.0 18.0
RequestQueue.addRequest(MyRequest) 4.0 1.0 5.0 5.0
OutputThread.println(String) 0.0 1.0 1.0 1.0
OutputThread.OutputThread() 0.0 1.0 1.0 1.0
OutputThread.getInstance() 0.0 1.0 1.0 1.0
MyRequest.toString() 0.0 1.0 1.0 1.0
MyRequest.setTransferring(boolean) 0.0 1.0 1.0 1.0
MyRequest.setTransfer(boolean) 0.0 1.0 1.0 1.0
MyRequest.setToFloor(int) 0.0 1.0 1.0 1.0
MyRequest.setToBuilding(char) 0.0 1.0 1.0 1.0
MyRequest.setPersonId(int) 0.0 1.0 1.0 1.0
MyRequest.setFromFloor(int) 0.0 1.0 1.0 1.0
MyRequest.setFromBuilding(char) 0.0 1.0 1.0 1.0
MyRequest.MyRequest(int, int, char, char, int, boolean, int, ...) 3.0 1.0 1.0 3.0
MyRequest.isTransferring() 0.0 1.0 1.0 1.0
MyRequest.isTransfer() 0.0 1.0 1.0 1.0
MyRequest.getTransferId() 0.0 1.0 1.0 1.0
MyRequest.getTransferFloor() 0.0 1.0 1.0 1.0
MyRequest.getToFloor() 0.0 1.0 1.0 1.0
MyRequest.getToBuilding() 0.0 1.0 1.0 1.0
MyRequest.getPersonId() 0.0 1.0 1.0 1.0
MyRequest.getInitFloor() 0.0 1.0 1.0 1.0
MyRequest.getInitBuilding() 0.0 1.0 1.0 1.0
MyRequest.getFromFloor() 0.0 1.0 1.0 1.0
MyRequest.getFromBuilding() 0.0 1.0 1.0 1.0
MyRequest.getEndFloor() 0.0 1.0 1.0 1.0
MyRequest.getEndBuilding() 0.0 1.0 1.0 1.0
MainClass.main(String[]) 10.0 1.0 4.0 8.0
Input.run() 30.0 3.0 12.0 13.0
Input.newReq(Request, boolean, int) 1.0 1.0 2.0 2.0
Input.newEle(RequestQueue, Request) 0.0 1.0 1.0 1.0
Input.newCon(RequestQueue, Request) 0.0 1.0 1.0 1.0
Input.Input(RequestQueue, ArrayList, ArrayList, ArrayList, ArrayList, RequestQueue) 0.0 1.0 1.0 1.0
Input.ifTransfer(ArrayList, PersonRequest) 12.0 6.0 6.0 7.0
Input.getTransferId(ArrayList, PersonRequest) 21.0 6.0 10.0 10.0
Input.findConveyor(ArrayList, int) 3.0 3.0 3.0 3.0
Elevator.setPersons(RequestQueue) 0.0 1.0 1.0 1.0
Elevator.setNumber(int) 0.0 1.0 1.0 1.0
Elevator.setMain() 0.0 1.0 1.0 1.0
Elevator.setFloor(int) 0.0 1.0 1.0 1.0
Elevator.setElevatorQueue(RequestQueue) 0.0 1.0 1.0 1.0
Elevator.setDirection(int) 0.0 1.0 1.0 1.0
Elevator.setBalcony(char) 0.0 1.0 1.0 1.0
Elevator.run() 29.0 3.0 16.0 22.0
Elevator.personDir(MyRequest) 1.0 2.0 1.0 2.0
Elevator.peopleOut(RequestQueue) 26.0 7.0 9.0 10.0
Elevator.peopleIn(ArrayList) 25.0 1.0 8.0 10.0
Elevator.needOpen(ArrayList, RequestQueue) 13.0 5.0 6.0 9.0
Elevator.move() 6.0 1.0 5.0 5.0
Elevator.hasSameDir(ArrayList, MyRequest) 10.0 4.0 7.0 8.0
Elevator.getPersons() 0.0 1.0 1.0 1.0
Elevator.getNumber() 0.0 1.0 1.0 1.0
Elevator.getFloor() 0.0 1.0 1.0 1.0
Elevator.getElevatorQueue() 0.0 1.0 1.0 1.0
Elevator.getDirection() 0.0 1.0 1.0 1.0
Elevator.getBalcony() 0.0 1.0 1.0 1.0
Elevator.Elevator(RequestQueue, char, int, int, double, int, RequestQueue, ...) 0.0 1.0 1.0 1.0
Elevator.doorOpen() 1.0 1.0 2.0 2.0
Elevator.doorClose() 1.0 1.0 2.0 2.0
Conveyor.setMessage(int) 0.0 1.0 1.0 1.0
Conveyor.run() 24.0 3.0 16.0 18.0
Conveyor.personDir(MyRequest) 2.0 2.0 2.0 3.0
Conveyor.peopleOut(RequestQueue) 19.0 6.0 7.0 7.0
Conveyor.peopleIn(ArrayList) 27.0 1.0 10.0 12.0
Conveyor.needOpen(ArrayList, RequestQueue) 13.0 5.0 6.0 9.0
Conveyor.move() 6.0 1.0 5.0 5.0
Conveyor.hasSameDir(ArrayList, MyRequest) 10.0 4.0 7.0 8.0
Conveyor.getPersons() 0.0 1.0 1.0 1.0
Conveyor.getNumber() 0.0 1.0 1.0 1.0
Conveyor.getMessage() 0.0 1.0 1.0 1.0
Conveyor.getIndex() 0.0 1.0 1.0 1.0
Conveyor.getFloor() 0.0 1.0 1.0 1.0
Conveyor.getElevatorQueue() 0.0 1.0 1.0 1.0
Conveyor.doorOpen() 1.0 1.0 2.0 2.0
Conveyor.doorClose() 1.0 1.0 2.0 2.0
Conveyor.dis(MyRequest) 0.0 1.0 1.0 1.0
Conveyor.Conveyor(RequestQueue, int, int, int, double, int, int, ...) 0.0 1.0 1.0 1.0
Channel.setEnd(boolean) 0.0 1.0 1.0 1.0
Channel.run() 29.0 7.0 12.0 13.0
Channel.isEnd() 2.0 2.0 2.0 3.0
Channel.Channel(RequestQueue, ArrayList, ArrayList, ArrayList, ArrayList, RequestQueue) 0.0 1.0 1.0 1.0
Channel.balconyAdd(MyRequest) 5.0 1.0 6.0 6.0
Total 489.0 156.0 333.0 371.0
Average 4.747 1.514 3.233 3.601

可拓展性分析

  • 电梯属性(编号,载客量,速度,方向等)都设置为类内属性,可以较好的满足电梯自定义的要求。

  • 电梯行为(开关门,乘客进出,移动)都分别设计为独立方法,调用清晰。

  • 乘客请求也重新设置了新的类而非使用官方PersonRequest类,满足后续设计要求。

UML

 

 

三、Bug分析与测试策略

Bug分析

第一次作业中强测出现了一个RTLE错误,经查是由于采用的调度策略的设计缺陷造成,在所提交的代码中,在处理类似

1-FROM-C-5-TO-C-1

2-FROM-C-6-TO-C-1

3-FROM-C-7-TO-C-1

4-FROM-C-8-TO-C-1

5-FROM-C-9-TO-C-1

6-FROM-C-10-TO-C-1

的样例时,会先到五楼,然后送到一楼,再到六楼,然后送到一楼......没有能实现尽可能捎带的功能。

第二次作业在强测与互测中均未出现错误。

第三次作业则出现了许多的错误。首先是CTLE错误,由于在同一楼座内存在多个电梯时,会出现某一电梯在waiting而另一电梯在运行时发出了notify信号从而打断waiting造成轮询。第二时一个WA,这是由于在最后一个请求较为特殊时,就会出现奇怪的错误,由于先执行peopleout,再添加到等待队列,会导致最后一段纵向运行无法得到该请求从而送不到目的地。第三是最玄学的RTLE错误,我改了又改看了又看,忙活了好几天也并未解决这一问题,最后仍然有两三个点会出现RTLE,而且每次不固定,可谓是按下葫芦浮起瓢,弄得心力交瘁,实在改不出来就摆烂了,扣分也没办法了。

测试策略

在本单元的测试中,大致可分为两种方法,一种是暴力生成数据直接取跑其他同学的代码进行盲目性Hack,另一种是通过阅读代码构造边界数据,特殊数据进行目的性Hack,也就是所谓的黑盒测试和白盒测试。

在第一次作业中的测试数据都是自动生成的直接拿来用,然而hack效果不错,后来发现是输出线程不安全的问题,即使输出结果无误,输出顺序出现问题一样是WA。

第二次作业大家似乎都做得很好,在经过第一次作业的debug,第二次作业仅仅增加了横向电梯改动不大,造的数据也基本hack不到人。

第三次作业问题出现的就很多了,基本随便生成的数据都会hack到同学,这主要是因为第三次作业中的设计繁杂,细枝末节之处易出各种BUG,WA,CTLE,RTLE层出不穷,所以也就仅仅采用了自动生成数据暴力测试策略。

四、心得体会

老师在课上说:第二单元比第一单元简单些。但是实际感觉似乎并不符合,第一单元的困难主要在于从0-1的过程,从一个并未接触OO的情形直接面对作业的困难;而第二单元的困难更多体现于知识难度上,任务复杂上,无论是锁的问题,多线程的调试,复杂的转乘策略,无法复现的BUG都给我带来了巨大的挑战,尤其是第三次作业的难度,bug多而杂,实在是困难重重。

线程安全问题在多线程电梯的设计过程中理解加深,如何处理共享资源,如何加锁都给我带来了不小的收获。

总而言之言而总之,无论多么困难的任务总归都已经过去了,总结经验,面向下一单元的学习才是更加重要的事情了。

posted @ 2022-05-03 22:20  espressos  阅读(34)  评论(1编辑  收藏  举报