BUAAOO Unit1:表达式求导

1 第一次作业

1.1 题目分析

第一次作业为简单多项式求导,因子中只有常数因子和变量因子,数字为带符号允许前导0的整数。

1.2 基本思想

Expression可以看成多个Term的 加减,Expression类在解析字符串时,一旦解析出一个完整的Term就会传给Term,在Term中实例化和计算,再讲Term返回给Expression

1.3 程序量化分析

1.3.1 类图

 

因为这次作业只涉及到幂函数的求导,函数比较简单,所以本次作业我并没有用继承(这也预示着我下一次作业的重构)

除去所有的getset方法

Term()类:主要有

  • computeD():根据项的系数和指数计算求导后的系数和指数

  • getSignedNum()方法:将一个带符号有前导0的数字的字符串,解析成数字

Expression()类:主要有

  • removeExtra():合并多个正负号

  • addTreeMap():将Term添加到TreeMap

1.3.2 核心类的度量

CogCCognitive Complexity,认知复杂度,是衡量代码被阅读和理解时的复杂程度。

evEssential Complexity,基本复杂度,是用来衡量程序非结构化程度的,非结构成分降低了程序的质量,增加了代码的维护难度,使程序难于理解。因此,基本复杂度高意味着非结构化程度高,难以模块化和维护。

ivModule Design Complexity,模块设计复杂度,是用来衡量模块判定结构,即模块和其他模块的调用关系。软件模块设计复杂度高意味模块耦合度高,这将导致模块难于隔离、维护和复用

vCyclomatic Complexity,圈复杂度,是用来衡量一个模块判定结构的复杂程度,数量上表现为独立路径的条数,即合理的预防错误所需测试的最少路径条数,圈复杂度大说明程序代码可能质量低且难于测试和维护,经验表明,程序的可能错误和高的圈复杂度有着很大关系。

Expression类:

Term类:

可以看到,两个类中比较复杂的是toString(),因为toString()是根据表达式/项生成求导后的字符串。其中需要考虑指数为1或0,系数为1等特殊情况,有很多的ifelse语句。

其次是类的构造函数,这是因为在构造函数中根据传入的字符串,借助while循环和正则表达式来解析字符串。

1.4 bug分析

本次作业强测和互测都没有测出bug,算是三次作业中完成最好的一次。在自己的debug过程中发现的bug主要有:

  • Term类中多个单项式相乘为系数相乘,指数相加,但在写的过程中却写成了系数相加,指数相加

  • 对于String进行操作时,要注意检查String是否为空串,否则很可能会抛出越界异常。

  • 注意String.replace()String.replaceAll()的区别。

  • 对系数为1和-1的项,很可能会省略系数,需要格外注意

  • 对于指数为1的项,要根据字符串中是否有x来判断指数为1还是0

圈复杂度与代码行差异:

我认为我这次作业的bug主要在于不够仔细认知,惯性思维了,实际上在圈复杂度和代码行上面出现了bug的Term类和没有出现bug的Expression类也没有区别,还是得细心

1.5 本次作业的总结反思

利用Matcher.group(num)来分割字符串,在当时确实感觉很方便,但隔一段时间再看,就不知道这块代表的具体是什么内容,没有很好的可读性,也不容易debug。

这次作业面向过程的影子很明显,在ExpressionTerm中都有很长篇幅的解析字符串的代码块,使得整个代码非常臃肿。而且我的代码只是面向第一次作业编程,并没有使用任何的继承,字符串的预处理和解析都在一个模块完成,并没有根据方法的功能严格划分各个方法,每个类的功能很复杂,并没有实现单个方法单个任务,类“形同虚设”,预示着我下一次作业的重构。

 

2 第二次作业

2.1 题目分析

本次作业为包含简单幂函数和简单正余弦函数的求导,因子可为表达式因子,支持嵌套的括号。

2.2 基本思想

一个表达式先传进Preprocess中进行预处理,再将预处理后的字符串传进TreeExpr类,在TreeExpr类中解析字符串。

2.3 程序量化分析

2.3.1 类图

FatherFactor:是所有因子的集合,所有的因子都可以看成coe*type**exp,其中type为xsin(x)cos(x),coe为系数,exp为指数,里面设置了求导方法和返回求导前和求导后的字符串的方法。

TreeExpr:借助数据结构所学的表达式树和后序遍历进行链式求导。

2.3.2 核心类的度量

TreeExpr类:

postfix方法为将中缀表达式转化为后缀表达式,违背了面向对象的设计思想,完全是面向过程的设计。checkFlagAscheckVar都是为了使postfix代码行减小而精简出来的方法,内聚性差,复杂度高是必然的。

FatherFactor类:

getFormat沿用了第一次作业的toString方法是根据求导后的coe、type、exp来求出最终的字符串表达式。

Symbol类:

Symbol复杂度高的原因是将加法、减法、乘法集合到一起,所以每个方法都会根据当前运算符进行ifelse判断,然后每个运算符有自己的计算方式,这一部分并没有分成三个类,导致内聚性差。

2.4 bug分析

