目录
- 设计策略
- 代码度量
- Bug分析
- 测试策略
- 心得体会
一、设计策略:
这三次作业我都采用了”生产者-消费者模式“,以调度器为生产者,电梯为消费者。不同之处在于第一次和第二次都只用了一个生产者-消费者队列,而第三次则使用了三个生产者-消费者队列。
为什么需要使用“生产者-消费者模式”?
因为在作业中,调度器产生 request 速度和电梯处理 request 的速度不相同,需要使用队列来作为缓冲器,平衡两者的处理速度。同时通过队列传递消息,避免调度器和电梯的直接交互,一方面可以解耦,另一方面也可以简化线程安全的实现,在这种模式下,由于不同线程只能通过队列进行消息传递,因此只需要保证队列的线程安全性即可。
二、代码度量:
类图:
第一次作业:
第二次作业:
第三次作业:
度量:
第一次作业:
Method ev(G) iv(G) v(G) Dispatcher.Dispatcher() 1 1 1 Dispatcher.addRequest(PersonRequest) 1 1 1 Dispatcher.getInstance() 1 1 3 Dispatcher.kill() 1 1 1 Dispatcher.patchRequest() 2 6 7 Dispatcher.resister(Elevator) 1 1 1 Dispatcher.run() 1 6 7 Elevator.Direction.Direction(int) 1 1 1 Elevator.Direction.getDirection() 1 1 1 Elevator.Elevator() 1 1 1 Elevator.close() 1 1 2 Elevator.determineDirection(int,int) 1 1 3 Elevator.getRequestQueue() 1 1 1 Elevator.move() 1 1 2 Elevator.open() 1 1 2 Elevator.passengerIn(int) 1 1 1 Elevator.passengerOut(int) 1 1 1 Elevator.pickPassenger(int) 1 2 3 Elevator.run() 3 3 4 Main.main(String[]) 3 2 4 RequestQueue.getRequest() 3 2 4 RequestQueue.isEmpty() 1 1 1 RequestQueue.kill() 1 1 1 RequestQueue.putRequest(PersonRequest) 1 1 1 Class OCavg WMC Dispatcher 2.57 18 Elevator 1.6 16 Elevator.Direction 1 2 Main 3 3 RequestQueue 1.5 6 第二次作业:
Method ev(G) iv(G) v(G) Direction.Direction(int) 1 1 1 Direction.getDirection() 1 1 1 Dispatcher.Dispatcher() 1 1 1 Dispatcher.addRequest(MyRequest) 1 1 1 Dispatcher.getInstance() 1 1 3 Dispatcher.kill() 1 1 1 Dispatcher.patchRequest() 1 2 2 Dispatcher.resister(Elevator) 1 1 1 Dispatcher.run() 1 6 8 Elevator.Elevator(int,int,int,int) 1 1 1 Elevator.arrive() 1 1 1 Elevator.close() 1 1 1 Elevator.determineDestination(ArrayList) 3 2 7 Elevator.determineDestination(int,int) 1 1 2 Elevator.determineDirection(int) 1 1 2 Elevator.getRequestQueue() 1 1 1 Elevator.getSame() 1 2 6 Elevator.isOut() 1 3 3 Elevator.move() 1 1 6 Elevator.open() 1 1 2 Elevator.passengersIn(ArrayList) 1 2 2 Elevator.passengersOut(ArrayList) 1 2 2 Elevator.run() 5 7 10 Main.main(String[]) 3 2 4 MyRequest.MyRequest(PersonRequest) 1 1 1 MyRequest.equals(Object) 2 1 2 MyRequest.getDirection() 1 1 2 MyRequest.getFromFloor() 1 1 1 MyRequest.getPersonId() 1 1 1 MyRequest.getToFloor() 1 1 1 MyRequest.hashCode() 1 1 1 MyRequest.toString() 1 1 1 RequestQueue.getRequest(int,int,int) 3 2 4 RequestQueue.getSame(int,Direction) 1 8 10 RequestQueue.isEmpty() 1 1 1 RequestQueue.kill() 1 1 1 RequestQueue.putRequest(MyRequest) 1 1 1 RequestQueue.tryGetRequest(int,Direction) 1 3 3 Class OCavg WMC Direction 1 2 Dispatcher 1.86 13 Elevator 2.64 37 Main 3 3 MyRequest 1.25 10 RequestQueue 3.17 19 第三次作业:
Method ev(G) iv(G) v(G) Direction.Direction(int) 1 1 1 Direction.getDirection() 1 1 1 Dispatcher.Dispatcher() 1 1 1 Dispatcher.Node.Node(int,int) 1 1 1 Dispatcher.addRequest(MyRequest) 1 1 1 Dispatcher.addTransfer(MyRequest,int,RequestQueue,RequestQueue) 1 1 1 Dispatcher.divReq(MyRequest,ArrayList) 7 14 19 Dispatcher.getInstance() 1 1 3 Dispatcher.kill() 1 1 1 Dispatcher.nodeInit(ArrayList) 3 2 4 Dispatcher.patchRequest() 1 3 3 Dispatcher.resister(Elevator) 1 1 1 Dispatcher.run() 1 6 8 Dispatcher.trySimPat(MyRequest,ArrayList) 3 4 4 Elevator.Elevator(String,int,int,int,int,int,TreeSet) 1 1 1 Elevator.arrive() 1 1 1 Elevator.close() 1 1 1 Elevator.determineDestination(ArrayList) 3 2 7 Elevator.determineDestination(int,int) 1 1 2 Elevator.determineDirection(int) 1 1 2 Elevator.getPasNum() 1 1 1 Elevator.getRequestQueue() 1 1 1 Elevator.getSame() 1 2 6 Elevator.getStopFloors() 1 1 1 Elevator.isOut() 1 3 3 Elevator.move() 1 1 6 Elevator.open() 1 1 2 Elevator.passengersIn(ArrayList) 1 2 2 Elevator.passengersOut(ArrayList) 1 2 2 Elevator.run() 5 8 11 Main.init() 1 6 6 Main.main(String[]) 3 2 4 MyRequest.MyRequest(PersonRequest) 1 1 1 MyRequest.MyRequest(PersonRequest,MyRequest) 1 1 1 MyRequest.equals(Object) 4 1 4 MyRequest.getDirection() 1 1 2 MyRequest.getFromFloor() 1 1 1 MyRequest.getOut() 1 1 2 MyRequest.getPersonId() 1 1 1 MyRequest.getToFloor() 1 1 1 MyRequest.hasArrived() 1 1 1 MyRequest.hashCode() 1 1 1 MyRequest.toString() 1 1 1 RequestQueue.getReqNum() 1 1 1 RequestQueue.getRequest(int) 5 3 7 RequestQueue.getSame(int,Direction) 3 8 11 RequestQueue.kill() 1 1 1 RequestQueue.putRequest(MyRequest) 1 1 1 RequestQueue.tryGetReq(int,Direction,int,int) 3 4 6 Class OCavg WMC Direction 1 2 Dispatcher 3.09 34 Dispatcher.Node 1 1 Elevator 2.5 40 Main 4.5 9 MyRequest 1.45 16 RequestQueue 4 24
UML协作图:
因为我三次使用的都是生产者-消费者模式,就只给出一个协作图,避免冗余。
设计分析:
这三次的作业基本上遵守了开闭原则,并且由于调度器和电梯解耦的程度比较高,我每次增添新需求的时候改动的代码都极少、增加完代码后需要修补的Bug也很少,因此作业完成的速度很快。
但同时也有不少问题:
首先,第一次我在楼层、开关门时间、运行时间上都使用了硬编码,这是在写面向对象程序时非常致命的问题,往后写程序的时候也要注意这个问题。
其次,从度量数据可以看出来,在第二、第三次作业中,Elevator和Dispatcher类的复杂度都是极高的,这是由于大量的任务都积累到这两个类来完成,而不是采用拆分的方式去完成。这就导致了代码比较复杂,可读性和维护性都有所下降,并且我目前也没有想到很好的方法去解决。
三、Bug分析:
由于我在写代码之前就详细分析过了线程之间交互的方式,并且严格控制共享对量及其访问途径,因此公测和互测的时候均没有被找出Bug。
同时我写完代码测试的时候也基本上没有出现过Bug,唯一的一个Bug还是因为修改代码的时候不小心把synchronized给删了导致的。
多线程Debug是十分困难的,因此我非常推崇在写代码之前就仔细分析好共享变量的问题,在写代码之前就预防Bug的出现,要比在写完代码之后痛苦地Debug要好很多,至少对于多线程编程而言是如此。
四、测试策略:
我无论是测试自己的代码还是测试互测时同组人的代码,都是用评测机自动生成请求、自动评测的,这一做法虽然并不高明,可能一些隐蔽的问题需要很长时间的测试才能测出来,甚至由于平台、负载的原因无法测出来。但是它能够基本解放人的劳动,提高效率。
因为有评测机,我就没有结合他人的具体代码去设计测试用例了,同时我使用的测试方法与第一单元相同——都是使用评测机。
五、心得体会:
- 线程安全方面:
- 首先是对共享变量的访问控制问题,尽量文档化共享变量可能的访问途径,并且控制访问途径,以简化线程安全性的分析。
- 注意相互依赖状态的修改问题,相互依赖的状态不能分开同步。
- 要善于使用 volatile 和 final 来解决同步性的问题,不要无脑使用 synchronized,要意识到对于多线程的性能优先原则。
- 牢记 Java 虚拟机提供给并发编程人员的保证—— happens-before 原则。
- 设计原则方面:
- 在设计原则方面,因为自己犯了硬编码的错误,因此我对这个问题深有感触。在面向对象程序设计过程中,要时刻意识到需求是会不断变化的,通过可变状态来控制行为无论是从可维护性上还是从可读性上都要远远好过硬编码的方式。
- 遵守开闭原则,许多的设计模式都是以此为基础的,面向不同的需求、可能的扩展需求和程序的实现方式要灵活地采用设计模式。