OO Unit 1 分析与总结
一、基于度量的程序分析
1.最终框架以及各个类的度量
以下是我借助IDEA里的插件,自行调整的类图,我将其分为了Data,Process,Calculate,Simplify四种类型,具体的分析将在迭代过程介绍中提及。
Data:
我的Data类主要都是数据对象,其中包括作业中提及的表达式、项以及各种因子,而各个对象中又包括它们所必须的各种数据和方法。
Process:
Process则是从读入字符串开始一直到解析完成表达式为止,其中使用了递归下降方法,通过Lexer、Parser讲表达式解析成Tokens。
Calculate:
Calculate可以说是我整个代码中的两个重点之一,其中一个重点我认为是Parser部分,而这个计算的Poly和Mono类则是另一个重点。
Simplify:
Simplify是简化部分,我单独写了两个方法,其中BracketRemover是用于去除不必要的括号,Delete是用于删除连续的加减号的。
2.类的内聚和相互间的耦合情况分析
为了分析类的内聚和相互间的耦合情况,我借助了工具MetricsReloaded对我第三次代码进行了分析:
代码圈复杂度
Method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
BracketRemover.isRemovable(StringBuilder, int, int) | 5 | 1 | 3 | 8 |
BracketRemover.removeUnnecessaryBrackets(String) | 11 | 1 | 6 | 6 |
Cusfunction.Cusfunction(String, ArrayList) | 0 | 1 | 1 | 1 |
Cusfunction.analysis(int) | 0 | 1 | 1 | 1 |
Cusfunction.getCount() | 0 | 1 | 1 | 1 |
Cusfunction.getExpression() | 0 | 1 | 1 | 1 |
Cusfunction.getNameOfFunc() | 0 | 1 | 1 | 1 |
Cusfunction.getVars() | 0 | 1 | 1 | 1 |
Cusfunction.putVars() | 3 | 1 | 3 | 3 |
Delete.deleKong(String) | 7 | 5 | 3 | 5 |
Delete.delete(String) | 18 | 2 | 10 | 11 |
Derivative.Derivative(Expression) | 0 | 1 | 1 | 1 |
Derivative.creatNew() | 0 | 1 | 1 | 1 |
Derivative.getExpression() | 0 | 1 | 1 | 1 |
Derivative.getIndex() | 0 | 1 | 1 | 1 |
Derivative.getResult() | 0 | 1 | 1 | 1 |
Derivative.getType() | 0 | 1 | 1 | 1 |
ExpFactor.ExpFactor(Expression, BigInteger) | 0 | 1 | 1 | 1 |
ExpFactor.creatNew() | 0 | 1 | 1 | 1 |
ExpFactor.getExpression() | 0 | 1 | 1 | 1 |
ExpFactor.getIndex() | 0 | 1 | 1 | 1 |
ExpFactor.getResult() | 0 | 1 | 1 | 1 |
ExpFactor.getType() | 0 | 1 | 1 | 1 |
ExpFactor.setExpression(Expression) | 0 | 1 | 1 | 1 |
ExpFactor.toString() | 0 | 1 | 1 | 1 |
Exponent.Exponent(Expression, BigInteger) | 0 | 1 | 1 | 1 |
Exponent.creatNew() | 0 | 1 | 1 | 1 |
Exponent.getExpression() | 0 | 1 | 1 | 1 |
Exponent.getIndex() | 0 | 1 | 1 | 1 |
Exponent.getResult() | 0 | 1 | 1 | 1 |
Exponent.getType() | 0 | 1 | 1 | 1 |
Exponent.setExpression(Expression) | 0 | 1 | 1 | 1 |
Exponent.toString() | 0 | 1 | 1 | 1 |
Expression.Expression(ArrayList, ArrayList) | 0 | 1 | 1 | 1 |
Expression.changeFactor(ArrayList, ArrayList) | 1 | 1 | 2 | 2 |
Expression.creatNew() | 1 | 1 | 2 | 2 |
Expression.getExpression() | 0 | 1 | 1 | 1 |
Expression.getIndex() | 0 | 1 | 1 | 1 |
Expression.getOps() | 0 | 1 | 1 | 1 |
Expression.getResult() | 4 | 1 | 3 | 3 |
Expression.getTerms() | 0 | 1 | 1 | 1 |
Expression.getType() | 0 | 1 | 1 | 1 |
Expression.toString() | 1 | 1 | 2 | 2 |
Lexer.Lexer(String) | 0 | 1 | 1 | 1 |
Lexer.lex() | 26 | 19 | 18 | 20 |
Lexer.move() | 0 | 1 | 1 | 1 |
Lexer.notEnd() | 0 | 1 | 1 | 1 |
Lexer.now() | 0 | 1 | 1 | 1 |
Main.main(String[]) | 3 | 1 | 3 | 3 |
Mono.Mono(HashMap<BigInteger, BigInteger>) | 0 | 1 | 1 | 1 |
Mono.add(Mono) | 12 | 1 | 8 | 8 |
Mono.derive() | 5 | 4 | 2 | 4 |
Mono.equal(Mono) | 3 | 3 | 2 | 3 |
Mono.getExpr(String, int) | 44 | 1 | 20 | 20 |
Mono.isZero() | 3 | 3 | 2 | 3 |
Mono.mul(Mono) | 7 | 1 | 4 | 4 |
Mono.pow(BigInteger) | 3 | 3 | 3 | 4 |
Mono.simplily() | 3 | 1 | 3 | 3 |
Mono.sub(Mono) | 12 | 1 | 8 | 8 |
Number.Number(String) | 0 | 1 | 1 | 1 |
Number.creatNew() | 0 | 1 | 1 | 1 |
Number.getExpression() | 0 | 1 | 1 | 1 |
Number.getIndex() | 0 | 1 | 1 | 1 |
Number.getResult() | 0 | 1 | 1 | 1 |
Number.getType() | 0 | 1 | 1 | 1 |
Number.toString() | 0 | 1 | 1 | 1 |
Parser.Parser(Lexer, ArrayList) | 0 | 1 | 1 | 1 |
Parser.getIndex() | 1 | 1 | 2 | 2 |
Parser.parserExpr() | 6 | 1 | 6 | 6 |
Parser.parserFactor() | 23 | 6 | 19 | 19 |
Parser.parserTerm() | 4 | 1 | 5 | 5 |
Parser.paserVarfactor(String) | 11 | 3 | 6 | 6 |
Poly.Poly(HashMap<Poly, Mono>) | 0 | 1 | 1 | 1 |
Poly.add(Poly) | 43 | 6 | 14 | 24 |
Poly.creatNew() | 4 | 1 | 3 | 3 |
Poly.derive() | 5 | 4 | 3 | 4 |
Poly.equal(Poly) | 48 | 5 | 11 | 21 |
Poly.getExpr(int) | 23 | 1 | 9 | 11 |
Poly.mul(Poly) | 40 | 7 | 11 | 16 |
Poly.pow(BigInteger) | 3 | 3 | 3 | 4 |
Poly.sub(Poly) | 43 | 6 | 14 | 24 |
Term.Term(ArrayList, Token) | 0 | 1 | 1 | 1 |
Term.changeFactor(ArrayList, ArrayList) | 13 | 1 | 10 | 10 |
Term.creatNew() | 1 | 1 | 2 | 2 |
Term.getExpression() | 0 | 1 | 1 | 1 |
Term.getIndex() | 0 | 1 | 1 | 1 |
Term.getResult() | 3 | 1 | 3 | 3 |
Term.getType() | 0 | 1 | 1 | 1 |
Term.toString() | 3 | 1 | 3 | 3 |
Token.Token(Type, String) | 0 | 1 | 1 | 1 |
Token.getContent() | 0 | 1 | 1 | 1 |
Token.getType() | 0 | 1 | 1 | 1 |
Token.toString() | 0 | 1 | 1 | 1 |
Var.Var(BigInteger, String) | 0 | 1 | 1 | 1 |
Var.creatNew() | 0 | 1 | 1 | 1 |
Var.getExpression() | 0 | 1 | 1 | 1 |
Var.getIndex() | 0 | 1 | 1 | 1 |
Var.getResult() | 0 | 1 | 1 | 1 |
Var.getType() | 0 | 1 | 1 | 1 |
Var.toString() | 0 | 1 | 1 | 1 |
不难发现,其中耦合度较高的是以下一些方法:
Method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
Poly.equal(Poly) | 48.0 | 5.0 | 11.0 | 21.0 |
Mono.getExpr(String, int) | 44.0 | 1.0 | 20.0 | 20.0 |
Poly.add(Poly) | 43.0 | 6.0 | 14.0 | 24.0 |
Poly.sub(Poly) | 43.0 | 6.0 | 14.0 | 24.0 |
Poly.mul(Poly) | 40.0 | 7.0 | 11.0 | 16.0 |
Lexer.lex() | 26.0 | 19.0 | 18.0 | 20.0 |
Parser.parserFactor() | 23.0 | 6.0 | 19.0 | 19.0 |
Poly.getExpr(int) | 23.0 | 1.0 | 9.0 | 11.0 |
Delete.delete(String) | 18.0 | 2.0 | 10.0 | 11.0 |
其中的确我在编写这些方法时由于不停的迭代,某些方法可能需要实现的功能过于复杂,但是我还是选择直接扩充而不是去分解这个方法,最终导致一些方法的“体量”不断地增大,同时还有一些比如“判断是否为0”,“克隆新的Poly、Mono”这样的需要在多个方法多次利用的方法会造成耦合度比较高。
二、架构设计体验
1.第一次作业成型
Java类:
设计:
对于第一次作业,我采用了递归下降法,编写了Token、Lexer、Paser实现了递归下降解析。
而对于表达式,我的构造是实现了poly类,表达式都可以以如下的形式表示。
s
u
m
=
∑
i
=
1
n
a
∗
x
b
sum = \sum_{i=1}^na*x^b
sum=i=1∑na∗xb
于是,我的Poly类是这样的:
public class Poly {
private BigInteger num;
private HashMap<Integer,BigInteger> vars;
public Poly(BigInteger num, HashMap<Integer,BigInteger> vars) {
this.num = num;
this.vars = vars;
}
左边的key是指数,右边的value是系数,我会通过getResult函数,不断递归地将表达式转换成一个Poly类,最后再为Poly写一个输出函数即可。
2.第二次作业迭代
第二次作业可以说是富有挑战性的,以为加入了两个新的需求,一个是自定义函数,一个是exp指数函数因子。
完成后的Java类:
设计:
1.关于自定义函数:我的想法是,既然自定义函数是提前给的,那么我们可以把自定义函数当成一个表达式,经过Lexer、Parser处理成一个Expression。最后,再将其中的Var类,也就是x,y,z替换成因子即可,注意,由于这种替换是即用即换,因此需要保证是深拷贝,否则会破环自定义函数本体。
其中我认为比较重要的是Parser里增加的识别自定义函数部分比较重要,具体代码如下:
public Factor paserVarfactor(String nameOfFunc) {
for (int i = 0; i < cusfunctionArrayList.size(); i++) {
if (cusfunctionArrayList.get(i).getNameOfFunc().equals(nameOfFunc)) {
lexer.move(); //来到 (
lexer.move(); //去掉 (
ArrayList<Factor> factors = new ArrayList<>();
for (int j = 0; j < cusfunctionArrayList.get(i).getCount(); j++) {
Factor factor = parserFactor();
factors.add(factor);
if (lexer.notEnd() && lexer.now().getContent().equals(",")) {
lexer.move();
}
}
Expression temexp = cusfunctionArrayList.get(i).getExpression();
Expression expression = new Expression(temexp.getTerms(),temexp.getOps());
Expression expression1 = expression.
changeFactor(cusfunctionArrayList.get(i).getVars(),factors);
lexer.move();//去掉 )
return expression1;
}
}
System.out.println("erro:没有找到自定义函数!");
return null;
}
其中的changeFactor方法就是替换因子的部分。
2.关于exp因子。为了实现exp指数因子的加入,且保证能够在原代码的基础和思想上实现,我将基项改成了由Mono和Poly递归定义实现的类:
M
o
n
o
=
∑
i
=
1
n
a
∗
x
b
Mono = \sum_{i=1}^na*x^b
Mono=i=1∑na∗xb
P o l y = ( M o n o ) ∗ e x p ( P o l y ) Poly = (Mono)*exp(Poly) Poly=(Mono)∗exp(Poly)
具体代码如下:
public class Poly {
private HashMap<Poly, Mono> polyHashMap;
public Poly(HashMap<Poly,Mono> temPolyHashMap) {
this.polyHashMap = temPolyHashMap;
}
public class Mono {
private HashMap<BigInteger,BigInteger> vars;
public Mono(HashMap<BigInteger, BigInteger> vars) {
this.vars = vars;
}
注意,Mono的key也需要换成大数。其中的Mono也就是作业一的“Poly”了。这样我们只需要编写Poly的计算方法(其中很多可以直接使用Mono里的方法)就可以实现含exp指数函数的计算了。同时,注意一下括号合法性即可。
3.第三次作业迭代。
由于第二次作业的努力,第三次作业的迭代的任务就显得很少。
1.首先是自定义函数自定义可以使用前面定义过的函数,实际上,我自定义函数的解析和表达式解析本就是使用相同的Lexer、Paser,也就是说,我作业二就实现了这个功能,只需要把主函数改改即可实现:
ArrayList<Cusfunction> cusfunctionArrayList = new ArrayList<>();
for (int i = 0;i < n;i++) {
cusfunctionArrayList.add(new Cusfunction(scanner.nextLine().replaceAll(" ","")
.replaceAll("\t",""),cusfunctionArrayList));
}
String input = scanner.nextLine();
2.接着是求导因子。由于我基项的定义,也就是:
M
o
n
o
=
∑
i
=
1
n
a
∗
x
b
Mono = \sum_{i=1}^na*x^b
Mono=i=1∑na∗xb
P o l y = ( M o n o ) ∗ e x p ( P o l y ) Poly = (Mono)*exp(Poly) Poly=(Mono)∗exp(Poly)
我只需要增加一个求导因子类,改写getResult为引用poly里的求导计算方法即可,原理为:
P
o
l
y
′
=
(
M
o
n
o
)
′
∗
e
x
p
(
P
o
l
y
)
+
M
o
n
o
∗
e
x
p
(
P
o
l
y
)
∗
(
P
o
l
y
)
′
Poly' = (Mono)'*exp(Poly)+Mono*exp(Poly)*(Poly)'
Poly′=(Mono)′∗exp(Poly)+Mono∗exp(Poly)∗(Poly)′
M o n o ′ = ∑ i = 1 n a b ∗ x b − 1 Mono'=\sum_{i=1}^nab*x^{b-1} Mono′=i=1∑nab∗xb−1
第一行使用递归嵌套方法即可实现,第二行则是简单的计算。
总之,第二次作业的良好架构使第三次作业异常简单~~~
4.可扩展性
针对于我的架构,实际上任何不打破我基项的定义,什么新的计算需求都会很好实现。而对于会破坏我基项的,例如sin等三角函数。实际上可以将基项增添几个数据即可,因为三角函数与之前的基项内容是无法直接计算相消的,这样编写新的计算模式就行。
三、程序Bug分析
作业一 Bug
作业一的Bug总结起来只有一个,那就是乘方方法实现失误。本来0次方应该结果为1,我手误敲成了保留原来的系数。
作业二 Bug
1.括号合法性不对,在一些不应该使用双层括号的地方给多打了,没有特判只有一个因子的情况,实际上这个Bug是我的优化和处理产生的冲突,本来我的实现是保证最后都会是一个a*x^b的形式,但是我化简成了单独的a,这个时候就只能加一层。
2.指数使用的还是Int,没有改成BigInt,导致超出范围。
作业三 Bug
作业三实现较简单,无Bug出现。
四、Hack他人经验
1.自行设计比较刁钻或者偏的的数据点。
2.使用评测机,大量的跑一些普通数据。
但是我并没有针对被Hack者的代码架构来设计数据点,因为理解其他成员的代码太费时间了()。
五、优化分析
我所进行的优化比较简单,因为我的设计不论是解析字符串还是计算还是输出都是使用了嵌套方法,为了防止时间复杂度过高,导致爆re。
1.最基本的,在计算时就实现了,也就是同类合并。
a
∗
x
b
+
c
∗
x
b
=
(
a
+
b
)
∗
x
b
a*x^b+c*x^b=(a+b)*x^b
a∗xb+c∗xb=(a+b)∗xb
m ∗ e x p ( P 1 ) + n ∗ e x p ( P 2 ) = ( m + n ) ∗ e x p ( P 1 ) m*exp(P1)+n*exp(P2)=(m+n)*exp(P1) m∗exp(P1)+n∗exp(P2)=(m+n)∗exp(P1)
m ∗ e x p ( P 1 ) ∗ e x p ( P 2 ) = m ∗ e x p ( P 1 ∗ P 2 ) m*exp(P1)*exp(P2)=m*exp(P1*P2) m∗exp(P1)∗exp(P2)=m∗exp(P1∗P2)
2.输出时省略1/0:
a
∗
x
0
=
a
a*x^0=a
a∗x0=a
1 ∗ x b = x b 1*x^b=x^b 1∗xb=xb
由于我的化简工作做的并不多,因此对代码的简洁性和可读性影响并不大。
六、心得体会
这一轮的作业说实话给我打击挺大的,首先是第一次作业分数并不高,同时还有失败的作业二。
我感觉我失败的原因是时间给的不够多。我还处于OOPre的状态,以为花10个小时就可以写完作业,但事实上编写、调试等等工作量加起来在作业比较难的时候10个小时是远远不够的。我希望我在之后的学习中,能够拿出更多的时间,去编写代码,去充分测试,去深度思考。给自己在OO的学习中不留遗憾
抛开那些悲观因素,实际上我学到的东西还是很多的,比如递归下降算法,还有设计模块化的思想,还更深刻理解了SOLID原则……这些都是非常宝贵的学习经验。
总之,在这一轮作业,有喜有悲,但好在我还没有放弃,保持信心,同时热情的助教学长的鼓励也让我更加坚定地想要学好OO。
七、未来方向
我觉得本单元的学习在开始阶段可以多加一些对关键算法的讲解或者过度,这样让大家在上手第一次作业时不会那么手足无措。