1 架构设计
经过了接近一学期的程序设计训练,在这一单元的第一次作业中我就非常注重架构的设计,竭力避免像之前一样陷入“第一次作业凑合,第二次作业重构”的不健康的迭代模式。整体上来说,我对我本次作业的架构还是很满意的。废话不多说了,直接上类图:
从类图也可以看出来,整体的架构非常工整。MyUmlGeneralInteraction
类是整个程序的中心,负责实现接口UmlGeneralInteraction
的全部方法,以及输入的读取和分配。它下面所属的三个类是ClassModel
, StateModel
和CollabModel
,分别是UML类图、UML状态图和UML顺序图的管理类。最下面一层的类是以Res
开头的,分别对应相应的UmlElement,如ResClass
对应的是UmlClass
,ResInter
对应的是UmlInteraction
等等。这些底层类储存的数据不仅包含相应的UmlElement中的属性,还有相应的用于和其他类相关联的数据。以ResItf
(对应的是UmlInterface
)为例,其中的属性包括id
, name
, HashSet<ResItf> parents
, HashSet<ResItf> allParents
,后两个分别是该接口的直接父接口集合以及所有祖先接口集合(用HashSet存是因为Java支持接口的多继承)。这个类的方法也是为上述的数据服务的。除去getters和setters以外,还有一个更新方法updateAllParents()
,其作用是确保通过parents
属性来递归地找出全部的父类,并放到allParents
里。ResItf
的代码框架如下图:
最后一个需要指出的类是DirectedGraph
,它是一个单独的进行图运算的类,封装了建图方法和相应的图算法,作用和上一单元中的图运算类相似。这个类是第二次作业中新加入的,加入的原因是pre-check的相关操作是基于有向图的,而图结构在其它方法中又没有用处,所以需要独立出来,避免和其它类形成无意义的高耦合。DirectedGraph
类的代码框架如下图:
程序运行时,首先通过MyUmlGeneralInteraction
类对输入进行分配:实例化它下面所属的三个类之后,对于类图的输入,分配到ClassModel
;对于状态图的输入,分配到StateModel
;对于顺序图的输入,分配到CollabModel
。至于接口方法的具体实现,只需要调用相应类里面的同名方法即可,具体的方法逻辑放在下面三个类中去实现。
2 总结自己在四个单元中架构设计及OO方法理解的演进
2.1 架构设计的演进
这学期其实是我第一次真正意义上的运用OO方法到我自己的程序中,因此我能清晰地感受到入门的过程。第一阶段是拒绝OO方法,仍然待在自己的comfort zone里。在第1单元的第一次作业中,我还是习惯性地用以前所熟知的面向过程的思路设计程序。虽是在Java语言中,可是只有一个类,所有的方法都堆在里面,自然也没有什么逻辑分层和功能划分。
第二阶段是愿意去尝试,但是不得要领。具体来说就是知道要用OO方法将问题结构化,但是不知道怎么将需求转化为Java语言中的类。有时在具体写方法时才意识到有些数据应该放在一个类中的,于是又得推到重来;有时程序全部完成后才发现其实某些类里的某些数据是完全没有关系的,其实应该分成两个类单独处理……总之,这一阶段算是在摸索和不断试错中艰难前进的。
第三阶段是终于可以自如地运用OO方法设计出合理的架构。具体说来就是先建类和类里面需要的方法壳子,方法的内部实现以后再回来写,类里的属性也可以依照需要随时添加。这种自上而下的设计+编程方法可以兼具架构和代码框架,同时也还算灵活(如果发现架构有问题也不会做无用功),算是这一学期我自己探索的成果。我认为这一单元的两次作业就是很好的例子,详细的架构设计在上一节已经介绍了,在这里不再赘述。
2.2 OO方法的演进
这一学期的四个单元分别有四个主题。第一个主题其实算是一个Java引言,通过多项式求导这个任务让大家熟练Java语言,理解面向对象的思维方式和设计方法,并适应逐步迭代的需求变化。第二个主题是多线程程序,具体的任务则是多部电梯调度,目的是锻炼编写多线程Java程序的能力,更重要的是搞清JVM在进行多线程调度时的机制和原理,从而才能更好地使用锁来确保数据的一致。第三个主题是JML及程序规格,具体的任务是根据JML规格撰写Java代码,目的是让大家不能一设计好程序框架便一头钻进代码细节中,而是应该捋清方法的内部逻辑、权利和义务。第四个主题是UML,具体的任务是解析UML图,目的我认为是让大家从一个更高的视角来看待程序结构,进一步弱化Java代码细节,而将注意力集中在程序架构设计上。这四个主题相对独立,但是同时也是能够串联成一个整体的。经过了四个主题15次编程和博客作业的训练,我认为我已经对于Java语言以及基本的OO方法有了全面的了解。
3 总结自己在四个单元中测试理解与实践的演进
测试这个环节其实是和架构设计同等重要的,而且它们都属于在课堂上老师不会具体讲到的知识,需要通过练习和思考才能够熟练。
最开始时的我进行的测试其实是比较原始的,只是盲目地在输入空间里乱射,试图去覆盖到所有可能的边界。这样做的缺点是有很多无意义的、多余的测试样例,即这个样例的覆盖面被其它一个或多个样例包含在内了;而且,缺乏条理的测试也几乎不可能覆盖到所有分支,使得测试的目的没有百分之百达到。
当前我的测试方法就更加合理了一些,我大体上通过整体+局部的方法进行测试。整体上是根据输入的特征(比如在第四单元中,按查询指令的类别来划分输入)有目的地去覆盖每个大分支和边界情况;局部则是指针对每个类甚至是每个方法分别进行细致的测试。通常这些类和方法通常是逻辑比较复杂、代码量比较大、不容易在整体测试中覆盖全的。在对这些实体进行测试时,我认为不能完全遵从契约设计的规范,即想当然地认为输入具有某种约束和限制。有时候虽然这个方法的代码没有问题,但是其他地方的bug会导致错误的输入被传播到这里。换一种方式来说,要做到同一个错误不仅仅只能在一个地方才能检查出来并报错,要争取让更多的类和方法在遇到这个错误时都能够报错,这也是我对于程序鲁棒性的一种理解,我想。
我认为对我来说今后仍旧需要进一步锻炼和改进的地方是自动化测试。虽然我现在的测试方法相对来说很合理,但是就时间上来说并不高效。其中,撰写测试样例倒并不是最麻烦的,思考其实占据了相当大的一部分时间。我想通过自动化的程序生成测试用例似乎是一个省时省力的办法,不过还不是很清楚如何让这样自动生成的测试做到准确和全面,这也是我在以后需要学习和探索的。
4 整体课程收获
这一学期的OO课程下来,我认为有相当多的成果和收获,而且更重要的是这些收获不仅仅是局限于面向对象相关知识的,而是方方面面的。首先最直观的就是编程能力的提高。截止这一学期开始的时候我做过的最庞大的项目是在编译原理实验课上的两三千行代码量的编译器,而在这一学期有的项目自己就是近千行代码量的工程,而这仅仅是一周的project。纵观整个学期,总代码量已经到达了万行数量级。现在我能明显感觉到代码的可靠性相比于上学期甚至学期初都有了显著的提高,出错的情况更少了,可读性也有所上升,不像之前的代码一样过若干个月已经看不懂自己当时的思路了。
第二个收获是架构设计上的,如上文所提到的,我认为现在自己的设计更加逻辑清晰,同时对需求的更新和迭代也更加友好。这一点,老师在课堂上也曾经多次提到,在以后的工作中是很有帮助的一项技能,因为在实际的工程项目中需求不可能是一成不变而且是一次性全部交代的,必然会随着项目的发展增加、删减或修改,因此我们的程序也应该具有与之相匹配的特性。
第三个收获是面向对象式的思维方法。在这学期以前我接触的程序全部都是C风格的面向过程程序,即便在Python里有类的概念也是作为面向过程的一种辅助,而Java则是一个“一切皆对象”的语言,通过这门语言第一次接触到了完完全全的面向对象设计。起初我不是很能理解,也不是很能接受,但随着时间和作业的向前推进我现在能明白为什么面向对象是必要的,尤其是在复杂工程中。只有通过拆分数据和封装功能,才能在不失效率的同时写出高可靠性、高可读性的代码。曾经有人把面向过程比作编年史,而把面向对象比作纪传史,现在看来这个比喻是极为恰当的。
5 立足于自己的体会给课程提三个具体改进建议
- 个人感觉,理论课的内容有时候会有一点过于形而上学,和作业、实验的结合不是特别紧密
- 实验课没有任何形式的结果反馈,经常会心里没底,不知道自己作答得好不好,也不知道下次实验前应该做哪些准备
- 在学习JML部分内容时感觉很无力,网上几乎没有任何资料,相关IDE工具也很初级很原始(这一条尚不知道时什么原因造成的)