首页 > 其他 > 详细

OO第一单元总结

时间:2021-03-27 23:50:21      阅读:55      评论:0      收藏:0      [点我收藏+]

第一单元总结

一、程序结构

名词和标记解释

名词解释
CogC 每个非抽象方法的认知复杂性,嵌套的控制结构越多,认知复杂性就越高
ev(G) 每个非抽象方法的基本复杂性,用图论度量方法的控制流结构,范围1-v(G)
iv(G) 方法的设计复杂性,与对其他方法的调用之间的相互联系有关
v(G) 每个非抽象方法的圈复杂度,即if‘s, while‘s, for‘s, do‘s, switch cases, catches, conditional expressions, &&‘s , ||‘s的数量

技术分享图片

第一次作业

类表

类(共9个)说明功能Source Code Lines(709)
MainClass 主类 输入和输出 21
Expression 表达式处理函数类 处理表达式 256
Operator 基本运算符类 运算符类的父类 84
Add Operator子类,加减法类 生成和化简加减法表达式 173
Mult Operator子类,乘法类 生成和化简乘法表达式 80
Power Operator子类,指数类 生成指数表达式 48
Term 常量/变量类 常量和变量 30
OperatorFactory 运算符类工厂类 工厂模式生成运算符类 17
SignedOpe (Add内)带正负性的Operator类 用于Add内的表达式化简 (25)

UML类图

说明:为了脉络清晰,在此仅列出最主要的类关系和方法

技术分享图片

OO度量

MainClass
method(2)LinesCogCev(G)iv(G)v(G)
main(String[]) 11 0 1 1 1
process(String) 7 2 1 3 3
Expression
method(9)LinesCogCev(G)iv(G)v(G)
getIterm(String,String) 26 12 5 7 8
getMultCoe(Operator) 22 8 5 5 6
getMultIndex(Operator) 22 8 5 4 6
getOpePriority(String) 14 5 5 5 6
getSymbol(String) 49 19 12 19 22
getTermArray(String) 24 6 3 3 4
getTree(String) 59 46 12 14 17
setMultCoe(Operator,BigInteger) 15 5 4 4 4
simplify(Operator) 12 5 3 4 6

分析:可以看出,主要是getTree(生成表达式树)、getSymbol(分解出项的前置符号)和getIterm(分解出不带符号的项)这3个方法的度量数值偏高。这三个方法的CogC偏高的原因是原因是在这两个方法中运用了较多的if判断语句,造成方法的认知复杂;getTree的v(G)偏高的原因是在方法中调用了较多Expression类下的static方法,而getSymbol和getIterm的v(G)偏高的原因是使用了较多的正则表达式匹配方法和String.equals方法。

Operator
property(7)类型说明
aa Operator 左子树
bb Operator 右子树
type String 表明类型,如Single,Add,Mult
term term Single型包含的数据
isSingle boolean 是否为Single
certain boolean isSingle & term是常量
positive boolean 表明子类型,如Add分为加法和减法
method(14)LinesCogCev(G)iv(G)v(G)
addSingle(Term) 6 0 1 1 1
getaa() 2 0 1 1 1
getbb() 2 0 1 1 1
getDerivative() 10 3 3 1 3
getNew(Operator,Operator,boolean) 2 0 1 1 1
getNumberOpe(BigInteger) 4 0 1 1 1
getResult(Operator) 2 0 1 1 1
getType() 2 0 1 1 1
isCertain() 2 0 1 1 1
isPositive() 2 0 1 1 1
isSingle() 2 0 1 1 1
Operator(Operator,Operator,boolean) 16 7 1 4 7
setType(String) 2 0 1 1 1
toString() 7 2 2 2 2
Add
method(11)LinesCogCev(G)iv(G)v(G)
Add(Operator,Operator,boolean) 3 0 1 1 1
getAddArray(Operator,boolean) 29 13 3 7 7
getDerivative() 14 5 3 3 3
getNew(Operator,Operator,boolean) 3 0 1 1 1
getResult(Operator) 55 27 5 12 13
toString() 42 22 1 12 12
SignedOpe.getOperator() 2 0 1 1 1
SignedOpe.getPositive() 2 0 1 1 1
SignedOpe.setOperator(Operator) 2 0 1 1 1
SignedOpe.setPositive(boolean) 2 0 1 1 1
SignedOpe.SignedOpe(Operator,boolean) 3 0 1 1 1

