OO第二单元总结

HW1

思路

本次作业,需要完成的任务为单部多线程可捎带电梯的模拟。

IO处理中,本次作业使用官方包,不需要进行格式检查。

电梯系统可以采用任意的调度策略,基于LOOK算法,本次作业中的调度策略思路为:

如果电梯中有乘客,电梯按照原始方向继续运行,捎带中途所有乘客;

如果电梯中没有乘客,电梯运行到当前有请求的最近楼层或停止运行等待请求;

当电梯所移动的方向上不再有请求时立即改变运行方向。

本次作业采用生产者消费者模式:

Scheduler类维护了共享的请求队列,以及volatile类型的end flag

Input类为生产者,读入Passenger请求,加入请求队列;

Elevator类为消费者,取出Passenger请求,加入电梯内部的Passenger队列。

程序结构

分析工具:UML,MetricsReloaded,Statistic,DesigniteJava

UML分析

Class Diagram

1-1

Passenger封装了以官方包中PersonRequest为基础的一些新操作,方便之后的迭代开发。

Scheduler类为共享对象,共享请求队列与结束标志变量,有基于调度策略的methods

Elevator类作为消费者,包含描述电梯状态的各种变量,封装了电梯的基本运行、调度运行methods。其中维护了电梯内PassengerSet,使用其有序性方便电梯调度。

Input类作为生产者,调用官方输入包读入请求。

Main中,创建了共享Scheduler对象的一个生产者线程和一个消费者线程。

Sequence Diagram

Screen Shot 2020-04-15 at 10.51.43 PM

代码规模分析

Total LOC analyzed: 295

Number of classes: 5

Number of methods: 30

Screen Shot 2020-04-15 at 9.52.28 PM

属性和方法规模见Class Diagram

复杂度分析

Screen Shot 2020-04-15 at 10.01.30 PM

其中OCavg代表类的方法的平均循环复杂度,WMC代表类的方法的总循环复杂度。

Screen Shot 2020-04-15 at 9.59.27 PM

