BUAA-OO-Uint1总结

本文讲述了作者在BUAA-OO-Unit1项目中的学习过程,涉及架构设计、自定义函数处理、求导、度量分析、Bug修复及优化策略,强调了优秀架构在代码组织和性能提升中的重要性。
摘要由CSDN通过智能技术生成

BUAA-OO-Unit1总结

一、架构设计体验

hw1

在这里插入图片描述

  • 通过Processor预处理,去除空格、合并符号、将乘方拆分为多个x相乘的形式(例如:x^3 → x * x * x)
  • 将processor处理后的表达式输入到Lexer中,Lexer通过next()方法解析表达式,再通过peek()方法将解析出的"词"传入Parser中。
  • Parser对Lexer传出的curToken在进行分析,运用parseExpr()方法获得Expr、parseTerm()获得Term、parseFactor()获得Factor。
  • expr包中类使用HashMap返回系数、指数到Poly中进行多项式计算。
hw2

在这里插入图片描述
本次作业并不适用于第一次作业的架构,故选择重构。此次重构增加Unit类和Poly类,以替代Hashmap来进行表达式的计算和简化。

Unit类:

a ∗ x b ∗ e x p ( ( ∑ i = 1 n a i ∗ x i b ∗ e x p ) ) a*x ^ b * exp((\sum_{i=1}^na_i*x_i^b*exp)) axbexp((i=1naixibexp))
coe为系数,exp为varible的指数,expRatio为exp()因子的表达式因子的Poly形式。

Poly类:

∑ i = 1 n U n i t i \sum_{i=1}^nUnit_i i=1nUniti
用ArrayList存储各个unit,最后用toString()方法输出最终表达式。

自定义函数处理:

专门新建一个FunctAnalysis类用于对函数表达式的处理和转换。处理主要是提取出函数名和表达式,转换是将待处理的带函数的表达式转换为无函数表达式。

FunctAnalysis
  • funcMap: 用于对函数名和函数表达式的映射。
  • parametric: 用于函数名对于函数的未知变量的映射。
FuncFactor
  • result: 经过FunctAnalysis转换后的表达式字符串。
  • expr: 将result转换为Expr类的形式。
hw3

在这里插入图片描述

任务清单
  • 求导
  • 自定义函数嵌套
  • 化简
求导

本次迭代增加了求导因子,需要对表达式进行求导运算。解决方案是添加DerivativeFactor,并在Poly中增加deprivePoly()方法,实现加减法连接的式子的求导。在Unit中增加deriveUnit(),实现乘法连接的式子的求导。

  • DerivativeFactor: 增加导数因子类,内部包含一个Expr。
public class DerivativeFactor implements Factor {
    private final Expr expr;
}
  • deprivePoly():Poly类增加求导方法。
public Poly derivePoly() {
        Poly derivative = new Poly();
        for (Unit it : units) {
            derivative.addUnitList(it.deriveUnit());
        }
        return derivative;
}
  • deriveUnit():Unit类增加求导方法。
public ArrayList<Unit> deriveUnit() {
        if (exp.equals(BigInteger.ZERO)) {
            return expZero();
        } else {
            return expNotZero();
        }
}
自定义函数嵌套

解决方案是在FuncAnalysis类中的转换函数中进行递归调用,直至消除所有函数,得到最终的表达式。

化简

这一步大概是整个项目中最繁琐的,我将其分为三种情况:

  • 系数等于1:只需要输出形如 x*exp() 的字符串。
  • 系数等于-1:只需要输出形如 -x*exp() 的字符串。
  • 其他情况:输出形如 coe * x * exp() 的字符串。

在每一种情况下,需要对exp()内部的Poly进行提取最大公因数的操作,即在Poly中增加getGcd()方法,返回一个Hashmap,包含最大公因数和提取完成后的表达式。实现:exp((2 * x-100 * x ^ 2)) 转变为 exp((x-50 * x ^ 2 )) ^ 2

二、度量分析

度量指标:

指标说明
OCavg类的平均操作复杂度
OCmax类的最大操作复杂度
WMC类的加权方法复杂度
CogC方法的认知复杂度
ev(G)方法的基本圈复杂度,衡量程序非结构化程度。
iv(G)方法的设计复杂度。模块和其他模块的调用关系。软件模块设计复杂度高意味模块耦合度高,这将导致模块难于隔离、维护和复用。
v(G)方法的独立路径的条数
度量类的属性个数、方法个数、类总代码规模分析
hw1

在这里插入图片描述
hw1类的方法数和属性数较少,个别类的规模较大,主要是在预处理和多项式计算,需要简化。

hw2

在这里插入图片描述
hw2与hw1相比,类的代码规模显著减少,代码更加简洁,只是自定义函数分析类的代码简洁方面仍待改进。

hw3

在这里插入图片描述
hw3代码规模与之前相比增大了许多,主要是在Unit类的最后表达式简化方面写的太多了,由于是对字符串的繁琐遍历,代码简洁性上面显著降低了。

