OO第二单元博客总结
前言
一些概念简述:
ev(G)基本复杂度是用来衡量程序非结构化程度的,非结构成分降低了程序的质量,增加了代码的维护难度,使程序难于理解。因此,基本复杂度高意味着非结构化程度高,难以模块化和维护。实际上,消除了一个错误有时会引起其他的错误。
iv(G)模块设计复杂度是用来衡量模块判定结构,即模块和其他模块的调用关系。软件模块设计复杂度高意味模块耦合度高,这将导致模块难于隔离、维护和复用。模块设计复杂度是从模块流程图中移去那些不包含调用子模块的判定和循环结构后得出的圈复杂度,因此模块设计复杂度不能大于圈复杂度,通常是远小于圈复杂度。
v(G)是用来衡量一个模块判定结构的复杂程度,数量上表现为独立路径的条数,即合理的预防错误所需测试的最少路径条数,圈复杂度大说明程序代码可能质量低且难于测试和维护,经验表明,程序的可能错误和高的圈复杂度有着很大关系。
OO第二次作业:模拟电梯调度
第一次作业:单部多线程傻瓜调度(FAFS)电梯的模拟
1. 程序简介:
基本结构:
- 类的个数:4个;方法个数:10个;线程个数:3个
- 由于思路过于简单,仅仅是输入线程负责实时输入,调度器线程负责每拿到一个任务后直接分配给电梯,电梯线程只需要根据被分配的任务去执行就可以了,就不在此赘述。
2. 结构分析
- 复杂度分析
可以看出,总体的复杂度并不是很高
第二次作业:单部多线程可捎带调度(ALS)电梯
1. 程序简介:
可以看到,这次的代码规格并不是很大,只有500行不到的代码量。
基本结构:
ALS(可捎带电梯)规则介绍
可捎带电梯调度器将会新增主请求和被捎带请求两个概念
主请求选择规则:
如果电梯中没有乘客,将请求队列中到达时间最早的请求作为主请求
如果电梯中有乘客,将其中到达时间最早的乘客请求作为主请求
被捎带请求选择规则:
电梯的主请求存在,即主请求到该请求进入电梯时尚未完成
该请求到达请求队列的时间小于等于电梯到达该请求出发楼层关门的截止时间
电梯的运行方向和该请求的目标方向一致。即电梯主请求的目标楼层和被捎带请求的目标楼层,两者在当前楼层的同一侧。
其他:
标准ALS电梯不会连续开关门。即开门关门一次之后,如果请求队列中还有请求,不能立即再执行开关门操作,会先执行请求。
------引用自《面向对象电梯系列第二次指导书》
本次电梯的构造,我采用的方式不完全为ALS制度,在ALS的基础上进行了一定程度的更改。
首先我对于一个已有的任务,我会分析电梯的状态:
- 如果电梯此时已经停止,我会将处于调度器请求队列中的第一个请求交给电梯,并且遍历整个的调度器请求队列,将所有与该请求运行方向一致的任务交给电梯的请求队列。
- 如果电梯正在运行,且正在载客,则遍历整个的调度器请求队列,将所有与电梯运行方向一致且起始楼层在路上的任务交给电梯的请求队列。
- 如果电梯正在运行,且正在前往载客,则遍历整个的调度器请求队列,将所有与电梯运行方向一致且起始楼层在路上,而且目标楼层也在路上的任务交给电梯的请求队列。
其次我认为这次的设计的一个重点为如何让电梯停下来。我的处理方式为,在电梯线程中增添一个busy的状态,并对外提供检查电梯是否停止运行的接口。主调度器在轮询到来请求的时候,会增加输入是否停止 (即是否读到EOF)的判定。如果检测到输入EOF,则将stop相应的置位。此时只要等到电梯的运行结束,就可以停止运行。
2. 结构分析
- 复杂度分析
- 方法复杂度
- 类复杂度
3. 自我评价
可以看出来,我的elevator里面的flow是一个重灾区。事实上也是这样的。在第三次作业的时候,我甚至强行拼凑出60行的极限操作。究其原因,其实是flow这个函数承担了一个无法分割而且功能性很强大的任务。这个函数是负责在每一层的人员流动 flow。由于我的电梯中的任务是在电梯不改变方向执行主任务的时候一定可以全部完成的,所以我需要在每到一个新楼层的时候就遍历一遍调度器的请求数组,将能捎带的进行捎带;再遍历本电梯的请求队列,如果可以上下电梯,则开门,进行上下人流的处理。同时开门等待0.4s之后需要再次遍历调度器的请求队列,以防有刚到来的请求被遗弃了。所以就导致有很多的循环,很多的特判,于是就发生了复杂度相当高的情况。
但是换一个角度想想,这说明我的elevator函数自己具有相当强的独立性。
4. 分析自己的bug与发现别人的bug
这次的程序出现了特别严重的错误,本人在这次强测中仅仅得到了65分,当时看到这个分数的时候整个人都自闭了将近两天。其实对于这次作业的debug心得,我自己着实没有多少自己的见解,而且容易发生bug的问题也很少见到共性,往往是由于不同的架构而产生的不同的漏洞,所以在这里就不再赘述。
本次修复只修复了一个问题:关于在电梯运行到目标楼层的过程中的捎带问题。形如:
1-FROM-5-TO-1
2-FROM-1-TO-5
这样的问题,这个时候1为主请求,但是到1的过程中可以捎带2请求。 原来的问题为,会捎带所有的同向请求,如:
3-FROM-1-TO-8
也会被加入电梯请求队列,就导致出现了电梯无限往上或无限往下走的现象。
本次主要更改了对于getBring函数的判断,执行的位置,同时又新加了一个getBring函数,以此来更改上述问题。
主要体现为,在Scheduler中启动刚开始的getbring删除,在电梯到每一层的时候优先判定是否有捎带请求,并更换电梯上下方向判定方法以适应getBring函数
第三次作业:多部多线程智能(SS)调度电梯
1. 程序简介:
- 基本结构:
2.结构分析
3. 自我评价
这次的代码复杂度确实是相当的重灾区。首先是elevator里面的flow函数,在第二次作业的基础上,又增加了其功能性,导致其复杂度进一步提升。其实我这两次的作业都有过这个问题,对于某一个方法,或者说,某一个核心的功能需要过多的代码,然而其中需要很多的变量又很难分割,强行分割的话,每个方法的功能性又不独立,这其实也是我一直想要了解其他人怎么解决的。
另外一个重灾区就是很多的判断捎带的函数。这些函数往往都需要很多很多的循环和判断,复杂度自然相当的高
我想在这里介绍一下我的Request类,我觉得自己设计的还是蛮巧妙的,可以稍微介绍一下。
划重点
这个类的功能是在personRequest上面再进行一层封装,核心思想是让request自动对外显示换乘时需要的fromfloor和tofloor。具体的做法是:
在request里面涉及一个tempFloor,里面存的是需要换乘的楼层,如果不需要换乘的话就是0。不得不说,没有0层真的是帮了大忙。其次,另设一个boolean型变量transfer,用来记录这个请求是否已经换乘。所以当需要知道request的fromfloor和tofloor的时候,就会根据当前的transfer情况以及tempFloor的情况,判断出当前的电梯换乘情况,从而自动的显示出当前所需要的fromfloor和tofloor。
int getFromFloor() {
if (tempFloor != 0 && transfer) {
return tempFloor;
}
return request.getFromFloor();
}
int getToFloor() {
if (tempFloor != 0 && !transfer) {
return tempFloor;
}
return request.getToFloor();
}
这个时候就有人问了,有的请求你怎么确定要不要换乘呢?怎么确定换乘的楼层呢?
我另外设立了一个setTempFloor方法,这个方法会根据具体的电梯来确定,也就是根据传入的电梯,来确定当前的换乘楼层。比如有一个5-8层的请求,如果只有C电梯的话,就只需要在7楼换乘,而如果是B电梯就自然可以直达了。
4. 分析自己的bug与发现别人的bug
在本地测试的时候,经常有一些我没有想到的bug会出现,而且往往都会隐藏的很深很深。比如我的C电梯在调度 5-2 这种请求的时候,会判断为在3楼为换乘楼层,这样就会导致该请求永远无法再次换乘。但是由于大部分情况下,我的5-2这种的请求都会被B电梯直接的接走。所以说这次的电梯的调度最重要的debug方面应该为在不同的运行环境下测试同一个请求。
同时,困扰我很久的另一个问题就是如何让电梯停下来。由于我的处理换乘的方法是,如果换乘之后没有到达目的地的话,就将该请求的transfer修正为1并且将其放回调度器的请求队列,所以输入EOF之后,调度器的请求队列也有可能接到电梯给出的新请求。经过认真的思索可以发现,整个程序停止运行当且仅当所有的请求队列(主调度器的请求队列以及三个电梯里的请求队列)均为空。在整个程序停止之前,所有的电梯都不能停止运行,因为不知道是否有其他的请求(换乘请求)会再次分配给该电梯。所以我在主调度器里设立了另一个轮询的死循环。
if (sstop || canStop()) {
while (sstop && !canStop()) {
try { wait(100); }
catch (Exception e) { e.printStackTrace(); }
}
if (canStop()) {
elevator1.stopElevator();
elevator2.stopElevator();
elevator3.stopElevator();
notifyAll();
break;
}
}
通过在其他的电梯完成每一个请求之后,判断该请求是否需要换乘,如果还需要换乘,并且主调度器的sstop被设立为了1,则重置sstop,以此来预防换乘还未完成,电梯就停止运行的情况发生。
整体评价---写在最后的话
这次是我第一次接触多线程的作业。说实在的,对于线程自认为掌握的还是不太牢固。作业要求从多线程的协同和同步控制方面,分析和总结自己三次作业的设计策略,这里我来发表一下我浅薄的看法。
其实我在本次作业的过程中,经历过很多很多的死锁的情况,究其原因是我对于线程安全的问题太过于恐惧,从而导致我的几乎所有方法都被上了锁。这在第一次作业不明显,但是第二次作业我就删了一定数量的synchronize。至于到了第三次作业,我真的是上锁一时爽,删锁更加爽删了很多很多的锁。不然的话,三个电梯之间完全无法跑起来。而且是那个时候我才了解到,当在一个加锁的方法中调用另一个方法,被调用方法也拥有调用方法所拥有的锁。对于同步的控制,其实我对于wait的用法也不是掌握的很透彻,经常乱加notifyall,最后还是通过大量的100ms的轮询来实现的。
其实对于这次的电梯设计,我了解到一件事情,那就是不要害怕代码的复杂度。由于每一次电梯会开关门0.4s,上下楼甚至需要更长时间,相比来说,遍历一次请求列表所需要的时间反而就不足挂齿了。我原先总是想着尽量少的遍历,能遍历一次绝不遍历第二次,就导致很大的代码复杂度,其实完全不需要这样的,甚至可以做到在每一层都进行一次遍历。