2019北航面向对象课程第四单元作业(UML图)个人总结

在本次博客作业中,我将完成以下几个部分的工作:

(1)总结本单元两次作业的架构设计

(2)总结自己在四个单元中架构设计及OO方法理解的演进

(3)总结自己在四个单元中测试理解与实践的演进

(4)总结自己的课程收获

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

一、总结本单元两次作业的架构设计

(1)第一次作业

  在这一次作业中,需要完成的是“根据给出的信息,针对类图进行查询”,需要完成的功能包括:“模型中一共有多少个类”“类中的操作有多少个”“类中的属性有多少个”“类有几个关联”“类的关联的对端是哪些类”“类的操作可见性”“类的属性可见性”“类的顶级父类”“类实现的全部接口”“类是否违背信息隐藏原则”。

  在初步构建类图时,我的方法的关键在于,将不同类型的UmlElement进行分类,根据其不同的类型,进行不同优先级的不同处理:先构建class和interface,存储operation和association;再将parameter加入对应的operation中,将associationend加入对应的association中,将generation加入对应的class或interface中;最后加入构建好的operation和association。在“获取与类相关联的类列表”“获取类属性可见性”“获取顶级父类”“获取实现的接口列表”“获取类中未隐藏的属性”时,采用类似于深度遍历的想法,层层向上查询是否有被当前类或接口继承的类或接口。

 2019北航面向对象课程第四单元作业(UML图)个人总结_第1张图片

2019北航面向对象课程第四单元作业(UML图)个人总结_第2张图片

2019北航面向对象课程第四单元作业(UML图)个人总结_第3张图片

2019北航面向对象课程第四单元作业(UML图)个人总结_第4张图片

2019北航面向对象课程第四单元作业(UML图)个人总结_第5张图片

2019北航面向对象课程第四单元作业(UML图)个人总结_第6张图片

(2)第二次作业

  在这一次作业中,需要完成的是“根据给出的信息,针对类图、顺序图、状态图进行查询,并在查询前对类图进行有效性检查”,需要完成的功能包括:“模型中一共有多少个类”“类中的操作有多少个”“类中的属性有多少个”“类有几个关联”“类的关联的对端是哪些类”“类的操作可见性”“类的属性可见性”“类的顶级父类”“类实现的全部接口”“类是否违背信息隐藏原则”“给定状态机模型中一共有多少个状态”“给定状态机模型中一共有多少个迁移”“给定状态机模型和其中的一个状态,有多少个不同的后继状态”“给定UML顺序图,一共有多少个参与对象”“给定UML顺序图,一共有多少个交互消息”“给定UML顺序图和参与对象,有多少个incoming消息”“R001:针对下面给定的模型元素容器,不能含有重名的成员(UML002)”“R002:不能有循环继承(UML008)”“R003:任何一个类或接口不能重复继承另外一个接口(UML009)”。

  在这次作业中,我将interaction、lifeline、message归于顺序图,将state、pseudostate、finalstate、transition、region、statemachine归于状态图,其余全归于类图。这样归类在概念上并不完全正确,但在完成这一次作业时是正确且简便有效的。

  在主要类中new出对应的图,将归好类的元素传入对应的图中进行构建和处理。Precheck的类图检查部分,引用并检查的就是在主要类中构建的类图。具体检查方式如下:

①   R001:针对下面给定的模型元素容器,不能含有重名的成员(UML002)

public HashSet getSameNameAttriAssoEnd() {

    HashMap allClasses =

            myClassInteraction.getAllClasses();

    HashSet temp = new HashSet<>();

    for (MyClassDiagram theClass : allClasses.values()) {

        temp.addAll(theClass.getSameNameAttriAssoEnd());

    }

    return temp;

}

public HashSet getSameNameAttriAssoEnd() {

    HashSet temp = new HashSet<>();

    HashMap ttemp = new HashMap<>();

    for (String name : theAttributesNameToNum.keySet()) {

        if (ttemp.containsKey(name)) {

            ttemp.put(name, ttemp.get(name) + 1);

        } else {

            ttemp.put(name, 1);

        }

        if (theAttributesNameToNum.get(name) != 1) {

            ttemp.put(name, ttemp.get(name) + 1);

        }

    }

    for (int i = 0; i < theAssociationEndNames.size(); i++) {

        String name = theAssociationEndNames.get(i);

        if (name == null) {

            continue;

        }

        if (ttemp.containsKey(name)) {

            ttemp.put(name, ttemp.get(name) + 1);

        } else {

            ttemp.put(name, 1);

        }

    }

    for (String name : ttemp.keySet()) {

        if (ttemp.get(name) != 1) {

            temp.add(new AttributeClassInformation(

                    name, theClass.getName()));

        }

    }

    return temp;

}

②  R002:不能有循环继承(UML008)

