17231129 吴章杰
一、本单元作业的架构设计
第一次作业
本次作业最终需要实现一个UML类图解析器,可以通过输入各种指令来进行类图有关信息的查询。本次作业的程序主干逻辑均已实现,只需要完成对类图中各属性的查询操作。
首先,先分析类图的各个属性之间的关系:
(图片来源:https://course.buaaoo.top/assignment/80/discussion/245,感谢王珊珊同学的分享)
通过上面这张图,我们可以很清晰地看到各个标签之间的联系和层次结构。由此我们可以将类图依据层次关系划分成UmlClass与UmlInterface两个类,其余的标签都作为属性附属于这两个大类。我们对UmlClass和UmlInterface
MyUmlClass
分别根据他们的属性信息建立相应的数据结构以及传输信息的接口。
其次,具体分析每一条指令的查询内容,建立相应的查询方式。在查询信息时,可以运用缓存的思想,省去一些重复的搜索,但是可能会带来Flag横飞的后果。对于查询内容,还是应该加强对具体概念的理解,确保在查询时不会发生错误。(细心!)
最后,对输入信息进行分类,并根据各类信息的层次关系将各属性传入建立好的UmlClass与UmlInterface两个类中。由于各类标签是有一定的层次关系的,所以应该先对信息进行分类,将信息整理与建立模型单独进行,确保在无序输入的情况下也适用。
第二次作业
本次作业,在上次作业基础上,扩展解析器,使得能够支持对UML顺序图和UML状态图的解析,并能够支持几个基本规则的验证。
本次作业是在前次作业的基础上进行一些功能扩展,基本架构与上次作业相似。
首先,本次作业需要对类图、顺序图和状态图三种不同的图进行解析,所以在上一次作业的基础上,对解析器分别建模UmlModelParser、UmlStateMachineParser和UmlCollaborationParser,以实现对模型的解析。
由于这三种图是相对独立的,且输入数据中可能不止有一种模型,所以在输入时这三个解析器应该是并列的
public MyUmlGeneralInteraction(UmlElement... elements) throws UmlParseException { umlModelParser = new UmlModelParser(elements); umlStateMachineParser = new UmlStateMachineParser(elements); umlCollaborationParser = new UmlCollaborationParser(elements); }
同时有些属性可能不止在一种图中都出现,比如UmlAttribute可能同时出现在类图和顺序图中,若是顺序图的UmlAttribute就无法获得类图的parent。所以应该对解析器中所有get操作前进行判断是否是空指针,否则可能会喜提NullPointerException。
在建立好解析器之后,我们考虑各指令查询方式。
- UML状态图
- 状态数目(STATE_COUNT):即起始状态、UmlState和终止状态的总和。注意在本次作业中,若状态图中有多个起始状态(UmlPseudostate)或多个结束状态(UmlFinalState),都只计入一次。
- 状态转移数目(TRANSITION_COUNT):即UmlTransition的个数。
- 某状态的后继状态数(SUBSEQUENT_STATE_COUNT):即某个状态之后所有可达状态的数目。(BFS搜索一下即可)
- UML顺序图
- 参与对象数目(PTCP_OBJ_COUNT):即UmlLifeline的个数。
- 交互消息数目(MESSAGE_COUNT):即UmlMessage的个数。
- incoming消息数目(INCOMING_MSG_COUNT):即某个UmlLifeline传入的信息个数。(遍历UmlMessage,统计target是该UmlLifeline的数目)
- 模型有效性检查
模型有效性检查部分,将在实例化完毕后自动按序触发执行,不通过指令的形式。且一旦发现不符合规则的情况,将直接退出,不进行后续有效性检查和指令查询。
-
- R001:在原来的基础上对每个类进行遍历,检查类中所有的UMLAttribute及其关联对端所连接的UMLAssociationEnd是否有重名。
- R002与R003:将所有的类和接口视为节点,以所有的继承关系(类与类、接口与接口、类与接口)和实现关系(类与接口)为边,建立有向图。
- R002等价于判断每个节点(即类/接口)是否存在自己到自己的通路(循环继承)。
- R003等价于判断每个节点(即类/接口)以自己为起点的所有通路是否存在相同的节点(重复继承)。
二、架构设计与面向对象思想
通过第一单元的学习,我开启了面向对象新世界的大门。从面向过程到面向对象无疑是思维方式和架构设计的一次巨大转变。
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了;而面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
构造抽象层次,进行归一化处理。同时在保证正确性的前提下,对程序进行优化。
多线程
通过第二单元的学习,我掌握了多线程的基础知识和基本方法。多线程在给我们带来更高的资源利用率的同时,也给我们带来了程序设计复杂度的提升。线程之间的交互往往非常复杂。不正确的线程同步产生的错误非常难以被发现,并且重现以修复。这就需要我们更好地掌握多线程的运行机制来维护线程安全。
以程序的鲁棒性为主。在保证鲁棒性的前提下,追求性能的优化。
JML规格
通过第三单元的学习,我熟悉和掌握了JML的基本使用方法,了解了规格化概念在实际工程中的重要作用。JML帮助我们在进行程序设计时,使用更加规范的方式来描述每个类和方法的状态。通过基于规格的单元测试方法,我们可以进一步验证程序的正确性。
在本单元中,我学习和掌握了一些程序设计中的小trick,例如java容器的使用、图模型、中间数据缓存、复杂度优化等,拓宽了我的程序设计思维。
以JML规格为出发点,形式化验证程序的正确性,并结合算法和数据结构,对程序的复杂度进行优化;将性能与架构设计紧密结合,提升了程序的鲁棒性和高效性。
UML
通过第四单元的学习,我掌握了UML的基本知识,学会用类图描述程序的基本框架、用状态图和顺序图来描述程序运行时的状态和行为。在构造UML模型的过程中动态维护相关的查询数据,针对不同类型的对象构造层次关系。
UML使用系统化、模型化的语言来表示设计结果,为我们提供了一种更为直观明了的表现形式,便于我们开展架构设计思考。
三、测试与实践
在第一单元的多项式求导中,随机生成测试数据成了大多数人测试代码的手段。对拍器,懒人的标配——能拍中是福分,拍不中也是福分。但是往往拍中的bug都是同质bug,多交也不得分。这就需要自己构造一些特殊的测试样例,例如压力测试和非法输入测试。这些测试方法产出比高且容易得分,深受广大狼人喜爱。但是真正的狼人,是敢于直面代码。通过阅读代码,分析程序的逻辑结构,才可以挖掘出深层bug。
在第二单元的多线程中,测试变得极为困难,即使你通过某个样例找到一个bug,但由于多线程的不确定性,bug可能难以复现。可以利用JProfiler等软件分析CPU的运行情况,协助寻找bug。
在多线程程序中,bug极可能出现在线程等待和唤醒的地方。在定位bug时,可以在的wait/notify
的前后输出相关信息,判断线程阻塞和唤醒的情况,同时还需要在程序中的某些循环语句中加入相应的标识,以便准确定位bug出现的地方。
在第三单元的JML规格设计中,我们通过编写单元测试类和方法,来实现对类和方法实现正确性的快速检查和测试。通过维护规格和测试代码的一致性,代码的可维护性得到了保障,程序的质量水平得到了提高。
四、课程收获
以上两点是我在学习课程知识上的收获。
但是OO带给我的不只有知识水平的丰富和实践能力的锻炼,还有心理素质的提升。
每一次重构,每一次爆零,
每一次被hack,每一次bug修复,
都是对我心理素质的磨炼。
曾经我以为,计组都熬过来了,还能有什么!直到我遇见了OO……
五、改进建议
-
建议课程组可以对实验课进行一些改进。既然实验课的定位不是“考试”,那么我觉得可以对实验课的题量和难度进行重新考量。同时,可以提前发布有关实验课的相关内容,以便同学能够有所准备。否则当天上午上完理论课,下午就上机实验有些令人难受。
-
建议课程组可以对理论课的课程内容进行适当的补充与精简。我个人感觉有时候OO课的内容太多了,以致于老师只能对着ppt泛泛而谈。一节课下来感觉什么都听了,却啥也没听懂。我觉得课上应该讲解课程的核心知识(可以多讲讲程序设计的架构,以及应用领域),而其它次要的内容可以作为补充材料供我们课下阅读。
-
建议在讨论区中对进行问题分类,并增加搜索功能。
感谢OO课程组各位老师、助教一学期的辛勤付出!
谢谢你们!希望OO课能越来越好!