本次在强测中挂了1个点,在互测中挂了6个点。

  • 对减号进行求导时,当左边为空时,需要保留-

  • 乘法求导的结果是左原*右导+左导*右原,但源代码中只判断了当左导数或者右导数为空,还需要判断原数字为空的情况,否则会输出空括号。

  • 当是减号时,-1*()-()的区别,在某些情况下,后者会被判WF。

都是Symbol类中加减乘的输出问题,因为思维上的不缜密导致了setOrisetDer频繁出错。

圈复杂度与代码行差异:

我认为我这次作业的bug还是在于不够仔细认真,也有可能是我没有把每个运算符都单拎出来写的缘故,导致藕合度高。

2.5 本次作业的总结反思

本次作业虽然较上一次把预处理单独拎出来一个模块写,但是在SymbolFatherFactory类里,没有把每个不同的情况分成不同的类,比如说Symbol应该分成AddMultSub单独来写,FatherFactory应该分为常数、幂函数、sin函数、cos函数。

这次采用表达式树来解析表达式,并没有使用递归下降法,对下一次作业的风格检查和sin、cos里面套表达式无法适用,没有很好的扩展性。

 

3 第三次作业

3.1 题目分析

本次作业为包含简单幂函数和正余弦函数的求导,因子可为表达式因子,正余弦函数里面可以嵌套因子,支持嵌套的括号。

3.2 基本思路

采用递归下降法,在解析字符串的同时进行格式检查。

3.3 程序量化分析

3.3.1 类图

本次作业我设置了一个Node类,Node里面分为TermSymbolFactorSymbol里放各种运算符,Factor放各种因子。

由于表达式可以看成多个项的加减,项可以看成多个因子的相乘,所以在Term中可以建立乘法树,在Expression里可以建立加减法数。

因为他们最终都需要建立二叉树的结构以及实现求导和求原表达式的功能,所以继承了Node类,便于统一管理。

3.3.2 核心类的度量

Parse类:

通过递归下降法来解析字符串,并且根据解析字符串的进度时刻更新当前字符串。

Expression类:

Term类:

TermExpression类似,都采用了中缀表达式转后缀表达式(postfix)和后序遍历求原表达式和求导后表达式(postOrder

3.4 bug分析

强测挂了3个点(2个WF),互测挂了3个点

  • 在计算原表达式时,如果为乘号,当乘号左边右边为空串时,整个结果都应该为空串,而不是在一边为空串时,还输出另一边

  • SinesetOriExpr中,当sinFactor为空串时,也应该返回空,否则会出现sin()的错误格式

  • 第二条同理对于Cosine,在setDerExprsetOriExpr中都需要判断cosFactor是否为空串

  • Sine类的setDerExpr中有一种情况漏写了一个*号,导致WF

圈复杂度和代码行差异:

我每次作业都有一些很细节的bug,比如说输出字符串的时候少打了一个*号,真是太致命了,代码行和圈复杂度都没有什么区别(上面有图),主要还是要细心!细心!!细心!!!

4 hack策略

这三次作业我只在最后一次作业中hack了别人,我没有自动测评机,只能采用手动构造样例的方式,将自测时候构造的一些数据来hack别人,然后就是读代码(虽然也读不太懂),还有就是在在交作业期间水群大佬提供的一些帮助debug的测试数据。

 

5 重构经历总结

通过类图和/度量数据来对比重构前和重构后的程序结构

第一次重构前 vs 重构后

第一次重构前:

重构后(第二次重构前):

第二次重构后:

第一次重构前和第一次重构后对比,增加了运算符的符号类,同时把预处理的部分抽象成一个类,降低了和表达式处理的耦合。但总体来说,没有将每种运算符和每种类型的因子划分成一个个不同的类,而是集合在一起,增加了方法中的if分支,使得代码整个很臃肿。

第三次作业又在第二次作业的基础上重构了但在TermExpression类上还是复用了一部分第二次作业的代码,所以总体来说第三次作业的工作量还是较第二次小了很多。

6 第一单元心得体会

第一单元真的很窒息,由于我本身对Java就很不熟悉,再加上假期没有做pre,导致前三周我的作业都完成的十分匆忙,而且基本上都是在用面向过程的思想在实现,导致每一次的“迭代开发”都变成了重构,直到第三次作业才开始慢慢地摸到门道。

三次作业里面后两次我都没有做优化(因为实现基本功能对我来说已经很难了),导致性能分很低,而且即使没有优化,提交上去的作业还是有很多bug,让我真的很烦躁,有很多bug都是由于粗心或者简单的复制粘贴导致的,所以看到强测得分很低的时候就会开始懊恼自己当时为什么没有再仔细检查一下(拍桌)以及为什么不会写自动评测机(爬)。

这次作业感觉只是OO恐怖面目的冰山一角,希望自己也能按时完成下一单元的电梯作业叭,再少些bug,多对过程进行点对点的检查,而且要面向三次作业进行架构,不想每次重构了(哭)。

posted @ 2021-03-27 11:37  哆哆啦  阅读(187)  评论(1编辑  收藏  举报