分析:Add的度量数值偏高,主要体现在getResult(化简)和toString(字符串输出)上。这是因为两个方法内都涉及大量的if判断。同时,使用BigInteger的方法和String.equals方法也使这两个方法的v(G)值偏高。

Mult
method(6)LinesCogCev(G)iv(G)v(G)
getDerivative() 9 2 2 2 2
getMultArray(Operator) 10 4 2 3 3
getNew(Operator,Operator,boolean) 3 0 1 1 1
getResult(Operator) 25 7 3 5 6
Mult(Operator,Operator,boolean) 3 0 1 1 1
toString() 20 7 1 6 7
Power
method(5)LinesCogCev(G)iv(G)v(G)
getDerivative() 14 4 2 3 3
getNew(Operator,Operator) 3 0 1 1 1
getResult(Operator) 5 1 2 2 2
Power(Operator,Operator) 3 0 1 1 1
toString() 14 3 1 2 3
Term
property(3)类型说明
type int 0-常量 1-变量
number BigInteger 常量的值
name String 变量名
method(4)LinesCogCev(G)iv(G)v(G)
addTerm(String) 4 2 2 1 2
isCertain() 2 0 1 1 1
Term(int,BigInteger,String) 7 0 1 1 1
toString() 7 2 2 2 2
OperatorFactory
method(1)LinesCogCev(G)iv(G)v(G)
getOperator(Operator,Operator,String) 14 4 5 5 5
平均
AverageCogCev(G)iv(G)v(G)
Homework1 4.44 2.31 3.17 3.63

说明

在第一次作业中,我试图采用面向对象的思想来架构总体框架。我的总体构思是以表达式树作为核心算法,并采用工厂模式去构造表达树。该构思的主要难点在于表达式树的构建以及化简。

由于本人对面向对象的理解的不成熟和思考上的狭隘,在此次作业中大量的代码仍然是面向过程的产物,在架构上也出现很多的缺陷。

由上面的数据不难发现,该作业的代码主要集中于Expression、Operator、Add和Mult类。原因:虽然Operator作为符号类的父类,但Operator中存储了太多的属性导致代码多。另外Expression.getTree(生成表达式树)、Add.getResult(加减法表达式化简)、Mult.getResult(乘法表达式化简)的方法构思上的不足,导致代码冗长。

另外,在类的内聚和耦合方面,类与类之间的耦合程度过大,类的内聚程度不够,容易导致意想不到的bug,以及对bug的定位和修复带来了很大的困难。比如,在Expression中整合了过多的方法,造成代码繁杂和难以维护;Add和Mult的getResult(表达式化简)等方法,对于Expression类中的方法调用依赖性过多,在定位bug过程中,常常在多个类中反复跳转,阻碍重重。

总之,此次作业的初步采用了面向对象方法来设计,但在方法代码的合理划分和分配、类的内聚和耦合方法还存在很大的改进空间。

第二次和第三次作业

类表

类(共12个)描述描述Source Code Lines(1978)
MainClass 主类 输入和输出 85
Express 表达式处理函数类 处理表达式 363
Operator 基本运算符类 运算符类的父类 83
Add Operator子类,加减法类 生成和化简加减法表达式 230
Mult Operator子类,乘法类 生成和化简乘法表达式 232
Power Operator子类,指数类 生成指数表达式 56
Sin Operator子类,Sin类 生成Sin(x) 41
Cos Operator子类,Cos类 生成Cos(x) 41
Term 常量/变量类 常量和变量 30
OpeFac 运算符类工厂 工厂模式生成运算符类 24
SignedOpe (Add内)带正负性的Operator类 用于Add内的表达式化简 (23)
Poly 项解析类 描述项的特征,用于Mult内化简 81

UML类图

说明:为了脉络清晰,在此仅列出最主要的类关系和方法

技术分享图片

OO度量