public HashSet getCircleClassesAndInterface() {

    HashMap allClasses =

            myClassInteraction.getAllClasses();

    HashMap allInterfaces =

            myClassInteraction.getAllInterfaces();

    HashSet temp = new HashSet<>();

    HashMap classes = new HashMap<>();

    for (MyClassDiagram theClass : allClasses.values()) {

        classes.put(theClass, true);

    }

    for (MyClassDiagram theBeginClass : classes.keySet()) {

        if (classes.get(theBeginClass)) {

            HashSet ttemp = new HashSet<>();

            HashSet tttemp = new HashSet<>();

            ttemp.add((UmlClassOrInterface)theBeginClass.getSimpleClass());

            classes.put(theBeginClass, false);

            tttemp.add(theBeginClass.getclassid());

            MyClassDiagram theClass = theBeginClass;

            boolean findCircle = false;

            while (theClass.getWithGeneralization()) {

                theClass = allClasses.get(

                        theClass.getGeneralizationTarget());

                if (tttemp.contains(theClass.getclassid())) {

                    if (theClass.getclassid().

                            equals(theBeginClass.getclassid())) {

                        findCircle = true;

                    }

                    break;

                }

                ttemp.add((UmlClassOrInterface)theClass.getSimpleClass());

                tttemp.add(theClass.getclassid());

            }

            if (findCircle) {

                temp.addAll(ttemp);

                classes.put(theBeginClass, false);

            }

        }

    }

    for (MyInterface theinterface : allInterfaces.values()) {

        if (!temp.contains((UmlClassOrInterface)

                theinterface.getSimpleInterface())

                && theinterface.getWithGeneralization()) {

            ArrayList path = new ArrayList<>();

            path.add(theinterface.getinterfaceid());

            HashMap path2 = new HashMap<>();

            path2.put(theinterface.getinterfaceid(), 1);

            if (findCircleInterface(path, path2)) {

                temp.add((UmlClassOrInterface)

                        (allInterfaces.get(theinterface.getinterfaceid()).

                                getSimpleInterface()));

            }

        }

    }

    return temp;

}

        public boolean findCircleInterface(ArrayList path,

                                   HashMap path2) {

    HashMap allInterfaces =

            myClassInteraction.getAllInterfaces();

    String head = path.get(0);

    String tail = path.get(path.size() - 1);

    if (head.equals(tail) && path.size() != 1) {

        return true;

    }

    MyInterface myInterface = allInterfaces.get(tail);

    if (myInterface.getWithGeneralization()) {

        List targetids = myInterface.getGeneralizationTargetids();

        for (int i = 0; i < targetids.size(); i++) {

            if (targetids.get(i).equals(path.get(0))) {

                return true;

            }

            if (!path2.containsKey(targetids.get(i))) {

                ArrayList newpath = new ArrayList<>();

                newpath.addAll(path);

                newpath.add(targetids.get(i));

                HashMap newpath2 = new HashMap<>();

                newpath2.putAll(path2);

                newpath2.put(targetids.get(i), 1);

                if (findCircleInterface(newpath, newpath2)) {

                    return true;

                }

            }

        }

    }

    return false;

}

 

③    R003:任何一个类或接口不能重复继承另外一个接口(UML009)

        public HashSet getRepeatedClassAndInterface() {

    HashMap allClasses =

            myClassInteraction.getAllClasses();

    HashMap allInterfaces =

            myClassInteraction.getAllInterfaces();

    HashSet finaltemp = new HashSet<>();

    for (MyClassDiagram theclass : allClasses.values()) {

        MyClassDiagram thisclass = theclass;

        List temp = new ArrayList<>();

        // 当前类 以及所有父类 实现的接口

        for (temp.addAll(theclass.getInterfaceList());

             theclass.getWithGeneralization();

             temp.addAll(theclass.getInterfaceList())) {

            theclass = allClasses.get(theclass.getGeneralizationTarget());

        }

        theclass = thisclass;

 

        // 接口继承

        for (int i = 0; i < temp.size(); i++) {

            if (allInterfaces.containsKey(temp.get(i))

                    && allInterfaces.get(temp.get(i)).

                    getWithGeneralization()) {

                temp.addAll(allInterfaces.get(temp.get(i)).

                        getGeneralizationTargetids());

            }

        }

        // 是否有重复接口?

        HashMap ttemp = new HashMap<>();

        for (int i = 0; i < temp.size(); i++) {

            if (ttemp.containsKey(temp.get(i))) {

                finaltemp.add((UmlClassOrInterface)thisclass.

                        getSimpleClass());

                break;

            } else {

                ttemp.put(temp.get(i), 1);

            }

        }

    }

    for (MyInterface theinterface : allInterfaces.values()) {

        List temp = new ArrayList<>();

        temp.add(theinterface.getinterfaceid());

        // 当前接口 接口继承的所有接口 层层向上

        for (int i = 0; i < temp.size(); i++) {

            MyInterface interf = allInterfaces.get(temp.get(i));

            temp.addAll(interf.getGeneralizationTargetids());

        }

        // 是否有重复接口?

        HashMap ttemp = new HashMap<>();

        for (int i = 0; i < temp.size(); i++) {

            if (ttemp.containsKey(temp.get(i))) {

                finaltemp.add((UmlClassOrInterface)

                        theinterface.getSimpleInterface());

                break;

            } else {

                ttemp.put(temp.get(i), 1);

            }

        }

    }

    return finaltemp;

}

 2019北航面向对象课程第四单元作业(UML图)个人总结_第7张图片

