OO第二单元电梯胡乱分析

二次博客作业

摘要

  • 从多线程的协同和同步控制方面,分析和总结自己三次作业的设计策略。

  • 基于度量来分析自己的程序结构度量类的属性个数、方法个数、每个方法规模、每个方法的控制分支数目、类总代码规模计算经典的OO度量画出自己作业的类图,并自我点评优点和缺点,要结合类图做分析通过UML的协作图(sequence diagram)来展示线程之间的协作关系(别忘记主线程)从设计原则检查角度,检查自己的设计,并按照SOLID列出所存在的问题

  • 分析自己程序的bug分析未通过的公测用例和被互测发现的bug:特征、问题所在的类和方法特别注意分析哪些问题与线程安全相关关联分析bug位置与设计结构之间的相关性

  • 分析自己发现别人程序bug所采用的策略列出自己所采取的测试策略及有效性,并特别指出是否结合被测程序的代码设计结构来设计测试用例分析自己采用了什么策略来发现线程安全相关的问题分析本单元的测试策略与第一单元测试策略的差异之处

  • 心得体会从线程安全和设计原则两个方面来梳理自己在本单元三次作业中获得的心得体会

  • 第七次作业专栏

设计策略

第一次作业采用了标准的生产者消费者模型,为了提高运送速度,我将InputHandler和Elevator作为两个并发的线程。他们只竞争一个Queue资源,同步全部加在Queue上。锁的对象都是this,如果是多电梯显然效率会下降但是单电梯完全可以。

第二次作业仅仅在第一次作业的基础上增加了楼层判断。基本没有改变

第三次作业采用调度器上帝视角管理一切。三个电梯仅在运行层面并行。对于需要换乘的请求,调度器不断查看电梯队列第一个请求是否已被解决,若解决则添加第二个请求。在调度器队列,三电梯队列均为空且受到null信号时才杀死自己。这次的架构很安全,不会出现死锁,但是并行度渣的一批。故而考虑第二种架构

3-2架构依然使用生产者消费者模式,调度器,三个电梯均为线程,通过共享对象Queue链接。调度器拥有被拆解的请求。当第一个请求被解决的时候,Queue中对应元素被删除。调度器即可获得信息。若仅仅是取走第一个请求,则设置布尔变量为false表示其他电梯不可再取。电梯拥有自己的队列。取走queue的元素需要判断自己是不是可以,再判断bool是不是true。当调度器接收到null时,等自己调度队列为空,释放null到queue。电梯挨个结束。这种架构依然是生产者消费者。有一定的风险不安全。但是架构清晰。

度量分析

首先看一下整体架构

 

 

 

电梯和调度器相互拥有,耦合度非常大,十分不好

队列被我包装成了package可以理解为package就是queue

类复杂度分析

 

可以看到三个主要类elevator,queue,schedule均有较高的循环复杂度,三个类之间比较独立,但是方法比较麻烦

尤其是判断结束的过程,需要从调度器出发遍历三个电梯,深入访问电梯队列,再返回才能得到解果。并且判断结束的过程每次都要经历,十分浪费资源

 

 

 

调度器解析请求拆分为几个队列的这个方法条条爆红。这是因为我使用随机的办法不断的查询有没有电梯能搭载上乘客,并且调用的是电梯这个类,因此牵涉到的无关元素比较多。可以考虑将电梯的可行队列先解出来,然后单独传递这个?

 

这是电梯拥有的queue,其循环复杂度实在是有点大。原因正是因为把电梯类错误地放在了调度器和队列之间。而实际操作时,调度器只是在调度电梯队列。此外,MyQueue还有更深的子类

 

然后就是电梯类,同样的原因吧?

 

调度器的平均循环度比较大,但是总循环度比较小。总体还行?主要是decodeandstore那个方法太麻烦了

 

bug分析

第一次作业无特别注意bug

第二次作业出现电梯访问0层的情况。出现的原因就是电梯的楼层不是连续的,所以在楼层转换的时候容易出现疏漏。这和多线程无关。

第三次作业的Bug是有一个电梯线程霸道的和main线程一样占用全部资源并且乘客既到不了目的地又永远不结束。

 

 

最开始认为是多线程导致的线程不安全,但后来发现是由于在载客量满的时候会陷入死循环。由于没能成功进入强测所以错失了和线程安全问题打交道的机会。但这个bug让我会了很多多线程调试方法,还是有所得的。

互测的Bug,前两次没有bug(没有测出来)。

关于多线程测试,多线程可能出现的问题就是峰值问题,所以在0s集中投放是比较好的测试。由于多线程的随机性,还应该批量测试,最好写个测评机。

和第一单元相比,本单元测试就是模拟随机数据,辅助以定点测试。第一单元是逻辑分析为主,乱序轰炸为辅。主要按照一定的规律生成测试数据。

