BUAA-OO-2021 第四单元总结

第一次作业

类图

classDiagram MyUmlInteraction MyNode MyInterface MyMethod Main MyNode .. MyInterface MyNode .. MyMethod class MyUmlInteraction { +getClassCount(String) int +getClassOperationCount(String) int +getClassAttributeCoutn(String) int +getClassOperationVisibility(String, String) Map~Visibility_Integer~ +getClassAttributeVisibility(String, String) Visibility +getClassOperationParamType(String, String) List~OperationParamInfomation~ +getClassAssociatedClassList(String) List~String~ +getTopParantClass(String) String +getImplementInterfaceList(String) List~String~ +getInfomationNotHidden(String) List~AttributeClassInfomation~ -queryClassByName(String) List~MyNode~ -queryClassByNameUnique(String) MyNode -queryAttrByName(MyNode, String) List~UmlAttribute~ -queryAttrByNameUnique(MyNode, String) UmlAttribute -queryAttrByNameUnique(String, String) UmlAttribute -getTypeName(NameableType, boolean) String } class MyNode { addAttr(UmlAttribute) void addMethod(MyMethod) void addAssoc(MyNode) void addInterface(MyInterface) void setParent(MyNode) void hashCode() int equals(Object) boolean getThisClass() UmlClass getParentClass() MyNode getAttributes() List~UmlAttribute~ getMethods() List~MyMethod~ getAssociations() List~MyNode~ getInterfaces() List~MyInterface~ getAncestors() List~MyNode~ getAncestorTop() MyNode } class MyInterface { addFather(MyInterface) void getThisInterface() UmlInterface getFathers() Set~MyInterface~ getAncestors() Set~MyInterface~ hashCode() int equals(Object) boolean } class MyMethod { addParam(UmlParameter) void getParamOutput() NameableType getParamInput() List~NameableType~ getOperation() UmlOperation } class Main { main(String[]) void }

第一次作业不怎么需要考虑时间复杂度。主要难是在于trivial的需求理解。

代码度量

OCavg = Average opearation complexity(平均操作复杂度)

OCmax = Maximum operation complexity(最大操作复杂度)

WMC = Weighted method complexity(加权方法复杂度)

CogC = Cognitive complexity(认知复杂度)

ev(G) = Essential cyclomatic complexity(基本圈复杂度)

iv(G) = Design complexity(设计复杂度)

v(G) = cyclonmatic complexity(圈复杂度)

  • 代码统计

    Source File Total Lines Source Code Lines Source Code Lines[%] Comment Lines Comment Lines[%] Blank Lines Blank Lines[%]
    Main.java 10 8 80% 0 0 2 20%
    MyInterface.java 53 43 81% 0 0 10 19%
    MyMethod.java 42 33 79% 0 0 9 21%
    MyNode.java 85 66 78% 0 0 19 22%
    MyUmlInteraction.java 294 271 92% 0 0 23 8%
    Total: 5178 3338 64% 1228 24% 612 12%
  • 类复杂度

    class OCavg OCmax WMC
    main.MyUmlInteraction 2.89 18.0 52.0
    main.MyMethod 1.4 3.0 7.0
    main.MyInterface 1.14 2.0 8.0
    main.MyNode 1.12 2.0 18.0
    main.Main 1.0 1.0 1.0
    ... ... ... ...
    Total 561
    Average 1.62 2.25 6.52
  • 方法复杂度

    method CogC ev(G) iv(G) v(G)
    main.MyUmlInteraction.MyUmlInteraction(UmlElement...) 38.0 7.0 20.0 24.0
    main.MyUmlInteraction.getClassOperationParamType(String,String) 11.0 7.0 3.0 7.0
    main.MyUmlInteraction.getTypeName(NameableType,boolean) 11.0 6.0 7.0 9.0
    ... ... ... ... ...
    Total 393 500 548 637
    Average 1.09 1.39 1.53 1.77

通过复杂度分析,本次代码除了MyUmlInteraction外不高。如此高复杂度在于初始化时的大量if-else

BUG分析

本次作业暂无BUG

第二次作业

类图