2019北航面向对象课程第四单元作业(UML图)个人总结_第8张图片

2019北航面向对象课程第四单元作业(UML图)个人总结_第9张图片

2019北航面向对象课程第四单元作业(UML图)个人总结_第10张图片

2019北航面向对象课程第四单元作业(UML图)个人总结_第11张图片

2019北航面向对象课程第四单元作业(UML图)个人总结_第12张图片

2019北航面向对象课程第四单元作业(UML图)个人总结_第13张图片

2019北航面向对象课程第四单元作业(UML图)个人总结_第14张图片

2019北航面向对象课程第四单元作业(UML图)个人总结_第15张图片

2019北航面向对象课程第四单元作业(UML图)个人总结_第16张图片

2019北航面向对象课程第四单元作业(UML图)个人总结_第17张图片

2019北航面向对象课程第四单元作业(UML图)个人总结_第18张图片

2019北航面向对象课程第四单元作业(UML图)个人总结_第19张图片

  本次作业中存在的问题,是在进行第二项规则检查时,没有考虑到两个子类同时继承一个父类的情况,使得程序在一些情况下会出现死循环。

二、总结自己在四个单元中架构设计及OO方法理解的演进

(1)第一单元作业

  第一单元作业的主题是求导。为表达式的求导。第一次作业为简单多项式的求导,第二次作业加入三角函数项,第三次作业需要进行递归思想下的拆分,难度上可以说是有了一个台阶式的提升。

  在这一单元的三次作业中,我的程序的bug主要由以下2种:

  1.在程序设计中由于考虑不周全导致的处理、运行上的bug,具体表现为,一些合法的输入会引起我所写的程序的歧义、数组越界或爆栈。针对这一类bug,我的修正思路主要是:在设计程序结构时,进行细化思维,力求考虑周全;不同“层次”的问题归类进行比较和讨论,同类问题做相似的处理,避免出现错误或遗漏。

  2.在写代码过程中手误敲出了bug,比如正则表达式可以匹配空格的问题。针对这一类bug,我的思路是在写代码时尽力认真细致,每写完一部分,就回过头去认真检查。

  在公测和互测的过程中,除了以上2中类型的bug,还有2种bug,即在后续步骤中修改先前步骤保存的数值而造成的bug,以及在化简结构表达式时因考虑不周、化简错误而出现的bug。这需要我们在设计程序时,将执行不同功能的代码段化为小块,不同小块保持相对的独立性,不直接调用对方所存储的数据,而是通过数据接口进行传输;同时在写代码的任何环节,都要始终如一,考虑周全,起码要做到对结果式的情况的讨论不少于对输入表达式的讨论。

  在互测为同组同学的代码找bug时,我主要从结构和功能的角度出发,先尝试多组我自己在写代码的过程中“设计”出的易于产生bug的数据,再细致考察对方代码的结构,着重寻找、分析他和我不同的部分。这样,即使找不到bug,也能在他人的思路中收获不少。

  比如在此次第三次作业的互测阶段,我就是在第二次作业的基础上,分析从第二次作业到第三次作业有哪些改变,又有哪些沿袭的地方,再针对性地进行测试。同样,我会阅读他人的代码,结合被测程序的代码设计测试用例。

  综合分析我的三次作业,我的前两次作业,因为在写代码时对“面向对象”概念的理解尚不明晰,加之当时的作业功能实现要求较低、代码的实现较为简单,所以并没有很好地进行类的设计和划分,导致程序的复用性不强,为第三次作业带来了一些麻烦。在完成第三次作业时,我主要运用了接口和递归嵌套的思想进行设计,对输入的求导式不断地进行拆分,直到得出能够直接写出导函数的“单项”,如x、x^2、sin(x)、cos(x^4)等,然后将每一层的导数返回给上一层,最后将整个求导式的导数返回到主函数中输出。

