BUAA_OO_2022 第一单元总结

BUAA_OO_2022 第一单元总结

1.架构设计

架构流程:

输入 -----> 预处理 -----> 拆项建树 -----> 合并 -----> 化简 -----> 输出

主体架构:

我的第一单元三次作业的核心思路都是建立二叉树,以操作符为结点,以两个操作数作为子节点。

以“ x ^ 3 + ( x ^ 2 + x + 1) * ( x + 2 ) ”为例,进行拆项建树:

image

  • 第一次作业的操作符只有 + 和 *,叶结点也只能为 x 或 常数,拆项一拆到底。
  • 第二次作业,操作符不变,但是叶结点扩充至表达式因子、三角函数因子等 。
  • 第三次作业中操作符增加了 **, 解决幂指数较大递归多次的问题。

合并时,自底向上,通过每个操作符,将子结点链接起来。

在第一次作业中,Variable类和Constant类聊胜于无,功能集中在Expression类和Operator类中(面向对象,面向过程!)

image

在第二次作业中,正式步入面向对象。对于求和函数和自定义函数,采用了预处理替换的方式,简单直接不利于后续迭代开发

image

在第三次作业中,将自定义函数和三角函数都作为因子处理,并在相应的类中,代换后作为表达式处理,建立分支树。

image

细节实现:

  • 建树

    采用递归的方式建树,依次寻找 +、*、 **三个操作符

    public void buildTree(String expr) {
    	if(getPlus(expr) > 0) {
            ...
            buildTree(expr.subString(getPlus(expr)));
        } else if(getMul(expr)) {
            ...
            buildTree(expr.subString(getMul(expr)));
        }
        ...
    }
    
  • 化简:

对于以加号/减号分隔的每一项:

  • 以BigInteger类型的constant保存系数。

  • 以int类型的power保存x的幂指数。

  • HashMap<String, Integer>类型的triangle保存三角函数及其指数,之后对三角函数降幂排列

  • HashMap<String, BigInteger>类型的coefficient保存这一项。

    再通过对三角函数内部表达式降幂排列,每一项构造出如下形式。
    image

	3*x**2*-1*cos(x**2)*sin(x**2)**2
    constant = -6;
    power = 2;
    triangle.put("cos(x**2)", 1);
	triangle.put("sin(x**2)", 2);
	String term = -x**2*sin(x**2)**2*cos(x**2);
    coefficient.put(term, constant);

通过coefficient索引表达式,获取系数进行合并。

2.程序结构度量

方法复杂度:(仅列出较为重要的类)

方法 CogC ev(G) iv(G) v(G)
Function.getSpilt(String) 14.0 1.0 6.0 7.0
Sum.getSpilt(String) 14.0 1.0 6.0 7.0
Simplify.getSplit(String) 14.0 1.0 6.0 7.0
Expression.getPower(String) 9.0 3.0 5.0 6.0
Expression.buildBranch(String) 8.0 1.0 9.0 9.0
Simplify.merge() 8.0 1.0 5.0 5.0
Expression.getPlus(String) 8.0 4.0 5.0 6.0
Operator.polyMul(String, String) 7.0 1.0 5.0 5.0
Constant.Constant(String) 7.0 1.0 4.0 4.0
Triangle.Triangle(String, HashMap) 7.0 1.0 6.0 6.0
Simplify.triangleSort(HashMap) 7.0 1.0 5.0 5.0
Simplify.powerSort() 6.0 1.0 4.0 4.0
Expression.getMul(String) 6.0 4.0 3.0 4.0
Expression.getPos(int, String) 4.0 1.0 3.0 4.0
Simplify.getPower(String) 3.0 3.0 2.0 3.0
Expression.powerType(int, String, int) 3.0 1.0 4.0 4.0
Operator.getAnswer() 3.0 1.0 4.0 5.0
Triangle.getPower(String) 2.0 2.0 2.0 2.0
Sum.Sum(String, HashMap) 1.0 1.0 2.0 2.0
Constant.getBase(String) 1.0 1.0 2.0 2.0
MainClass.main(String[]) 1.0 1.0 2.0 2.0
Constant.getPower(String) 1.0 1.0 2.0 2.0
Function.Function(String, HashMap) 1.0 1.0 2.0 2.0
Average 4.09 1.19 2.89 3.12

相较于递归下降方法,我建树的思路的方法复杂度和类复杂度的数值都较高,因为在建树的过程中,使用了很多的if-else语句去进行分支判断,从而判断拆分的每一项属于哪种类型或因子。

类复杂度:

