OO第一单元总结
OO第一单元总结博客
一.写在前面
转眼间,面向对象第一单元的三次作业已经结束。一路走来,发现了自己很多问题,当然也有一些做的好的地方,都将在这篇博客中做一总结。前车之鉴,后事之师,希望我未来的OO学习能在这一单元经验的基础上走得更顺利一些。
二.作业分析
2.1第一次作业
2.1.1作业要求
读入一个包含加、减、乘、除、乘方运算,带括号的,符合指导书形式要求的单变量表达式,输出恒等变形后尽可能短的符合规范的表达式。
2.1.2类图
2.1.3类的度量
属性个数 | 方法个数 | 总行数 | 源代码行数 | 源代码占比 | |
---|---|---|---|---|---|
Term | 5 | 22 | 384 | 357 | 93% |
OoT1 | 4 | 12 | 251 | 235 | 94% |
class | OCavg | OCmax | WMC |
---|---|---|---|
Term | 4.043478260869565 | 11.0 | 93.0 |
OoT1 | 5.166666666666667 | 13.0 | 62.0 |
OCavg是类平均圈复杂度的意思,其中圈复杂度是一种代码复杂度的衡量标准,表示代码中线性无关路径的数量,圈复杂度大说明代码判断逻辑复杂。OCmax是类最大圈复杂度,WMC是类总圈复杂度。
method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
OoT1.prNoOne(int, BigInteger, boolean) | 12.0 | 1.0 | 6.0 | 6.0 |
OoT1.prOne(int, boolean) | 12.0 | 1.0 | 6.0 | 6.0 |
OoT1.exeBracket(int) | 18.0 | 6.0 | 4.0 | 9.0 |
OoT1.decomEx() | 20.0 | 1.0 | 11.0 | 13.0 |
OoT1.main(String[]) | 34.0 | 3.0 | 13.0 | 14.0 |
OoT1.exeStBeginInt(int, char[]) | 8.0 | 3.0 | 1.0 | 7.0 |
OoT1.exeExpo(int) | 4.0 | 3.0 | 1.0 | 4.0 |
OoT1.exeNorn(int) | 4.0 | 3.0 | 1.0 | 4.0 |
OoT1.prNegOne(int) | 3.0 | 1.0 | 3.0 | 3.0 |
OoT1.prNoNegOne(int, BigInteger) | 3.0 | 1.0 | 3.0 | 3.0 |
OoT1.replaceT(int, int) | 1.0 | 1.0 | 1.0 | 2.0 |
OoT1.exeNorx(int) | 0.0 | 1.0 | 1.0 | 1.0 |
Term.forwOpHash(HashMap, int) | 1.0 | 1.0 | 2.0 | 2.0 |
Term.forwTermHash(HashMap, Term, int) | 1.0 | 1.0 | 2.0 | 2.0 |
Term.replaceT(int, int) | 1.0 | 1.0 | 1.0 | 2.0 |
Term.termValue() | 1.0 | 1.0 | 1.0 | 5.0 |
Term.add(Term) | 2.0 | 1.0 | 2.0 | 3.0 |
Term.sub(Term) | 2.0 | 1.0 | 2.0 | 3.0 |
Term.exeExpo(int, HashMap) | 4.0 | 3.0 | 1.0 | 4.0 |
Term.exeNorn(int, HashMap) | 4.0 | 3.0 | 1.0 | 4.0 |
Term.multi(Term) | 4.0 | 1.0 | 3.0 | 4.0 |
Term.judgeTruth(String) | 7.0 | 2.0 | 2.0 | 4.0 |
Term.exeSignBigInt(char[], String) | 9.0 | 3.0 | 1.0 | 6.0 |
Term.exeSignInt(char[], String) | 9.0 | 3.0 | 2.0 | 6.0 |
Term.exeTermBracExpo() | 9.0 | 5.0 | 3.0 | 7.0 |
Term.exeTermExponent() | 10.0 | 3.0 | 1.0 | 6.0 |
Term.expoOp(int) | 10.0 | 2.0 | 3.0 | 5.0 |
Term.calcExpre(HashMap, HashMap, boolean) | 12.0 | 1.0 | 10.0 | 10.0 |
Term.exeTermNormal() | 18.0 | 1.0 | 2.0 | 7.0 |
Term.exeTermBracket() | 20.0 | 3.0 | 9.0 | 14.0 |
*OCavg是类平均圈复杂度的意思,其中圈复杂度是一种代码复杂度的衡量标准,表示代码中线性无关路径的数量,圈复杂度大说明代码判断逻辑复杂。OCmax是类最大圈复杂度,WMC是类总圈复杂度。*
从只有两个的类和每个类中繁多的方法不难看出第一次的作业我并没有很好地践行面向对象的原则,事实也确是如此。在拿到第一次作业后,由于题目限制多、数据结构简单,我很快提取出基本项的数据类型为a*x**b。而由于懒惰我并没有做递归下降的练习,所以错失了一次更正我结构的良机。用面向过程的写法写完并写了自动测评后就不管了,这就为之后的重构埋下了伏笔。
此外,结合后两次的debug经历来看,较高的圈复杂度和认知复杂性确实也对应着较多的bug,控制结构的边界、变量在控制结构中的改变都带来了许多bug,希望自己能从中吸取教训,之后在构造方法时多想一想,不要因为思维的惰性而草草下笔。
再次审视第一次作业,我发现第一次的作业中其实已经实现了后面要实现的基础功能,但是由于没有迭代构造、面向对象的意识,所以把功能代码写的七零八落、耦合度高、内聚性差,在后面的作业中根本没法用,走向了不得不重写的道路。这突出了先学完指导书再做题的重要性:)
2.1.4bug分析
第一次作业在公测和互测中均未发现bug。
在测试他人程序中,我采取了使用自动评测测评他人程序的方法测评,发现了四个bug,一个是没有考虑边界条件,另外三个都是在乘方的运算化简中出现了问题。
2.1.5架构设计
将因子分为四类:Normal、Bracket、Exponent、BracExpo,分别代表常数和x、无指数表达式因子、幂函数、带指数括号。OoT1类负责读入要处理的字符串并分析其中的每一项,找出每一项归属的类型后调用Term类对这一项进行计算分析,将结果以a*x**b的形式存在Term的BigInteger[]中,最后通过Term中实现的运算进行合并。
2.2第二次作业
2.2.1作业要求
读入一个包含加、减、乘、除、乘方运算、三角函数、自定义函数、求和函数,带括号的,符合指导书形式要求的单变量表达式,输出恒等变形后尽可能短的符合规范的表达式。
2.2.2类图
2.2.3类的度量
属性个数 | 方法个数 | 总行数 | 源代码行数 | 源代码占比 | |
---|---|---|---|---|---|
Cos | 6 | 2 | 69 | 64 | 93% |
Dataset | 3 | 7 | 85 | 76 | 89% |
DefFunction | 7 | 7 | 184 | 172 | 93% |
Exponent | 4 | 2 | 43 | 37 | 86% |
Factor | 5 | 18 | 469 | 446 | 95% |
Main | 4 | 1 | 26 | 22 | 85% |
Normal | 2 | 2 | 61 | 55 | 90% |
Parser | 0 | 17 | 365 | 342 | 94% |
Sin | 6 | 2 | 69 | 64 | 93% |
SumFunction | 4 | 3 | 77 | 70 | 91% |
class | OCavg | OCmax | WMC |
---|---|---|---|
Cos | 3.3333333333333335 | 8.0 | 10.0 |
Sin | 3.3333333333333335 | 8.0 | 10.0 |
Normal | 4.0 | 9.0 | 12.0 |
DefFunction | 4.625 | 10.0 | 37.0 |
Parser | 4.705882352941177 | 16.0 | 80.0 |
Factor | 5.157894736842105 | 13.0 | 98.0 |
DataSet | 2.7142857142857144 | 7.0 | 19.0 |
Exponent | 3.0 | 7.0 | 9.0 |
SumFunction | 3.25 | 5.0 | 13.0 |
Main | 2.0 | 2.0 | 2.0 |
method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
Cos.Cos() | 0.0 | 1.0 | 1.0 | 1.0 |
Cos.Cos(String, HashMap) | 0.0 | 1.0 | 1.0 | 1.0 |
DataSet.getCosList() | 0.0 | 1.0 | 1.0 | 1.0 |
DataSet.getSinList() | 0.0 | 1.0 | 1.0 | 1.0 |
DataSet.getVaryExpo() | 0.0 | 1.0 | 1.0 | 1.0 |
DataSet.setVaryExpo(BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
DefFunction.DefFunction() | 0.0 | 1.0 | 1.0 | 1.0 |
DefFunction.DefFunction(String, String) | 0.0 | 1.0 | 1.0 | 1.0 |
Exponent.Exponent() | 0.0 | 1.0 | 1.0 | 1.0 |
Exponent.Exponent(String) | 0.0 | 1.0 | 1.0 | 1.0 |
Factor.getFactorData() | 0.0 | 1.0 | 1.0 | 1.0 |
Factor.noRelationCopyDs(DataSet) | 0.0 | 1.0 | 1.0 | 1.0 |
Normal.Normal() | 0.0 | 1.0 | 1.0 | 1.0 |
Parser.creaCharKey(int) | 0.0 | 1.0 | 1.0 | 1.0 |
Parser.exeExpo(int, ArrayList, HashMap, int) | 0.0 | 1.0 | 1.0 | 1.0 |
Sin.Sin() | 0.0 | 1.0 | 1.0 | 1.0 |
Sin.Sin(String, HashMap) | 0.0 | 1.0 | 1.0 | 1.0 |
SumFunction.SumFunction() | 0.0 | 1.0 | 1.0 | 1.0 |
Test.main(String[]) | 0.0 | 1.0 | 1.0 | 1.0 |
DataSet.equalsDataSet(DataSet) | 1.0 | 1.0 | 1.0 | 3.0 |
Factor.noRelationCopyFactor() | 1.0 | 1.0 | 2.0 | 2.0 |
Main.main(String[]) | 1.0 | 1.0 | 2.0 | 2.0 |
MainTest.main(String[]) | 1.0 | 1.0 | 2.0 | 2.0 |
Normal.Normal(String) | 1.0 | 1.0 | 2.0 | 2.0 |
Parser.arrayCharToString(ArrayList) | 1.0 | 1.0 | 2.0 | 2.0 |
Parser.arrayStringToString(ArrayList) | 1.0 | 1.0 | 2.0 | 2.0 |
Parser.exeFcExpo(int, ArrayList, HashMap) | 1.0 | 1.0 | 2.0 | 2.0 |
Parser.exeSumFc(int, ArrayList, HashMap, int) | 1.0 | 1.0 | 2.0 | 2.0 |
Parser.replaceCharKey(int, int, char, ArrayList) | 1.0 | 1.0 | 2.0 | 2.0 |
Factor.add(Factor) | 2.0 | 1.0 | 3.0 | 3.0 |
Factor.sub(Factor) | 2.0 | 1.0 | 3.0 | 3.0 |
DefFunction.exeFcDef(String) | 3.0 | 3.0 | 3.0 | 3.0 |
Factor.multi(Factor) | 3.0 | 1.0 | 3.0 | 3.0 |
Factor.noRelationCopyAal(ArrayList>) | 3.0 | 1.0 | 3.0 | 3.0 |
Parser.exeCos(int, ArrayList, HashMap, int) | 3.0 | 3.0 | 2.0 | 3.0 |
Parser.exeSin(int, ArrayList, HashMap, int) | 3.0 | 3.0 | 2.0 | 3.0 |
DefFunction.replaceFormD(String) | 4.0 | 1.0 | 4.0 | 4.0 |
DefFunction.replaceFormE(String) | 4.0 | 1.0 | 4.0 | 4.0 |
DefFunction.replaceFormQ(String) | 4.0 | 1.0 | 4.0 | 4.0 |
Factor.expoExe(BigInteger) | 4.0 | 1.0 | 3.0 | 3.0 |
SumFunction.SumFunction(String) | 4.0 | 3.0 | 3.0 | 4.0 |
SumFunction.setFactorData() | 4.0 | 1.0 | 3.0 | 3.0 |
Factor.prOther(DataSet, BigInteger) | 6.0 | 1.0 | 6.0 | 6.0 |
Parser.exeDefFc(int, ArrayList, ArrayList, HashMap, int) | 6.0 | 3.0 | 4.0 | 4.0 |
Factor.delSinZero() | 7.0 | 1.0 | 5.0 | 5.0 |
Factor.judgeTruth(String) | 7.0 | 2.0 | 2.0 | 4.0 |
Exponent.setFactorData() | 8.0 | 5.0 | 3.0 | 7.0 |
Parser.exeExpoEndInt(int, ArrayList) | 8.0 | 4.0 | 6.0 | 7.0 |
Parser.exeNorm(int, ArrayList, HashMap, int) | 8.0 | 5.0 | 8.0 | 11.0 |
Parser.exeStBeginInt(int, ArrayList) | 8.0 | 3.0 | 5.0 | 7.0 |
Factor.mergeCongeners() | 9.0 | 3.0 | 5.0 | 6.0 |
Factor.mergeCosList(ArrayList>) | 9.0 | 3.0 | 5.0 | 6.0 |
Factor.mergeSinList(ArrayList>) | 9.0 | 3.0 | 5.0 | 6.0 |
Factor.prNegOne(DataSet) | 9.0 | 2.0 | 8.0 | 8.0 |
Factor.prOne(DataSet) | 9.0 | 2.0 | 8.0 | 8.0 |
Parser.exeRbracket(int, ArrayList) | 9.0 | 4.0 | 2.0 | 5.0 |
SumFunction.replaceI(String, BigInteger) | 9.0 | 4.0 | 8.0 | 8.0 |
Cos.setFactorData() | 10.0 | 6.0 | 4.0 | 8.0 |
Sin.setFactorData() | 10.0 | 6.0 | 4.0 | 8.0 |
DefFunction.setFactorData() | 15.0 | 1.0 | 10.0 | 10.0 |
Normal.setFactorData() | 17.0 | 3.0 | 10.0 | 11.0 |
Parser.exeBracket(int, ArrayList, HashMap) | 17.0 | 1.0 | 12.0 | 12.0 |
DataSet.equalsCos(ArrayList>) | 18.0 | 6.0 | 7.0 | 10.0 |
DataSet.equalsSin(ArrayList>) | 18.0 | 6.0 | 7.0 | 10.0 |
Factor.factorToString() | 18.0 | 4.0 | 10.0 | 10.0 |
Parser.calcExpression(ArrayList, HashMap) | 21.0 | 1.0 | 13.0 | 13.0 |
Parser.parse(String, HashMap, ArrayList, int) | 30.0 | 1.0 | 23.0 | 23.0 |
DefFunction.exeFcCall(String) | 32.0 | 7.0 | 10.0 | 12.0 |
Factor.sinToString(ArrayList>) | 43.0 | 1.0 | 12.0 | 12.0 |
Factor.cosToString(ArrayList>) | 53.0 | 1.0 | 13.0 | 13.0 |
2.2.4bug分析
本次在强测和互测中被hack了三处bug。其中一处是由于没有考虑到自定义函数代入因子的指数运算,导致带入符号的指数被认为是常数,这就导致了会在计算表达式的时候出现空指针;一处是因为没有考虑到括号中第一个符号是减号的情况,导致列表越界;一处是因为将sin(0)全部化为0,没有考虑到零次幂的情况。
现在将MetricsLoaded的结果与当时的bug进行对比,发现果然圈复杂度高的方法容易出现bug,在控制结构的边界、变量变换的过程中一个没考虑到就会留下bug的隐患。
本次互测中通过将自己测试时的数据输入找到他人一个bug,是由于处理含幂指数与三角函数表达式因子的指数形式时不能正确化简导致的。
2.2.5架构设计
由于第一次作业代码的针对性太强、扩展性太差,本次作业无法进行迭代,只能重构。在这次重构时,我充分考虑了迭代的需求,按照题目的形式化表述将基本因子建类:三角函数的sin和cos类,自定义函数类,求和函数类,常数类,幂指数类,并让他们继承因子类。
为了存储每一项的基本形式
a * x ** b * sin(defined factor)^exp * ... * cos(defined factor)^exp * ...
其中definedfactor是符合指导书要求的因子,具体为系数为1的幂函数或者常数,我新建了一个类Dataset,在类中使用BigInteger存储指数,使用两个二维数组存储sin和cos的连乘。最后再建立一个HashMap,用Dataset做key,系数BigInteger做value。这样做虽然达成了我设计时的想法:每一个factor都可以存储多个factor的值,最后将一个个factor合并成一个factor即可,就像人工进行数学运算时的那样。但是,这样一个直观上十分清晰的构造却在后续的debug环节给我带来了无尽的麻烦,我将在心得体会中详细讨论。
Main类用来读入字符串并调用Parser类。在Parser类中,parse方法用来将字符串中的项识别提取出来,将对应的字符子串传入相应的类,计算出结果后返回已经存储了这段字符串对应内容的Factor,然后将字符串中的子串替换为一个数值大于200的特殊字符。将特殊字符作为key,factor作为value存入HashMap中,如法炮制,将整个字符串处理后就得到了一个特殊字符和+-*的字符序列,将序列按照相应运算化简就可以得到最终的一个factor,这个factor中就存储着我们需要的数据。
2.3第三次作业
2.3.1作业要求
读入一个包含加、减、乘、除、乘方运算、三角函数、自定义函数、求和函数,带括号的,符合指导书形式要求的单变量表达式,输出恒等变形后尽可能短的符合规范的表达式,在第二次作业的基础上放开了许多对数据的限制。
2.3.2类图
2.3.3类的度量
属性个数 | 方法个数 | 总行数 | 源代码行数 | 源代码占比 | |
---|---|---|---|---|---|
Cos | 6 | 2 | 56 | 50 | 89% |
Dataset | 3 | 4 | 25 | 19 | 76% |
DefFunction | 6 | 7 | 169 | 155 | 92% |
Exponent | 4 | 2 | 43 | 37 | 86% |
Expr | 2 | 3 | 23 | 16 | 70% |
Factor | 5 | 13 | 307 | 290 | 94% |
FactorMap | 1 | 4 | 22 | 15 | 68% |
Main | 2 | 1 | 27 | 22 | 81% |
MyFunctions | 0 | 21 | 294 | 267 | 91% |
Normal | 2 | 2 | 59 | 53 | 90% |
Parser | 0 | 13 | 361 | 343 | 95% |
Sin | 5 | 2 | 55 | 50 | 91% |
SumFunction | 4 | 3 | 69 | 62 | 90% |
KeyIntStore | 1 | 2 | 14 | 10 | 71% |
class | OCavg | OCmax | WMC |
---|---|---|---|
DataSet | 1.0 | 1.0 | 4.0 |
Expr | 1.0 | 1.0 | 4.0 |
FactorMap | 1.0 | 1.0 | 4.0 |
KeyIntStore | 1.0 | 1.0 | 2.0 |
Main | 2.0 | 2.0 | 2.0 |
MyFunctions | 2.7083333333333335 | 7.0 | 65.0 |
Cos | 3.0 | 7.0 | 9.0 |
Exponent | 3.0 | 7.0 | 9.0 |
Sin | 3.0 | 7.0 | 9.0 |
SumFunction | 3.0 | 5.0 | 12.0 |
Normal | 3.6666666666666665 | 9.0 | 11.0 |
DefFunction | 4.0 | 10.0 | 32.0 |
Parser | 4.6923076923076925 | 16.0 | 61.0 |
Factor | 5.3076923076923075 | 14.0 | 69.0 |
method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
Cos.Cos() | 0.0 | 1.0 | 1.0 | 1.0 |
Cos.Cos(String, FactorMap, ArrayList, KeyIntStore) | 0.0 | 1.0 | 1.0 | 1.0 |
Cos.setFactorData(KeyIntStore, FactorMap) | 9.0 | 5.0 | 5.0 | 7.0 |
DataSet.getCosList() | 0.0 | 1.0 | 1.0 | 1.0 |
DataSet.getSinList() | 0.0 | 1.0 | 1.0 | 1.0 |
DataSet.getVaryExpo() | 0.0 | 1.0 | 1.0 | 1.0 |
DataSet.setVaryExpo(BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
DefFunction.DefFunction() | 0.0 | 1.0 | 1.0 | 1.0 |
DefFunction.DefFunction(String, String, ArrayList, KeyIntStore, FactorMap) | 0.0 | 1.0 | 1.0 | 1.0 |
DefFunction.exeFcCall(String, KeyIntStore, FactorMap) | 5.0 | 1.0 | 5.0 | 5.0 |
DefFunction.exeFcDef(String) | 3.0 | 3.0 | 3.0 | 3.0 |
DefFunction.replaceFormD(String, KeyIntStore) | 4.0 | 1.0 | 4.0 | 4.0 |
DefFunction.replaceFormE(String, KeyIntStore) | 4.0 | 1.0 | 4.0 | 4.0 |
DefFunction.replaceFormQ(String, KeyIntStore) | 4.0 | 1.0 | 4.0 | 4.0 |
DefFunction.setFactorData(KeyIntStore, FactorMap) | 15.0 | 1.0 | 10.0 | 10.0 |
Exponent.Exponent() | 0.0 | 1.0 | 1.0 | 1.0 |
Exponent.Exponent(String) | 0.0 | 1.0 | 1.0 | 1.0 |
Exponent.setFactorData() | 8.0 | 5.0 | 3.0 | 7.0 |
Expr.Expr() | 0.0 | 1.0 | 1.0 | 1.0 |
Expr.Expr(ArrayList, ArrayList) | 0.0 | 1.0 | 1.0 | 1.0 |
Expr.getDefFuncs() | 0.0 | 1.0 | 1.0 | 1.0 |
Expr.getExpression() | 0.0 | 1.0 | 1.0 | 1.0 |
Factor.add(Factor) | 2.0 | 1.0 | 3.0 | 3.0 |
Factor.cosToString(HashMap, FactorMap, KeyIntStore) | 21.0 | 1.0 | 10.0 | 10.0 |
Factor.expoExe(BigInteger) | 4.0 | 1.0 | 3.0 | 3.0 |
Factor.factorToString(FactorMap, KeyIntStore) | 30.0 | 6.0 | 14.0 | 14.0 |
Factor.getFactorData() | 0.0 | 1.0 | 1.0 | 1.0 |
Factor.judgeTriNoBr(String) | 1.0 | 1.0 | 5.0 | 5.0 |
Factor.multi(Factor) | 3.0 | 1.0 | 3.0 | 3.0 |
Factor.prNegOne(DataSet, FactorMap, KeyIntStore) | 9.0 | 2.0 | 8.0 | 8.0 |
Factor.prOne(DataSet, FactorMap, KeyIntStore) | 9.0 | 2.0 | 8.0 | 8.0 |
Factor.prOther(DataSet, BigInteger, FactorMap, KeyIntStore) | 6.0 | 1.0 | 6.0 | 6.0 |
Factor.setFactorData(HashMap) | 0.0 | 1.0 | 1.0 | 1.0 |
Factor.sinToString(HashMap, FactorMap, KeyIntStore) | 21.0 | 1.0 | 10.0 | 10.0 |
Factor.sub(Factor) | 2.0 | 1.0 | 3.0 | 3.0 |
FactorMap.add(Character, Factor) | 0.0 | 1.0 | 1.0 | 1.0 |
FactorMap.FactorMap() | 0.0 | 1.0 | 1.0 | 1.0 |
FactorMap.get(Character) | 0.0 | 1.0 | 1.0 | 1.0 |
FactorMap.replace(Character, Factor) | 0.0 | 1.0 | 1.0 | 1.0 |
KeyIntStore.getKeyInt() | 0.0 | 1.0 | 1.0 | 1.0 |
KeyIntStore.KeyIntStore() | 0.0 | 1.0 | 1.0 | 1.0 |
Main.main(String[]) | 1.0 | 1.0 | 2.0 | 2.0 |
MyFunctions.arrayCharToString(ArrayList) | 1.0 | 1.0 | 2.0 | 2.0 |
MyFunctions.arrayStringToString(ArrayList) | 1.0 | 1.0 | 2.0 | 2.0 |
MyFunctions.copyDataSet(DataSet) | 0.0 | 1.0 | 1.0 | 1.0 |
MyFunctions.copyExpr(Expr) | 0.0 | 1.0 | 1.0 | 1.0 |
MyFunctions.copyFactorData(HashMap) | 1.0 | 1.0 | 2.0 | 2.0 |
MyFunctions.copyHashExprBi(HashMap) | 1.0 | 1.0 | 2.0 | 2.0 |
MyFunctions.creaCharKey(int) | 0.0 | 1.0 | 1.0 | 1.0 |
MyFunctions.delPnArrChar(ArrayList) | 1.0 | 1.0 | 2.0 | 2.0 |
MyFunctions.exchangeStToArray(String) | 1.0 | 1.0 | 2.0 | 2.0 |
MyFunctions.exeExpoEndIntForPeek(int, ArrayList) | 8.0 | 4.0 | 5.0 | 6.0 |
MyFunctions.exeExpoEndIntForTwin(int, ArrayList) | 8.0 | 4.0 | 6.0 | 7.0 |
MyFunctions.exeRbracket(int, ArrayList) | 9.0 | 4.0 | 2.0 | 5.0 |
MyFunctions.exeStBeginInt(int, ArrayList) | 8.0 | 3.0 | 5.0 | 7.0 |
MyFunctions.exeZeroOne(String) | 17.0 | 5.0 | 10.0 | 11.0 |
MyFunctions.judgeDataSet(DataSet) | 1.0 | 1.0 | 2.0 | 2.0 |
MyFunctions.judgeTruth(String) | 7.0 | 2.0 | 2.0 | 4.0 |
MyFunctions.noRelationCopyArrArrBigI(ArrayList>) | 1.0 | 1.0 | 2.0 | 2.0 |
MyFunctions.noRelationCopyArrBigI(ArrayList) | 0.0 | 1.0 | 1.0 | 1.0 |
MyFunctions.noRelationCopyArrChar(ArrayList) | 0.0 | 1.0 | 1.0 | 1.0 |
MyFunctions.noRelationCopyArrSt(ArrayList) | 0.0 | 1.0 | 1.0 | 1.0 |
MyFunctions.refineFactorData(HashMap) | 11.0 | 3.0 | 6.0 | 7.0 |
MyFunctions.replaceChar(ArrayList, int, int, Character) | 1.0 | 1.0 | 2.0 | 2.0 |
MyFunctions.replaceCharKey(int, int, char, ArrayList) | 1.0 | 1.0 | 2.0 | 2.0 |
MyFunctions.replaceSinCosT(String) | 7.0 | 1.0 | 8.0 | 8.0 |
Normal.Normal() | 0.0 | 1.0 | 1.0 | 1.0 |
Normal.Normal(String) | 0.0 | 1.0 | 1.0 | 1.0 |
Normal.setFactorData() | 17.0 | 3.0 | 10.0 | 11.0 |
Parser.calcExpression(Expr, FactorMap, KeyIntStore) | 26.0 | 1.0 | 17.0 | 17.0 |
Parser.exeBracket(int, ArrayList, FactorMap, KeyIntStore) | 17.0 | 1.0 | 11.0 | 11.0 |
Parser.exeCos(int, ArrayList, FactorMap, KeyIntStore, ArrayList) | 0.0 | 1.0 | 1.0 | 1.0 |
Parser.exeDefFc(int, ArrayList, ArrayList, FactorMap, KeyIntStore) | 6.0 | 3.0 | 4.0 | 4.0 |
Parser.exeExpo(int, ArrayList, FactorMap, KeyIntStore) | 0.0 | 1.0 | 1.0 | 1.0 |
Parser.exeFcExpo(int, ArrayList, FactorMap, KeyIntStore) | 1.0 | 1.0 | 2.0 | 2.0 |
Parser.exeMul(FactorMap, ArrayList, int, KeyIntStore) | 0.0 | 1.0 | 1.0 | 1.0 |
Parser.exeNorm(int, ArrayList, FactorMap, KeyIntStore) | 6.0 | 4.0 | 5.0 | 7.0 |
Parser.exePnInBr(int, int, ArrayList, FactorMap, Factor, KeyIntStore) | 16.0 | 1.0 | 10.0 | 10.0 |
Parser.exeRedundantPn(int, int, ArrayList) | 0.0 | 1.0 | 1.0 | 1.0 |
Parser.exeSin(int, ArrayList, FactorMap, KeyIntStore, ArrayList) | 0.0 | 1.0 | 1.0 | 1.0 |
Parser.exeSumFc(int, ArrayList, FactorMap, KeyIntStore) | 1.0 | 1.0 | 2.0 | 2.0 |
Parser.parse(String, FactorMap, ArrayList, KeyIntStore) | 30.0 | 1.0 | 23.0 | 23.0 |
Sin.setFactorData(KeyIntStore, FactorMap) | 9.0 | 5.0 | 5.0 | 7.0 |
Sin.Sin() | 0.0 | 1.0 | 1.0 | 1.0 |
Sin.Sin(String, FactorMap, ArrayList, KeyIntStore) | 0.0 | 1.0 | 1.0 | 1.0 |
SumFunction.replaceI(String, BigInteger) | 8.0 | 1.0 | 8.0 | 8.0 |
SumFunction.setFactorData(KeyIntStore, FactorMap) | 4.0 | 1.0 | 3.0 | 3.0 |
SumFunction.SumFunction() | 0.0 | 1.0 | 1.0 | 1.0 |
SumFunction.SumFunction(String, KeyIntStore, FactorMap) | 3.0 | 3.0 | 2.0 | 3.0 |
2.3.4bug分析
本次在强测和互测中被hack了三处bug。一处是因为在处理括号时,在删去括号中字符后,没有及时地更新左右括号的位置导致出错。一处是因为在进行Factor之间的加减运算时,在逻辑上分析后认为不需要深克隆而选择了浅克隆。一处是因为因为将去除重复加减号的处理放置的偏后,导致在处理括号嵌套时,左括号索引与实际位置不符。
结合分析工具提供的数据来看,三处bug,有两处都出现在圈复杂度和认知复杂性大的地方。与第二次相结合,更让人深刻意识到复杂控制结构的不可靠性。要想改进这个问题,一方面,尽量少使用复杂的控制结构,在写代码之前想一想有没有更简洁的表述形式;如果实在没办法使用了复杂的控制结构,那么在测试时就要针对复杂的控制结构做全面的测试,既要在逻辑上证明,也要在测试数据中覆盖到每一个分支,而不是简单的随机数据。
纵观第二、第三次作业,虽然我使用了自己写的随机数据,但还是有不少bug,原因在于我的随机数据不能针对自定义函数和求和函数,对于边界数据也构造的很少;而自己又没有针对自己的代码逻辑手动构造数据,也没有认真地重新审视自己的代码,而这比一个并不成熟的自动评测显然更为重要。
本次hack到他人两处bug,一处是sum的边界问题,一处是三角函数的处理没有按照指导书要求进行。其中sum的边界是阅读了小组成员sum处的代码发现逻辑上的漏洞,三角函数则是依靠自动评测发现。
2.3.5架构分析
本次架构和第二次大体上相同,相同部分不再赘述。这里主要将不同的点列出。
首先,第三次作业和第二次作业最大的不同就是每一项的基本形式的不同,sin、cos中由之前的幂指数转换成了表达式,具体如下:
a * x**b * sin(Expr)^exp * ... * cos(Expr)^exp * ...
针对Expr我新建了一个Expr类对表达式信息进行存储,并修改了之前的DataSet。值得一提的是,在把Expr加到Factor的过程中,我花了很多时间和精力,因为在我原本的设想中,Expr应该直接存储Factor的列表,但是这样就会造成Factor中有Expr,Expr中有Factor的情况,非常不利于后续的复制等操作,试了很多方法后还是不能将这样的嵌套结构进行完美解耦复制,但是后来我才发现其实可以先把较原始的形式存入,等到用的时候在化简,类似的例子还有不少,都是想要一步得到最终结果,但是实现过程给我带来了很多麻烦,我会在心得体会中做一总结。
其次,我针对存储特殊字符->Factor的HashMap和生成特殊字符的整数专门建立了类,解决了两次作业一直让我十分头疼的特殊字符重复和HashMap不统一的情况,遗憾的是这个方法我在最后才想出来。
三.心得体会
3.1对于深浅拷贝的理解
在二三次作业中,由于使用了不少HashMap和ArrayList,我被深浅拷贝折磨的很惨,在自己的debug过程中和强测都因为这个而焦头烂额。深浅拷贝本身并不是为了坑人而存在的,我之所以被折磨还是因为自己学艺不精,不能把所学和自己的代码相结合。接下来我就谈谈我对深浅拷贝的理解。
深浅拷贝就像C语言中学过的指针,它本身存储的是一个地址,但这又与我们对与数据结构直观的认知相悖。在直观的认知下,ArrayList就是一个列表,HashMap就是一个字典,我复制一个列表、一个字典得到一个新列表、新字典和原来有什么关系?没有关系!但是JVM中没有列表和字典,有的只是一个指向一块空间的地址。就像你把宿舍号抄在纸上,难道拿着这张纸就找不到对应的宿舍了吗,不可能的。所以在复制的时候我们都新建一个ArrayList和HashMap,再putAll、addAll不就行了吗。很抱歉,还是不行。如果你的key是类的对象的话,它本身又是一个地址,你使用putAll是没把存储结构的地址复制过去,但是你把对象的地址复制过去了,这样也会产生bug。那我再新new一个类的对象,然后把原来对象的属性都复制过去不就...欸?聪明的人一定已经发现了,如果你对象中有这样的数据结构,还是不行。所以深浅拷贝本身的概念我觉得并不难,难的是如何在实际中层层套娃中把每一层中的地址都揪出来,而且在实际编码中想要不使用这些数据结构我觉得还是十分困难的,所以一定要在第一次定义的时候就想好、做好注释,提醒自己。
深浅拷贝引起的错误主要有两类,一类是由于地址相同,所以你在改动一个对象时可能会引起另一个“毫不相干”的对象的变化;还有一类就是由于地址相同,所以在HashMap和HashSet中无法共存,不知不觉中,你就少了很多对象。具体到实际中那就太多了,在此就不列举了。
3.2对于间接的理解
在第二三次作业中,我在不少地方都因为过于追求直接而导致或者无法继续向前推进或者为设计和实现带来了很多问题。比如让指导书中的基本因子直接继承含有数据结构的Factor类,比如在处理含有表达式的三角函数时一心想把Factor存到Factor里面去,比如在优化时针对最终的表达式进行三角函数的合并优化。这些都让我感到非常困难,但其实这里面不少困难都是我自找的。比如我明明可以新创建一个接口,让基本因子实现这个接口,然后在此之外再建立一个专门存储数据的类,他跟基本因子类没有继承关系,只是在基本因子类处理完字符串后接受得到的数据。比如我还可以把表达式的中间化简形式也就是特殊字符串存储在factor中,把特殊字符与factor的关系存在一个全局的“类"中,然后在最后化简的时候再把两者结合。比如我可以在每一个sin和cos最终结果得到的时候就化简,这样的数据要好处理很多。最终的解决方法基本都是增加了一个“中间层”,看来名人名言确实是有他的道理的。以后在遇到这种很纠结的问题时,一定要先停下来,认真地想一想,能不能通过间接的方法来解决,而不是一味地蛮冲。
3.3对于时间的管理
在这一单元中,尽管我在思维和代码能力上都有不少的问题,但我觉得对我影响最大的就是时间问题。每次开始的时间晚,也没有熬夜肝,导致最后很多想做的事情都没有时间做完。我应该认识到,想要完成写、调试和优化,以我的能力是很难在不熬夜的情况下做完的。第一周没什么课还尚可,第二三周所有课一起上的时候就捉襟见肘了。所以我应该在完成作业的过程中给自己每一阶段设置DDL,时间到了没做完就晚上做。当然,除了进行必要的熬夜,正式开始前的时间也应该利用起来,虽然每周前几天没有完整的时间供OO使用,但是我可以先认真读题后思考自己应该怎么完成这次作业,要在开始之前有一个基本的思路。
以上就是第一单元的总结了,整体而言,虽然过程并不愉快,但是结束后确实感受到了自己的提升。第二单元,我们下周见^-^