(2)第二单元作业

   第二单元作业的主题是电梯调度。第一次作业为“傻瓜”电梯,规则为简单的先进先出;第二次作业需要进行调度算法的设计和构造,并在正确的基础上以运行时间作为评分标准,难度有了一些提升;第三次作业是多线程,是多电梯的调度,同时有“某电梯只能停靠某几层”的设定,在构思上具有很大难度。在这一单元的作业中,我认识到了,好的设计架构,是保证线程安全和代码可复用性的基础。

  在第一次作业中,我主要设计了Requestinput、Dispatch、Elevator三个类,Requestinput负责输入指令,Dispatch负责接受并储存输入的指令、向电梯模块输出所要执行的指令、Elevator负责根据得到的指令进行运行。Requestinput、和Elevator是进程,共享共享对象Dispatch。线程安全主要体现在Requestinput输入指令到Dispatch和Elevator从Dispatch指令并运行互不干扰。

  在第二次作业中,在第5次作业的基础上,我在Dispatch中构造dowith()函数,使得Elevator在模拟电梯运行时,可以实时讨论是否进行捎带,在符合要求的情况下尽可能进行捎带,减少相同指令集对应的程序运行时间;相应的,Elevator中构造queue动态队列存储电梯“一次”运行中所要执行的指令。这次作业的线程安全,还体现在判断捎带函数对Dispatch中指令队列的调用与改变上。

  在第三次作业中,Dispatch中maina、mainb、mainc分别对应应该改由Elevatora、Elevatorb、Elevatorc执行的指令,而Elevatora、Elevatorb、Elevatorc中又分别有queuea、queueb、queuec存储电梯“一次”运行中所要执行的指令。Requestinput输入的指令,如果可由某电梯直达,则直接放入对应的main队列中,否则先进行拆分,然后循环判断能否直达、是否需要拆分,直到所有拆分出来的指令都放入了对应的main队列中。此次电梯的线程安全设计在前两次作业的基础上,还主要体现在,Elevatora、Elevatorb、Elevatorc三个进程的运行互不影响,对maina、mainb、mainc的输入与输出通过加“锁”保证安全。

  在分析自己程序的bug时,我:

  (1)分析自己未通过的公测用例和被互测发现的bug;特征、问题所在的类和方法

  (2)特别注意分析哪些问题与线程安全相关

  (3)关联分析bug位置与设计结构之间的相关性

  在第一次作业中,在Dispatch调度器类中对是否捎带进行判断讨论时,我对捎带条件的讨论出现了重复之处:

   2019北航面向对象课程第四单元作业(UML图)个人总结_第20张图片

    2019北航面向对象课程第四单元作业(UML图)个人总结_第21张图片

  这些错误导致了面对这样的数据集
  [0.0]0-FROM--2-TO—3
  [0.0]8-FROM-14-TO—3
  的时候,会在到达-3层后持续下降,无法停止,更无法返回上层执行接下来的指令。重新讨论捎带条件,可以对错误语句进行修正:

    2019北航面向对象课程第四单元作业(UML图)个人总结_第22张图片

    2019北航面向对象课程第四单元作业(UML图)个人总结_第23张图片

  可以成功改正之前出现的错误。

  在分析自己发现别人程序bug所采用的策略时,我:

  (1)列出自己所采取的测试策略及有效性,并特别指出是否结合被测程序的代码设计结构来设计测试用例

  (2)分析自己采用了什么策略来发现线程安全相关的问题

  (3)分析本单元的测试策略与第一单元测试策略的差异之处

  在第一次作业中,我主要测试他人代码基本的输入指令、存储指令和取出指令并进行运行等功能。因为此次作业仅有一台,且不要求捎带,故没有什么错误的空间。

  在第二次作业中,我除了进行在第5次作业中进行的测试内容外,还考察了他人代码捎带功能的强弱程度。为了测试捎带功能,我构造了许多组十分考验捎带的指令集,测试表明最快运行时间为27s左右,而组里很多份代码需要60s至70s才能运行完毕,最慢的一份代码需要84s——但提交互测数据的结果显示,大家都处于测试机认定的合理时间范围之内♪(・ω・)ノ。

  在第三次作业中,在进行前两次作业已经进行了的测试的基础上,我就需要转乘的情况进行了测试。事实证明,还是有代码对于换乘情况考虑不清,讨论不全,甚至无法执行拆分后的第二条指令(比如指令1-FROM-7-TO—3,再被拆分成1-FROM-7-TO-1和1-FROM-1-TO—3后,第二条指令1-FROM-1-TO—3无法被执行)。

 

