BUAA_OO_2022_U4_UML解析器

第四单元总结

一、本单元作业架构设计

1.类的设计

对官方包中的每一个类都设计了自己的类,如MyClass、MyInterface等等。虽然有些类确实是多余的,可以直接使用官方类,但是统一使用自己设计的类,可以减少很多不必要的逻辑负担,比如什么时候要用自己的类,什么时候要用官方的类。

2.解析的顺序和思路

本单元的架构借鉴了第一单元的递归下降的思想,因为类图、顺序图和状态图内部元素之间都存在着父子关系,所以解析时,采用了反复遍历elements的方式逐层解析三种UML图中的元素。具体如下:

2.1 元素之间的父子关系:

注:“->” 指该元素内的属性指向某些元素
UML类图

  • UmlClass

    • UmlOperation
      • UmlParameter
    • UmlAttribute
  • UmlAssociation

    • UmlAssociationEnd
  • UmlInterface

    • UmlAttribute
  • UmlInterfaceRealization

  • UmlGeneralization

UML顺序图

  • UmlCollaboration
    • UmlInteraction
      • UmlLifeline -> UmlAttribute
      • UmlMessage -> UmlLifeline UmlEndPoint
      • UmlEndPoint
    • UmlAttribute

UML状态图

  • UmlStateMachine
    • UmlRegion
      • UmlPseudostate
      • UmlState
      • UmlFinalState
      • UmlTransition -> UmlPseudostate UmlState UmlFinalState
        • UmlEvent
        • UmlOpaqueBehavior

2.2 解析的顺序

基本思路:父元素 先于 子元素解析;元素之间有“A->B”(2.1所示)关系的,B先于A解析。
基于这样的思路,总共需要四次循环解析,实现对模型的构建。

  • 第一个循环解析

    • UmlClass , UmlInterface, UmlAssociation
    • Collaboration
    • UmlStateMachine
  • 第二个循环解析

    • UmlGeneralization, UmlOperation, UmlAssociationEnd
    • UmlAttribute
    • UmlInteraction
    • UmlRegion
  • 第三个循环解析

    • UmlParameter,UmlInterfaceRealization(解析完接口的继承,直接把类继承的所有接口给它解析出来)
    • UmlLifeline,UmlEndPoint
    • UmlPseudostate,UmlState,UmlFinalState
  • 第三个循环解析

    • UmlMessage
    • UmlTransition
  • 第四个循环解析

    • UmlEvent
    • UmlOpaqueBehavior(没用到)

3.拯救风格分

这个单元要实现的接口都在MyImplementation类(下称主类)中,而且数量多,内容重。前两次的作业中,为了实现的方便,大部分的属性和方法实现都放在了主类中,在风格上暂时没有问题,但是实现完第三次作业后,我的主类中,总共就有近700行代码了,风格分直接折半,于是进行了一系列“缩代码”操作(包括删除空行和注释这些无关痛痒的操作)。

3.1 将属性移到对应的类中

比如主类中的private HashMap<String, MyClass> classes = new HashMap<>();移到MyClass类中并设置为静态private static HashMap<String, MyClass> classes = new HashMap<>();
设置为静态有两个方面的考虑,一方面也是最主要方面是,这一属性是所有MyClass类对象的集合容器,不为某一对象独有;另一方面是移去了classes属性后,主类的一系列报错可以直接用MyClass.getClasses()代替,然后再考虑将对应的方法实现也移到其他类中,这样可以避免在缩代码的时候产生一些恼人的bug。

3.2 对方法进行封装

对方法的封装又分为两类。
一类是重复代码的封装。比如在要实现的接口中,有多个方法有相同的异常判断,因为上一步(3.1)已经将对应的属性移到对应的类中了,所以这里可以直接在相应的类中实现一个静态检测异常的方法,然后主类只需要调用这个方法就好。如下:

// MyClass
public static void try2ThrowClass(String className)
            throws ClassDuplicatedException, ClassNotFoundException {
        if (dupClasses.containsKey(className)) {
            throw new ClassDuplicatedException(className);
        } else if (!name2Classes.containsKey(className)) {
            throw new ClassNotFoundException(className);
        }
    }
	
// MyImplementation
public int getClassSubClassCount(String className)
            throws ClassNotFoundException, ClassDuplicatedException {
        MyClass.try2ThrowClass(className);
        return MyClass.getName2Classes().get(className).getSubClassNum();
    }

(重看代码发现,并不是每一个检测异常我都封装了,当时代码行数小于500就没管那么多了,笑)

另一类是降低耦合度,把对某个类的对象的操作移到该类中,主类只需要调用方法就好。具体细节就不赘述了。
封装之后,非常明显的视觉变化就是,主类的每一个需要实现的方法都变得简洁,而且整齐划一。如下:

    @Override
    public void checkForUml006() throws UmlRule006Exception {
        for (MyCollaboration collaboration : collaborations.values()) {
            if (collaboration.failUml006()) {
                throw new UmlRule006Exception();
            }
        }
    }

    @Override
    public void checkForUml007() throws UmlRule007Exception {
        for (MyInteraction interaction : interactions.values()) {
            if (interaction.isRecAfterDel()) {
                throw new UmlRule007Exception();
            }
        }
    }

    @Override
    public void checkForUml008() throws UmlRule008Exception {
        for (MyRegion region : regions.values()) {
            if (region.isFinalTransOut()) {
                throw new UmlRule008Exception();
            }
        }
    }

二、架构设计思维总结及OO方法理解的演进

