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))
a∗xb∗exp((i=1∑nai∗xib∗exp))
coe为系数,exp为varible的指数,expRatio为exp()因子的表达式因子的Poly形式。
Poly类:
∑
i
=
1
n
U
n
i
t
i
\sum_{i=1}^nUnit_i
i=1∑nUniti
用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![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/d321211832fa4feba9d769f7f8c957fe.png#pic_center)
由于没有使用好的设计架构,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我感觉自己对面向对象编程的陌生减少了许多,对代码的写作也更加偏向于设计一个优秀的架构,而不是追求一个精妙的函数解决问题。
七、未来方向
我认为可以增加一点对代码性能的要求。