(3)第三单元作业

  第三单元的3次作业,其主题都是在阅读jml规格的基础上完成程序,实现相应的功能(虽然我觉得这是数据结构+++,感受十分不好,tle杀我)。3次作业的内容,分别是:实现Path路径类和PathContainer路径容器类、实现Path路径类和在PathContainer路径容器类基础上扩展生成的Graph图类、实现Path路径类和在Graph图类基础上扩展生成的RailwaySystem地铁信息查询类。   

  第一次作业整体上比较简单,需要完成Path路径类和PathContainer路径容器类。MyPath主要通过Arraylist顺序保存某路径(即某Path型变量)中的点,并通过Hashmap保存改路径中所有不同的点(这样设计,在需要判断该path中有多少个不同的点时,只需要返回对应hashmap的大小即可)。同时,为方便判断该path中是否含有某个具体的点,用点的序号作为hashmap的key值,用该点在该路径中出现的次数作为hashmap的value,即可通过containskey()判断该点是否存在。同时,通过阅读指导书可以知道,还需要完成迭代器函数、字典序比较函数、equals()函数和hashcode()函数。

  MyPathContainer涉及到对多条Path的Pathid分配,储存,判断是否存在,增加和删除操作。在这里,我使用了4个HashMap,分别表示path到index,index到path,pathid到index和index到pathid的对应关系。使用nextid和nextpathindex用于获取下一条增加的路径对应的pathid和index。同时,再用1个HashMap存储所有路径中的不同节点(用点的序号作为hashmap的key值,用该点在所有路径中出现的次数作为hashmap的value)。此处主要需要注意的是,在addpath,removepath时需要同时维护共计5个的hashmap,特别是存储不同节点的hashmap。

  我的第二次作业主要是在第一次作业的基础上完成的。我的4个类中:

  Main类负责按照要求调用AppRunner函数

  Pathnodes类全面继承第9次作业的Pathnodes类(在debug的过程中,我一度以为我的一个bug的错误是因为hashcode的原因,因此可能稍微改变了Pathnodes类中的hashcode函数)。

  Pathgraph类一部分继承自第9次作业的PathContainer类,主要负责存储不同的Path路径,并完成Graph类要求的功能函数。

  Edges类的构造,主要是为了与hashmap结合,表示“一条边中fromnode+tonode+通一条边的出现次数”这样的关系。

  就整个程序的运行而言:

  每当需要增加一个path类型的量(增加一条路径),调用Pathgraph类的addPath函数。首先判断这个path的合法性,不合法,返回0。合法,判断是否已经含有一条一模一样的路径(这一步判断的正确实现,依赖于Pathnodes类的equals函数,需要两个path型变量对应节点位置和对应节点序号完全相同,节点数完全相同,才能判断是同一path;也就是说,“1 2 3”和“1 2 3 4”不是同一路径,“1 2”和“2 1”也不是同一路径)。

  如果不包含一模一样的path,那么,需要为这个path分配pathid,为这组path+pathid的组合分配index,并将它们存进从第9次作业继承来的pathtoindex、indextopath、pathidtoindex、indextooathid中去,并且更新用于存储所有path不同点和点出现次数的temp。

  每当需要删除一个path类型的量(删除一条路径),调用Pathgraph类的removePath函数或者removePathById函数。首先判断这个path的合法性,以及当前已有路径中是否包含这个path,如果不合法或者不包含,则抛出异常。

  在此基础上,需要把这个path从pathtoindex、indextopath、pathidtoindex、indextooathid中删掉,并且更新用于存储所有path不同点和点出现次数的temp。

  在判断是否相连,或者求最短距离时,需要在判断是否有一条路径包含起点、终点,搜索往次广度遍历保存结果的情况下,返回对应正确结果,或者在进行一次确定起点的广度遍历,再返回对应结果。

  我的“判断是否相连”,或者“求最短距离”两个功能,调用的是同一个广度遍历函数,准确地说,是“判断是否相连”调用广度遍历函数,“求最短距离”调用“判断是否相连”。而在“判断是否相连”调用广度遍历函数时,需要设置好传入广度遍历函数的参数。

  我按照近似于“树”的方式理解对于无向图的广度优先遍历,即从树顶由上至下一层层遍历:list表示即将被遍历的一层中的所有点(到起始点距离——深度相同的所有点,永远是处于同一层的所有点),check表示已经被遍历过的所有点(从起始点所在树顶到当前层的所有点)。然后开始一层层进行广度遍历。

  每到达新的一层,都要保存起始点到当前层所有点的距离(能被遍历到的点,一定从起始点可达)。如果当前层包含终止点,那么,广度遍历就停止在当前层,否则,要为下一层准备新的list和check,并且调用自身。如果待查找的队列空,且未找到终止点,那么说明起始点到终止点不可达,存 -1 。

  我的第三次作业主要是在第二次作业的基础上完成的。在我的10个类中:

  Main类负责按照要求调用AppRunner函数

  ThePath类全面继承第10次作业的Pathnodes类(在debug的过程中,我一度以为我的一个bug的错误是因为hashcode的原因,因此可能稍微改变了ThePath类中的hashcode函数)。

  TheRailwaySystem类一部分继承自第10次作业的Pathgraph类,主要负责存储不同的Path路径,并完成MyRailwaySystem类要求的功能函数。同时,由于涉及到对连通块个数的判断,我构造了新的Chart类,来储存一个连通块。TheRailwaySystem类的“是否相连”“最短距离”“最低票价”等功能,都需要先判断具体与哪个连通块有关,再在对应的Chart中调用对应的函数。

  EdgeLength、EdgePathId、EdgePrice、EdgeTrans、EdgeUnpleasent等类的构造,沿用了第10次作业中Edges类的构造思想,主要是为了与hashmap结合,表示“一条边中fromnode+tonode+对应信息(出现次数、最短距离、最低票价……)”这样的关系。

  在第11次作业中,我区别于第10次作业的地方,主要在于将彼此不连通的连通块们分别存储成不同的Chart,并在此基础上修改了原本的“判断是否相连”“计算最短距离”功能,进一步完成了“计算连通块数量”“计算最少换乘”“计算最低票价”“计算最小不满意度”功能。

  每当需要新增一个path,就调用TheRailwaySystem类中的addPath函数。在判断新增的path的合法性、是否重复之后,需要将这个新增的path加入到differnode、differpath、differpathid中去。其中,在使用renewDiffers时,signal==1表示增加path,signal==-1表示减少path。

  之后,我需要判断,新增的path,是需要单独构成一个连通图,还是加入到一个已经存在的连通图之中?如果是单独构成一个连通图,那么说明,path不与任何已经存在的连通图相连;如果是加入到一个已经存在的连通图,说明path与至少一个任何已经存在的连通图相连。因此,在“加入到一个已经存在的连通图”的情况下,需要讨论,path的加入,是否会造成本不连通的几个连通图彼此连通了。如果是,需要合并这些图,具体方法是:将连通图B读出,将B从连通图的队列中删除,再将B中所有路径与路径对应路径编号放入A中,实现连通图A与连通图B的合并。

  而在将path加入具体的一个连通图chart时,需要:

  “清空保存在finalLength、finalTrans、finalPrice、finalUnpleasent中的以往dijkstra计算结果”

  “更新indextopath、pathtoindex、indextoid、idtoindex、differnode、nodequeue”

  “为graphLength、graphTrans、graphPrice、graphUnpleasent增加对应格式的新‘边’”

  “为graphTrans、graphPrice、graphUnpleasent增加该path中所有点与他们‘同点不同路径’的点的‘距离’权值关系”

  “为graphLength、graphTrans、graphPrice、graphUnpleasent增加该path中所有相邻点的对应关系”

  “更新nodetopathid的对应关系(同一node,可以存在于哪些path中,存储对应的pathid)”

  每次删除一个path,在判断path合法性与是否存在的基础上,需要找到path所在chart,读出保存,将这个chart从连通图的队列中删除,再将chart中除了path的其他路径一条条加回去,除了对应pathid是保存在chart中的pathid,加回去是需要考虑的部分与addPath大体相同。

  Chart类中不用实现removePath的功能,因为removePath时会直接删除对应chart。

  在TheRailwaySystem中“计算连通块数量”,直接返回连通块队列的大小。

  在TheRailwaySystem中“判断是否相连”“计算最短距离”“计算最少换乘”“计算最低票价”“计算最小不满意度”时,都要先对起始点、终止点进行讨论,在两点都存在,且存在于同一个连通块中的情况下,转到Chart类中调用同名的函数进行计算(“判断是否相连”时,不用进入Chart,可以直接返回true,因为处于同一连通块的两点必定相连)。在Chart类中,为“计算最短距离”“计算最少换乘”“计算最低票价”“计算最小不满意度”,分别构造了4组“储存结构*2 + 函数*2”。

  储存结构*2,为保存对应连通图的“graphxxxxx”和保存dijkstra结果的“finalxxxxx”。

  函数*2,是进行dijkstra运算的函数“dijkstraxxxxx”和为查找结果、dijkstra运算做准备、调用“dijkstraxxxxx”并返回最终结果的“getxxxxxxxxxx”。

  经过一段时间对JML规格的阅读以及上次上机时自己真正尝试写JML规格,我深深感受到了JML语言的重要性。只有使用JML语言,在进行程序编写,特别是不同人组队完成一个大型程序的编写时,才可以在最大程度上保证不同人完成在代码可以融合在一起,而不会产生各种各样奇妙的bug。

  在完成这3次作业时,我绝大部分时间都用在搭建储存结构框架、选择算法、写代码、debug的过程中了,主要是“在tle error的边缘疯狂试探”。希望我能进一步地学习JML规格语言。

