菜单 学习猿地 - LMONKEY

VIP

开通学习猿地VIP

尊享10项VIP特权 持续新增

知识通关挑战

打卡带练!告别无效练习

接私单赚外块

VIP优先接,累计金额超百万

学习猿地私房课免费学

大厂实战课仅对VIP开放

你的一对一导师

每月可免费咨询大牛30次

领取更多软件工程师实用特权

入驻
26
0

BUAA OO 第一单元总结

原创
05/13 14:22
阅读数 34257

 

BUAA OO 第一单元总结

程序结构

第一次作业

  • 类图

图1

上图(图1)中Gram是语法分析类,Expression是多项式类,Item是项类。

  • 基于类的度量

ClassCSACSOLOC
Expression 1 16 42
Gram 3 27 187
Item 2 18 62
MainClass 0 13 8
表1

参数解释:

CSA: 计算每个类的属性(或字段)总数。

CSO: 计算每个类的操作(或方法)总数。

LOC: 计算类代码行数。

由上表(表1),第一次作业功能计较简单,所以只有语法分析类Gram的代码行数较长。

  • 基于方法的度量

方法数量较多,只展示较复杂的方法与关键的方法。

MethodCONTROLLOC
Gram.number() 5 25
Item.toString() 8 32
Item.derive() 1 10
Expression.toString() 4 20
Expression.derive() 1 7
表2

参数解释:

CONTROL: 计算每个方法控制分支数目。

LOC: 计算每个方法的操作(或方法)总数。

由上表(表2),以上是控制分支数量最多的几个方法,其中toString方法是因为要优化输出,有许多判断分支。

  • 类的内聚与耦合

ClassCBOLCOM
Expression 3 1
Gram 3 1
Item 2 1
MainClass 2 1
表3

参数解释:

CBO:计算每个类“耦合”的类或接口的数量。如果一个类依赖于另一个类,或者这个类依赖于另一个类,则将它声明为与另一个类耦合。由于继承而产生的依赖项不计算在内。

LCOM:计算一个类的内聚程度。使用Hitz和Montazeri设计的LCOM度量的一个变体,它更适合Java。这个度量表示,如果一个类的两个方法共享一个变量使用,或者一个方法调用另一个方法,那么它们是相关的。然后,度量是方法关系图的组件数量的计数。值1表示一个高度内聚的类,它不容易被划分为更小的类。较高的值可能表明类可能“做得太多了”,应该拆分。

由上表(表3)可知,类的内聚性很好,因为第一次作业功能比较简单,类数目较少,所以耦合的类的数量占总类数量的百分比较高。

  • 优缺点分析

    • 优点

      • 基于递归下降实现,可以较为清晰的实现函数的识别,也为后续作业打下基础。

    • 缺点

      • 没有为之后的作业留下接口,实现之后的作业需要重构

第二次作业与第三次作业

由于本人设计二三次作业基本没有什么区别,只在第三次作业中补全了错误处理接口error,所以在一起展开,此外相比于第一次作业基本上算是重构了。

  • 类图

虚线是继承关系,实现是依赖关系。

图2

上图(图2)中Gram是语法分析类(递归下降)。

Add是加法类,Multiply是乘法类,Tri是三角函数类,Bracket是嵌套函数类,这四个类都能嵌套其他类,Power是多项式类,Constant是常数类,这两个是基本类。这六个类都实现了接口Drive。

  • 基于类的度量

ClassCSACSOLOC
Add 3 37 264
Bracket 3 37 170
Constant 2 34 80
Gram 4 29 295
MainClass 0 13 17
Multiply 3 36 235
Power 2 34 116
Tri 4 36 200
表4

由上表(表4)可以看出能嵌套的类Add,Multiply,Tri,Bracket代码行数都比较多,Gram语法分析类的代码行数也很多。

  • 基于方法的度量

方法数量较多,只展示复杂度高的方法和关键方法

MethodCONTROLLOC
Add.addMember() 15 50
Multiply.addMember() 15 58
Tri.addContent() 11 41
Bracket.addContent() 7 27
Add.toString() 14 53
Multiply.derive() 5 32
Add.simply() 3 16
表5

由上表(表5)可以看出addMember(或addContent),toString方法控制分支数量过多,这是由于在这两个方法内都有化简操作,addMember要实现合并同类项与去嵌套,toString要实现优化输出,比如x**1要输出x。个人认为addMember函数的控制分支数量有下降空间,其控制分支很高是因为每次addMember时,要判断instance of Add、Multiply、Bracket、Tri,然后再调用simply函数。如果将Add、Multiply、Bracket、Tri继承一个抽象类Nest(嵌套),然后Nest来实现Derive接口,这样的架构会更好,就不用分别判断了,只需判断instance of Nest即可。

改写后类图如下(没有具体实现):

Derive为接口,Nest(嵌套),Basic(基本)为抽象类,虚线为继承关系,实线为依赖关系。

图3