MainClass
methodLinesCogCev(G)iv(G)v(G)
process(String) 16 0 1 1 1
main(String[]) 22 6 1 7 7
formatCheck(String) 38 7 6 3 6
Express
property(1)类型说明
canInt boolean 是否能中断
methodLinesCogCev(G)iv(G)v(G)
getBigInteger(Operator) 8 2 2 2 2
getBraP(ArrayList,int,String) 56 27 12 15 19
getIterm(String,String) 16 6 2 5 6
getOpePri(String) 17 7 6 7 8
getPrint(Operator) 20 7 3 5 5
getSin(ArrayList,String,int,int) 35 25 7 10 12
getSinP(ArrayList,int) 23 10 5 5 7
getSymbol(String,boolean) 32 11 8 8 10
getTermArray(String) 25 6 3 3 4
getTree(String,boolean) 57 40 7 18 19
interrupt() 6 1 2 1 2
removeBr(String) 32 19 7 10 13
simplify(Operator) 19 10 4 7 8

分析:相比第一次作业,Express的方法变得更多,方法的复杂性也都有所上升。这主要是因为增加了更多的新功能。

Operator
property(7)类型说明
aa Operator 左子树
bb Operator 右子树
type String 表明类型,如Single,Add,Mult
term term Single型包含的数据
isSingle boolean 是否为Single
certain boolean isSingle & term是常量
positive boolean 表明子类型,如Add分为加法和减法
methodLinesCogCev(G)iv(G)v(G)
addSingle(Term) 5 0 1 1 1
getaa() 2 0 1 1 1
getbb() 2 0 1 1 1
getDerivative() 7 2 2 1 2
getNew(Operator,Operator,boolean,String,boolean) 3 0 1 1 1
getNumberOpe(BigInteger) 3 0 1 1 1
getResult(Operator) 2 0 1 1 1
getType() 2 0 1 1 1
isCertain() 2 0 1 1 1
isPositive() 2 0 1 1 1
isSingle() 2 0 1 1 1
Operator(Operator,Operator,boolean,String,boolean) 18 7 1 4 7
setCertain(boolean) 2 0 1 1 1
toString() 7 2 2 2 2
Add
methodLinesCogCev(G)iv(G)v(G)
Add(Operator,Operator,boolean) 2 0 1 1 1
getAddArray(Operator,boolean) 32 17 3 10 10
getDerivative() 16 5 2 2 3
getNew(Operator,Operator,boolean) 51 25 12 17 19
getResult(Operator) 57 27 5 12 13
toString() 42 22 1 12 12
SignedOpe.getOperator() 2 0 1 1 1
SignedOpe.getPositive() 2 0 1 1 1
SignedOpe.setOperator(Operator) 2 0 1 1 1
SignedOpe.setPositive(boolean) 2 0 1 1 1
SignedOpe.SignedOpe(Operator,boolean) 3 0 1 1 1

分析:相比上次的Add,主要是getNew(产生新Add并化简)的度量值增加,这是因为getNew分担了部分getResult(化简)的功能。设计原因:化简的代码过多,全放在getResult中显得过于冗杂。而getNew和getResult配套使用,外界仅能调用getNew,而调用getNew的同时也调用了getResult,故因而可以把部分getResult中的化简代码放在getNew,使得代码分布不那么过分集中。

值得注意的是,运算符类工厂在产生新的运算符时,调用的是运算符的getNew()方法,然而在getNew()中调用了getResult()的方法。在getResult()中产生新的Add时,若直接调用运算符工厂去生成新的Add,则会调至无限递归死循环而爆栈,因此在getResult()中只能使用Add的默认构造器。

技术分享图片

Mult
methodLinesCogCev(G)iv(G)v(G)
getDerivative() 22 6 2 2 4
getMultArray(Operator) 9 4 2 3 3
getNew(Operator,Operator,boolean) 23 8 7 8 9
getPoly(Operator) 36 9 5 6 6
getResult(Operator) 28 6 2 3 5
getSinglePoly(Operator) 43 15 2 12 14
isPolyQualified(Operator) 31 20 8 8 10
Mult(Operator,Operator,boolean) 2 0 1 1 1
toString() 27 9 1 6 9

分析:getNew()的复杂性提高的原因同Add.getNew()。

