2021 OO 第一单元总结(表达式求导)
2021 OO 第一单元总结(表达式求导)
1.作业的结构分析和度量分析
1.1第一次作业
1.1.1 作业需求
- 要求实现简单多项式的求导,支持线性运算、乘法运算以及幂函数运算。只有常数和幂函数两种因子。
1.1.2 程序框架和思路
- 第一次作业刚开始做时,还是沿用了面向过程编程的思维习惯,在Mainclass类中进行了字符串的读取和处理。在Mainclass类中实例化一个Expression的对象。通过While循环以及使用正则表达式按顺序逐个匹配字符串中的幂函数和常数项。
- 表达式类Expression只有两个属性,都为TreeMap类型,存储的元素为项。键为项的系数,值为项的指数。一个Map用来存储字符串的表达式,另一个用来存储求导后的表达式。
- Term类有两个属性,分别为系数和指数。实现了乘法方法以将一个项中的全部因子都相乘。
1.1.3 度量分析
-
类的属性个数:Mainclass类无独有属性,表达式类Expression只有两个属性,都为TreeMap类型,Term类有两个属性,分别为系数和指数。
-
代码行数
![]()
由于需求较少,故此次代码长度较短
-
类的度量,分析类的内聚和耦合情况。
-
类的复杂度
![]()
可以看出Expression类和Mainclass类的复杂度较高,原因主要是因为没有面向对象编程,导致每个类的分工不够清晰独立。
-
方法复杂度
Expression类方法:
![]()
Mainclass类方法:
![]()
可以看出Expression的printout方法(也就是toString方法)的复杂度很高,原因主要在于对可以化简得项逐个进行判断,增加了方法复杂度。
Mainclass的类方法复杂度高的主要原因是将字符串的读取和处理都放在主方法中执行,使得复杂度增高。
-
类图分析此次作业
![]()
-
总体结构
Mainclass用来读入字符和分隔各个项与因子。
Expression用来存储解析出来的表达式和求导后的表达式,并用printout方法将结果以字符串形式输出。
Term用来保存项的系数和指数。
-
-
-
作业分析
- 优点:由于第一次作业的需求较少,代码行数较少,便于理解。
- 缺点:没有使用面向对象编程,使得各个类的耦合程度较高,在后续需求增加时可能会重构。Mainclass的方法与其他类的耦合度较高。
1.1.4 Bug分析
- 这次作业强测和互测没有出Bug。
- 在互测时hack出了别人一个微小的bug(其在三个正负号相连时,对正负判断失误)。
1.2 第二次作业
1.2.1作业需求
- 第二次作业增加了可以带指数的三角函数因子、表达式因子,并且表达式因子可以多层嵌套。
1.2.2 程序框架和思路
- 这次作业增加了括号嵌套之后,字符串复杂度也大大增高。如果继续沿用作业一的框架和思路则难度很大。因此对框架做了重构,转变为面向对象编程。参考了指导书中的提示并采用化整为零的思想。
- 对于每一种因子(常数、幂函数、三角函数、表达式因子),建立类。
- 对于每一种函数组合规则(乘法、加减法),建立类
- 每一种因子都继承自抽象类Factor,实现了求导derivative方法和toString方法。
- 函数组合规则实现了求导接口,能够分别对乘号连接成的项/加减号连接成的表达式求导。
- 通过上述两种类及其求导接口/方法,把整个表达式构建为树结构,进行链式求导。
- 使用了工厂模式,根据输入工厂的字符串格式不同,调用工厂中不同的构造函数,并返回不同类型的因子。
1.2.3 度量分析
-
类的属性个数:所有类的属性个数不超过一个。
-
类的方法个数:2~4个
-
总代码行数
![]()
总长为500,工厂StringFactory的代码行数较长。
-
类的度量,分析类的内聚和耦合情况。
-
可以由下图看出StringFactory、Mainclass、MultDeriTool类的复杂度较高
![]()
-
下图是StringFactory的方法复杂度图
![]()
可以看出生成表达式因子的方法expressionCreator复杂度较高,主要原因是要进行括号匹配和正负号的读取与处理。对字符串的处理比较琐碎,在编写程序时需要边写边检验。
-
下图是MultDeriTool的方法复杂度图
![]()
我在这个方法中实现了乘法法则的求导,需要对项的因子遍历求导,再遍历把其他因子与其相乘。过程比较复杂增加了复杂度。
-
类图分析此次作业
![]()
-
-
代码优点:面向对象分工清晰。每个类完成的方法在定义上相似,只用逐个填充。在调用Expression的构造函数之前先将空格去除,将多个相邻的正负号化为一个。这样正则表达式复杂度降低。使用了工厂模式,使得生成因子在一个工厂类方法中完成。
-
代码缺点:直接去除空格会影响后面作业三的格式判定。求导方法返回字符串类型,使得后面化简不方便。
1.2.4 Bug分析
- 在强测和互测中没有测出bug。
- 在hack别人时找到一个微小bug(当只有一个常数求导时,未输出0。这应该是过度优化导致的bug)。
1.3 第三次作业
1.3.1 作业需求
- 这次作业主要增加了三角函数括号中含有因子的形式,以及对格式的检查
1.3.2 程序框架和思路
- 第三次作业沿用了第二次作业的思路和框架。
- 求导部分:由于只增加了三角函数括号中含有因子的形式,故需要改动的只有sin和cos函数类的derivative求导方法,使其以字符串的形式返回链式求导结果即可。
- 格式检查部分:我将格式错误分为了两大类。一类是由空格出现的位置不合法导致的错误,另一类是其他类型的错误(如出现其他字符、括号不匹配、指数大小不合法等)。
- 对于由空格出现位置不合法导致的错误,我单独写了一个WhiteSpaceError类来判断各种由空白符导致出错的情况。并且在Mainclass的main方法读入字符串后,优先进行空白字符格式检查,若有错误则输出错误信息并退出;若无空白符导致的格式错误则将所有的空白符去掉后,解析表达式。这样对表达式预处理使得后续解析表达式要方便一点。
- 对于其他类型的错误,在StringFactory工厂生成相应对象时即可一并检查,若传入的字符串不能匹配格式导致无法生成相应格式的函数,则报错并退出。
1.3.3 度量分析
-
类属性个数:cos和sin类因为增加了内层因子,所以属性为指数和因子两个。其余类的属性均不超过1个。
-
总代码行数
![]()
可以看出工厂StringFactory的代码函数较多,在里面进行了表达式、项和各种因子的解析。
-
类的度量,分析类的内聚和耦合情况。
-
类的复杂度
![]()
由上图可知,Mainclass、MultDeriTool和StringFactory类的复杂度较高。 -
MultDeriTool类未作修改,其方法复杂度参见作业二。
-
StringFactory的方法中,可以看出生成sin、cos类的方法复杂度有所增加,因为需要解析出括号内的因子。
![]()
-
-
类图分析
![]()
- 优点:格式检查分为两个阶段进行,先用一个类方法检查空白字符,在检查其他错误。各个类分工明确,适合增量开发。架构整体比较清晰。
-
缺点:对于空白字符格式的验证,我是在WhiteSpaceError类里面一一枚举的WRONG_FORM,这很有可能出现漏掉某种情况的现象。这一点在强测中就出现了bug。
1.3.4 Bug分析
- 没有在互测中出现bug
- 在强测中错了一个关于空格的点(sin(+ 030)),这是前面缺点中所说的,漏掉某种空白字符出错的情况导致的。
2. 发现别人Bug采用的策略
2.1 第一次作业
- 手动构造测试数据,没有使用自动评测。
- 主要测试对多个正负号号相邻时的检验。
- 尝试阅读别人的实现方法,从中分析Bug
2.2 第二次作业
- 加入了嵌套规则,测试重点放在括号嵌套和三角函数
- 检查ta是否有过度优化导致的Bug
- 手动构造
2.3 第三次作业
- 主要判断别人的格式判断是否正确,但是评测机有要求提交的数据要合法
- 找到一些关于WRONG FORMAT的错误但是无法提交hack。
3. 重构经历总结
-
我第一次作业是以面向过程的思维来编程的,所以到第二次作业就发现很难沿用之前的思路再写下去。因此在写第二次作业进行了重构。
-
重构时我主要依照指导书的提示:
-
对于每一种因子(常数、幂函数、三角函数、表达式因子),建立类。
-
对于每一种函数组合规则(乘法、加减法),建立类。
-
每一种因子都继承自抽象类Factor,实现了求导derivative方法和toString方法。
-
函数组合规则实现了求导接口,能够分别对乘号连接成的项/加减号连接成的表达式求导。
-
通过上述两种类及其求导接口/方法,把整个表达式构建为树结构,进行链式求导。
-
-
使用了工厂模式,使得工厂能够根据不同格式的输入字符串,生成不同的函数类。
-
由于进行了重构,第三次作业只用在第二次作业的基本框架下进行sin、cos求导的完善以及格式检查。重构使得增量开发十分方便。
4. 本单元心得体会
-
感觉第一单元的难度坡度比较陡。对于不久前才接触java语言和面向对象编程思维的我来说,需要学习的知识点还很多。拿到作业后考虑不仅要有本次作业的需求,还要想想后续新的需求。并且在构建框架时就要为后续的迭代开发留有空间。这样才能尽量避免拿到新的作业需求后被迫重构。当然重构也是一种可以提升自己编程经验的方式。
-
接触了工厂模式后,在第二三次作业中运用了工厂模式。工厂模式使得生成不同类型的Factor(因子)变得方便。
-
没有使用递归下降匹配表达式的方法,可能在面对超长表达式时会有出bug的可能。
-
某些类如乘法求导类MultDeriTool、工厂类StringFactory的方法复杂度较高。
-
在第二三次作业中,都只是用了ArrayList容器,对别的容器如TreeMap、HashMap容器还没有灵活使用。
-
对二三次作业没有进行特别优化,使得性能分较低。
-
通过写第一单元的作业规范了我的代码风格。















浙公网安备 33010602011771号