本人虽然没有进行重构,但是在构思时也能发现,按照上图(图3)的架构实现的话,会使方法控制分支数量大大降低,也可以减少类的代码行数(可以继承抽象类的方法,避免重复造轮子),也会降低不同类之间的耦合度。

  • 类的内聚与耦合

ClassCBOLCOM
Add 6 1
Bracket 5 1
Constant 6 4
Derive    
Gram 9 1
MainClass 2 1
Multiply 6 1
Power 4 2
Tri 8 1
表6

由上表(表6)可知,类的内聚性较好,其中Constant、Power类内聚度过高,可以拆分。但是类间耦合度过高,很多类耦合的类数量过多,按照图3的架构,增加层次,可以降低类间的耦合度。

  • 优缺点分析

    • 优点

      • 采用递归下降分析,很容易进行错误处理以及表达式树的建立

      • 之前构建表达式树为二叉树,但是二叉树很难优化,最后用列表来存储加法、乘法中的成员,使得表达式树可以有n个分支,便于合并同类项等优化

    • 缺点

      • 层次深度不够,耦合度过高,采用以上流图的结构重构后,耦合度应该会下降,架构会更清晰。

      • 一些方法还可以进行分离成两个以上方法来重复调用,实现高内聚。

BUG分析

  • 第二次作业由于本地测试不充分,出现的BUG很多:
    • 没有进行深拷贝

      • 出现BUG的方法,derive,addMember,由于没有进行深拷贝,传入的参数经过 这两个方法后值发生改变,导致了BUG。

      • 代码行与圈复杂度差异对比

        methodv(G)LOC
        Add.addMember() 17 50
        Multiply.addMember() 17 58
        Tri.addContent() 14 8.0
        Multiply.addMember() 17 13.0
        Average 3.5 12.6
        表7

        参数解释:

        v(G): 计算每种非抽象方法的圈复杂度。 循环复杂度是对每种方法中不同执行路径数量的度量。

        LOC: 计算方法代码行数。

        由上表(表7)可以看出出现BUG的方法的圈复杂度v(G)明显高于平均值,此外addMember方法的行数也明显多于平均值。可以说,圈复杂度高,代码行数长的方法是出现BUG概率较大的地方。

    • 对合并同类项的eqauls方法与approach方法设计逻辑上出错

      • 出现BUG的方法equals,approach,这两个方法是用来合并同类项的。

      • 代码行与圈复杂度对比

        methodv(G)LOC
        Bracket.equals() 6 16
        Tri.approach() 6 16
        Multiply.equals() 5 15
        Add.approach() 4 14
        average 3.5 12.6
        表8

        由上表(表8)可见,出现BUG的方法的圈复杂度与代码行数也略高于平均值。以后设计时要重点关注这部分。

  • 第三次作业强侧没有出现BUG,互测中有一处笔误的BUG,就不进行对比了。

HACK策略

我对与HACK时用的数据是自己本地测试时的数据,没有进行具体读代码的白盒分析。

在互测过程中,有一部分同学设计的程序遇到嵌套层数过多的函数会出现CPU_TLE的BUG,该BUG出现的频率较高,一部分原因是因为调用toString过多,没有用临时变量保存。

重构经历

我的重构是在第一次作业到第二次作业时,如下图(图4):

 

图4

第一次作业没有继承关系,只有表达式类和项类。第二次作业新建立了了一个Drive接口,Power、Constant、Add、Multiply、Tri和Bracket实现了这个接口。其中Power和Constant是最基础的函数,不依赖Derive,其它4个实现Derive接口的类还要依赖于Derive,需要嵌套。

这次重构是因为第一次作业的架构无法实现后续作业的功能,重构后继承层数增加,更加归一化。

在第三次作业后,在研讨会上听了同学们的分析,也重新构思了一下架构,但没有实现代码,如下图(图5):

 

图5

由于在前文分析图3时就已经对重构后的架构做了阐述,这里就不赘述了。

心得体会

第一次作业和第三次作业完成较好,而第二次作业完成较差,究其原因还是本地没有测试充分,我在进行及三次作业时就吸取了教训,每增加一个功能,就针对这个功能进行测试,这样可以尽可能的充分测试。

在正确性的基础上,还需要对性能进行追求。第一次作业对性能分要求比较严格,但第一次作业的性能分也比较好拿,也有一些细节我没有注意到,比如x**2可以写成x*x,负项提前这些小细节,以后也要多在细节上下功夫。对于第二三次作业,课程组给的性能比得分函数也体现了,要先保证正确率,只要性能分过了山崖,就可以拿到不错的性能分。第三次作业中,我只实现了合并同类项,常数优化和减少嵌套层数这三样优化,就拿到了不错的性能分,所以还是要先保证正确率,第二次作业的惨痛经历难以忘记...

通过这单元的作业结合老师课上的讲解以及研讨课同学的分享,我对自己面向对象的一些思想有了提高,意识到了架构的重要性,一个好的架构可以事半功倍。

也希望之后的课程也能顺利完成吧,通过这门课能够学到知识,锻炼自己的面向对象的思维能力。

 

发表评论

0/200
26 点赞
0 评论
收藏
为你推荐 换一批