其中Essential Complexity (ev(G)、Module Design Complexity (iv(G))、Cyclomatic Complexity (v(G)。

总体上代码复杂度低。Elevator类中的run方法进行了结束判断以及等待请求判断。这里的等待请求处理利用了阻塞容器的方法,增加了一些判断,造成了复杂度高。实际上到后边的作业发现还是需要自己扩展取请求方法。

Type Name Method Name Implementation Smell Cause of the Smell
Elevator updateState Complex Conditional The conditional expression ((boardList.first().getToFloor() > currentFloor) && (currentState == -1)) || ((boardList.last().getToFloor() < currentFloor) && (currentState == 1)) is complex.

updateState这一method中判断电梯是否需要转换方向这一判断条件复杂。

HW2

思路

本次作业,需要完成的任务为多部多线程可捎带调度电梯的模拟。另外,相比第一次作业,增加了电梯的载客量限制。

思路和HW1基本相同,沿用HW1的架构。增加了ElevatorControl类,用于创建多部电梯。

程序结构

UML分析

Class Diagram

Screen Shot 2020-04-15 at 10.33.58 PM

ElevatorControl类根据输入的电梯数创建多部电梯。

Elevator类中增加了capacity属性,只需在进入电梯的boarding方法中加入容量限制即可。

其他类同HW1

Sequence Diagram

Screen Shot 2020-04-16 at 12.16.48 AM

代码规模分析

Total LOC analyzed: 353

Number of classes: 6

Number of methods: 33

属性和方法规模见Class Diagram

复杂度分析

Screen Shot 2020-04-15 at 11.10.56 PM Screen Shot 2020-04-15 at 11.11.43 PM

整体在HW1基础上修改不多。

Elevator类中的run方法进行了结束判断,当电梯内无人且输入结束且请求队列为空时结束,这里复杂度高。但是相比HW1,封装了取出请求的调度方法,复杂度有所下降。

当电梯运行到每一层,都会调用Scheduler类中的boarding方法进行捎带。此方法中遍历请求队列加入可稍带对象,此时复杂度高,在后续的作业中使用map容器,以floorkey,可以简化遍历操作。

Type Name Method Name Implementation Smell Cause of the Smell
Elevator updateState Complex Conditional The conditional expression ((boardList.first().getToFloor() > currentFloor) && (currentState == -1)) || ((boardList.last().getToFloor() < currentFloor) && (currentState == 1)) is complex.

同HW1。

HW3

思路

本次作业,需要完成的任务为多部多线程可捎带调度电梯的模拟。相比上一次作业,增加了实时增一部可以使用的电梯的功能,并且本次多部电梯的可停靠楼层,运行时间,最大载客量不同的固定值

沿用上一次作业的架构,另外:

将请求以queue修改为请求map,以FromFloorkey,简化调度相关的methods

由于存在中转乘客,Elevator类既是生产者,又是消费者。中转乘客在第一程结束后出电梯,产生第二程新请求加入请求队列;

对于中转的处理:

根据电梯的可达楼层可发现中转乘客仅需中转一程;

不预先规划路线,根据实时电梯运行情况进行捎带(即不更改捎带规则),之后随机选择中转站避免电梯负荷不平均;

设置passengertypedirectWaynextRoute属性描述passenger的中转信息。属性使用one-hot编码方便计算。

修改结束标志。设置cnt记录目前请求数量,中转乘客出电梯时不减cnt(中转乘客的第二程一定为直达类型);

封装官方输出包为线程安全包。

程序结构

UML分析

Class Diagram

Screen Shot 2020-04-15 at 11.52.04 PM

Scheduler类为共享对象,共享请求请求hashmapInput线程结束标志变量、Elevator线程结束标志变量。增加types属性用于调度中的捎带方法。

Elevator类作为生产者和消费者。

Input类作为生产者,调用官方输入包读入请求。

ElevatorControl类增加实时添加一部电梯方法。

其他同HW2。

Sequence Diagram

Screen Shot 2020-04-16 at 12.23.49 AM

代码规模分析

Total LOC analyzed: 532

Number of classes: 7

Number of methods: 48

Screen Shot 2020-04-16 at 12.29.34 AM
Type Name Method Name LOC CC PC
SafeOutput println 3 1 1
Scheduler Scheduler 9 2 0
Scheduler setElevatorEnd 4 1 0
Scheduler getEnd 3 1 0
Scheduler setEnd 4 1 0
Scheduler updateTypes 13 3 2
Scheduler biType 11 3 1
Scheduler add 7 1 2
Scheduler boarding 29 8 3
Scheduler findNearest 23 5 3
Scheduler toString 3 1 0
Scheduler isEmpty 3 1 0
Scheduler isEmpty 8 3 2
ElevatorControl ElevatorControl 7 1 1
ElevatorControl createElevator 19 4 3
ElevatorControl runDefault3Elevators 6 2 0
ElevatorControl addnRun 5 1 2
Elevator Elevator 14 1 8
Elevator getnickname 3 1 0
Elevator toString 3 1 0
Elevator printState 8 2 2
Elevator printState 8 2 3
Elevator aMove 12 3 0
Elevator sleep4awhile 8 1 1
Elevator rush 17 5 1
Elevator in 14 4 1
Elevator out 19 5 0
Elevator update 14 3 0
Elevator updateState 18 5 0
Elevator run 20 4 0
MainClass main 9 1 1
Input Input 5 1 2
Input run 26 5 0
Passenger Passenger 14 3 4
Passenger getType 3 1 0
Passenger isdirect 3 1 2
Passenger isinrange 3 1 3
Passenger addTransit 12 3 3
Passenger splitRoute 6 1 3
Passenger getDirectWays 3 1 0
Passenger getNextRoute 3 1 0
Passenger getFromFloor 3 1 0
Passenger getPersonId 3 1 0
Passenger getToFloor 3 1 0
Passenger equals 10 3 1
Passenger hashCode 3 1 0
Passenger compareTo 9 3 1
Passenger toString 3 1 0

复杂度分析

Screen Shot 2020-04-16 at 12.30.19 AM Screen Shot 2020-04-16 at 12.30.49 AM

飘红情况同HW2,另外,由于在Scheduler类的增加封装了调度的方法,Elevator类的复杂度相比HW2稍有下降。

Type Name Method Name Implementation Smell Cause of the Smell
Elevator updateState Complex Conditional The conditional expression ((boardList.first().getToFloor() > currentFloor) && (currentState == -1)) || ((boardList.last().getToFloor() < currentFloor) && (currentState == 1)) is complex.
Input run Long Statement The length of the statement "scheduler.add(new Passenger(((PersonRequest)request).getPersonId()'((PersonRequest)request).getFromFloor()'((PersonRequest)request).getToFloor()`1)'1);" is 151.
Scheduler isEmpty Complex Conditional The conditional expression (!storageTakers.get(key).isEmpty()) && (floors.contains(key)) && ((biType(key) & (tp)) != 0) is complex.isEmpty

第一条同HW2。

中转乘客结束第一程后会增加新的第二程请求,为了重用Passenger的构造函数,更改参数列表后,对于输入中的创建请求语句长度过长。这里可以override

在调度策略中,当电梯为空时,将寻找最近请求楼层,此调度方法中就包含了复杂判断:此楼层存在请求且此楼层为可达楼层且此楼层中请求可稍带。

总结

Test n Bugs

测试方法

本单元的测试方法和上一单元有所不同,设置断点的方法往往不能发现线程安全问题,所以主要采用System.err.println通过输出信息进行debug

对于一个bug,通过批量随机测试+重复测试先判断此bug为线程安全性bug或非线程安全性bug。非线程安全性bug按照以往经验定位修改即可;由于线程安全问题的不可复现性,通过随机测试发现总结错误特点,将涉及共享资源部分逐步检查是否存在不安全问题。

  • 基础性测试

构造所有可能出现的进出楼层电梯搭配进行基础性测试。

  • 自动化测试

使用bash,python package subprocess、random、signal进行随机测试用例生成、合法检查。

生成测试用例并结合重定向运行代码;

with subprocess.Popen([r'java', r'-jar', r'Elevators.jar'],
                      stdin=subprocess.PIPE,
                      text=True,
                      stdout=open(r'stdout.txt', "w"),
                      stderr=open(r'stderr.txt', "w")
                      ) as subpcs:
    covernRun(subpcs, 'test.txt')

按照指导书中的正确性要求进行检查;

保存结果到文件。

测试情况

  • 互测中主要采用自动化测试的办法。在第一次作业中,hack到电梯层数的bug(floor=0)。
  • 在第三次作业中,被hack到关于结束标志设置的bug,当所有乘客请求均满足的一段时间后以增加电梯请求为输入结束,这时我的程序无法结束。是因为我在调度算法的“当电梯为空则寻找最近请求”这一方法中的结束标志设置不完全。这里出现问题也因为盲信自动随机评测,没有手动构造有针对性的覆盖性测试用例。比如关于程序的结束是一个重要问题点,对于这一点应进行重点测试。
  • 在第三次作业中room内大多数同学都hack到了bug,但是我跑的自动评测机一直没有hack成功,后来总结发现手动设计测试用例的重要性。

SOLID原则

设计原则:关于设计的整体要求和约束,通过满足设计原则来获得好的设计质量。

设计原则是对OO设计思维的整体要求。

SRP

Single Responsibility Principle每个类或方法都只有一个明确的职责,所以类所管理的数据应“聚焦”。

Elevator类管理电梯的状态,实现了电梯简单的开关门,上下人,上下运行的方法。Scheduler类负责调度的方法,职责划分明确,互不影响。

OCP

Open Close Principle无需修改已有实现,而是通过扩展来增加新功能。

这次电梯的设计可扩展性较好,三次作业均改动不多,沿用架构。总体架构基于生产者消费者模式。

Scheduler类维护共享资源,根据调度策略可扩展相关调度方法;结束标志也在三次作业中体现了扩展性。

Input类作为生产者,调用官方输入包读入请求,当输入请求类别增加时,Passenger类相应扩展了其构造函数。

Elevator类作为生产者和消费者,当电梯增加新维度的约束时可在此类增加属性和方法,电梯参数在构造方法时传入,方便新增电梯。

LSP

Liskov Substitution Principle任何父类出现的地方都可以使用子类来代替,并不会导致使用相应类的程序出现错误。

本次作业中Passenger类封装了官方包中PersonRequest,相当于继承了此类,并实现了Comparator接口,用于建立电梯内乘客的有序set

ISP

Interface Segregation Principle通过接口来建立行为抽象层次具有更好的灵活性。

本次作业中没有使用接口。

DIP

Dependency Inversion Principle通过接口来建立行为抽象层次具有更好的灵活性。

输入和输出模块互不依赖,各个电梯互相独立。

但是对于调度器和电梯的通信应通过一个消息类抽象,而不是具体的容器。

心得体会

线程安全

在HW1的自动测试中就发现了线程安全问题的不可复现性,因为习惯于断点定位的debug pattern,第一次main对此就有些措手不及,但是随着后边几次作业测试中陆续遇到了线程安全问题进行debug,发现多线程的安全性问题从测试结果中就可大致定位,并且代码中对于共享资源的维护与访问一共也不多,逐部排查总能发现(go off my loop _)。另外,线程安全问题也在很大程度上依赖于手动设计魔鬼测试去发现,在这一点上本次作业做得还不够(比如自己写着写着第三次作业发现结束信号设置的有问题-> 我是怎么通过第二次作业的 -> 第二次作业中没有构造这种测试情况用例进行测试)。

System.err.println真的很好用。

我遇到的线程安全问题有:

电梯在“空时寻找最近请求”中轮询。后修改了进入寻找的条件以及增加wait。

运行不结束。后修改了结束标志,通过输入请求结束标志、电梯为空、请求队列为空、电梯线程结束标志协同控制。

当无输入请求时,电梯在“空时寻找最近请求”中死锁等待。后修改了进入等待的条件。

设计原则

多线程部分总体设计比较简单,在实现安全性时只需要维护共享资源的安全性。

我认为要遵守OCP原则,SRP原则是很重要的一点。在第一次设计时就将职责合理划分,输入、调度、电梯,于是可扩展性就很好。

posted @ 2020-04-17 20:51  bambambing  阅读(143)  评论(0编辑  收藏  举报