(4)第四单元作业

  第四单元作业的主题是JML图。

  在第一次作业中,需要完成的是“根据给出的信息,针对类图进行查询”,需要完成的功能包括:“模型中一共有多少个类”“类中的操作有多少个”“类中的属性有多少个”“类有几个关联”“类的关联的对端是哪些类”“类的操作可见性”“类的属性可见性”“类的顶级父类”“类实现的全部接口”“类是否违背信息隐藏原则”。

  在初步构建类图时,我的方法的关键在于,将不同类型的UmlElement进行分类,根据其不同的类型,进行不同优先级的不同处理:先构建class和interface,存储operation和association;再将parameter加入对应的operation中,将associationend加入对应的association中,将generation加入对应的class或interface中;最后加入构建好的operation和association。在“获取与类相关联的类列表”“获取类属性可见性”“获取顶级父类”“获取实现的接口列表”“获取类中未隐藏的属性”时,采用类似于深度遍历的想法,层层向上查询是否有被当前类或接口继承的类或接口。

  在第二次作业中,需要完成的是“根据给出的信息,针对类图、顺序图、状态图进行查询,并在查询前对类图进行有效性检查”,需要完成的功能包括:“模型中一共有多少个类”“类中的操作有多少个”“类中的属性有多少个”“类有几个关联”“类的关联的对端是哪些类”“类的操作可见性”“类的属性可见性”“类的顶级父类”“类实现的全部接口”“类是否违背信息隐藏原则”“给定状态机模型中一共有多少个状态”“给定状态机模型中一共有多少个迁移”“给定状态机模型和其中的一个状态,有多少个不同的后继状态”“给定UML顺序图,一共有多少个参与对象”“给定UML顺序图,一共有多少个交互消息”“给定UML顺序图和参与对象,有多少个incoming消息”“R001:针对下面给定的模型元素容器,不能含有重名的成员(UML002)”“R002:不能有循环继承(UML008)”“R003:任何一个类或接口不能重复继承另外一个接口(UML009)”。

  在第二次作业中,我将interaction、lifeline、message归于顺序图,将state、pseudostate、finalstate、transition、region、statemachine归于状态图,其余全归于类图。这样归类在概念上并不完全正确,但在完成这一次作业时是正确且简便有效的。

  在主要类中new出对应的图,将归好类的元素传入对应的图中进行构建和处理。Precheck的类图检查部分,引用并检查的就是在主要类中构建的类图。