Power
methodLinesCogCev(G)iv(G)v(G)
getDerivative() 17 7 2 5 5
getNew(Operator,Operator) 9 3 1 6 6
getResult(Operator) 5 1 2 2 2
Power(Operator,Operator) 2 0 1 1 1
toString() 14 3 1 2 3
Sin
methodLinesCogCev(G)iv(G)v(G)
getDerivative() 17 4 2 2 4
getNew(Operator) 4 0 1 1 1
getResult(Operator) 2 0 1 1 1
Sin(Operator) 2 0 1 1 1
toString() 9 2 1 2 2
Cos
methodLinesCogCev(G)iv(G)v(G)
Cos(Operator) 2 0 1 1 1
getDerivative() 17 4 2 2 4
getNew(Operator) 4 0 1 1 1
getResult(Operator) 2 0 1 1 1
toString() 9 2 1 2 2
Term
property(3)类型说明
type int 0-常量 1-变量
number BigInteger 常量的值
name String 变量名
methodLinesCogCev(G)iv(G)v(G)
addTerm(String) 4 2 2 1 2
isCertain() 2 0 1 1 1
Term(int,BigInteger,String) 7 0 1 1 1
toString() 7 2 2 2 2
OpeFac
methodLinesCogCev(G)iv(G)v(G)
getOpe(Operator,Operator,String) 20 6 7 7 7
Poly
property(5)类型说明
coe BigInteger 系数
xxIndex BigInteger x的指数
sinIndex BigInteger sin的指数
cosIndex BigInteger cos的指数
qualified boolean 是否能参与化简
methodLinesCogCev(G)iv(G)v(G)
equals(Poly) 10 3 2 5 6
getCoe() 2 0 1 1 1
getCosIndex() 2 0 1 1 1
getPolyOpe() 37 12 2 9 9
getSinIndex() 2 0 1 1 1
getxIndex() 2 0 1 1 1
isQualified() 2 0 1 1 1
Poly(BigInteger,BigInteger,BigInteger,BigInteger) 5 0 1 1 1
setQualified(boolean) 2 0 1 1 1

说明:getPolyOpe(根据Poly生成相应的Operator)的复杂度高的原因是在该方法中调用了较多OpeFac(表达式类工厂)的方法。

说明

相比第一次作业,第二次作业增加了sin(x)函数和cos(x)函数,以及表达式因子。

相比第二次作业,第三次作业增加了对sin函数和cos函数内含表达式因子的运算,以及输入格式判断。

第二次作业开始,对第一次作业的某些类的架构进行了部分重构,总体架构保持不变。

首先我针对内聚耦合问题做了一定的优化。在第一次作业,Add类和Mult类的getResult(表达式化简)方法过度依赖Express类中的方法,即我将只会在Add类和Mult类中用到的函数错误地整合到了Express中,导致架构的混乱。所以在第二次作业的小重构中,我将Express类中的方法进行了进一步的拆分和整合,新增了Poly类并移植了部分函数,从而加强了各个类的内聚程度,降低了类与类之间的耦合程度。

另外,对于面向过程方法的代码过长的问题,我也做了一定的优化。我提取了Express.getTree(生成表达式树)中的多处用到的相似代码,并将其整合为一个static方法,优化了代码的运行逻辑,增强了简洁性。

第三次作业开始,对第二次作业的代码未做较大修改,仅仅增量开发了sin、cos函数和表达式格式检查。

经过一定优化后的代码,相比第一次作业来说,虽然代码量增加了,但逻辑性更加清楚,类的功能更加明确。但是,老问题依然存在,类的内聚和耦合性还存在欠缺,代码的冗杂性和可读性都有不足之处。

主要类功能介绍

MainClass

主要功能

输入和输出

主要方法说明
main 主程序,承担输入与输出功能
process 对输入表达式进行必要处理,如去除空白符
formatCheck 对输入表达式进行初步格式检查
处理流程:

技术分享图片

Expression

主要功能

处理表达式

主要方法说明
getTree 由表达式字符串生成表达式树
simplify 对表达式树进行化简
getTree流程:

技术分享图片

Add、Mult

主要功能

生成和化简表达式

主要方法说明
getNew 生成新的表达式类并化简
getResult 表达式化简
toString 字符串输出
getDerivative 求导,并以字符串输出
Add.getResult流程

技术分享图片

Mult.getResult流程与Add.getResult类似

二、Bug分析

第一次作业

