oo第二次总结

第五次作业:

         第五次作业以前几次的电梯作业为基础,改变成为三部电梯同时运行的模式,电梯的调度策略与前两次作业完全相同,由于是第一次多线程作业,所以上手难度相对较大。这次作业虽然沿用了之前的调度策略,但是在实现上与前两次作业有着巨大的差距,由于采用了实时模拟运行的方式,使得之前的调度器,请求队列的意义发生了巨大的变化。前一次作业中,请求队列负责保存所有的请求,调度器几乎囊括了电梯的所有属性,控制电梯的状态和运行策略,而在本次作业中,调度器的职责退化为按照规则分配请求,请求队列退化为一个托盘,存放所有未被执行完的请求,而由电梯类自行根据请求规划行动方法。因此之前的代码几乎无法复用,这是一次全新的OO作业(笑)。由于需要采用可捎带的运行策略,电梯自身必将保有一个待执行的请求队列,电梯需要规划运行状态,控制输出,以及完成时间控制,因此承载了大量的功能,显得较为驳杂,代码量达到了惊人的500行。其实,对于每一部电梯,都相当于集合了前次作业的所有功能,因此仍应拆分成队列,调度器,电梯等多个类分别完成,减少这个类的负担。

  此次作业中,由于首次涉及多线程控制的问题,导致整体实现较为复杂,由于需要保证电梯和调度器在修改电梯状态时不发生冲突,调度器在执行分配前需要获得所有电梯以及请求队列的锁,所有线程竞争访问,没有使用notify语句,也就是说,所有现成的调度完全取决于jvm。因此由于线程运行的不确定性,在获得所有电梯以及请求队列的锁的过程中,会出现有些电梯状态还未被更新,导致请求分配,捎带判断出现异常。比如该考虑运动量的时刻,由于状态更新不同步,使得请求被直接分配给更新完处于个空闲状态下的电梯。为了解决这个问题,付出了至少100行代码的代价。

  

  本次作业虽然不能直接复用前一次的代码,但是由于运行原则一致,在方法上仍然可以复用,为了减少工作量,我直接使用了上一次的捎带判断函数,因此较大的圈复杂度和参数数量仍然是上一次作业的遗留问题。

关于设计原则

  由于是第一次多线程作业,实际上在设计过程中,还未引入设计概念,整体以完成功能为目标。在实现的过程中,在run方法下进行了大量的操作,比如状态转移,条件判断等等,这些应当被置于某个私有方法下,而不是一味地罗列在run方法中。此外电梯类的功能太过复杂,应该进行进一步的抽象。此外,在部分实现中,受困于c语言风格,使用数组隐式存储了请求以及电梯的状态,应当尽力改正。

Bug分析

         在本次作业中,由于疏忽导致在输出同质请求时,额外输出了一个井号,导致了一个bug,这种无谓的损失实在是令人惋惜。

第六次作业

  本次作业较为独特,其主要难度在于对于指导书的不理解,触发器逻辑等总体实现上较为简单。需要解决的问题一共有两个,一是文件访问的线程安全,二是多线程竞争访问的线程安全,对于第二种线程安全,虽然并未被提到,但是在前一次作业中已经涉及到了,比如对于请求队列的竞争访问,对于输出模块的竞争访问,因此在本次作业中就显得十分简单。本次作业的主要矛盾点在于,如何保证文件的线程访问安全,显然单纯的在SafeFile中将File类的所有方法加上synchronized并不能解决问题,因为每次访问文件时,都是新创建的SafeFile对象,多个线程并不能够构成竞争关系,感谢dalao的提携,为我提供了一种可行的解决方案,通过两层嵌套,使得多线程访问时,同一文件路径使用唯一的对象,使得多个线程文件操作出现竞争关系,实现线程安全。

基于度量的分析,

  本次作业由于有前一次度量分析的基础,在方法参数个数方面有了明显的改善,方法主要依赖的数据来源应该是对象本身提供的,若是大量的依赖与外部数据,类的封装性会遭到极大的破坏。

圈复杂度以及分支的降低主要是由于本次作业逻辑上的简化,但是循环判断也是本次作业不可避免的一个点,因此同样难以进一步优化

关于设计原则

   本次作业最主要的设计原则的违背来自于出现了god类,对于监视器和触发器我并没有采取有效的分离,导致由路径生成的pathtrigger集合了全部10中功能,而至于使用哪各功能取决于输入进来的字符串,会造成很大的浪费。

Bug分析

  由于使用了较为安全的文件操作,因此在测试中没有出现太多的奇怪现象,我的测试任务中出现了由于我手滑输入了额外的换行符,导致整个程序崩溃,是在是非常可惜。

第七次作业

         设计策略,本次作业中,由于出租车的行为模式与第五次作业中的电梯极为相似,因此最开始的想法是针对每一个出租车单开一个线程,采用完全相同的处理策略,但是100个线程在实际运行过程中,不但不能提升程序的运行效率,反而会由于频繁的上下文切换,导致整个程序开销增大,注意到所有出租车的初始运动时间与运动状态完全相同,在运动过程中所有状态切换的时间点都是200ms的倍数,因此选择采用单个线程的方法来控制所有的出租车。该线程以200ms为单位,更新时间,并传递给所有出租车,出租车按序完成自身状态的更新。

  由于本次作业同样采用实时模拟的运行方法,因此程序的运行时间会成为一个阻碍,若是运行速度太慢,会导致程序的输出时间与实际时间出现较大出入,不是非常合理,因此选择在初始化的过程中完成所有点的距离计算,在模拟运行时,以查表的方式获得路径,加速运算。

  关于线程安全,本次由于使用了最小时间单位的策略,因此所有出租车的状态必将是同步的,且系统时间仅仅有出租车系统决定,每当一次时间更新,更新完所有出租车的状态后,再notify调度器线程,完成剩余的请求分配工作。有效避免了类似于前文第五次作业中提到的问题。

 

基于度量的分析

         本次作业的圈复杂度主要来自于gui以及初始化过程中bfs计算所有两点之间距离的过程,平均方法长度等有了较大的改善,但是在调度器的run方法中还是出现了过多的分支判断,应该进一步整合。

关于设计原则

         本次设计中,由于课上已经介绍了基本的设计原则,在设计中避免了god类的出现,所有类的总长度控制在200行左右,由于调度器与出租车会出现较多的数据传递,使用共享类来传递这些数据,避免在方法中反复传送,非常繁琐。同时保护了出租车运行的安全性,降低了对出租车状态误操作的可能性。

Bug分析

在本次作业中,主要采取了覆盖性测试的方法对程序进行测试,针对bug树上的点进行构造,并多次尝试。在我的测试任务中,出现了边界出租车不移动的问题,可能是寻路机制有误所致,除此之外是一份非常优秀的代码。

 

心得与体会

  过去的三个星期是夜夜笙歌的三个星期,只要oo作业不截止,睡觉什么的就都是不存在的,但是这种训练的效果也是惊人的,对于多线程的安全问题,已经有了一个较为宏观的概念,有了一些具体的解决策略,互斥与同步已经融入到了代码的每一个角落。互测这种机制使得我们对于安全性的追求可谓是达到了顶峰,同时也感受到了需求与实现之间的巨大的鸿沟。希望能在接下来的几周中与同学们一起努力,携手度过最后的三次作业。

原文地址:https://www.cnblogs.com/t68i663/p/8977866.html