OCavg OCmax WMC
Constant 1.83 4.0 11.0
Expression 4.1 10.0 41.0
Function 2.4 7.0 12.0
MainClass 1.5 2.0 3.0
Operator 3.33 7.0 20.0
Simplify 4.9 15.0 49.0
Sum 2.4 7.0 12.0
Term 1.0 1.0 4.0
Triangle 2.2 6.0 11.0
Variable 1.0 1.0 4.0
Average 2.93 6.0 16.7

​ 对于自定义函数、三角函数类、括号类,需要紧密依赖Expression类,继续拆项,出现了和表达式类自圈的情况,类似于状态机如下图。导致这几个类的耦合度很高,代码复杂度也很高。

image

类图

image

3.个人bug分析

第一次作业

编号 bug描述 bug位置 总行数:未修复/修复 复杂度:未修复/修复
1 化简处理时,直接replace("+1", ""),忽略11x的情况 表达式类 619/625 2.46/2.48
2 添加结点出错,导致ArrayList索引越界 表达式类 619/625 2.46/2.48
3 括号处理异常,会把-(x-x)处理为(-x-x) 表达式类 619/625 2.46/2.48
4 乘法处理错误,两个项相乘,中间直接加上了* 操作符类 619/625 2.46/2.48

第一次作业的bug主要是出在括号项的处理上,括号项的乘法以及括号项前的正负号没有得到很好的处理。

第二次作业

编号 bug描述 bug位置 总行数:未修复/修复 复杂度:未修复/修复
1 如f(x)=x**2,f(-2)带入时未给2带上括号 主类 606/614 3.24/3.24
2 未注意幂有前导0的情况,直接使用了replace("x**0", 1) 主类 606/614 3.24/3.24
3 误以为幂次不超过10,使用replace("**1", "")的方式替换 化简类中 606/614 3.24/3.24

第二次作业的bug主要是出在化简和自定义函数的处理上。化简时,草率的将x0和x1两种情况直接化简,未考虑前导零以及幂指数超过10的情况。自定义函数bug的修复较大,在三角函数类中需要处理冗余的括号以及内部化简,之前的设计是只处理内部常数的幂函数的化简,修复后三角函数内部作为expression处理。

第三次作业

编号 bug描述 bug位置 总行数:未修复/修复 复杂度:未修复/修复
1 未考虑s < e的情况 求和函数类 791/795 2.93/2.95
2 求和函数中-i的处理有误,负号会被直接忽略 求和函数类 791/795 2.93/2.95
3 输出格式有误,sin(sin(1)*cos(1))缺少括号 三角函数类 791/795 2.93/2.95

​ 第三次作业的问题主要是忽略了求和函数 s < e的情况。

总体分析

​ 总体而言,行数和圈复杂度的对比,也说明程序的bug主要还是集中在一些细节上。实际上,三次作业的大部分bug,都是在不断优化(卷性能分)中产生的,但是一些细节没有考虑周到,导致了bug的出现。

4.互测策略

​ 在对自己的作业进行测试时,主要归纳出了以下几个测试方向

  • 正负号。

  • 空格、缩进。

  • 幂指数前导0。使用如x**002这类数据测试。

  • 大数。求和函数中的s、e容易被忽略。

  • 求和函数s > e。这是指导书中的细节,规定了s > e 时,求和结果应为0。

  • 求和函数的i替换。求和函数对表达式中的i替换时,会把sin中的i也替换掉。

    此外,如f(x)=x**2,f(-2)这种类型也很容易出现问题。但是在互测中不允许出现自定函数,这类情况只能用于自测。

5.心得体会(自刀)

  • 不要刻意卷性能分!!!绝大部分的bug都是因为卷性能分产生的(为了20分性能分丢80分功能分显然亏本)。

  • 拒绝面向对象!!!第一次作业,成功被我写成了面向过程,代码可读性以及结构的鲁棒性极差。(不能理解为什么面向过程的代码复杂度最低)

  • 为代码的后续迭代做好准备!!!在第二次作业中,我将求和函数和自定义函数在预处理时替换,并将幂函数和三角函数放在用一个类中。在第二次作业限制较多的情况下,这样做完全是可以的,但是却不利于后续迭代,导致第三次作业几乎是完全重构。

  • 遵循高内聚低耦合的原则!!!耦合度高的代码块在debug时堪称灾难~~~代码的高内聚做的也不好,经常性一个方法几十行,导致方法的复杂度很高,debug牵一发动全身。之后的设计还是应该细致拆分,高内聚低耦合!!!

posted @ 2022-03-25 15:16  WIT23  阅读(48)  评论(2编辑  收藏  举报