类的内聚和相互间的耦合情况分析
hw1

在这里插入图片描述
hw1的类的最大操作复杂度以及类的平均操作复杂度都控制的相对较好,但在类的加权方法复杂度上没有掌控好,有些类中针对表达式的解析方法过于复杂,需要改进。

hw2

在这里插入图片描述
hw2相较于hw1,类的最大操作复杂度以及类的平均操作复杂度都显著降低了,类的加权方法平均复杂度也降低了,但在个别类的如Unit类的方法复杂度仍然较高,主要原因是对于表达式的简化上需要对字符串进行复杂处理。

hw3

在这里插入图片描述
hw3类的最大操作复杂度和类的平均操作复杂度都相对较好,但在Unit类简化字符串的方法上复杂度较高。

方法复杂度分析
hw1在这里插入图片描述

由于没有使用好的设计架构,hw1的方法复杂度都很高。

hw2

在这里插入图片描述
hw2对hw1进行了重构,使用了较为优化的架构,所以方法的复杂度相对于hw1显著减少,但由于对表达式的简化操作仍然较为繁琐,所以在个别方法上复杂度较高。

hw3

在这里插入图片描述
hw3延续了hw2的架构,所以在方法复杂度上变化不大,复杂度较高的方法主要还是在对字符串处理上。

三、Bug分析

bug案例

hw2出现bug,bug原因是对于指数因子exp(…)括号内是否需要加括号的处理上。例如:exp((exp(x)+exp(x ^ 2))),会错误输出exp(exp(x)+exp(x ^ 2)),问题出在正则表达式的识别上:

Pattern pattern = Pattern.compile("exp\\((.*?)\\)");
Matcher matcher = pattern.matcher(str);

我误认为正则表达式能够将exp(x)+exp(x ^ 2)识别为一个表达式,但其会将其识别为一个exp()因子,导致将括号错误消除了。解决方案是进行括号识别:

if (str.charAt(0) == 'e') {
            int count = 0;
            for (int i = 0; i < str.length(); i++) {
                if (str.charAt(i) == '(') {
                    count++;
                } else if (str.charAt(i) == ')') {
                    count--;
                } else if ((str.charAt(i) == '*' || str.charAt(i) == '+'
                        || str.charAt(i) == '-') && count == 0) {
                    return false;
                }
            }
            return true;
        } else {
            return false;
 }
对比分析

出现bug的方法:

Pattern pattern = Pattern.compile("exp\\((.*?)\\)");
Matcher matcher = pattern.matcher(str);

未出现bug的方法:

if (str.charAt(0) == 'e') {
            int count = 0;
            for (int i = 0; i < str.length(); i++) {
                if (str.charAt(i) == '(') {
                    count++;
                } else if (str.charAt(i) == ')') {
                    count--;
                } else if ((str.charAt(i) == '*' || str.charAt(i) == '+'
                        || str.charAt(i) == '-') && count == 0) {
                    return false;
                }
            }
            return true;
        } else {
            return false;
 }

显然出现bug的方法代码行少于未出现bug的方法,圈复杂度也显著小于未出现bug的方法。但出现bug也是因为对多种情况的考虑较少,所以未出现bug的方法,个人认为是比较简洁的了。

四、hack策略

自动化评测机评测

优点:简便,测试数量多,可测出的bug多
缺点:对于数据生成程序的要求较高,自动生成数据的强度不容易控制,容易出现数据较弱或不合法的情况。

结合被测程序的代码设计测试用例

优点:正确率高
缺点:难度大,耗费时间长

五、优化

parseFactor()方法优化

对不同Factor进行分类,将特定Factor的解析用其他方法来替代,如识别到的是NumFactor,则进入parseNumFactor()函数进行解析,返回Factor。优势在于显著降低了方法的复杂度,方法间的独立性高,容易debug。

多项式计算优化

使用Unit类和Poly类,Unit代表一个计算单元,Poly是Unit的集合,代表多项式。Unit中有addUnit()和mulUnit()方法,Poly中有addPoly()和mulPoly()方法。在计算多项式的时候,addPoly()方法能够调用addUnit()方法进行计算单元之间的加法,多个计算单元的加法也就完成了多项式的加法。这样优化能够保证代码的简洁性和正确性。

六、心得体会

Unit1的学习过程是比较困难的,一开始对面向对象编程并没有什么概念,所以在hw1的架构设计上比较面向程序,类和方法写的又臭又长,debug难度很大,而且可扩展性非常差。在hw2痛定思痛,我对自己的代码进行了重构,毫无疑问,推倒重来是最痛苦的。但很庆幸,自己的重构是相对成功的,所以hw3没有花费多久就写完了。由此可见,优秀的架构真的能够减少很多工作量。通过Unit1我感觉自己对面向对象编程的陌生减少了许多,对代码的写作也更加偏向于设计一个优秀的架构,而不是追求一个精妙的函数解决问题。

七、未来方向

我认为可以增加一点对代码性能的要求。

  • 23
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值