OO第一单元博客总结
前言
第一次尝试使用博客,第一次查询使用markdown编辑器,如有文风不当多多包涵
一些概念简述:
ev(G)基本复杂度是用来衡量程序非结构化程度的,非结构成分降低了程序的质量,增加了代码的维护难度,使程序难于理解。因此,基本复杂度高意味着非结构化程度高,难以模块化和维护。实际上,消除了一个错误有时会引起其他的错误。
iv(G)模块设计复杂度是用来衡量模块判定结构,即模块和其他模块的调用关系。软件模块设计复杂度高意味模块耦合度高,这将导致模块难于隔离、维护和复用。模块设计复杂度是从模块流程图中移去那些不包含调用子模块的判定和循环结构后得出的圈复杂度,因此模块设计复杂度不能大于圈复杂度,通常是远小于圈复杂度。
v(G)是用来衡量一个模块判定结构的复杂程度,数量上表现为独立路径的条数,即合理的预防错误所需测试的最少路径条数,圈复杂度大说明程序代码可能质量低且难于测试和维护,经验表明,程序的可能错误和高的圈复杂度有着很大关系。
第一次作业:简单多项式的求导
1. 程序简介:
基本结构:
- 类的个数:3个;方法个数:8个
- 由于思路过于简单,仅仅是使用正则表达式读取各个单项式之后,逐个添加到单项式到到Monomial类,求导方式也较为简单,就不再赘述
2. 结构分析
- 复杂度分析
可以看出,总体的复杂度并不是很高,只有print函数和单项式中的构造器较为复杂,v(G)较多,这是因为Monomial的构造器需要对传进来的字符串进行直接处理,需要进行的工作较为复杂,而print函数需要判断各种化简的情形,也是较为复杂造成的。而由于PolynomialCal需要循环读入单项式传给Monomial,所以其OCavg比较高。
第二次作业:多项式(含三角函数)的求导
1. 程序简介:
基本结构:
-
Factor类:该类为因子,是为单项式的更细的划分
- 变量
- type决定类别(幂函数/正弦函数/余弦函数)
- index代表当前类别的指数
- 方法
- setType, setIndex, getType, getIndex, derivate
- 变量
-
PowerFunction类、TrigonometricFunction类
-
幂函数因子、三角函数因子,继承自父类Factor
-
变量
- 幂函数type=1,正弦函数type=2,余弦函数type=3
-
方法(继承自父类)
-
@override:derivate求导函数:对单个因子求导,返回一个Monomial类型的变量
-
@override:toString将因子转化为字符串(为输出做准备)
-
两种构造器
1)通过字符串识别相应部分构造(以适应标准输入)
2)通过直接输入相应属性构造(以方便在使用过程中临时构造)
-
-
-
Monomial类
-
变量
- 单项式系数coefficient
- 因子链表mono,将上述因子放入其中
-
方法
-
构造器:我个人的构造器仅为识别正负,即给构造器传入的字符串仅有+-,构造器仅能识别并set单项式系数为±1
-
multFactor(添加因子)
我有两个multFactor方法,分别通过读入字符串和读入Factor型变量添加因子,方法的目的为将因子添加到当前单项式的mono链表中(可用作添加因子,也可用作单项式和因子相乘的结果)
-
sameMonomial:
意在识别两个单项式是否为可加减单项式,即两单项式的x, sin(x), cos(x)的指数部分是否相同
-
derivate求导函数:对整个单项式求导,返回一个Polynomial类型的变量
-
@override:toString:将单项式转化为字符串(为输出做准备)
-
setCoefficient, getCoefficient
-
-
-
Polynomial类
-
变量
- 链表 poly ,其中每个节点为一个单项式
-
方法
-
addElement添加元素
输入单项式,将单项式加入到poly中(如果已有该单项式则直接改变系数,否则直接在链表结尾生成新节点)
-
addPoly多项式相加
在本多项式的基础上,加上输入的多项式。实现方法为通过addElement一个一个增加输入多项式中的单项式
-
@override:toString:将多项式转化为字符串(为输出做准备)
-
derivate求导函数(对整个多项式求导,其实为每个单项式求导后相加)
-
-
-
PolynomialCal类
- 首先匹配空字符,观察空字符是否合法,确认合法后删除空字符,并检查空串
- 视情况在首位添加"+",以满足每一个项的形式统一
- 在外层循环中,通过[+-]来分割单项式,读取单项式(如果出现连续3个[+-]或者不是[+-]则直接WF)
- 在内层循环中,通过*来分割因子,依次读入因子,将因子传入单项式中
- 回到外层循环添加单项式,依次生成多项式
- 生成多项式并输出
2. 结构分析
- 复杂度分析
- 方法复杂度
- 类复杂度
3. 自我评价
这次的代码中,我部分的考虑了延拓性,使用了一个很简单的继承类的结构框架,但是代价就是整个代码显得冗长而复杂。本来只需要三个指数就可以解决的问题转换为了许多的判断,就导致v(G)的上升。
同时,由于本身还是具有一些C的想法,就导致即使自己的框架结构写的相对清晰,但是部分的方法仍然显得很复杂。我认为我把每一个因子,每一个类的提取方式、构建方式、添加方式、运算方式与toString的方法写的很独立,确实是实现了每个类自己所具有的功能,但是仍然存在一个主函数PolynomialCal中会进行大段大段的判断以及构造,导致PolynomialCal的各项指标都很高。
再其次观察我的各项数据,便是simplify系列的ev(G)比较高了。这几个函数的意义在于化简,首先是对$$asin(x)2+b*cos(x)2$$进行化简,其次为了防止循环化简的产生,只对$$a-asin(x)2$$和$$a-a*cos(x)2$$
两种情况进行化简。但是这两种情况需要反复遍历(至少两次)多项式链表,造成了ev(G)的增高。
4. 分析自己的bug与发现别人的bug
由于这次作业的正确性判断方面很难出现纰漏(因为只涉及到乘法法则,单个因子的求导结果相当单一)所以查bug的重心应当放在WRONG FORMAT!和Exception上。一般在设计之前,我都会先针对可能的WF做一些测试集,以此来思考可能出现的各种各样的问题。
例如我发现这次的作业中,由于大部分人是通过循环读入单项式的模式进行读入,所以会有很多的边界问题。例如“1*”这种输入,会因为使用乘号作为分隔符而未考虑乘号后面没有其他输入而导致出现错误。同时还有我自己为自己de出的一个bug:”++“,这个输入会因为通过正负号判断符号后,理所应当的认为还有后续输入而导致爆栈。我认为,出现这种问题的原因就是设计框架的时候没有考虑好鲁棒性,推荐像我这种写之前先花时间全面考虑一下可能出现问题的情景的方式。
至于本人,错了一个判断空格合法性的正则表达式上面的小错误,说明本人创建的测试集还不够完善,还需要再进一步加强。
第三次作业:复杂多项式的求导
1. 程序简介:
- 基本结构:
接下来是我个人代码每一个类每一个方法的详细介绍,如果不想了解的可以直接跳过,不影响阅读
-
Factor**抽象类:该类为因子,是为单项式的更细的划分
- 变量
- type:决定类别(幂函数/正弦函数/余弦函数)
- index:代表当前类别的指数
- expressionInTrig是三角函数因子中的表达式因子
- type:决定类别(幂函数/正弦函数/余弦函数)
- 方法
- setType, setIndex,setExpressInTrig,
- getType, getIndex,getExpressInTrig,
- derivate求导函数,返回值为一个单项式monomial
- 变量
-
PowerFunction类、TrigonometricFunction类:幂函数因子、三角函数因子,继承自父类Factor
-
变量
-
幂函数type=1,正弦函数type=2,余弦函数type=3
特别的,当三角函数中的表达式因子中的内容为真正的表达式因子时,其type为对应的相反数(例:$$sin(x)$$的type=2,但是$$sin((x+x))$$的type=-2
-
-
方法
-
@override:derivate求导函数:对单个因子求导,返回一个Monomial类型的变量
对于三角函数的求导,需要在整个三角函数因子求导的基础上直接乘以expressInTrig的导数(此项为表达式因子)
-
@override:toString将因子转化为字符串(为输出做准备)
-
构造器×2
1)通过字符串识别相应部分构造(以适应标准输入)
在三角函数的构造器中,需要加入对表达式因子的识别,以构造expressInTrig部分。同时三角函数输入字符串的合法性判断也将在这里进行
2)通过直接输入相应属性构造(以方便在使用过程中临时构造)
-
-
-
Monomial类:单项式
-
变量
- coefficient:单项式系数
- mono:因子链表,将上述因子放入其中
- 表达式因子express,每个单项式仅有一个表达式因子,并初始化为null。表达式因子独立于因子链表而存在,因为其构造的特殊性。
-
方法
-
构造器×2:
1)无需传入参数,构造一个单项式1
2)仅为识别正负,即给构造器传入的字符串仅有+-,构造器仅能识别正负并set单项式系数为±1
-
multFactor×2:添加因子
1)通过读入Factor型变量,将因子添加到当前单项式的mono链表中(可用作添加因子,也可用作单项式和因子相乘的结果)
2)通过读入并分析字符串,将因子添加到当前单项式的mono链表中(可用作添加因子,也可用作单项式和因子相乘的结果)这部分的添加因子只能添加常数项以及幂函数因子。对于三角函数类的因子,由于其结构的复杂性,需要在多项式的读入过程中直接进行创建与添加
-
multExpression:添加表达式因子
- 通过读入表达式因子来进行对当前单项式表达式因子的添加
- 特别的,如果输入表达式为null,直接返回
- 如果输入表达式是0,将本单项式的系数直接设为0
- 如果输入的表达式为因子,则将本单项式直接乘上表达式代表的因子
- 否则,让当前单项式的表达式因子乘以目标表达式因子
- 通过读入表达式因子来进行对当前单项式表达式因子的添加
-
multmono:单项式相乘,将本单项式乘以输入单项式,分为因子和表达式因子处理
-
sameMonomial:意在识别两个单项式是否为可加减单项式
对于表达式因子和因子链表分开判断是否相等。判断相等的时候进行了适当的简化,仅判断两表达式因子转化为字符串形式后是否相等
对于因子链表,判断相等时仍然进行了适当的简化,对于每个因子,仅判断toString后是否相等
-
derivate求导函数:对整个单项式求导,返回一个Polynomial类型的变量
求导的方式为,遍历因子链表,对每个因子求导后乘以其他因子以及表达式因子。然后对表达式因子求导,得到的表达式因子需要乘以该单项式的因子链表(即乘法的求导法则)
-
@override:toString将单项式转化为字符串(为输出做准备)
-
clones克隆函数,克隆一个当前单项式(最后没用上)
-
setCoefficient, getCoefficient, getPoly, getExpress
-
-
-
Polynomial类
-
变量
- 链表 poly (其中每个节点为一个单项式)
-
方法
-
构造器×2:
- 构造一个空多项式
- 根据输入的字符构造多项式(这将成为整个程序的入口!)
- 首先判断当前多项式的首位是否有正负号,如果没有的话自动补上一个正负号
- 接下来循环取单项式,方式为:
- 首先读取符号,构建单项式。(如果符号数量错误或者有其他字符就报错)其次通过循环正则获取单项式中的因子,对于常数因子和幂函数因子,由整个的正则表达式来判断,并传入multFactor函数来进行添加。对于表达式因子以及三角函数因子,需要进行分类判断后进行括号匹配,匹配成功后将相应字符串加入到单项式中。(括号匹配将在后面的RegEx类中介绍,功能是根据当前的前括号得到与其相对应的后括号中的字符串输出。)读完单项式后继续检查符号,进行循环
-
addElement:添加元素
输入单项式,将单项式加入到poly中(如果已有该单项式则直接改变系数,否则直接在链表结尾生成新节点)
-
addPoly:在本多项式的基础上,加上输入的多项式
通过addElement一个一个增加输入多项式中的单项式
-
multMono:多项式乘单项式(最后未能使用,可以不做思考)
-
multPoly多项式乘多项式(最后未能使用,可以不做思考)
-
clones复制一个多项式(最后未能使用,可以不做思考)
-
@override:toString将多项式转化为字符串(为输出做准备)
-
derivate求导函数(对整个多项式求导,其实为每个单项式求导后相加)
-
getPoly
-
-
划重点!!!
-
ExpressionFactor类
-
变量
-
一个多项式链表poly,存储的意义为若干多项式相乘的形式。
例如,((x+3)(5 * x+8))存储的结构为:poly[0]=x+3,poly(1)=5x+8
-
-
方法
-
构造器×2
-
1)构造一个空的表达式因子
-
2)根据输入字符串构造表达式因子
首先将表达式最外层的若干层括号去掉,此时需要用到括号匹配,以防对于$$(1)+(2)$$误删为$$1)+(2$$的情况。其次检查是否为空串,空串则报错。接下来通过多项式的构造器读入剩余字符串构造多项式并加入到poly链表中
注:本构造器只能实现单个多项式的读入功能,形如($$cos(x))*(x)$$也会被当做一个单独的多项式)
-
-
multExpress:乘表达式因子
采用简化的模型,直接在当前多项式链表poly后面依次添加输入表达式因子的各个多项式
可能的优化:理想的状态是可以识别需要相乘的表达式因子,进行一定程度的化简考虑(将当前的多项式与输入多项式相乘比较长度后决定式采取添加多项式还是多项式乘开的操作
-
isSameExpress:判断是否为同样的表达式因子
采取简化的模型,只是判断两表达式化为字符串后是否相同。
可能的优化:将表达式因子“上浮”,就是指对于那些多项式里面只有一个单项式,而且单项式里只有一个表达式因子的,可以去掉中间的单项式和多项式结构,构建为表达式因子,但是需要递归遍历查找,耗费时间过长
-
derivate---表达式因子求导,返回另一个表达式因子
进行对于多项式的乘法求导法则:对多项式链表poly中的每个多项式求导,之后乘上其他的多项式转化为的表达式因子,最后相加
-
@override:toString 将表达式因子转化为字符串(为输出做准备)
其中每个多项式均为一个表达式因子,表达式因子之间用*连接
例如:$$((x+2)*(cos(x)+8))$$
-
isFactor:判断是否为因子,返回值为boolean型
对于当前表达式因子,如果多项式链表中只有一个多项式,且多项式中的单项式链表中只有一个单项式,而且单项式中因子链表中仅有1个因子或没有因子,而且不存在表达式因子,可判定为是因子,为化简做准备
可能的优化:查找更深层次的因子,如上述的单项式中因子链表中无因子,而表达式因子中恰放着一个因子,这种情况无法识别
-
-
-
RegEx类:常数存放类(新的想法,可供参考)
-
在这个类中我放入了各种各样的正则表达式的String 以及Pattern,这样的话在其他的类中如果需要调用这些正则表达式,只需要建立一个RegEx类,从里面直接抽取,虽然会使程序执行变得复杂,但是我认为对于代码的简洁与可读性有一定帮助
-
这个类里有两个不基于本类的方法:
- bracketMatch----括号匹配:根据输入字符串的第一个字符'('找到与其对应的')',并输出两括号内的字符串
- 构建一个栈,遇到'('则压栈,遇到')'则出栈,栈为空的时候即为匹配的时候。如果字符串结束时栈不为空,那么证明'('的数量大于')',直接报错。
- 匹配成功后,输出中间的字符串。至于原串,只需要在方法外边用substring方法将输出字符串的长度+2部分删除即可
注:对于后括号数量多于前括号的情况,具体的报错机制需要在方法外面判断,正常使用正则即可
- matchTrig----匹配三角函数因子:根据输入字符串,输出三角函数对应的字符串部分
- 将三角函数的前括号进行括号匹配并记录字符串长度,观察括号匹配后面的字符串有没有指数项。如果有的话将长度加上相应部分,然后输出
例:输入$$sin((x+x))^3+5*8$$, 输出$$sin((x+x))^3$$
- bracketMatch----括号匹配:根据输入字符串的第一个字符'('找到与其对应的')',并输出两括号内的字符串
-
-
Expression类
-
主类,具体操作过程为:
判断空格是否合法,删除空格,判断是否为空串,建造多项式,求导,输出
在输出的时候,我添加了判断,如果多项式为一个表达式因子,则输出时去掉两边的括号
-
2.结构分析
3. 自我评价
这个是我这次代码的整体结构。可以看出来是一个递归层次的建构,而且对于括号嵌套类的(如$$sin(sin(sin(sin(x))))$$),递归深度会很深。
自我感觉这次代码的耦合度其实还是有点高。虽然我仍然把每一个因子、每一个类、多项式、表达式因子的提取方式、构建方式、添加方式、运算方式与toString的方法写的很独立,确实是实现了每个类自己所具有的功能,但是可能是部分函数的功能要求太高,导致部分函数循环过多,规模较大,造成的复杂度相当高。
观察数据可以发现,其实很多功能性的东西加了太多的特判,用来针对各种各样的WF,这样也会造成程序的冗杂,这也是需要提高的一部分。
4. 分析自己的bug与发现别人的bug
首先我想先提一下WRONG FORMAT!和Exception的问题。同样这次在设计之前,我也做了一些测试集,不光是WF,这次我还寻找到一些可能出现的表达式的情况,我觉得这是有助于清晰地理解题目允许我们干什么的。
这次的作业中,边界问题是依然存在的。例如”++“,这个输入会因为通过正负号判断符号后,理所应当的认为还有后续输入而导致爆栈。同时还有$$sin(x)*-8$$这种前几次没问题,代码升级后反而出现问题的。虽然这次这种答案没办法提交,但是我觉得这也是要引起惊醒的。
至于本人,强测主要出错错在了TLE上面,究其原因,是因为我有很多判断表达式是否为0的比较,都是直接调用了ExpressionFactor的toString算法,这样会造成无意义的深层递归,本来简单的判断是否为空的作业现在需要递归调用,就导致时间消耗相当巨大。
同时本人还有一个粗心的bug,在三角函数输出的过程中,判断三角函数里的表达式是否为因子我只判断了因子链表的长度是否为1,没有考虑到2*x这种形式的,导致了bug的产生。
整体评价---写在最后的话
总体来说,这是我第一次写java的工程化构架,也是第一次完成这么多代码量的代码。总体来说是完成了从面向过程到面向对象转换的第一步。对于代码的重构,我认为我的结构在这个层面上基本上就是如此了。如果需要重构的话,只能通过表达树之类的新的数据结构谋求新的突破。
同时我也感受到了设计的重要性。在第二次和第三次作业中,我分别都进行了几乎全部代码的删除与重写,就是因为设计的很初步,没有考虑到后面代码的发展。希望以后的课程中,我能取得更好的进步!