classDiagram MyUmlGeneralInteraction MyUmlInteraction MyUmlInteractionState MyUmlINteractionNext MyNode MyInterface MyMethod MyState MyStateMachine MyRole MySequence Main MyUmlGeneralInteraction *-- MyUmlInteraction MyUmlGeneralInteraction *-- MyUmlInteractionState MyUmlGeneralInteraction *-- MyUmlInteractionNext MyStateMachine .. MyState MyNode .. MyInterface MyNode .. MyMethod MySequence .. MyRole class MyUmlInteraction { +getClassCount(String) int +getClassOperationCount(String) int +getClassAttributeCoutn(String) int +getClassOperationVisibility(String, String) Map~Visibility_Integer~ +getClassAttributeVisibility(String, String) Visibility +getClassOperationParamType(String, String) List~OperationParamInfomation~ +getClassAssociatedClassList(String) List~String~ +getTopParantClass(String) String +getImplementInterfaceList(String) List~String~ +getInfomationNotHidden(String) List~AttributeClassInfomation~ -queryClassByName(String) List~MyNode~ -queryClassByNameUnique(String) MyNode -queryAttrByName(MyNode, String) List~UmlAttribute~ -queryAttrByNameUnique(MyNode, String) UmlAttribute -queryAttrByNameUnique(String, String) UmlAttribute -getTypeName(NameableType, boolean) String } class MyUmlInteractionState { +getStateCount(String) int +getSubseqentStateCount(String, String) int +getTrasitionTrigger(String, String, String) List~String~ -queryStates(String) List~MyState~ -queryState(String, String) MyState } class MyUmlInteractionNext { +getParticipantCount(String) int +getIncomingMessageCount(String, String) int +getSentMessageCount(String, String, MessageSort) int -queryInteractions(String) MySequence -queryLifeline(String, String) MyRole } class MyNode { addAttr(UmlAttribute) void addMethod(MyMethod) void addAssoc(MyNode) void addInterface(MyInterface) void setParent(MyNode) void hashCode() int equals(Object) boolean getThisClass() UmlClass getParentClass() MyNode getAttributes() List~UmlAttribute~ getMethods() List~MyMethod~ getAssociations() List~MyNode~ getInterfaces() List~MyInterface~ getAncestors() List~MyNode~ getAncestorTop() MyNode } class MyInterface { addFather(MyInterface) void getThisInterface() UmlInterface getFathers() Set~MyInterface~ getAncestors() Set~MyInterface~ hashCode() int equals(Object) boolean } class MyMethod { addParam(UmlParameter) void getParamOutput() NameableType getParamInput() List~NameableType~ getOperation() UmlOperation } class MyRole { addOut(UmlMessage) void addIn(UmlMessage) void getMsgOut() Map~String_UmlMessage~ getMsgIn() Map~String_UmlMessage~ getActor() UmlLifeline } class MyState { getState() UmlElement addTrasition(MyState, UmlTrasition) void getNext() Set~UmlElement~ getTo(String) List~UmlElement~ } class MyStateMachine { addState(MyState) void getStates() List~MyState~ getStateMachine() UmlStateMachine } class MySequence { addRole(MyRole) void getInteraction() UmlInteraction getRoles() Map~String_MyRole~ } class Main { main(String[]) void }

第二次作业就是在第一次作业类图的基础上添加了状态图和顺序图。

代码度量

  • 代码统计

    Source File Total Lines Source Code Lines Source Code Lines[%] Comment Lines Comment Lines[%] Blank Lines Blank Lines[%]
    Main.java 10 8 80% 0 0 2 20%
    MyInterface.java 53 43 81% 0 0 10 19%
    MyMethod.java 42 33 79% 0 0 9 21%
    MyNode.java 85 66 78% 0 0 19 22%
    MyRole.java 37 28 76% 0 0 9 24%
    MySequence.java 27 20 74% 0 0 7 26%
    MyState.java 56 47 84% 0 0 9 16%
    MyStateMachine.java 27 20 74% 0 0 7 26%
    MyUmlGeneralInteraction.java 152 130 86% 0 0 22 14%
    MyUmlInteraction.java 294 271 92% 0 0 23 8%
    MyUmlInteractionNext.java 105 96 91% 0 0 9 9%
    MyUmlInteractionState.java 133 123 92% 0 0 10 8%
    Total: 8806 5774 66% 1966 22% 1066 12%
  • 类复杂度

    class OCavg OCmax WMC
    main.MyUmlInteractionState 3.67 12.0 22.0
    main.MyUmlInteractionNext 3.0 9.0 18.0
    main.MyUmlInteraction 2.89 18.0 52.0
    main.MyState 1.6 4.0 8.0
    main.MyInterface 1.14 2.0 8.0
    main.MyNode 1.12 2.0 18.0
    main.Main 1.0 1.0 1.0
    ... ... ... ...
    Total 971
    Average 1.60 2.34 7.30
  • 方法复杂度

    method CogC ev(G) iv(G) v(G)
    main.MyUmlInteraction.MyUmlInteraction(UmlElement...) 38.0 7.0 20.0 24.0
    main.MyUmlInteractionState.MyUmlInteractionState(UmlElement...) 19.0 1.0 14.0 15.0
    main.MyUmlInteractionNext.MyUmlInteractionNext(UmlElement...) 15.0 1.0 9.0 9.0
    ... ... ... ... ...
    Total 635 855 917 1089
    Average 1.02 1.38 1.48 1.76

