• BUAA_OO_2020_Total_Summary


    概述

    本博文从Unit4架构设计、四单元中架构设计及OO方法理解的演进、四单元中测试实践与理解的演进、课程收获、改进建议与线上学习体会六个方面展开,总结本学期的OO之旅.

    1. Unit4架构设计

    1.1 第一次作业

    UML图

    分析

    • 从上述UML图中可以看出,第一次作业中并未进行过多宏观上的设计,仅仅从功能角度进行类的拆分,但基本上重要的信息都提炼出来了,后面的作业中把信息放在一个单独类中存储即可,仅仅只是视图上的重构而不涉及到内部逻辑的重构.
    • 具体类介绍
      • MyUmlInteraction
        • 实现官方的UmlInteraction接口
      • Node
        • 各Uml元素对应于一Node,存储该元素的子元素并对子元素进行分类
      • Initialer
        • 对一些信息进行初始化
      • Adder
        • 对一些数据结构进行信息的添加
      • Checker
        • 对一些信息进行检查
    • 大体思路
      • 用一个HashMap类型的elementMap存储元素id与相应的元素实体的对应关系.
      • 考虑到各Uml元素的子元素信息在工程实现中会大量应用,故已经抽象出一个Node类冗余存储子元素信息,并用一个HashMap类型的idTree存储元素id与相应的结点Node的对应关系.
      • 用一个HashMap类型的extendPic以邻接表的形式存储类与类、接口与接口的继承关系.
      • 用一个HashMap类型的implementPic以邻接表的形式存储类与接口的实现关系.
      • 用一个HashMap类型的associatePic以邻接表的形式存储各Uml元素的关联关系.
      • 加入元素的过程中,将classElements、associtationElements单列出来以便后续使用. 同时每加入一个元素,则调用adder中的addElement2IdTree方法,结合其parentId信息更新一次idTree,当更新某一节点的childIds信息时,判断childId对应的Uml元素类型,将operationChildIds、parameterChildIds、attributeChildIds以HashSet的形式再存放以便后续使用;对于表示继承或实现的Uml元素,分别调用adder中的addRelation2ExtendPic及addRelation2ImplementPic方法(在第三次作业中已将两方法合并),更新extendPic或implementPic. 所有元素加入完毕后,利用associationElements及elementMap中存储的信息,通过调用initialer中的initAssociatePic方法初始化associatePic.
      • 完成元素加入环节后,接口中各函数的实现均可通过以上所述的属性信息,结合DS知识顺利完成.

    1.2 第二次作业

    UML图

    分析

    • 本次作业中加入了顺序图和状态图,所要实现的接口函数都很容易,在较为缓和的阶段,我从整体视图上调整了一下第一次作业中的架构,而保持第一次作业中的代码内部逻辑不变
    • 新加类介绍
      • MyUmlClassModeInteraction
        • 实现官方的UmlClassModeInteraction接口(大量搬运第一次作业成果)
      • MyUmlCollaborationInteraction
        • 实现官方的UmlCollaborationInteraction接口
      • MyStateCharInteraction
        • 实现官方的UmlStateChartInteraction接口
      • ElementInformation
        • 将第一次作业中的idTree、classElements、extendPic以及本次作业中的transitionPic等存储关键信息的属性(具体见上述类图)单独放在此类中管理
    • 大体思路
      • 根据作业需求在ElementInformation、Node这两个类中增加有用的全局信息存储容器、Uml子元素信息存储容器,后续功能的实现利用好这些新加入的属性信息即可,无需修改原有代码的内部细节,符合开闭原则

    1.3 第三次作业

    UML图

    分析

    • 本次作业的架构设计类似于第二次作业,根据需求在ElementInformation及Node类中继续添加所需的容器属性,由此可见存储信息的管理部分是本次作业的重要迭代点.
    • 将UmlStandardPreCheck中检查函数的主体部分在Checker类中通过静态方法实现,在ElementInformation中设一HashMap<Integer, Boolean> checkRet标记当前UML解析器的各项检查的通过情况,ElementInformation初始化完成后将其传至checker类进行检查.
    • checker类先调用一checkAll函数,依次对类信息、继承信息、实现信息、元素名字信息、属性信息、最终状态信息、最先状态信息进行检查,若检查出相关错误则立刻退出该函数. 只要在ElementInformation及Node类中将相关信息存储清楚,这些模型检查函数的难度也仅仅只是在实现上而非架构上了.

    2. 四单元架构设计与理解

    2.1 第一单元

    • 第一单元既是OO课程的开幕,又是本课程的难度顶峰. 值得庆幸的是,我一开始就对对象作了比较细致的层次性划分,因此三次作业均较为顺利地完成了下来,而并未出现过多的迭代. 从整个表达式组成的角度出发,我设置了Expression类、Item类与实现Factor接口的各因子类、各因子类中又满足一定的嵌套关系. 对于输入解析部分,我在Expression类中进行,用替换法将第三次作业中的嵌套表达式替换为类似于"(#)"的类型以向第二单元的解析部分靠拢. 对于因子的管理,我亦采用了所学的工厂模式,让工厂外部充当“信息”传递者,让工厂根据外部传递的“信息”制造“产品”,工厂中又分为主工厂与子工厂,主工厂分析“信息”类比向合适的子工厂下发任务,子工厂向上返回“产品”,以递归的形式完成嵌套方面的化简. 总而言之,通过第一单元的学习,我对OO项目的架构设计已经形成一个初步的认识.

    2.2 第二单元

    • 第二单元虽然整体代码量小于第一单元,但由于初次接触多线程熟悉程度尚有待提高、以及debug较为困难的原因,本人在此单元中投入的时间是四个单元最多的. 本单元最终的设计比较简单,InputHandler类将输入的PersonRequest转为包含捎带请求信息的PersonRequest1(命名不够明确,可以考虑将类名换为ProcessedPersonRequest,正如阿里巴巴手册中所说的变量名、类名及方法名不应因追求简洁而降低其可读性,毕竟即使名字长现代IDE也有自动补全功能来补偿),PersonRequest1被传给管理器Manager类后管理器像电梯Elevator发送相应请求. 整体服从“生产者-消费者”模型,其中InputHandler为“生产者”、PersonRequest1为“消费者”、Manager为互斥访问的传送带、Elevator为电梯. 在第三次迭代完成后,Manager类月Elevator的代码均略显臃肿,虽然足以解决第三次作业,但不利于程序本身的可扩展性. 因此,可以通过新增播报器类、内部请求队列类、电梯队列类、外部请求队列类、唤醒器类与分配器类等方法,让设计更加符合SPC的要求,即每个类或方法只有一个明确的职权.

    2.3 第三单元

    • 个人认为第三单元的作业设置开放度较低,同学们并没有许多在整体架构上自由发挥的余地,主要任务集中在JML的学习与阅读及算法的设计上. 我设计了IdTuple和DisTuple这两个辅助类辅助并查集、Dijkstra等算法的实现,并将并查集类单独出来完成其有关属性的存储和方法的实现. 算法方面,还可将Tarjan算法与Dijkstra算法的实现核提取出来,减轻MyNetwork类的负担,使其更好地呈现出“领导“其余的主要类(MyPerson、MyNetwork、MyGroup)”各司其职“的功能而非关注算法的实现. 各对象既可以是像第一单元中的各因子一样保持一种平行或从属关系,也可以是使用与被使用的关系,让使用者仅关注于使用被使用者提供的功能.

    2.4 第四单元

    • 1.部分已进行过具体介绍,故在此不加以赘述.

    3. 四单元测试实践与理解

    3.1 第一单元

    • 在计算机组成原理课中,我的所有测试均是穷举式的“白盒测试”,最终在P7时不慎因课下的穷举的疏漏的出现问题. 在OO课程中,我开始尝试自动化测试,通过大量随机生成的测试用例检查程序的基本功能. 自动化测试能够确保程序在一般情况下的正确性,因此我认为在准确理解题意的前提下,对各单元作业展开有效的自动化测试,可避免在强测中遭受超低分的袭击.
    • 第一单元第一次作业我基于Linux命令行和Python程序开展自动化测试,并未构造过多的手动样例用于测试. 然而在互测中,一位同学未对求导后的表达式不含任何项的边界情况进行特判而出现bug,用5*x**0这一手动样例即可成功hack,但我的自动化测试程序却一直测不出来. 这让我开始反省自动化测试程序的书写,随机生成的样例要长、短结合,同时最好设置一比例系数,让一些边界值、特殊值以一个较大的概率出现,毕竟bug大部分情况下萌生于边界之处. 同时,我亦认识到即使采用自动化测试,手动测试的作用同样是不可忽视的,较好的测试模式是手动与自动的结合,通过手动测试检测程序的边界抗压能力,通过自动测试检测程序的基本功能. 这样的基本测试路线一直渗透到后续的三个单元之中.
    • 在第二次作业中,我重点构造了测试正则表达式的样例,以保证正则表达式解析的正确性. 在第三次作业中,我重点构造了包含复杂嵌套、易引发程序深层递归、易导致TLE的样例,并结合作业指导书中设定的各边界点进行测试. 在测试TLE的时候,既要根据程序的具体实现算法构造易提高语句执行频度的测试用例,又要让测试用例的复杂程度略高于指导书中的数据限制,以保证程序具有足够满足需求的的运行性能.

    3.2 第二单元

    • 第二单元相较于第一单元程序的正确性更易于保证,相应的边界情况较少,测试上更多的关注点在线程安全方面. 经过互测发现,许多同学的程序存在死锁问题,甚至有些死锁数据复现的频率极低,近十次hack均未能成功,但即使能够逃过互测也不代表我们无需关注触发频率极低的死锁. 死锁的测试相当依赖于自动化测试,通过大量的随机样例碰撞发现死锁的情况,从概率统计的角度上来说就是通过增大样本容量来让小概率事件暴露出来. 倘若我们一味采用手测的方法,效率必然是极低的. 因此,我认为第二单元开展自动化测试是相当有必要的,即使不通过自动化的方式来验证stdout的正确性,也要通过自动化生成大量的随机测试用例以测试死锁情况.
      • 注:需要通过课程组提供的输入处理接口反编译出相应的原码并进行处理,以模拟评测中的输入输出方式.
    • 弄清楚需要互斥访问的临界区和数据,每完成一相应函数后,检查相关的临界区和数据是否得到有效保护,尽量避免在第一版代码完成后才开始对代码进行字面上的检查. 基本功能部分(如电梯的升降、开关门规则)可通过类似于JML的思想在coding之前写一个能无二义性地概括有关规则的“简要规格”(确保其符合题意),完成一部分后检查此部分是否严格满足该“简要规则”的约束,避免代码的前后出现逻辑上的冲突.

    3.3 第三单元

    • 采用JUnit单元测试对较为复杂的非pure方法进行初步检测. 在开展单元测试的过程中,对于任一给定规格的方法,应尽可能根据规格中不同的前置条件对正常、异常情况均开展充分的测试,并通过repOK验证方法结束时各后置条件是否满足. 在有具体JML规则的情况下各模块所要完成的任务的二义性得以消除,相应测试的思路围绕JML即可展开,即保证程序的实现满足JML的约束,测试也因此变得条理清晰许多.
    • 通过Python随机生成测试样例,开展大量的多人对拍. 本单元中测试的样例生成较为简单,大部分同学通过对拍即可保证程序的正确性,而出错更多集中在TLE上. 在测试算法运行性能时,既要关注算法的复杂度,又要关注实现细节与常数方面,譬如在实测中,将ArrayList和HashMap作较小规模的初始化可提高程序运行的效率,而对其作较大规模的初始化则反而会导致add、put等方法的运行速度显著降低. 有时候疏忽大意,将循环中无需修改的某个值对应的计算表达式放在循环条件中,让每次循环时都对该值做不必要的重新计算,亦会降低程序的运行效率.

    3.4 第四单元

    • 相较于给出JML的第三单元,本单元各接口所涉及到的函数功能由于仅有指导书上的文字叙述而略显模糊,需要不断地通过向助教来或许无二义性的需求信息. 在获取到明确的需求信息后,我用较为数学化的语言对其中一部分信息加以归纳总结,并将代码中各接口函数的实现逻辑与相应的信息进行对照,保证功能实现尊重于需求. 将需求及UML模型结构梳理清楚既是完成本单元代码实现部分的重点,又是开展本单元功能实现的关键.
    • 本单元仍通过Python随机生成测试样例,与上一单元不同的是本单元的测试数据生成难度较大. 而自动测试程序的编写过程中同样应尽可能采取OO的思想,比如将类、类属性、类方法、类方法参数耦合起来作为一大对象,将接口、接口属性耦合起来作为一大对象,在更为顶层的视角上将这些对象通过继承、实现等捆绑起来. 只有这样,自动测试程序才可保持较好的迭代性.

    4. 课程收获

    • 对Java语言有更深入的掌握
      • 学习到深浅拷贝、抽象类与接口、正则表达式、线程交互及JVM工作原理等Java相关知识,提升使用Java语言编程的熟练度,感受到Java语言显著区别于C语言的的面向对象特征.
    • 认识到许多设计模式
      • 如工厂模式、单例模式、生产者-消费者模式、观察者模式、工人模式、适配器模式. 这些设计模式是前人在开发过程中抽象凝练出来的用以解决特定问题的方式,应用这些设计模式完成作业或实验,能够使代码的逻辑更为清晰、处理更为简便.
    • 提升大代码的完成能力
      • OO课程使用现代化IDE——IDEA,其中集成了各种各样评价代码结构、静态检查代码的插件,并且具有查看方法原码、统计方法使用位置、自动补全接口方法、打jar包、反编译jar包、打印运行日志、分屏等功能,极大提升完成大代码时的编程体验. 自主探索工具链的使用以提高编程效率,是一件有意思的事情.
      • 通过对整体问题进行拆分,将大代码拆解为多个模块,对各模块逐一实现,并保证各模块之间的协调性,从而化整为零,不再从长度上畏惧大代码. 尤其是在第四单元中得以体会到完成代码的过程明显比第一单元轻松许多,设计的思路更为清晰. 在coding之前,养成想好各模块要干哪些事,模块与模块之间的关系是什么样的,输入和输出分别要如何处理或传递这些问题的习惯,即学会给出一个明确的框架性设计,并衡量这种设计能否满足功能的可扩展性,若不能满足应作哪些调整而避免后续的重构. 做好设计往往能够coding过程中的效率提升,达到“磨刀不误砍柴工”的效果.
      • 对大代码的测试亦能比较顺利地完成,既能抓住边界、抓住单元开展白盒测试(手动测试),又能针对整体功能开展黑盒测试(自动测试),进而在测试中发现程序的问题并进一步提高程序的鲁棒性. 在整个OO课程的四个单元作业中,我没有在强测、互测中被发现任何bug,并且课下的中测基本上前两次即能通过,这亦说明了我所开展的测试还是相当有用的. 同时,通过自动化测试的编写,亦学到了许多关于Linux命令行和python的相关知识.
    • 初步培养OO思想
      • 在学习OO课程之前,我从未真正意义上编写过任何面向对象的工程项目,所编写的程序均是过程式的“面条状”程序. 而OO课程让我感受到对象式程序的魅力,一是把对象作为设计单元,让编写的代码更加生活场景化,更具有可读性;二是将逻辑上紧密连接的数据通过封装聚合在一起,极大地提升了代码的可扩展性;三是测试亦能从对象的层面上展开,更具有条理性和精确性.
      • 在完成代码后,能够根据面向对象的六大设计原则——开闭原则、里式代换原则、依赖倒转原则、接口隔离原则、最少知道原则及合成复用原则对设计进行评价. 这六大原则虽然所表达的事实很显然,但正如python之禅一样通过逐条对照可发现设计中的许多值得改进之处.
      • 通过UML单元的学习后,能够将面向对象程序的基本模型通过UML图提练出来,从而在更抽象的层面上梳理架构设计并分析其优缺点. 通过JML单元的学习,亦可感受到实际工程项目的完成中设计与实现相分离的特点,体会到以契约形式明确需求对代码实现及测试带来的诸多好处.

    5. 改进建议

    1. 完善实验体系

      • 实验应设成绩反馈,一是让同学们发现自己的问题所在,二是对于具有一定主观性的实验(如UML单元第一次实验),可以让同学们在对成绩有所质疑时提出申诉,避免误判.
      • 实验中的代码应适当提高可读性,如UML单元第一次实验,许多变量的含义以及函数的功能是模糊的,虽然通过让同学们阅读代码猜测这些含义和功能可以锻炼同学们代码理解能力,但仍应将一些关键点解释清楚而非一味地通过追求问题的“抽象”以确保实验难度.
    2. 增加互测趣味性

      • 设置点赞功能,每个屋的同学通过阅读互测代码,从架构设计与可读性的角度挑选出其中3份优质代码进行点赞,避免同学们在互测环节只是进行纯粹的黑盒测试:
        • 不参与点赞将扣除个人互测活跃分;
        • 点赞一次必须点赞3份,且最多仅能点赞3份;
        • 点赞后需要一定字数的评论;
        • 为防止加大同学们的课业负担,点赞进设置在第一、第二单元的最后两次作业中,根据获赞数可以对互测环节进行适当加分(考虑到可能存在私下串通的问题,这部分加分无需加的过多,主要是为鼓励同学们阅读代码).
      • 设置留言功能,被留言者将在bug修复通过后看到留言,这主要是考虑到二单元电梯中遇到许多死锁代码却hack不到的情况,通过留言可以向被留言者传达”注意代码中死锁问题“的讯息.
      • 根据历次hack的情况设置狼人等级,每一次分配hack屋时尽可能保证同屋有各种狼人等级的同学,避免出现明明有许多bug却无人参与hack的“僵尸屋”,同时通过“等级化”、“段位化”来激励同学们积极参与互测(讨论区也可设置类似的讨论区等级,鼓励同学们积极参加讨论).
    3. 完善第三单元的作业

      • 第三单元的训练要点是JML,但在架构上的框定略显死板,同学们基本按照课程组下发的接口实现各函数即可顺利完成作业,而且实际训练中同学们因惧怕TLE而将关注点更多地放在算法复杂度的优化上,相较之下契约式编程思想的培养并无过多体现.
      • 第三单元各次作业的迭代本质上没有在JML规则以及架构方面有所上升,建议在下一届的本单元作业中训练继承中的规格关系这一知识点,使课上所传授的内容在作业中有更加完整的体现.

    6. 线上学习体会

    • OO课程在去年改革后保持较高的质量,又具有一定的趣味性,因此本学期这门课程可以说是我学习体验感最佳的一门. 每个周二晚上公布新作业题,我会在第一时间内下载并构思,在当天晚上基本写个大概,在第二天白天完成第一版本代码,利用后续时间进行功能的测试与性能的改进,能够维持一个有序的学习节奏.
    • 线上研讨课中许多同学分享了作业设计中的思想方法与一些有用的知识、实用的技巧,开拓了我的眼界. 通过与老师、同学的交流,我对许多问题的理解也有所加深. 总而言之,虽然是线上学习,但仍要重视沟通与讨论,在同他人的思维碰撞中获得更多收获,达到1+1>2的效果.
  • 相关阅读:
    async 和 await
    C#中lock死锁
    Attribute特性
    数据库优化
    EF(ORM)
    依赖注入
    面向接口编程
    EF乐观锁与悲观锁
    为什么要使用RESTFUL风格?
    cloudsim 3.0.3下载与安装教程
  • 原文地址:https://www.cnblogs.com/palemodel/p/13166144.html
Copyright © 2020-2023  润新知