一、第四单元作业设计架构
第一次作业
首先附上第一次作业的UML类图:
设计思路
由于每一个UmlElement都是从一个UML图中拆分出来的元素,那么我再根据每个元素之间的关系,将其重组成一张图,就可以在这张图上比较简便快捷地查找我所需要的任何有关图的信息了。所以作业的重点其实就放在如何能够正确且高效地重建一张图。通过下面的类解析我来详细介绍一下我的图架构。
类解析
- MarsUmlInteraction:这是交互层,是接收外部提供的UmlElement进行建图,并给外界提供图的相关查询算法接口的一层。图的结构也是在这一层中进行存储的,有一个存储所有节点的容器,和三个存储所有边的容器,因为本次作业中节点与节点的关系有关联,继承和实现三种,所以其实有三张图,这三张图有相同的节点,但是边是不同的。
- MarsUmlTopClass:这是节点类。类图中的节点无非就是类或者接口,而这就是它们共同的父类。该类中有着一些他们共有的基本方法,比如getId,getName等等。
- MarsUmlClass:这是封装类的类,是根据UmlClass类进行构造的,其中有存放着MarsUmlOperation和MarsUmlAttribute的容器,也有一些查询方法。
- MarsUmlInterface:这是封装接口的类,其中有存放着MarsUmlOperation的容器,同样有一些查询方法。
- MarsUmlAttribute:这是封装类属性的类,被存放在其对应的MarsUmlClass类下,自身有一些查询方法。
- MarsUmlOperation: 这是封装方法属性的类,被存放在其对应的MarsUmlClass或者MarsUmlInterface类下,自身有一些查询方法。
- MarsUmlParameter:这是封装方法参数的类,被存放在其对应的MarsUmlOperation类下,记录了对应方法的参数信息。
具体流程
在每一个UmlElement进入的时候,就会被纳入图重组的范围内,所以在MarsUmlInteraction的构造方法结束之后,整个图的结构就已经板上钉钉了。所需要实现的各种查询方法也只是对存储图的容器的一种访问过程和计算过程而已,具体的方法由于比较简单,便不再赘述。
复杂度分析
可以从复杂度分析图中看到,除了最上层的交互层和由于交互层代码过多而创建的MarsHandle类之外,其他的类独立性都是比较好的,这也跟我们这次作业的性质有关。交互层本身就是一个提供查询接口的类,所以其需要和其他所有的类有着比较广泛的关联,而其他类则只要管理好自身内部的数据就可以了。
第二次作业
首先附上第二次作业的UML图:
设计思路
第二次作业是在第一次作业的基础上扩展了对于时序图和状态图的查询方法,所以我将第一次作业的所有代码都进行了保留。由于交互层有了改变,所以我将第一次作业的交互层改了一个名字为classGraph类,并使用了单例模式,这样就能够让上一次作业的代码从这一次的交互层中独立出来,能够保证不会误操作而修改第一次作业中的各种容器,保证类图查询的正确性。
时序图和状态图的结构都比较的简单,只需要将图中各个元素的关系重现出来,就能够正确完成查询任务。
而最后三个有效性原则的检查其实也只需要建立在第一次搭建的类图之上,弄清楚了每一条原则的所有情况,就可以轻松地完成任务。这也充分说明了,在构造时就将图搭建完成对于日后关于这个图的扩展有着非常大的帮助。
类解析
- MarsUmlGeneralInteraction:这是第二次作业的交互层,功能与第一次作业的交互层大体相同,只是多了时序图和状态图的查询方法接口。
- MarsClassGraph:这其实是第一次作业的交互层,只不过换了一个名字,同时利用单例模式放在了交互层的下面,代表着与类图相关的所有操作。
- MarsUmlInteraction:这是封装时序图的类。这个类存放的是某个时序图中的所有信息以及查询方法。
- MarsUmlStateMachine:这是封装状态图的类。这个类存放的是某个状态图中的所有信息以及查询方法。
具体流程
在每一个UmlElement进入交互层的时候,如果其属于类图的范畴,则直接传递给MarsClassGraph进行处理及储存,类图的相关信息不存放在交互层中。而若是属于时序图或者状态图的范畴,那么就会被存放在对应的时序图或者状态图下,然后所有的时序图和状态图都会被存放在交互层当中。这样就保证了第一次作业的结构不被破坏,同时第二次作业的功能扩展也比较好地完成。
复杂度分析
同样可以看到,依赖度和复杂度比较大的仍旧是提供查询接口的交互层,其他类之间的关联关系没有那么紧密。从这个角度来看,我觉得该程序的扩展性不会那么的糟糕,起码逻辑上有一个比较清晰的结构。
二、架构设计及OO方法理解的演进
Unit1
一开始从面向过程编程到面向对象编程的转变有些吃力,通过这一单元对于多项式求导的实践,我逐渐体会到了以信息的传递为构造线索的编程方式。该单元的重点就是建立起表达式,因子,项的数据抽象,各自建立自己的类,通过层层递进的信息传递方式拼凑出最终的求导结果。给人总的感觉就是各取所需,各有所能,每个类圈定自己的责任范围,不同类之间通过约定的数据信息进行交互。
Unit2
这一单元所涉及的主要是多线程的编程知识。多线程更加能够体现出面向对象和面向过程编程之间的差异,不同的类可能在不同的线程当中工作,各个类的工作也没有严格的顺序关系,只有一些约束关系或者同步关系。除此之外,在本单元的实践中,我们还学到了一些常见模式,比如生产者-消费者模式,单例模式,工厂模式,观察者模式等等。在作业实现当中,我使用的主要是生产者-消费者模式和单例模式的结合,将调度器当作托盘,同时调度器使用单例模式。
Unit3
这一单元所涉及的主要是JML规格化语言的学习和应用,我认为这大部分属于架构设计方面的范畴。值得一提的是,学习这一单元的过程中,老师请了研究所的职员来为我们做讲座的时候也提到了这个问题,经过老师的讲解和自我的体会,我认为JML这种规格化语言在项目设计之初统一众人设计思想的阶段作用很大,能够用无二义性的语言准确描述类或者方法的行为。但是这并不是必须的,比如一些带有显而易见功能的方法,或者比较小型的项目中,花在撰写规格上的时间其实是不必要的。
Unit4
这一单元和上一单元有些类似,主要是学习关于UML的基础知识。不过在这个学习过程中,我不仅学习到了一种被广泛使用的语言,还加深了对与面向对象编程的理解。考量一个程序,我们可以使用类图,时序图和状态图三个角度去看待:类图让我们明晰类与类之间的关联关系;时序图让我们明晰类与类之间的消息传递过程和相对顺序;状态图让我们明晰一个类在程序运行过程中的状态空间和迁移路径。从这些不同的角度去考量一个程序,有着不同的侧重点,同样也有着不一样的结论。在日常学习中,构造UML结构图也有着非常重要的意义。
三、测试理解与实践的演进
Unit1
这一部分的测试由于涉及到格式判断和求导结果正确性检查,所以我采用了设计样例和自动生成数据结合的黑盒测试方法。首先根据容易出错的格式设计一些样例,然后使用递归下降法编写了一个自动数据生成器,用以生成大量的随机多项式。在测试时主要使用的是对拍的方法,使用sympy比较多个程序输出的表达式是否是正确的。
Unit2
由于这一单元主要涉及多线程,所以错误主要存在于线程安全上。但是由于线程安全问题具有不易复现性,所以我在黑盒测试之外只能读对方的关键代码,观察是否在控制线程时有死锁,死循环,或者提前退出的情况。所以在互测阶段,我采用的方法是先自行编写一些检测功能的样例,然后让每一个程序都跑二十次,观察输出是否正确,且二十次输出是否均符合要求。
Unit3
在这一单元中,我学习到了Junit的测试方法,并且将其应用在我的测试过程当中。起初我也想使用自动化测试工具来检测方法是否符合规格,但是好像都不太尽如人意,不管是openjml还是jmluniting都有或多或少的问题,所以最终还是选择老老实实编写Junit的测试数据来进行测试。在互测阶段,我是直接拿着我的Junit测试样例去检测其他人的程序。
Unit4
这一单元由于没有互测,所以测试环节相对来说比较简单。但是由于输入是一个UML图,使用自动生成器效果不好,所以我选择的是自己构造对应相应情况的UML图,然后再导出元素信息,进行测试。这样测试的优势是可以精准地检测一些特殊情况,缺陷就是很难全方位覆盖到所有的情况。
四、课程收获
终于,OO课程也接近了尾声,这趟惊险刺激的旅程也要暂时告一段落了。毫无疑问,这一学期的课程是挑战的,刺激的,同时也是收获满满的。每一个单元的开始,我都会拿着助教发给我们的指导书无所适从:这是啥?这怎么能写出来?我感觉我不太行……但是,进度的压力逼迫着我不断学习新知识,并快速运用。在这种被拉扯,被鞭策的过程中,我成功完成了所有任务,感慨颇多。
代码能力的增强
每一次的作业,代码量都基本在五百行以上。在课程开始前,我基本就没怎么写过这么大代码量的程序。但是这一个学期下来,我现如今开发五百行一千行代码已经比较的轻松了,虽然不一定都是高质量的代码,但代码能力确实得到了很大的提高,之前有些模糊的编程概念也在这个过程中被不断打磨清晰。
编程思想的转变
虽然我对面向对象编程思想的理解还远远不够,但是我现在已经能够比较清晰地区分面向过程和面向对象之间的区别,它们各自都有各自的特点,同时各自有各自的适用场所。在开始前,我认为面向对象是更高级的思想,现在我认为它们只是适用场合各有侧重,并且不是非此即彼的。将来面对不一样的工作,不一样的项目,还是要根据具体特点来选择编程手段。
分析问题的能力得到较大提升
这是我在这门课开始之前没有想到过的。由于面向对象编程思想的需要,我们需要从任务指导书中自己抽象出模块,厘清数据在各个模块之间传递的方式与顺序,然后再设计我们的类。不在动手之前设计好各个类的功能,会导致程序一团糟,不仅影响当次实验,而且影响之后的扩展,体验很差。只有抽丝剥茧,正确区分了主要矛盾和次要矛盾,找到问题的关键,才能比较顺利地解决问题,同时又让程序有比较好的可扩展性。
心性的磨练
由于OO课程的特性,我们的程序会经过弱测、中测、强测和互测四个测试阶段,这对我们的程序正确性就提出了很高的要求。以前我在编写程序的时候,容易分心,容易走神,但是由于代码量比较小,涉及的主要是简单的问题,所以无伤大雅。像OO这种略微有点规模的项目,需要在编程的时候十分专注,否则就会在不经意的地方出现差错。这一个学期下来,我逐渐形成了编程的习惯,比较平静,不再非常的浮躁了。
五、给课程的改进建议
- 希望课上实验能够给予我们正确性反馈,并且给予标准答案,供我们参考。很多时候在课堂上不会做,课下也只能够问其他同学,没有正确答案大家都是瞎猜。总的来说,我在课上实验的收获比较的少。
- 希望每一次的作业指导书能够考虑得再全面一些,不要每次做到一半开始对指导书进行修改或者注释,这样很影响已经写好的同学,造成许多同学其实都是最后两天才开始着手写作业。
- 希望课程组能够跟其他课程组商量一下,合理分配一周的作业时间,降低同学们在短时间内的作业压力。
- 希望课程组在课程开始时设定的规则不要随意进行更改,因为这样很影响我们的规划。学期中的南湖捞人事件我觉得就很没有必要,真的损害了很多认真完成作业同学的利益。虽然理解课程组的好意,但是破坏规则的行为还是不希望看到。