(5)总结心得

  通过这四个单元的作业,我完成了从“面向过程”编程思想,到“面向对象”编程思想的转变;从单线程到多线程的转变,我深入理解了封装、继承、多态,抽象,接口,代码复用和工厂模式;我掌握了线程之间的协作方式,能够使用多线程的方法使程序更高效的运行;我对如何构架更合适更高效的程序结构构架也有了自己的体会和认识。

三、总结自己在四个单元中测试理解与实践的演进

  在第一单元的三次作业中,我的程序的bug主要由以下2种:

  1.在程序设计中由于考虑不周全导致的处理、运行上的bug,具体表现为,一些合法的输入会引起我所写的程序的歧义、数组越界或爆栈。针对这一类bug,我的修正思路主要是:在设计程序结构时,进行细化思维,力求考虑周全;不同“层次”的问题归类进行比较和讨论,同类问题做相似的处理,避免出现错误或遗漏。

  2.在写代码过程中手误敲出了bug,比如正则表达式可以匹配空格的问题。针对这一类bug,我的思路是在写代码时尽力认真细致,每写完一部分,就回过头去认真检查。

  在公测和互测的过程中,除了以上2中类型的bug,还有2种bug,即在后续步骤中修改先前步骤保存的数值而造成的bug,以及在化简结构表达式时因考虑不周、化简错误而出现的bug。这需要我们在设计程序时,将执行不同功能的代码段化为小块,不同小块保持相对的独立性,不直接调用对方所存储的数据,而是通过数据接口进行传输;同时在写代码的任何环节,都要始终如一,考虑周全,起码要做到对结果式的情况的讨论不少于对输入表达式的讨论。

  在互测为同组同学的代码找bug时,我主要从结构和功能的角度出发,先尝试多组我自己在写代码的过程中“设计”出的易于产生bug的数据,再细致考察对方代码的结构,着重寻找、分析他和我不同的部分。这样,即使找不到bug,也能在他人的思路中收获不少。

  比如在此次第三次作业的互测阶段,我就是在第二次作业的基础上,分析从第二次作业到第三次作业有哪些改变,又有哪些沿袭的地方,再针对性地进行测试。同样,我会阅读他人的代码,结合被测程序的代码设计测试用例。

  综合分析我的三次作业,我的前两次作业,因为在写代码时对“面向对象”概念的理解尚不明晰,加之当时的作业功能实现要求较低、代码的实现较为简单,所以并没有很好地进行类的设计和划分,导致程序的复用性不强,为第三次作业带来了一些麻烦。在完成第三次作业时,我主要运用了接口和递归嵌套的思想进行设计,对输入的求导式不断地进行拆分,直到得出能够直接写出导函数的“单项”,如x、x^2、sin(x)、cos(x^4)等,然后将每一层的导数返回给上一层,最后将整个求导式的导数返回到主函数中输出。

  在第二单元中,在分析自己程序的bug时,我:

  (1)分析自己未通过的公测用例和被互测发现的bug;特征、问题所在的类和方法

  (2)特别注意分析哪些问题与线程安全相关

  (3)关联分析bug位置与设计结构之间的相关性

  在第一次作业中,在Dispatch调度器类中对是否捎带进行判断讨论时,我对捎带条件的讨论出现了重复之处,导致我在面对这样的数据集“[0.0]0-FROM--2-TO—3 [0.0]8-FROM-14-TO—3”时,会在到达-3层后持续下降,无法停止,更无法返回上层执行接下来的指令。重新讨论捎带条件,可以对错误语句进行修正,可以成功改正之前出现的错误。

  在分析自己发现别人程序bug所采用的策略时,我:

  (1)列出自己所采取的测试策略及有效性,并特别指出是否结合被测程序的代码设计结构来设计测试用例

  (2)分析自己采用了什么策略来发现线程安全相关的问题

  (3)分析本单元的测试策略与第一单元测试策略的差异之处

  在第一次作业中,我主要测试他人代码基本的输入指令、存储指令和取出指令并进行运行等功能。因为此次作业仅有一台,且不要求捎带,故没有什么错误的空间。

  在第二次作业中,我除了进行在第5次作业中进行的测试内容外,还考察了他人代码捎带功能的强弱程度。为了测试捎带功能,我构造了许多组十分考验捎带的指令集,测试表明最快运行时间为27s左右,而组里很多份代码需要60s至70s才能运行完毕,最慢的一份代码需要84s——但提交互测数据的结果显示,大家都处于测试机认定的合理时间范围之内♪(・ω・)ノ。

  在第三次作业中,在进行前两次作业已经进行了的测试的基础上,我就需要转乘的情况进行了测试。事实证明,还是有代码对于换乘情况考虑不清,讨论不全,甚至无法执行拆分后的第二条指令(比如指令1-FROM-7-TO—3,再被拆分成1-FROM-7-TO-1和1-FROM-1-TO—3后,第二条指令1-FROM-1-TO—3无法被执行)。

  在第三单元的作业中,我学会了部署SMT Solver,并选择方法进行验证。

  SMT(Staisfiability modulo theories) Solver,是一个定理证明器其工作机理是:openJML将JML规范转换为SMT-LIB格式,并将Java+JML程序所隐含的证明问题传递给后端SMT求解器。

  openJML支持主要的SMT解决方案,如Z3、CVC4和YIES;其证明的成功将取决于SMT解算器的能力、代码+规范的特定逻辑编码以及代码和规范编写的复杂性和样式。

  OpenJML将JML规范转换为SMT-LIB格式,并将Java+JML程序所隐含的证明问题传递给后端SMT求解器。

  OpenJML支持主要的SMT解决方案,如Z3、CVC4和YIES。

  证明的成功将取决于SMT解算器的能力(例如,它支持哪些逻辑)、代码+规范的特定逻辑编码以及代码和规范编写的复杂性和样式。

  例,尝试进行检查:

   2019北航面向对象课程第四单元作业(UML图)个人总结_第24张图片

  运用指令“openjml -check .\Exp\Main.java”进行格式检查,无误。
  删除第二行注释末尾的一个分号,再次进行格式检查,发现会报错:

  2019北航面向对象课程第四单元作业(UML图)个人总结_第25张图片

  恢复第二行的分号。运用指令“openjml -esc -prove .\Exp\Main.java”进行静态审查。返回了许多报错,其中部分如下:

  2019北航面向对象课程第四单元作业(UML图)个人总结_第26张图片

  我也学会了部署JMLUnitNG/JMLUnit,针对Graph接口的实现自动生成测试用例,结合规格对生成的测试用例和数据进行简要分析。依然是对这个程序进行测试:

   2019北航面向对象课程第四单元作业(UML图)个人总结_第27张图片

   使用指令“java -jar ..\..\openjml\jmlunitng.jar .\Exp\Main.java”自动生成众多测试文件。对这些文件进行编译,进行运行测试,得到如下结果:

   2019北航面向对象课程第四单元作业(UML图)个人总结_第28张图片

    2019北航面向对象课程第四单元作业(UML图)个人总结_第29张图片