与第一次作业类似。此处不多言了。

BUG分析

本次作业暂无BUG

但是其实感觉是有问题的,因为我处理TRANSITION_TRIGGER时没有检查迁移是否存在,如果迁移不存在需要抛出异常,只有迁移存在且无事件时才返回空列表。在提交结束后不久我发现了这一点,并立刻push了一次。当然,这里也有一点插曲,那就是初版的指导书里是没有这方面的细节描述的,所以只有看过后来更新的内容才能得知这部分需求的具体要求。所以可能助教组人性化地把相关数据剔除了,也可能这样的数据本身不太合理就没放在强测中。

第三次作业

类图

代码度量

与第二次作业类似,多了若干CheckForUml00X,因为与第二次作业差不多,遂此处省略。

算法分析

  • Rule002 检查是否有循环继承。使用拓扑排序,不断删除出度为0的结点,结束后没有被删除的所有结点就是有循环继承的元素。
  • Rule003 检查是否有多重继承。如果某个接口,其父接口为多重继承则其亦为多重继承,否则若其在图论上顺着有向边可抵达的结点个数(不包括自己)少于其所有父接口可抵达个数之和则其为多重继承,其它情况则不为多重继承。

BUG分析

本次作业有两处BUG

  • Rule003的数据中,一个接口可以继承同一个接口多次,也就是在图论意义上的重边。这样的话,该接口将被认为是重复继承的。由于我记录出边从最初开始就使用Set的,所以会过滤掉重边,因而在此出现了问题。可以改成List来修复。

  • Rule008中获取状态时出现了问题,这个其实有点意思。

    //修改前
    MyState src = states.get(transitions.get(event.getParentId()));
    
    //修改后
    MyState src = states.get(transitions.get(event.getParentId()).getSource());
    

    这部分是笔误,其实IDEA也会将类型不匹配的get给标出来,但是我还是没领悟到。通过查看Map的源代码,发现get的参数竟然是Object,而put则使用了KV的强类型限制。

    V get(Object key);
    V put(K key, V value);
    
    //思考:难道不应该是这样吗?
    V get(K key);
    

    似乎这在有完善类型系统的编程语言中看来有点不可思议(这难道不应该设计成编译错误吗),但这和设计者的意图有关。这样的好处是比较键值用的是自定义的equals,可以同质化一些被认为等价的对象。比如Map<Int64, Object>中查询时,可以不仅仅是被强制用Int64去查询,而同样可以使用Int32查询(这里只是打个比方,当然需要自己重写equals)。

闲聊一下其它方面

关于R003检查多重继承的时间复杂度

鉴于上述只言片语的介绍,我们使用的检查多重继承的方法是,对于每个接口向父接口连有向边,每个接口记录其所有祖先接口的集合(Set)、其是否为多重继承(Boolean),随后枚举每个接口判断即可(类似记忆化搜索)。那么这个的时间复杂度是\(O(|V|*|E|)\)

然则某年某月某日某时,吾之室友zzy,欣然谓我一\(O(|E|*\log|V|)\)之法。

