BUAA OO 第一单元总结
一、基于度量分析程序结构
-
类的属性个数、方法个数分析
类名 属性个数 方法个数 MainClass 0 1 Parser 2 10 Lexer 3 5 Expr 1 17 Term 2 16 Factor(Interface) 0 2 Number 1 8 Var 1 5 Exp 1 6 Func 4 6 - 由表可知,
Expr
类与Term
类中的方法数偏多,可通过建立工具类改进。
- 由表可知,
-
代码规模分析
- 项目总规模800+lines,其中
Parser
、Expr
、Term
类代码规模占主要部分。
- 项目总规模800+lines,其中
-
复杂度分析
-
方法复杂度
- 其中
CogC
(认知复杂度) 描述理解一段代码的难度,ev(G)
(基本圈复杂度) 描述一段代码的非结构化程度,iv(G)
(设计复杂度) 描述模块间的耦合程度,v(G)
(圈复杂度) 描述一段代码结构的复杂程度。 - 由图分析,方法复杂度较低,可读性较好。
- 其中
-
类复杂度
- 其中
OCavg
代表类的方法的平均循环复杂度,OCmax
代表类的方法的最高循环复杂度,WMC
代表类的总循环复杂度。 - 由图分析,
Parser
、Expr
、Term
类复杂度较高,应考虑建立工具类方法进行优化以降低复杂度。
- 其中
-
-
UML类图
- 其中,红色字体方法为核心方法,绿色背景为 hw 2 新增内容,蓝色背景为 hw 3 新增内容。
Main
类负责读入自定义函数以及输入字符串表达式,并将处理(删除空白符)后的字符串传给 LexerLexer
类负责对输入的字符串进行词法分析,将其分为不同的 token 并通过 next() 方法进行移动,通过 peek() 方法访问当前 tokenParser
类负责输入表达式的解析,其中还包含 parseTerm() 方法 与 parseFactor() 方法分别对项与因子进行解析Expr
类负责管理Terms,并实现了表达式与因子相乘,表达式的展开与化简以及求导等方法Term
类负责管理Factors,并实现了项与项相乘,项展开为表达式以及项的化简和求导等方法Factor
为接口,并声明了toString方法和clone方法,由number
、Var
、Exp
、Expr
类实现。Number
类实现Factor接口,为常数因子Var
类实现Factor接口,为变量因子(幂函数)Exp
类实现Factor接口,为指数因子Func
类存储函数名、函数形参自变量、函数表达式等,并通过MainClass中的funcList访问
二、架构设计
- 第一次作业要求实现包含±*^的单层括号表达式的展开
- 解析表达式得到表达式树,对每一项进行展开与化简,输出拼接得到新的不含括号的表达式。
- 将每一项化为标准形式
a * x ^ b
,然后根据系数相等合并同类项后进行输出。
- 第二次作业要求实现嵌套括号、指数因子、自定义函数
- 发现第一次化简不到位,可能出现多余的 ‘+’ 号,于是新增
easyExpr()
方法对化简后的表达式进行进一步的化简。 - 发现第一次的展开并不适用于多层括号,于是每解析一个表达式返回其展开后的表达式,对应新增方法
expandExpr()
。 - 新增指数因子,新增
Exp
类实现Factor
接口,同时实现指数相乘的方法,指数因子的指数存为表达式。 - 新增自定义函数因子,新增
Func
类,用于存储函数名、函数形参自变量、函数表达式;在调用时,对形参进行字符串替换,得到自变量x
的表达式,然后将该字符串进行表达式解析,返回化简表达式。 - 最简项的变化:由
a * x ^ b
->a * x ^b [* exp(c)]
。
- 发现第一次化简不到位,可能出现多余的 ‘+’ 号,于是新增
- 第三次作业要求实现自定义函数中嵌套已定义函数、表达式求导
- 由于自定义函数调用时进行字符串替换,所以自定义函数中嵌套已定义函数不影响原自定义函数的调用过程,故此处无需修改。
- 而对于表达式求导,新增对于标准项的求导方法
derive()
,先将表达式进行化简,然后对每一项求导并合并,对新的表达式再进行一次化简,返回新的最简表达式。
- 最终设计在新迭代情景上的可扩展性
- 支持自定义函数中包含求导因子,支持求导因子的嵌套
三、个人bug分析
-
第一次作业中
- 自测时发现化简消项后若项数为0,则会输出为空串不符合要求,于是对这种情况进行了特判,强制返回“0”。
- 而由于构造数据不完善,导致在将项展开为表达式时,若项中含两个以上表达式因子,在进行表达式相乘后未及时将得到结果存入循环结果表达式,仅会返回第一个表达式与最后一个表达式相乘的结果,导致在强测和互测中出现bug。
-
第二次作业
- 自测时发现表达式相乘时其中项的符号会变化导致结果错误,究其原因是在对符号进行调整时直接改变了原项的符号值,发现问题后采用深拷贝解决;
- 由于先前设计将符号放在了项所在的层,即将所有整数视为无符号整数,故在读取exp指数部分的因子时无法读取到负整数,导致解析错误;
- 同样的错误还发生在函数调用解析实参因子时,同样无法正确读取负整数因子。
-
第三次作业
- 未发现bug
四、互测小结
- 首先是将自己的易错样例进行提交,有效性较差,仅测出少部分bug;
- 然后对同房间成员的代码进行阅读与测试,能够发现其中部分逻辑不严谨之处,最终设计出相应的测试用例进行hack,成功率较高。
五、心得体会
-
先导课带来的帮助
- Git的使用
- OO工具链的使用
- Java编程基础
- 面向对象的基本思想
-
个人优化
- 先前代码耦合度过高,按照逻辑对部分方法进行拆分,使得方法复杂度与类复杂度降低
- 化简方面:将非全负项表达式的首个正号化掉,使得输出更加简短
-
总结与反思
- 通过本单元的学习,对面向对象的基本思想如封装、接口等有了更深的体会,同时在对表达式的解析中体会到了递归下降思想的高深之处。虽然在可扩展性上进行了一定的优化,但是一直未进行支持多变量的代码重构,除此之外,指数表达式的化简也并未做到最简,希望将来能有好的思路解决这一问题。