四、总结自己的课程收获

  通过一个学期对oo课程的学习,我收获颇丰:

  1. 熟练掌握了 java (喝杯 java 冷静一下.jpg)
  2. 掌握了接口,继承,多态等等概念以及相应的应用方法
  3. 从“面向方法”的编程思想转变为“面向对象”的编程思想,并能够较熟练地对程序进行模块化处理,使得代码的复用性得到大幅度提升
  4. 学会了部署SMT Solver,并选择方法进行验证
  5. 学会了部署JMLUnitNG/JMLUnit,针对Graph接口的实现自动生成测试用例,结合规格对生成的测试用例和数据进行简要分析
  6. 掌握了多线程,对消费者和生产者模式有了更深的理解
  7. 深刻地认识到了良好的架构设计的重要性
  8. 学习了JVM和UML的相关知识

五、立足于自己的体会给课程提三个具体改进建议

  我认为,oo课程的相关实际已经很成熟完善了,在此之外,个人一点小小的建议,如下;

  1. 适当提升中测成绩占比,希望能提升中测的完备性,并公布所有中测测试点
  2. 希望课程组能尽可能的完善指导书的陈述和说明,希望能多一些对边界情况、特殊情况的说明
  3. 希望能在在线平台上推出相关教程
  4. 感谢助教们、老师们一学期以来的辛勤付出和无私帮助 Thanks♪(・ω・)ノ

 

 

转载于:https://www.cnblogs.com/yuanshanhandai/p/11068776.html

你可能感兴趣的:(2019北航面向对象课程第四单元作业(UML图)个人总结)