第一次作业在强测和互测中,共发现2个问题。

(1)

第一个问题是设计上的一个bug,具体为不支持x前带有多个符号,若表达式中出现如“+-x”的形式,则表达式树生成时会变成“x”。产生原因是在生成表达树的过程中,忽略了x作为表达式第一项时,可以带多个前置符号的问题。

解决方法:修改代码。

(2)

第二个问题是运行超时问题。产生原因是在Express、Add、Mult类的toString方法中,调用了过多的toString方法。

解决方法:在一个方法中,只调用一次同一元素的toString,并设置String变量将其存下来,避免多次递归调用造成程序运行超时,甚至爆栈。

下面对比两次调用的OO度量:

Add.toString()CogCev(G)iv(G)v(G)
只调用一次toString 22 1 12 12
多次调用toString 22 1 12 12

可以看到,两者在OO度量上没有变化。

但在程序运行时间上,当表达式变长时,两者的时间差便以倍数增长。

第二次作业

第二次作业在互测中发现一个bug,为BigInteger的报错。

报错信息:

Exception in thread "main" java.lang.NumberFormatException: For input string: "(7)*(7)"

产生原因:化简过程中,对于表达式树的化简不彻底。当输入为(7-sin(x)+sin(x))*(7-sin(x)+sin(x))时,经过程序的化简,得到的结果是(7)*(7),并且得出这个表达式为确定的常数,于是 new BigInteger((7)*(7)) 时产生了上述报错信息。

解决方法:

在Express中添加了一个static方法getBigInteger(),在该方法中首先判断输入的字符串是否满足正则表达式"^\d+$",若满足,才生成BigInteger。

第三次作业

在强测和互测中均未发现bug。

三、互测挖掘BUG方法

(1)手动构造样例

根据自己测试程序的样例,能够找到他人的部分bug。

另外,根据读他人代码,可以针对性的对其存疑部分的代码功能进行特定的测试,以检测正确性。如在第一次作业互评中,见到一位同学根据有限状态机来做表达式的输入,于是我通过阅读他的代码,成功发现了他的一个bug:当输入x * 2时,他的程序不会读入x后的乘法项。

(2)自动评测机测试

自动评测机由Python编写,由Python的第三方库sympy支持表达式的求导和运算。根据指导书给出的规则,自动随机生成相应的测试样例并自动评测。

四、重构经历总结

本人在总体架构上没有进行大的重构,只在第一次作业到第二次作业过渡的过程中,对Express、Add、Mult类进行了部分功能重构。

相比第一次作业,后面的作业增加了一个新的Poly类,该类整合了Express、Add和Mult类中对表达式项分析的功能。

methodv(G)methodv(G)
getDerivative 2 getDerivative 4
getMultArray 3 getMultArray 3
getNew 1 getNew 9
    getPoly 6
getResult 6 getResult 5
    getSinglePoly 14
    isPolyQualified 10
Mult 1 Mult 1
toString() 7 toString 9

表格左边为第一次作业,右边为第三次作业。观察表格可以得出,第三次作业增加的getPoly、getSinglePoly和isPolyQualified方法,使得Mult类的v(G)值大幅增加。但是相应的,Poly类的内聚性很强。在Add类中也要调用Poly类中的相关方法,这些方法原先存在于Mult类中,如此一来便减小了Add类和Mult类的耦合。同时,代码的可读性和简便性也因Poly类而增强。

五、心得体会

本单元的作业,让我初步切实体会到了面向对象方法的特性:良好的可拓展性。面向对象的灵活性是以前我熟悉的c语言面向过程方法不可比拟的,因此我认为面向对象确实是非常重要的思考方法。

与此同时,我也认识到了面向对象方法对程序代码架构的依赖性。若程序架构不行,再好的面向对象思想都是无用功。而且,当类与类直接的耦合度过大的时候,面向对象的多个类反而增加了debug的难度,不得不说这几次作业中debug过程着实令我非常痛苦。

但痛苦只是暂时的,完成这个困难的作业并且通过强测互测的成就感,还是挺强的。同时作业对我理解面向对象的帮助还是挺大的,对于架构设计的思考也是不可或缺的学习历程。

OO第一单元总结

原文:https://www.cnblogs.com/Junly7/p/14587171.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!