OO第四单元博客总结
前言
一些概念简述:
ev(G)基本复杂度是用来衡量程序非结构化程度的,非结构成分降低了程序的质量,增加了代码的维护难度,使程序难于理解。因此,基本复杂度高意味着非结构化程度高,难以模块化和维护。实际上,消除了一个错误有时会引起其他的错误。
iv(G)模块设计复杂度是用来衡量模块判定结构,即模块和其他模块的调用关系。软件模块设计复杂度高意味模块耦合度高,这将导致模块难于隔离、维护和复用。模块设计复杂度是从模块流程图中移去那些不包含调用子模块的判定和循环结构后得出的圈复杂度,因此模块设计复杂度不能大于圈复杂度,通常是远小于圈复杂度。
v(G)是用来衡量一个模块判定结构的复杂程度,数量上表现为独立路径的条数,即合理的预防错误所需测试的最少路径条数,圈复杂度大说明程序代码可能质量低且难于测试和维护,经验表明,程序的可能错误和高的圈复杂度有着很大关系。
OO第四次作业:UML-基于StarUML的UML图
1. 本单元作业分析
1.0 UML简介
1.0.1 什么是JML
The Unified Modeling Language (UML) is a general-purpose, developmental, modeling language in the field of software engineering that is intended to provide a standard way to visualize the design of a system.
The creation of UML was originally motivated by the desire to standardize the disparate notational systems and approaches to software design. It was developed by Grady Booch, Ivar Jacobson and James Rumbaugh at Rational Software in 1994–1995, with further development led by them through 1996.---from [Wikipedia]
1.0.2 应用工具链
官方介绍的应用工具为 StarUML, 本次实验也是基于StarUML实现的。但是在这里也推荐几个其他的可以画UML的软件:VISIO,Enterprise Architect,PowerDesigner,Eclipse中的Green。
1.1 第一次作业:UML类图的实现
1.1.1 程序简介:
基本结构:
-
类的个数:5个
-
具体架构:由于所有的UML类图中的元素都被划分成了 UMLElement,所以本次作业对于一些本应该存在一起的element进行了封装
-
将 UmlClass 封装成 MyClass
-
将 UmlClass 里的方法和属性都加入到 MyClass 中,并设立相应的继承父类和所有的由该类实现的接口(仅为由该类实现),同时为了方便指令查找,保存所有的和该类有关的 Association 对端的AssociationEnd
class MyClass { private UmlClass umlClass; private ArrayList<MyOperation> operations = new ArrayList<>(); private ArrayList<UmlAttribute> attributes = new ArrayList<>(); private MyClass father = null; private ArrayList<MyInterface> interFathers = new ArrayList<>(); private ArrayList<UmlAssociationEnd> links = new ArrayList<>(); MyClass(UmlClass umlClass) { this.umlClass = umlClass; } }
-
-
将 UmlInterface 封装成 MyInterface
-
与 MyClass 的封装一致,只是去掉了继承父类。(之所以在接口中仍然有attribute,是考虑在 Java8 中允许 public static final 的属性出现在接口里的(即常量))
class MyInterface { private UmlInterface umlInterface; private ArrayList<MyOperation> operations = new ArrayList<>(); private ArrayList<UmlAttribute> attributes = new ArrayList<>(); private ArrayList<MyInterface> interFathers = new ArrayList<>(); private ArrayList<UmlAssociationEnd> links = new ArrayList<>(); MyInterface(UmlInterface umlInterface) { this.umlInterface = umlInterface; } }
-
-
将 UmlOperation 封装成 MyOperation
-
对于一个方法需要传入和传出的 parameter ,将其和其对应的 operation 放在一起显然是一个明智之举。同时有了 parameter ,方法的 OperationQueryType 也就固定了,对于调用者来说就不需要再去查询了
class MyOperation { private UmlOperation operation; private ArrayList<UmlParameter> parameters; MyOperation(UmlOperation operation) { this.operation = operation; parameters = new ArrayList<>(); } }
-
-
1.1.2 结构分析
- 复杂度分析
可以看出,总体的复杂度并不是很高。本质上是因为这次的作业有良好的封装,把很多的功能都模块化的分给了各个封装好的模块,使得每一个方法都不是很复杂。
1.1.3 难点分析
其实这次作业说难也难,说不难也不难。不同于以往的作业,这次有大量的概念要了解,大量的源码要阅读。由于课程组在一定程度上进行了类图关系的化简,所以总体来说并没有很难的编程问题。但是不得不说,在阅读程序源码时出现了很多处地方读不懂,而且其实很多的函数和方法是用来读入的,所以读懂每一个方法的意义,理清楚UML具体的运行机制着实花了不少功夫。
这里推荐一个UML类图关系的博客:UML类图关系
这次其实感觉最有用的方法就是先总结一下每一个 element 的各个 reference 所指的对象(特别是parentid),这很有助于对UML的深层理解和对于程序的实现。
1.2 第二次作业:状态图、时序图的实现
1.2.1 程序简介:
没错,你没看错,1677行
虽然很多大佬貌似写的比我还多,但是我觉得这次真的是我这几次作业中架构整理的相当清楚的一次了。
基本结构:
- 总架构:
- 类图架构
- 时序图架构
- 状态图架构
-
架构分析
先看一看我的文件架构:
很清晰,三个package classmodel sequencediagram statechart 分别代表了类图 时序图 状态图
而其中的 MyClassModelInteraction MysequenceInteraction MyStateInteraction MyPreCheck 又分别实现了UmlClassModelInteraction UmlCollaborationInteraction UmlStateChartInteraction UmlStandardPreCheck
这样,只需要在最终的 MyUmlGeneralInteraction 中调用上述的几个类的方法即可。
再看一些封装,封装方式同之前的一样,这里不再赘述,将几个新的封装类部分代码奉上
-
MyInteraction
public class MyInteraction { private UmlInteraction interaction; private ArrayList<UmlLifeline> lifelines; private ArrayList<UmlMessage> messages; private ArrayList<UmlAttribute> attributes; private UmlEndpoint endpoint; public MyInteraction(UmlInteraction interaction) { this.interaction = interaction; lifelines = new ArrayList<>(); messages = new ArrayList<>(); attributes = new ArrayList<>(); } }
-
MyStatemachine
public class MyStateMachine { private UmlStateMachine stateMachine; private UmlRegion region; private UmlPseudostate pseudostate = null; private UmlFinalState finalState = null; private ArrayList<UmlState> states = new ArrayList<>(); private ArrayList<MyTransition> transitions = new ArrayList<>(); public MyStateMachine(UmlStateMachine stateMachine) { this.stateMachine = stateMachine; } }
-
MyTransition
public class MyTransition { private UmlTransition transition; private UmlEvent event; private UmlOpaqueBehavior opaqueBehavior; public MyTransition(UmlTransition transition) { this.transition = transition; } }
上述架构中好多的属性都没有被用上(比如region, endpoint, event 和 opaqueBehavior),但是根据从属关系还是将其放在了相应的位置
最后的一个类 Tarjan 是一个计算有向图的强连通分支的算法。这里附上一个我认为讲的深入浅出的tarjan算法的博客:全网最!详!细!tarjan算法讲解
1.2.2 结构分析
-
复杂度分析
-
类复杂度
-
方法复杂度
(方法太多了,反正一般也没人看我就不粘了) -
包复杂度
-
1.2.3 难点分析
其实感觉这次作业在关于状态图和时序图的分析上,和类图大同小异,同样是找好了每一个element的各个reference 的具体含义之后进行封装即可,难度并不大。但是这次作业有很多的边界问题,如何处理边界问题成为了这次作业相当程度上的难点。当然处理方法是等讨论区里老师发暖心贴或者的等待基本数据限制
另外再有的难点就是3个 precheck 了。第一个precheck很简单,难点就在 R002 和 R003 。在R002里,我是将类和接口分开处理,处理类的时候依次遍历,用一个数组存储路径,同时找到循环之后将数组中循环的路径出栈并且加入异常输出类。关于接口我直接使用的tarjan算法的java模板
在R003里我是用的DFS遍历,同时只要遍历到父节点(包括父类或者实现、继承的接口)是需要报异常的,就都调整到异常输出里面。
同时,由于这次作业涉及的内容较多,所以构造测试也是重难点之一。我觉得构造数据就要针对边界数据构造,比如自继承、双重实现等等。同时要考虑在类的输入顺序并非期望顺序的时候。这里粘贴一个自己构造的UML图。
2. 架构设计及OO方法理解的演进
四次作业下来,其实每一次作业的架构都各有千秋。第一次作业的求导运算,与其说架构倒不如说是将面向过程的方法分开来写,虽然在层次上分的很清楚(因子,单项式,多项式,表达式),但是就方法而言,仍然是一个方法进行到底。第二次作业的电梯问题,多线程其实是相当难的,因为很难知道程序在某一刻执行到了什么情况(所谓不确定性)。至于架构,只是简单的输入进程,调度器,电梯,这次作业更讲究的是调度的方法,根据方法决定架构的设计。而第三次作业和第四次作业就很考验架构了。这两次作业也是让我对于封装有了更深层次的理解。所谓OO,就是应该将一个模型模块化,对于模块自己内部的属性方法,应该对于其调用对象尽量少的可见。这句话的意思并非是限制类中的成员不可对外界交互,而是说,要尽可能的避免这种情况。这不是一种规定,我们所看重的也不是减少交互这种结果,很是需要降低耦合,独立模块化这种架构的出现。这就是我理解的封装,我理解的OO的思想与方法。
3. 测试理解与实践的演进
-
第一单元
这一单元的测试重点为 Wrong Format,当时奉行的原则是,先构造测试集,以此来强迫自己思考边界数据,考虑所有情况,这对于架构的设计同样有好处
-
第二单元
这一单元的测试重点为各种情况下的电梯运行情况。其实多线程对于测试对于debug都是一个灾难性的存在。其不确定性和上手难度都是蛮让人崩溃的。针对这个单元的测试,写对拍器无疑是极好的选择
-
第三单元
重点来了。Junit,先当强大的工具。这个工具强大就强大在它把一个方法抽离出来了,针对单独的方法来进行测试。相对于一般的测试方法,由程序自己跑来验证无疑缩短了IO时间。但是这个软件更多的适用于类似后两个单元这样的指令类程序。像第二单元那种多线程的问题就有点捉襟见肘了。毕竟多线程的问题大多是在线程协作之间,并非一个方法就能测试出来的。倘若将各种协作都测试,种类又过多。同时这个工具不能实现过于复杂随机数据的测试
-
第四单元
这一单元的测试其实更多的是模拟的一种无法控制输入(输入非简单数据或指令)的测试,需要自己构造UML图来测试。但是就测试的方法来说还是大同小异,还是针对边界数据,针对大数据和复杂情况来构造测试数据
4. 课程收获
要说最实在的收获,那必然是学会了JAVA,学会了OO,但是其实还有更多的收获。
其实我原先一直对于长代码很是恐惧。大一一年下来写的代码最长的也就是200行左右,那时简直难以想象1000行乃至2000行的代码如何能写出来。而OO这门课算是克服了这种恐惧。
而要说最大的收获,其实是掌握了一种写代码的流程,工程化的方法。从(学习知识)解读需求,到设计架构,到自行上网寻找资料代码,再到实现架构,以及最后的鲁棒性测试。一套完整的流程走下来其实只需要三四天的时间,而如此来看其实千行代码并没有那么可怕。尤其是这一次作业写到了恐怖的1677行,其实写出来也没有那么的疲劳(当然和代码的难度是有关的)。
再有的话也就是OO的思想了。我假期自学了一点的python和java,但是感觉起来只是在学习语法,对于面向对象的思想其实没有任何收获。只有上了这门课,每次作业都是三层递进式的开展,这让我感受到自己的码代码水平逐渐上升的同时,也能更加深刻的理解面向对象的想法。
最后要说的就是写博客了。其实自己以前也没怎么写过博客,看着网上那些博客感觉他们都好厉害呀。但是其实当自己写了一个三周的作业之后,感觉其实可说的东西还是不少的。
总体来说,OO让我见识到了很多的新东西,受益匪浅!
5. 改进建议
我感觉这门课上下来还可以。实验课感觉问题不大,但是理论课,说实在的感觉帮助不大。并非理论课讲的东西不重要,而是理论课讲的东西有点超出接受范围。固然那些大佬们能听懂,但是他们可能本来就已经明白这些东西了。而我们这些小白其实听这些高屋建瓴的高谈论阔,理解起来还是有点难度的。
还有就是我很想说一说的上机实验。我不是很明白上机考试存在的意义。上午学完了下午实践么?还是检验所学是否牢固?如果是后者的话,那么给自己学习的时间难道只有课上和中午那点时间?如果是前者,重在实践的话,那么难度是否过难?题量是否过大?而且上完机之后也不开放题目?如果是实践的话,我希望能提供一些思路什么的,比如在上机的一些时间节点给一些提示,或者干脆在后半个小时限制提交,并且对于实验内容进行讲解。甚至有些实操的东西可以公布操作方法,让我们进行体会。可能助教们觉得那个题很简单,做半个小时就能做完,那么我对于像我这样的小白在那里干坐一个半小时表示愧疚。但是我觉得至少这一个半小时能不让我觉得这个时间是在浪费,而且还很大程度上打击对于OO这门课的自信心。
同时我想问一问关于强测的问题。强测的合并修复是不是一种变相作弊。我这学期一共进了3次C组,两次B组,可以说是个不怎么喜人的结果。但是我有两次C组的代码更改量应该都不多,应该都不多于10行。而只有一次进行了大规模的重构。然而事实证明在强测分相似的情况下,bug数量的多少就完全不影响总分数了。希望以后能针对这个小地方进行一些修改。
另外的另外,就是一些小问题了。比如指导书更新了能不能通知一下大家,无论是从微信通知群还是从讨论区。比如第四次作业的代码能否写一些简单的注释,比如这个函数大概是干什么的。
6. 写在最后的话
前面说了那么多坏话,这里还是得捧一下OO,不然估计这次博客就没分了
OO的确是一门很好的课。实话实说的是,自上大学以来,OO学到的东西是让我感觉到最贴近于将来就业需要的专业能力的。同时这一届的OO相比于上一届有很大的改观,这也全是助教大大们和老师的辛勤努力!我听说上一届的OO特别混乱,而这一届能进行的井井有条,也多亏了助教和老师们!助教们忙考期忙学业还能在讨论区帮我们答疑,老师宁愿牺牲自己午休的时间也愿意帮我们上课,这都是令人感动的事迹!
感谢老师们!感谢助教们!感谢OO!