面向对象设计与构造2019 第二单元总结博客作业
作业回顾
- 2.1 单部傻瓜式调度电梯设计
- 2.2 单部捎带式电梯设计
- 2.3 多部智能型电梯设计
一、三次作业的设计策略
第一次作业只有正确性要求,没有性能要求,对于多线程的知识也没有过多涉及。所以我就无脑写了一个FAFS型,不含捎带的电梯。在正确性上有十分充足的保证,但是性能上很差劲,也没有什么可以说的策略。
- 第二次作业加上了20分的性能分,但我并没有在性能上做出过多优化。我采用的策略是类似于磁盘扫描算法的LOOK算法,即模仿现实生活中电梯的算法。事实证明,这种调度策略在所有人的程序中并不算优秀。虽说我和其他部分人都是LOOK算法,但我在细节方面的实现非常粗糙,依然保留着上次作业的风格,并没有想尽可能多的去载人,而是尽可能让电梯路线不受牵制。这样,正确性虽好,但是性能分很低。
第三次作业适当的扣除了性能分占比,让我们能更专注于正确性。我在写的时候,策略和第二次基本相同,所以正确性上没有很大问题。关于中转的问题上,我依然是选择牺牲性能来完善正确性。我将所有一部电梯无法送达的指令送到1层或15层(哪个近去哪),拆分成两条来处理。这样子正确性上有保证,但是效率依然不够。最后,我的性能分也并不理想。
二、基于度量的分析
1.时序图和类图
- 时序图对于三次作业而言大同小异,多张图没有很大的参考价值,且占用空间。我在此放出第三次作业的时序图。可以看出,我采用的是生产者-消费者的模式,使用经典的生产者-消费者-容器三大类,对电梯运行进行有序处理。分发器采用notify的形式唤醒wait中的电梯,减少了CPU忙等待的时间。
- 三次作业的类图也是大同小异,我在此一并放出。
- 类图方面,可以看到类并不多,也没有建立过多类的必要。三次作业都以非常基本的设计模式构建。
- 但是,就SOLID设计原则而言,我的程序做的并不够好。我的程序没有很高的复用性和广泛性,许多类的方法功能都写得比较死,是在面向对象的每个方法内尽情使用面向过程的思路进行编写。尤其是电梯的上下人开关门处理,一套过程性思路行云流水,却没有体现出对象之间的交互性。
2.经典度量数据
Type Name | NOF | NOPF | NOM | NOPM | LOC | WMC | LCOM | FANIN | FANOUT |
---|---|---|---|---|---|---|---|---|---|
Elevator | 7 | 0 | 6 | 2 | 63 | 9 | 0 | 1 | 1 |
Input | 3 | 0 | 2 | 2 | 30 | 4 | 0 | 1 | 1 |
Main | 0 | 0 | 1 | 1 | 11 | 1 | -1 | 0 | 3 |
Switch | 1 | 0 | 3 | 3 | 12 | 3 | 0 | 3 | 0 |
Type Name | NOF | NOPF | NOM | NOPM | LOC | WMC | LCOM | FANIN | FANOUT |
---|---|---|---|---|---|---|---|---|---|
Elevator | 11 | 0 | 16 | 2 | 223 | 57 | 0 | 2 | 2 |
Main | 1 | 0 | 2 | 1 | 44 | 6 | 0 | 1 | 3 |
RequestList | 3 | 0 | 5 | 5 | 25 | 6 | 0 | 2 | 0 |
Type Name | NOF | NOPF | NOM | NOPM | LOC | WMC | LCOM | FANIN | FANOUT |
---|---|---|---|---|---|---|---|---|---|
Container | 4 | 0 | 8 | 8 | 38 | 11 | 0.375 | 3 | 1 |
Dispatcher | 4 | 0 | 7 | 2 | 91 | 17 | 0 | 2 | 3 |
Elevator | 12 | 0 | 23 | 2 | 306 | 76 | 0 | 2 | 3 |
Request | 3 | 0 | 5 | 5 | 24 | 5 | 0 | 3 | 0 |
TestMain | 0 | 0 | 1 | 1 | 17 | 1 | -1 | 0 | 3 |
- 以上依次是我第一、第二、第三次作业的度量情况。可以看到,在代码行数(LOC)方面,电梯类Elevator一直是大头。三次作业中的电梯类代码行数占了总行数的50%以上,可以说将一切处理全部聚合在一个类中。这种设计并不算好的架构,但我本次作业又不知道如何去更好的拆分。
- 第一次作业中,由于对生产者-消费者模型不熟悉,我甚至写了一个Switch类作为原子布尔型变量,作用是能够使得电梯线程停止工作。后面这种东西没有再出现。
- 第三次作业中,程序的扇入扇出值较为合理,高扇入(FANIN),合理扇出(FANOUT),可以感觉到自己初步掌握了OO的设计诀窍。
三、分析自己程序的bug
- 三次作业中,我为了追求正确性从而牺牲了许多性能,后果是三次作业中我都没有被检测出bug。课下我使用自己的测评机进行大量数据反复测试,得到的结果也十分稳定。所以在这里,我没有很多可以分享的内容。
- 助教曾经提到过,输出类TimeableOutput不是线程安全的。但是我在实际编写程序的时候,无论锁不锁输出,对结果都没有影响。虽然我锁了,但是我依然没有弄清楚不锁可能带来的问题。
四、找别人bug的策略
- 我坚信,越往后写,代码量越大,通过阅读他人代码的方式来找bug的行为会越发艰难。而且哪怕他人的代码有错,自己的思维也会非常容易被带入他人的错误写法中,从而导致一时间找不出错误。所以,我选择使用自动评测的方式,来寻找他人的bug。
- 一台自动评测机器所需要的基本功能:
- 随机数据生成功能
- 编译运行、控制输入输出流的功能
- 输出结果正确性判断功能
- 判断结果可视化导出功能
- 就这次的三次作业我自己写评测机的经验而言,难点在第二点,工程量大的重点在第三点。第二点是许多同学写出像样评测机的门槛,也就是定时输入的功能,给许多同学带来了困扰。这里我采用Python自带的subprocess模块来解决问题。此模块可以控制定时投放数据,也可以关闭输入流,非常方便。
- 而输出结果的正确性判断,只是简单的逻辑问题。指导书中对于正确性判断说的十分清楚,我们只需要把助教的言语转化为代码就可以完成此功能,但是这个工程量未必比写一台电梯要小。所以大家可以量力而行。到第三次作业的时候我已经完全摸了,拿了第二次的程序改成三台电梯直接用了,没有实现特定楼层、超载判定等功能,有点可惜。
五、三次作业的心得体会
三次作业下来,我切实的体会到了OO思想和OO课程的特点。面向对象程序设计特有的设计模式,和多线程特有的设计模式,都超脱于之前学的所有知识,让我于困境之中发现了更多的惊喜。虽然电梯功能很简单,但是从其中学到的东西和面向对象的知识,远远超过之前的求导程序。
多线程的学习中,线程安全很重要。线程安全的设计要点,在此之前的程序设计中从来没有涉及。对于临界资源的操作要谨慎再谨慎,是多线程有风险而有魅力的点。另外,wait()和notify()的使用要比忙等待更加节省资源,也是多线程设计中非常智能的设计功能。