zoukankan      html  css  js  c++  java
  • OO第二次博客总结

    一、 前言             

      OO的复杂程度可能和写完的时间成正比,多项式时周三电工实习不带电脑,到了出租车已经发展成周三下午5点开始写Readme……不过相比前几次为语法耗费时间,出租车更多的是为设计来投入精力。多线程debug也很有意思,在写多部电梯时,自己猛然发现调试时加入的一句System.out.println竟然会对结果产生影响(事后才发现原来是输出调试信息到控制台耗费了时间,导致输出结束时其他线程的状态信息更新完成了,若不加输出,其他线程更新尚未完成,发生错误),真的迷……

    二、 多线程电梯        

        由于三部电梯的出现,想要让他们在三个线程中自己完成调度,就不得不在一个大请求队列的基础上,增加三个小的请求队列,分别存储三部电梯的请求。

      每当InputHandler有了新的合法请求,就拿到大队列的锁,将其加入到大队列中,叫醒调度器。同样的,每当电梯跑完当前主请求,也会叫醒调度器。调度器被叫醒后,要把当前大队列中的指令分给空电梯或能捎带的电梯,直至InputHandler线程结束且四个队列都为空,程序结束。

      

       这次虽然要求使用继承,但由于有捎带的但电梯写的并不好,索性直接重写Scheduler类。值得一提的是时间的处理。但电梯由于使用假时间,导致时间的计算冗余重复。本次是使用系统时间,相应的就带来了误差,如由于程序运行导致的每层楼实际并不是3000ms,实际上会多出几十毫秒。误差的累计就导致了输入较多时,开始出现明显的时间误差。因此我使用了消除误差的方法,即在wait(3000)前,先计算所产生的误差时间,将睡眠时间改为wait(3000-误差),就使得时间完全满足要求。

      与之前的问题大致相同,圈复杂度和嵌套深度过大。出现问题的carry方法是用来判断是否可以捎带,代码如下。

     1 /**
     2      * 查找是否可被捎带
     3      * @param request
     4      * @return 捎带返回true,否则返回false
     5      */
     6     public boolean carry(Request request) {
     8         if (request.get_flag() == 2) {// ER
     9              if (eleList[request.get_ele() - 1].get_status() == EleStatus.RUNNING) {
    10                  if (eleList[request.get_ele() - 1].get_dir() == EleDir.UP && request.get_dstfloor() > eleList[request.get_ele() - 1].get_curflo()) {
    11                      move(request, eleList[request.get_ele() - 1]);
    13                      return true;
    14                  }
    15                  else if (eleList[request.get_ele() - 1].get_dir() == EleDir.DOWN && request.get_dstfloor() < eleList[request.get_ele() - 1].get_curflo()) {
    16                      move(request, eleList[request.get_ele() - 1]);
    17                      return true;
    18                  }
    19              }
    20              return false;
    21         }
    22         else {// FR
    24             int whichtocarry = 3, minsumdis = -1;
    25             for (int j = 0; j < 3; j++) {
    26                 if (eleList[j].get_status() == EleStatus.RUNNING) {
    28                     if (eleList[j].get_dir() == EleDir.UP && request.get_dir() == 1 && request.get_dstfloor() > eleList[j].get_curflo() && request.get_dstfloor() <= eleList[j].get_dstflo()) {// bugwhere
    29                         if (minsumdis == -1 || eleList[j].get_sumdis() < minsumdis) {
    30                             minsumdis = eleList[j].get_sumdis();
    31                             whichtocarry = j;
    33                         }
    34                     }
    35                     else if (eleList[j].get_dir() == EleDir.DOWN && request.get_dir() == 2 && request.get_dstfloor() < eleList[j].get_curflo() && request.get_dstfloor() >= eleList[j].get_dstflo()) {
    36                         if (minsumdis == -1 || eleList[j].get_sumdis() < minsumdis) {
    37                             minsumdis = eleList[j].get_sumdis();
    38                             whichtocarry = j;
    39                         }
    40                     }
    41                 }
    42             }
    43             if (whichtocarry != 3) {
    45                 move(request, eleList[whichtocarry]);
    46                 return true;
    47             }
    48             return false;
    49         }
    50     }

      不好的一点是没用枚举而是以变量来表示状态,但由于捎带判断条件比较复杂,可能也很难优化了。另一个圈复杂度高的方法是opendoor方法,用来输出,同样的,因为加入了时间的误差消除,使得方法更加复杂,代码就不放了。

      bug方面,自己没有被报bug,但是测试的程序问题较多。如捎带无法判断、时间计算错误等问题,可能是他没来得及调试导致的。

    三、 IFTTT        

      本次作业是一个IFTTT的文件扫描。由于之前使用过类似的IFTTT类型app和坚果云这样的支持增量更新的云盘,所以我对这个思想并不陌生。不过由于自己的拖沓,导致了测试接口是在ddl前半小时写的,完全没有调试,在被测时还有点担心。

      本次由于要扫描文件,而文件的位置(路径)实际上包含在了文件的名字中,因此没有用到树来存储,而是使用HashMap<String, long>来存储,以文件的路径作为key,以其大小和最后修改时间作为value,对目录进行snopshot。以一定时间间隔更新snopshot,对监控文件进行查找和比对,如果触发则进行任务,否则继续扫描。

      我采用的是每条指令一个线程。在更新snopshot后,每条指令开始对其监控文件进行查找。因此就出现了一个问题:多条指令监控一个文件,有recover、detail、summary操作时,若recover先完成,很可能导致后两者没有被触发而未记录;recover后完成,很可能导致文件变化两次都被detail和summary捕捉到,记录了两次。这个问题是由于多线程的不确定性导致的,因此不算做bug。但当互测时还是被申报了,而且申报者说他解决这这个问题,我猜应该是将recover先不执行,保证一定触发两次。

     

      可以看到,Monitor类很可能成为了一个god类,其中的snopshot方法和compareFile方法完全可以拿出来作为新的类。度量分析也印证了这个猜想:

      如果将这两个方法拿出来,情况会好很多。这是在设计上没有想好就直接写代码的结果。compareFile方法复杂的原因,很大程度上是由于其中分了四种触发器,拆成四个函数会大大降低复杂度。

      这次作业由于分类树设置的不好,导致同源错误很可能被多次挂树,如我的modified判断条条件多此一举的判断了大小,导致树上的detail、summary、监控文件、监控目录都挂了红……最后通过申诉解决了。我测试的代码同样出现了这个问题。除此之外,我已经多次通过读手中最核心的运算部分的代码,找到了难以测试的bug,也反映出了读代码的好处。

    四、 出租车        

      出租车即使给了写好的UI和map,也没有帮助测试……由于100辆车每200毫秒运动一次,非常难观察其运动的正确与否,很多时候当看到GUI能正常运行,就基本判断没什么bug了,因此读代码的重要性更加明显。

    本次我才用了100个出租车1个线程的假多线程,InputHandler线程输入后,将请求发给Scheduler线程。Snopshot则将每200ms的位置快照发送给调度器。调度器根据位置搜索派单车辆,更新该车辆状态,而TaxiList则每200毫秒sleep一次,再让所有车移动,然后叫醒调度器进行调度。

     

      

      可以看到,许多方法出现了复杂度高的情况。由于车辆状态较多,到知道move等方法的复杂。

      我测试的代码,出现了一个问题。通过读他的代码发现,他在判断派单车辆时,将信用大于当前队列最优的车加入队列,此后从中选择距离最小的一个,而忘记把信用较小的删除。同时,我的代码中也有个问题。快照对于每个位置的车辆,要用arraylist来存储,否则用int存其车辆编号,会导致同一位置有多辆车时,实际只存了1辆。

    五、 心得体会        

      这三次多线程作业,让我从完全不会多线程的使用,到对锁有了一定的理解,收获很大。但是熬夜更加严重了emmm……每周三还要赶一天工才能将将完成。活着真好……

  • 相关阅读:
    背水一战 Windows 10 (26)
    背水一战 Windows 10 (25)
    背水一战 Windows 10 (24)
    背水一战 Windows 10 (23)
    背水一战 Windows 10 (22)
    背水一战 Windows 10 (21)
    背水一战 Windows 10 (20)
    背水一战 Windows 10 (19)
    背水一战 Windows 10 (18)
    背水一战 Windows 10 (17)
  • 原文地址:https://www.cnblogs.com/bvb1909/p/8980245.html
Copyright © 2011-2022 走看看