在那之前,容我这里先介绍一些解决合并问题的数据结构的小小花园。我们假设有\(n\)个不同的元素,每次我们合并两个集合直到最后剩下一个集合。

  • 启发式合并(按秩合并)。我们用set维护集合,合并时将个数较少的集合中的元素全部插入另一个集合。时间复杂\(O(n\log n*插入的复杂度)\)。插入复杂度,如果集合是红黑树则为\(O(\log n)\),如果是哈希表则为\(O(1)\)
  • Treap合并。将xy合并时,设x为元素个数少的集合,按照其根节点的值将y剖成两棵树,再分别与x的左右子树合并。单次合并复杂度\(O(u*\log(\frac vu))\)(设u<v),总合并复杂度\(O(n\log n)\),常数较大。
  • Splay合并。将元素个数较少的Splay按中序遍历的顺序插入另一棵Splay中,时间复杂度\(O(n \log n)\)。证明可见2018年论文集董炜隽《浅谈Splay与Treap的性质及其应用》,其中谈到Dynamic Finger Theorem​。附:随便插入的话是两个\(log\),较玄学。
  • 线段树合并。将集合变为权值线段树的合并,时间复杂度\(O(n \log n)\)

于是我们将每个图上的结点维护成一个可持久化的权值线段树(也就是结点可重用,区间只有\(O(2n)\)种),每个结点合并其出边的结点的线段树,因为如果合并时发现有交集则该结点与该节点的后裔会直接被判断为多重继承,所以可以认为有效的只有合法的接口结点,看起来时间复杂度是\(O(|E|\log |V|)\)

不过我还是想到了一个可以\(hack\)的数据。

flowchart TD node1[1..n/2中奇数的结点] node2[1..n/2中偶数的结点] subgraph graph1 [总计n/2个结点] node3[n/2+1] node4[[n/2+2...n-1]] node5[n] end node1-->node3 node2-->node3 node1-->node4 node2-->node4 node1-->node5 node2-->node5

如果两棵权值线段树AB,一棵为全部是奇数的元素,一棵为全部是偶数的元素,则其合并是\(O(|A|+|B|)\)的(感性地可以借用吉利线段树中的势能去想,虽然总的势能确定,但是我们构造出了一个势能差最大的操作)。那么设计大量节点来合并这样的两个结点就能让时间复杂度退化到\(O(n^2)\)了,因为DAG还可以有更多的组合,所以这不是可持久化线段树(或者说函数式线段树)能简单解决的。

至此,这个算法的时间复杂度貌似被否定,那么有更优的时间复杂度吗?我们解决多重继承判定的方法是根据有向无环图的可达性,但是这个的解决方法貌似只能是\(O(n^2)\)的,不过可以使用bitset来做到常数加速,那么时间复杂度就是\(O(\frac{n^2}{32})\)\(O(\frac{n^2}{64})\)了。

单元总结

这单元又双叒叕出现了错误,根据经验,发现还是缺乏测试(缺乏测试❌,没有测试✔️)。感觉暑假将近,连测试的念想都没有了,只能摸摸鱼🐟🐠🐡🦈之类的(这是不行的)。

这一单元主要是对UML中各种元素的熟悉与查询。繁多的元素与模糊的定义理解是这一单元的难点。经历了OO第三单元和第四单元的洗礼,我感觉对编程有了新的认识,因为对PL有过肤浅涉略,导致我一直认为我是不会写出所谓“屎山”的代码的。但是稍短的工期、谜语的需求、无框架的写码、Java语言等诸多因素,令人放弃柏拉图式的纯粹编程,转而沦为敏捷的阶下囚,同时也试图将人从代码洁癖的悬崖勒回来。但是总之综合来说这一单元还是比前面的工作量小,放在最后也不失偏颇。

四个单元中架构设计及OO方法理解的演进

上课之前

OO的理解主要在具体的例子上。

  • UI框架,深受WPF的影响,略受HTML/CSS/JavaScript影响,自己也曾用Pascal写过类似的东西(非GPU加速)。比如一个可绘制的图可以多态成多种实现形式,可以是画布、压缩图、文本、动图等。

  • PBR基于物理的渲染,对图形学研究中。曾用C#PascalC++写过类似的东西。比如一个刚体可以多态成多种形状,可以是球体、胶囊体、长方体等。

第〇单元

6Task算是练练手,稍微使用一点AOP的思想来解决。

第一单元

根据形式化定义手写一个递归下降。面向对象的味道,比如一个函数可以多态成多种类型,可以是三角函数、指数函数等。