心得体会

  • 多线程的设计最应该注重的就是模式化规格化。

  • 多线程设计应该十分注重模拟现实,是对现实的复现,超越现实的算法一般很难发挥作用

  • 线程加锁范围应该尽可能小,这样有助于效率的提升。同时注意对共享对象状态的判断+修改应同时放在同步块里,否则容易出现死锁或者异常。

  • 保持模块独立且健全更容易拓展

  • 面向未来需求开发

  • 拖延症是面向对象的一大禁忌

第七次作业胡乱分析

  • main函数接受模拟器产生的请求

  • 调度器首先接受请求,并且进行翻译,适时投放,控制电梯的请求队列,调度器还可以询问每个电梯里的可用队列

  • 电梯管理自己的队列,上和下沿用上次作业的思路,可以转移请求,可以删除请求。他的请求被包装为含有楼层的。

遇到的难以解决的问题

  • 怎么判断电梯结束?调度器同步化的查询所有电梯的队列,需要同时满足电梯队列全空,接收到Null信号,调度器保存的队列为空。电梯查询调度器是不是为空。如果空了的话,就杀死自己。

文字分析设计上的不足(图表分析参照前面)

电梯的队列对于调度器是透明的,然而其实调度器不需要知道电梯究竟在干什么。如果可以变成调度器管理三个不同的队列,队列有自己的属性,电梯只需要开关上下就好很多。

就是电梯拥有调度器,调度器拥有电梯两者根本没有分离开,甚至直接访问对方内部的成员。

关于换乘问题,我原来采用的是查询第一个请求是不是还在,如果不在就把下一个请求放到队列里。有没有更好的办法呢???其实,把等待队列剥离出来就挺好的。这个请求处理完,取出下一个请求,放回其他电梯的请求队列。

至于Bug嘛

如果后来的修改没有错的话,所有bug都在这个函数里了

public void move(int nowFloor) {
       // add main dire to suitable for more elevator
       ArrayList floorArray = this.get(nowFloor); // can it??
       int id;
       if (floorArray != null && floorArray.size() != 0) {
           for (int i = 0; i < floorArray.size(); i++) {
               PersonRequest person = (PersonRequest) floorArray.get(i);
               id = person.getPersonId();
               if (!isFull() && person.getFromFloor() == nowFloor) {
                   passNum++;
                   TimableOutput.println(
                           String.format("IN-%d-%d-%s",
                                   id, nowFloor, eleName));
                   floorArray.remove(person);
                   i--;
                   int toFloor = person.getToFloor();
                   FloorQueue toArray = this.get(toFloor);
                   if (toArray != null) {
                       toArray.add(person);
                  }
              } else if (person.getToFloor() == nowFloor) {
                   TimableOutput.println(
                           String.format("OUT-%d-%d-%s",
                                   id, nowFloor, eleName));
                   floorArray.remove(person);
                   i--;
#1:                 passNum--;
              }
          }
      }
  }
  • #1处,由于电梯类我认为和第二次作业差别不大,因此修改的比较少,第一次忘记加这条语句,因此电梯满员之后就会有Bug

  • 第二次的bug依然是电梯满员的问题,原来是电梯处理该层楼的请求,知道本层楼请求为空,但是应该修改为for语句并且利用obj.size来判断结束。同时删除其中的一个元素,应该做一次回退。

  • 其他的bug嘛,我记得还有一个,但是暂时想不起来在哪里了

从摔倒的地方爬起来

因为没有通过弱测,所以,又是一次无效作业。不过,前事不忘后事之师。认真总结一下我究竟为什么又是无效作业了吧!

  • 工程化能力。本来第一第二次作业打下的基础比较好,整体架构清晰并且容易移植。但是第三次作业我由于考虑不够深入,导致在原来的基础上改动较多。并且在编写代码过程中换过一次思路,所以有一些难以发现的遗留bug。

    解决方案:

    构思一次完成,注重可扩展,推翻原来的思路应该果断坚决,早点重构早点无效。当然重构应该有标准,比如,这次我在发现自己的调度器几乎都是跳过电梯处理电梯队列的时候就应该让两个类从属关系反转了。

    重用之前的代码应该充分考虑当前的需求,在此基础上冷静地修改

  • 码速。我认为所有的问题都不过是码速的问题,写代码的速度太慢了,因此思维不容易连贯,工程周期也会相对较长。例如,同学可以5小时完成代码编写,测试,提交嗯,还有些一个测评机。但是我5个小时只是完成了基本功能,还有残留bug。当然,如果在极限的情况下能冷静点会更好。

  • debug能力。仅仅使用断点和print让我用了几乎一天的时间de#1号bug。虽然相当简单,但是逻辑分析让我带到不同的地方,断点加不对,所以没有线程能到出错的地方。最后尝试提交了一次,直接就知道自己为什么错了。面向测评机debug也是有用的,毕竟他的测试用例是精挑细选的,说不定就有自己出现bug的地方。

    解决办法:

    利用现成的包比如log4j,分析调试日志。

    JProfile调试

    测评机调试

posted @ 2019-04-23 14:18  Idolphint  阅读(211)  评论(0编辑  收藏  举报