在OO课上学习了工厂模式、单例模式、生产者消费者模式。第一单元借助第一次实验课的官方架构,采用递归下降的方法进行表达式解析,是我从面向过程编程到面向对象编程的初次体验,其中多少还夹杂着面向过程的思维,代码的冗余、混乱,耦合度高而内聚性低,以至于写第一单元的博客的时候,思路也是非常之不清晰。第二单元的多线程电梯单元,使用了生产者消费者模式,乘客(生产者)、候乘表(缓冲池)、电梯(消费者),再配合上调度器以及各种锁,这一单元的个人感受是,一定要充分地理解java的锁机制,尤其是synchronized的各种用法。第三单元对JML规格的理解,只需要读懂助教写的JML规格并使用相应算法就好,架构什么的都不用自己操心。第四单元对UML的解析,仍旧采用了第一单元的想法————递归下降,逐层解析,构建模型。
面向对象的三个特点是封装、继承和多态。个人的感受是有继承的地方往往有多态,有多态的地方一定有继承,在历次OO作业中继承和多态的出现一般都是官方代码的提示和引导(换言之,不到万不得已,我是不会自己写个有继承的架构的),而封装体现在方法、功能的设计上,也最能体现出一个project它是否是“高内聚、低耦合”的。
面向对象的五个原则SOLID:单一功能原则、开闭原则、里氏替换原则、接口隔离原则、依赖反转原则。我觉得这五个原则都是以“高内聚、低耦合”为出发点的,但是在作业中似乎总是被忽略了,历次作业都没有严格地遵守SOLID原则,唯一起作用的时候似乎就是checkstyle发现行数超了,要开始封装了,考虑考虑单一功能原则,当然也正如上文曾提到过的问题,封装了但没有完全封装。简而言之,对SOLID原则的体会还任重道远。

三、测试理解与实践的演进

  • 第一单元,采用随机生成数据 + 手动构造边界数据,利用python的模块自行判断结果的正确性,不需要和小伙伴对拍(但需要小伙伴的测试程序,不是)。研讨课上,也有同学分享数据构造的思路————树状分析式,可以保证随机生成的数据的覆盖面。
  • 第二单元,采用随机生成数据,这一单元的正确性检验比较复杂,原因也在于多线程输出的不确定性,所以在测试程序中用状态转换法对输出结果进行分析判断。这一单元的测试还有一个需要注意的是cpu的运行时间也要加到正确性判断中去,另外就是对轮询问题的检查,我就是因为轮询,直接没进强测屋,虽然轮询但是有些点其实不会ctle,奈何额外的判断程序给我直接kill了(qwq)。
  • 第三单元,采用随机测试 + 手动构造数据,然后和小伙伴对拍。除此之外还可以用Junit进行单元测试,在单元训练中有训练过。hack的策略主要是看代码实现然后手动构造数据。
  • 第四单元,采用随机测试 + 画UML导出模型,然后和小伙伴对拍。因为数据限制过多加上期末,随机测试的程序自身的bug比前三个单元来的要多,最后转到画UML然后手动测试,然后和小伙伴对拍。
  • 总的来说,随机生成数据能够解放双手和大脑,让程序自动进行没日没夜的测试,在数据量非常大的情况下还是能够发现很多细节上的bug的。但是随机生成数据存在局限性,生成数据的随机性越大,数据的强度越低;生成数据的随机性越小,数据测试也越单一化,更适合针对bug构造hack数据。而且随机生成数据也依赖编程者对作业要求的理解,如果在理解时忽视了一些细节,很可能就会有漏网之“虫”。

四、课程收获

  • 在理论层面上,学习了一些设计模式,面向对象的三大基本特征和SOLID基本原则,多线程的特点和同步锁机制,理解了JML规格化语言,UML作为一种统一建模语言它的表示结构。
  • 在实践层面上,第一次接触java语言,第一次接触面向对象,在一周一循环的机制下,促使我快速地熟悉了java的基本语法,熟练掌握一些容器的用法,实现从面向过程编程到面向对象编程的转变,也深刻体会到面向对象带来的一些便捷性。因为要构造测试数据,所以还学习了python的相关语法。(哎嘿,学java面向对象编程附赠python学习哦,这是什么学一送一活动
  • 在架构方面,每单元的第一次作业对我来说都是一个不小的挑战,因为是平地起高楼,还要注意后续的可迭代性,每次都会花很长的时间在“想”的方面,我觉得这是一个有待提高的地方。除了第一单元第一次作业被重构了以外,后续作业都没有经历过重构,在已有架构实现的基础上迭代花费的时间会少很多而且思路也会清晰许多,代码的迭代开发是一个很好的体验。

五、改进建议

  1. 关于强测,希望可以是“看代码找bug”的形式。我觉得课题组的初衷应该就是这样,但是最后大多数人还是选择随机数据轰炸(除了第三单元代码可读性比较好以外?)。我觉得,或许可以针对已有bug的代码,让本人修改后,分享未修改bug时的代码给大家,让大家找问题。或者可以在每个单元总结的时候,每个人都放一个自己曾出现的比较典型bug的代码段,自己分析或者让大家分析。
  2. 在研讨课上增加对代码实现的风格的讨论。针对面向对象的三大基本特征和SOLID基本原则分析评价一段代码或者一个方法的实现上的优缺点,以此加深对面向对象精髓的理解,同时也能帮助同学更快进入面向对象编程的状态。
  3. 要么适当提高中测数据强度,要么提高修改bug环节对强测分值的挽回比例。出发点:一方面觉得中测太弱了,即使周六之前写完并且过了中测,在周六仍然会焦虑地反复测试,另一方面又觉得中测把问题都找出来了还要强测干嘛。
posted @ 2022-06-28 21:02  cchang111  阅读(22)  评论(1编辑  收藏  举报