这一单元可以说感觉是和面向对象关系最大的一单元。也为大三的编译原理打下一定的基础。这个架构其实只要学生学习相关知识后自然就会在脑海中浮现出来。

有人说求导有什么用呢?一些数学软件如Wolfram Mathematica当然内置了求导操作。图形学中的物理引擎本质上也就是个微分器。以及现在有可微分渲染的概念,将渲染结果的差别放到神经网络里跑。神经网络中激活函数也要用到求导。求导的用途很广泛,这个题目设置得很引人遐想。

第二单元

用多线程写一个有调度功能的目的选层电梯。

感觉就是想锻炼学生的多线程场景下的思考,引导学习生产者-消费者等模型,悉知同步、异步、并行、并发、阻塞、原子操作、内存屏障等基本概念。同时操作系统也上到多进程、信号量相关部分。

架构方面调度主要的两种,一种是预分配,一种是抢占式。因为研讨课老师和大家分享的比较多了,就不再赘述。

第三单元

社交网络的图论杂烩。感觉像是是对C语言数据结构课程的补充,比如填补了并查集这个算法(不会也没关系)。

没有架构可言的感觉。仅仅是代码填空。

第四单元

UML图的查询。可能是模拟训练公司里的项目?需要读懂祖传的api接口,再组织出一些功能来解决需求。

架构就是UML的结构。像刷leetcode一样填各种函数。

四个单元中测试理解与实践的演进

首先必须承认的一点是,测试的充分程度与得分是正相关的。不过和一般事物的发展规律相反,感觉理论上四个单元应该会测试得越来越得心应手,实际上四个单元下来反而越到后面越疏于测试。这是什么原因呢,有心理原因,有题目的类型原因等。

对于测试,我们作业里主要分两种类型,一种是测试正确性的,一种是测试性能的。测试数据的产生的方式也有多种,比如随机数据手造数据等。而对于自己个人的代码自己肯定是最清楚的,可以借鉴图形学中的重要性采样的思路,进行部分着重测试。这是从OI的角度来讲。而通过这几单元的学习,又可以有新的认识,测试数据来找错则为黑盒测试,通过阅读源代码分析结构来找错则为白盒测试,二者结合可称灰盒测试。而单元测试则可以验证部分功能的正确性。同时在JML的约束下,可以做到可验证的覆盖测试,如果一个东西可以证明那就对其稳定性与鲁棒性非常值得信赖了。

课程收获

  • Java语言的编写
  • Git的使用
  • 面向对象的思路
  • 多线程编程
  • UML的理解

立足于自己的体会给课程提三个(以上)具体改进建议

  • 学生的写代码时间太短了

    很多时候不能充分地打磨一个项目,虽然到了后期也没有这个念想,但是给出充足的时间比较有必要。当然这其实是有考量的,比如考验学生在规定的DDL内解决问题的能力。不过缺乏时间会削减额外拓展的可能,比如第一单元我没来得及研究出有限域上的因式分解,第二单元迫于时间没有对架构做出血的改动,第四单元指导书修改后没能及时反应等。

    改动的建议(供参考),最大化利用一些无用的等待时间,当然这对助教公测的要求更高。由星期三中午开放作业持续5天,再花费2天时间互测与公测,提升学生中测和互测的时间。

    gantt axisFormat %w title 拟OO课程的时间轴 section 学生 完成作业 :t1, 2021-06-23 12:00, 5d 互测和公测 :t2, after t1, 2d BUG修复(复用一周时间) :after t2, 7d section 助教 辛苦地测试 :a1, 2021-6-28 12:00, 2d
  • 讨论区增强

    包括但不限于分类、标签、筛选、翻页、定位等功能。

  • 提交CD的改进

    15分钟可能会存在比较影响心态的情况,可以等价地改为将每天分割为半个小时一时区,一个时区里有2次提交机会。探索一些新的提交方式等。

  • 得分反馈

    实验课这部分可能助教不是一个个批改,而是用程序匹配答案,或辅以归一化等。横向对比数学之类学科的作业也是一个个单独批改。从公平与负责的角度出发是需要有反馈的,不过这无疑将加重助教批改的工作量。

    博客得分、讨论区活跃度统计的算法,这部分弹性分段主观性比较大,可以先搁置。

posted @ 2021-06-24 19:58  buaa-shy  阅读(144)  评论(0)    收藏  举报