「BUAA OO Unit 1 HW4」单元总结
Part 0 写在开头
对我来说,特别是第二次开始,每到一次作业互测截止之时,出强测结果之时,if(强测<90) 心率平稳->心跳加快->心率归零(逃
但是,第一单元的三次迭代作业已经全部结束。在这三周我有喜有悲,但人生总是这样,往往顺逆皆备,如何化逆为顺,笑对人生,才是我应该去做的事情,不妨相信一切都是最好的安排~
书归正文,第一单元作为北航OO的开门单元,我收获良多,以下是我第一单元的总结:
Part 1 第一次作业
对项目的度量
类层次统计
类名 | 属性个数 | 方法个数 | 类总代码规模 |
Main | 0 | 1 | 17 Lines |
Parser | 1 | 5 | 88 Lines |
Lexer | 1 | 3 | 127 Lines |
Func | 0 | 7 | 165 Lines |
Constant | 1 | 3 | 25 Lines |
Expression | 2 | 3 | 45 Lines |
Term | 1 | 3 | 36 Lines |
Variable | 1 | 3 | 25 Lines |
IntegrationFactors | 1 | 3 | 45 Lines |
ClassFunc | 0 | 2 | 61 Lines |
方法层次统计
Main类 | Parser类 | ||||
方法名 | 方法规模 | 方法控制分支数目 | 方法名 | 方法规模 | 方法控制分支数目 |
main | 14 | 1 | Parser | 5 | 0 |
parseExpression | 12 | 1 | |||
parseTerm | 11 | 1 | |||
parseIntegrationFactors | 14 | 1 | |||
parseFactor | 24 | 3 | |||
Lexer类 | Func类 | ||||
方法名 | 方法规模 | 方法控制分支数目 | 方法名 | 方法规模 | 方法控制分支数目 |
Lexer | 5 | 0 | rawProcess | 26 | 4 |
next | 50 | 11 | sort | 5 | 0 |
nextback | 56 | 14 | cal | 32 | 8 |
judge | 8 | 1 | |||
merge | 43 | 9 | |||
lastProcess | 18 | 2 | |||
endProcess | 20 | 3 | |||
Constant | Variable | ||||
方法名 | 方法规模 | 方法控制分支数目 | 方法名 | 方法规模 | 方法控制分支数目 |
Constant | 4 | 0 | Variable | 4 | 0 |
toString | 4 | 0 | toString | 4 | 0 |
Expression | Term | ||||
方法名 | 方法规模 | 方法控制分支数目 | 方法名 | 方法规模 | 方法控制分支数目 |
Expression | 5 | 0 | Term | 4 | 0 |
addterm | 4 | 0 | addintergrationfactor | 4 | 0 |
toString | 17 | 2 | toString | 13 | 1 |
IntegrationFactors | ClassFunc | ||||
方法名 | 方法规模 | 方法控制分支数目 | 方法名 | 方法规模 | 方法控制分支数目 |
IntegrationFactors | 4 | 0 | process | 29 | 7 |
addfactor | 4 | 0 | mul | 28 | 5 |
toString | 22 | 2 |
可以看出方法还是多而杂,且控制分支数目普遍较高,代码规模也比较大,主要是集中在了化简部分(Func类),客观上是为了实现最优化简所导致的码量增多,但也与我自己写代码思路不好有关系,后面迭代的时候发现有些地方完全可以省略。
内聚与耦合情况
类视图
OCavg : 每个类中所有非抽象方法的平均圈复杂度(继承的方法不计算在内)。
OCmax : 每个类中非抽象方法的最大圈复杂度(继承的方法不计算在内)。
WMC : 每个类中方法的总圈复杂度.
方法视图
ev(G)基本复杂度:是用来衡量程序非结构化程度的,非结构成分降低了程序的质量,增加了代码的维护难度,使程序难于理解。因此,基本复杂度高意味着非结构化程度高,难以模块化和维护。实际上,消除了一个错误有时会引起其他的错误。
Iv(G)模块设计复杂度:是用来衡量模块判定结构,即模块和其他模块的调用关系。软件模块设计复杂度高意味模块耦合度高,这将导致模块难于隔离、维护和复用。模块设计复杂度是从模块流程图中移去那些不包含调用子模块的判定和循环结构后得出的圈复杂度,因此模块设计复杂度不能大于圈复杂度,通常是远小于圈复杂度。
v(G)模块判定结构复杂度:是用来衡量一个模块判定结构的复杂程度,数量上表现为独立路径的条数,即合理的预防错误所需测试的最少路径条数,圈复杂度大说明程序代码可能质量低且难于测试和维护,经验表明,程序的可能错误和高的圈复杂度有着很大关系。
第一次作业只是对括号的展开与表达式化简合并,还未涉及三角函数和多重括号,实现较为简单,所以大部分方法耦合度都比较低一些,但在表达式化简的运算法则类,词法分析器和化简这三个类类间的耦合度比较高。
运算法则是因为乘法写太长了,超过了checkstyle的限制,只能被迫拆分函数,它们本来就是一个函数,所以耦合度高也比较合理。词法分析也是这样。
化简则是因为方法用的太多和方法之间划分的不太合理,导致了这一部分的代码又长又不高效。也导致了耦合度的上升。
内聚程度还可以,因为我一般会把一个功能都写一个类里面,这可能不是个好习惯,但确实因此内聚程度还比较好。
UML类图
设计考虑
第一次作业采用了递归下降的方法对表达式进行了解析,所以有了Lexer和Parser类,又根据题目文法设计了五个元素类——Expression,Term,IntegrationFactors,Constant,Variable类,在展开表达式的时候可能涉及一些多项式之间的运算,所以设计了ClassFunc存放这些运算法则,根据上面这些就得到了展开式,之后进行表达式化简,所以设计了Func类,由Main调用Func类对Parser的解析结果进行化简。
优缺点分析
优点
设计思路比较清晰,按需设计,工具独立于元素类之外,且经过分析,发现运算法则工具类只需实现多项式乘法,大大提高了代码复用率。
化简在展开之后,两者之间交集较少,不容易受到彼此的影响产生未知Bug。
缺点
化简部分过于繁杂冗余,复杂度都很高,很容易在这里写出不明Bug,且也反映了写优化时思路比较混乱,想到哪里写哪里的问题暴露。也导致了Bug容易产出。
测试
分析自我程序Bug
本次作业在中测,强测,互测中均未出现Bug,还都是100分,属于是开门红级别。
得益于课下自己写了Python评测脚本,对程序提前进行了测试,且本次脚本由于指导书要求简单覆盖全面,所以评测力度很高。
分析他人程序Bug
分到的房间里面大家的代码写的也都很好,利用评测机对剩余人进行测试均未能测出有效Bug。
第一次作业我自己的评测机强度我个人认为还是可以的,毕竟要求比较简单,也实现了纯随机。
由于找不出问题,所以没有结合代码设计结构进行特殊测试用例的设计。
Part 2 第二次作业
对项目的度量
类层次统计
类名 | 属性个数 | 方法个数 | 类总代码规模 |
Main | 0 | 1 | 53 Lines |
Parser | 1 | 6 | 102 Lines |
Lexer | 1 | 3 | 174 Lines |
ConsolidateProcess | 0 | 15 | 435 Lines |
Constant | 1 | 3 | 25 Lines |
Expression | 2 | 3 | 45 Lines |
Term | 1 | 3 | 36 Lines |
Variable | 1 | 3 | 25 Lines |
IntegrationFactors | 1 | 3 | 51 Lines |
ClassFunc | 0 | 2 | 61 Lines |
CustomFunction | 0 | 8 | 90 Lines |
ExprProcess | 0 | 5 | 110 Lines |
MergeProcess | 0 | 5 | 87 Lines |
PreProcess | 0 | 4 | 139 Lines |
Runner | 0 | 1 | 18 Lines |
TrigonometricFunction | 0 | 5 | 51 Lines |
红色为相较于上次更新的部分
蓝色为相较于上次新增的部分
方法层次统计
Main类 | Parser类 | ||||
方法名 | 方法规模 | 方法控制分支数目 | 方法名 | 方法规模 | 方法控制分支数目 |
main | 53 | 2 | Parser | 5 | 0 |
parseExpression | 12 | 1 | |||
parseTerm | 11 | 1 | |||
parseIntegrationFactors | 14 | 1 | |||
parseFactor | 26 | 4 | |||
Lexer类 | ConsolidateProcess类 | ||||
方法名 | 方法规模 | 方法控制分支数目 | 方法名 | 方法规模 | 方法控制分支数目 |
Lexer | 5 | 0 | rawProcess | 58 | 13 |
next | 50 | 11 | sort | 5 | 0 |
nextback | 56 | 14 | cal | 32 | 8 |
judge | 8 | 1 | |||
merge | 43 | 9 | |||
lastProcess | 18 | 2 | |||
endProcess | 20 | 3 | |||
mySpilt | 32 | 7 | |||
getString | 25 | 5 | |||
getInt | 25 | 5 | |||
settlement | 12 | 2 | |||
triProcess | 19 | 4 | |||
checkTri | 45 | 8 | |||
checkMulMerge | 35 | 5 | |||
spiltTri | 31 | 7 | |||
Constant类 | Variable类 | ||||
方法名 | 方法规模 | 方法控制分支数目 | 方法名 | 方法规模 | 方法控制分支数目 |
Constant | 4 | 0 | Variable | 4 | 0 |
toString | 4 | 0 | toString | 4 | 0 |
Expression类 | Term类 | ||||
方法名 | 方法规模 | 方法控制分支数目 | 方法名 | 方法规模 | 方法控制分支数目 |
Expression | 5 | 0 | Term | 4 | 0 |
addterm | 4 | 0 | addintergrationfactor | 4 | 0 |
toString | 17 | 2 | toString | 13 | 1 |
IntegrationFactors类 | ClassFunc类 | ||||
方法名 | 方法规模 | 方法控制分支数目 | 方法名 | 方法规模 | 方法控制分支数目 |
IntegrationFactors | 4 | 0 | process | 29 | 7 |
addfactor | 4 | 0 | mul | 28 | 5 |
toString | 22 | 2 | |||
CustomFunction类 | ExprProcess类 | ||||
方法名 | 方法规模 | 方法控制分支数目 | 方法名 | 方法规模 | 方法控制分支数目 |
CustomFunction | 4 | 0 | ExprProcess | 6 | 0 |
getFunction | 1 | 0 | getAns | 1 | 0 |
setFunction | 1 | 0 | splitTri | 31 | 4 |
getArgument | 1 | 0 | process | 32 | 4 |
setArgument | 1 | 0 | addBracket | 21 | 7 |
processArgument | 20 | 3 | |||
toString | 23 | 3 | |||
MergeProcess类 | PreProcess类 | ||||
方法名 | 方法规模 | 方法控制分支数目 | 方法名 | 方法规模 | 方法控制分支数目 |
getTreeSet | 3 | 0 | mySpilt | 24 | 6 |
setTreeSet | 3 | 0 | processCustom | 33 | 8 |
searchBest | 6 | 0 | pretreatment | 36 | 8 |
splitTri | 31 | 8 | |||
mergeDfs | 23 | 4 | |||
Runner类 | TrigonometricFunction类 | ||||
方法名 | 方法规模 | 方法控制分支数目 | 方法名 | 方法规模 | 方法控制分支数目 |
run | 13 | 1 | Trigonometric Function | 27 | 7 |
getExpression | 3 | 0 | |||
getAllexpression | 3 | 0 | |||
getValue | 4 | 0 | |||
toString | 19 | 5 |
内聚与耦合情况
类视图
方法视图
第二次作业加入了三角函数和自定义函数,以及要求实现多括号嵌套,难度相对于上一回大大加强了,所以我的代码量也是直线上升,可以看出多了很多方法。难度的上升也导致了类间的耦合情况相较于第一次大大上升。
这个问题尤其体现在了第一次作业的Func(现在更名为ConsolidateProcess类)中,这是一个优化类,在第一次中他的复杂度就比较高,后面又加入了三角函数的各种化简让其雪上加霜。
其次就是新加的方法,一个预处理preProcess和最后的文法校正exprProcess上耦合程度也是比较高,我分析后原因大概是我将所有的预处理都写在了preProcess,所有的校正都写在了exprProcess里面,还有就是这两个类里面会有一个方法几乎调用了整个类的所有方法,导致了耦合的程度大大提高。内聚程度和第二次作业一个原因,所以效果还可以。
UML类图
设计考虑
第二次作业在第一次的基础上进行迭代,在IntegrationFactors下新增三角函数类unc(TrigonometricFunction),对输入进行预处理,将函数展开,所以增加了preProcess预处理类和对应为其提供全套函数信息的CustomFunction类,之后就是进行进一步优化的操作,在ConsolidateProcess中加入了项内的诱导公式化简,平方和化简,在MergeProcess中加入了项间的诱导公式化简,最后对特殊文法进行校正,因此产生了ExprProcess类。
考虑到优化采用了优中选优,不断迭代的优化方式,可能会超时,因此开了一个子线程进行熔断处理,九秒之后自动输出当前最优答案,所以加入了Runner类。
优缺点分析
优点
对输入进行函数的预处理,提前展开函数,这种预处理与后面的解析耦合度较低,可以减少Bug的产生,也降低了难度。
进行了比较充分的优化,且为了防止优化超时创新才用了子线程方式来计时,超过九秒后自动熔断输出当前最优串。
缺点
还是在化简部分有点繁杂冗余,特别是在第二次作业化简永无尽头的这种情况下,这样导致了Bug会极其容易产生在这里。导致功亏一篑。
测试
分析自我程序Bug
开门红后天崩开局,强测错了5个点,自己跑评测机的时候一直测不出这个问题,强测测不出的问题倒是修了不少,有点蚌埠住了,也没办法,也只能说自己运气不好,评测机的制作上覆盖不够全面吧。
主要是错在了自定义函数的实参带入时,没有加括号再带进去,导致了改变了文意,解析错误。确实考虑不够全面,我只给整体函数的带入加了括号,完全忘记了实参也应该带括号T_T。
出现问题的方法在CustomFunction的toString方法中,圈复杂度和代码行均低于没有出问题的方法,这可能不是课程组想要的答案,我想这是由于我在测试时着重测试了那些圈复杂度和代码行多的模块,修好了那里的bug,忽略了这里的测试,导致了问题的产生。
分析他人程序Bug
房间7个人。利用自己制作的评测机对所有人的程序进行测试,刀出4个,有效性比较强。
大多都是一些cos(-000)无法解析,2*cos(x)*sin(x)=cos((2*x))这种优化上的问题,可见如果想得性能分,就要承担好因此错失正确分的后果。以及一定要仔细评测,自测防止优化出问题。
如果由于优化的问题导致了正确分的丢失,真的得不偿失了。
在这里针对第五位同学的代码设计结构进行了测试用例的设计,其代码设计当中运用了非常大量的正则表达式以及匹配,我觉得这里可能会出现错误匹配的问题,特殊设计了一些样例,真的出现了错误匹配的问题,由此找出了一个Bug。
Part 3 第三次作业
对项目的度量
类层次统计
类名 | 属性个数 | 方法个数 | 类总代码规模 |
Main | 0 | 1 | 49 Lines |
Parser | 1 | 6 | 102 Lines |
Lexer | 1 | 3 | 174 Lines |
ConsolidateProcess | 0 | 15 | 495 Lines |
Constant | 1 | 3 | 25 Lines |
Expression | 2 | 3 | 45 Lines |
Term | 1 | 3 | 36 Lines |
Variable | 1 | 3 | 25 Lines |
IntegrationFactors | 1 | 3 | 51 Lines |
ClassFunc | 0 | 2 | 61 Lines |
CustomFunction | 0 | 8 | 90 Lines |
ExprProcess | 0 | 5 | 120 Lines |
MergeProcess | 0 | 5 | 87 Lines |
PreProcess | 0 | 4 | 139 Lines |
Runner | 0 | 1 | 18 Lines |
TrigonometricFunction | 0 | 5 | 51 Lines |
DeriProcess | 0 | 9 | 216 Lines |
DerivationCount | 0 | 5 | 27 Lines |
DerivationMid | 0 | 5 | 30 Lines |
TriMergeUse | 0 | 5 | 30 Lines |
方法层次统计
Main类 | Parser类 | ||||
方法名 | 方法规模 | 方法控制分支数目 | 方法名 | 方法规模 | 方法控制分支数目 |
main | 49 | 2 | Parser | 5 | 0 |
parseExpression | 12 | 1 | |||
parseTerm | 11 | 1 | |||
parseIntegrationFactors | 14 | 1 | |||
parseFactor | 26 | 4 | |||
Lexer类 | ConsolidateProcess类 | ||||
方法名 | 方法规模 | 方法控制分支数目 | 方法名 | 方法规模 | 方法控制分支数目 |
Lexer | 5 | 0 | rawProcess | 63 | 13 |
next | 50 | 11 | sort | 5 | 0 |
nextback | 56 | 14 | cal | 32 | 8 |
judge | 8 | 1 | |||
merge | 43 | 9 | |||
lastProcess | 18 | 2 | |||
endProcess | 20 | 3 | |||
mySpilt | 32 | 7 | |||
getString | 25 | 5 | |||
getInt | 25 | 5 | |||
settlement | 12 | 2 | |||
triProcess | 19 | 4 | |||
checkTri | 45 | 8 | |||
checkMulMerge | 35 | 5 | |||
spiltTri | 31 | 7 | |||
checkTriMerge | 46 | 12 | |||
Constant类 | Variable类 | ||||
方法名 | 方法规模 | 方法控制分支数目 | 方法名 | 方法规模 | 方法控制分支数目 |
Constant | 4 | 0 | Variable | 4 | 0 |
toString | 4 | 0 | toString | 4 | 0 |
Expression类 | Term类 | ||||
方法名 | 方法规模 | 方法控制分支数目 | 方法名 | 方法规模 | 方法控制分支数目 |
Expression | 5 | 0 | Term | 4 | 0 |
addterm | 4 | 0 | addintergrationfactor | 4 | 0 |
toString | 17 | 2 | toString | 13 | 1 |
IntegrationFactors类 | ClassFunc类 | ||||
方法名 | 方法规模 | 方法控制分支数目 | 方法名 | 方法规模 | 方法控制分支数目 |
IntegrationFactors | 4 | 0 | process | 29 | 7 |
addfactor | 4 | 0 | mul | 28 | 5 |
toString | 22 | 2 | |||
CustomFunction类 | ExprProcess类 | ||||
方法名 | 方法规模 | 方法控制分支数目 | 方法名 | 方法规模 | 方法控制分支数目 |
CustomFunction | 4 | 0 | ExprProcess | 14 | 0 |
getFunction | 1 | 0 | getAns | 1 | 0 |
setFunction | 1 | 0 | splitTri | 31 | 4 |
getArgument | 1 | 0 | process | 32 | 4 |
setArgument | 1 | 0 | addBracket | 21 | 7 |
processArgument | 20 | 3 | |||
toString | 23 | 3 | |||
MergeProcess类 | PreProcess类 | ||||
方法名 | 方法规模 | 方法控制分支数目 | 方法名 | 方法规模 | 方法控制分支数目 |
getTreeSet | 3 | 0 | mySpilt | 24 | 6 |
setTreeSet | 3 | 0 | processCustom | 33 | 8 |
searchBest | 6 | 0 | pretreatment | 36 | 8 |
splitTri | 31 | 8 | |||
mergeDfs | 23 | 4 | |||
Runner类 | TrigonometricFunction类 | ||||
方法名 | 方法规模 | 方法控制分支数目 | 方法名 | 方法规模 | 方法控制分支数目 |
run | 13 | 1 | Trigonometric Function | 27 | 7 |
getExpression | 3 | 0 | |||
getAllexpression | 3 | 0 | |||
getValue | 4 | 0 | |||
toString | 19 | 5 | |||
DeriProcess类 | DerivationMid类 | ||||
方法名 | 方法规模 | 方法控制分支数目 | 方法名 | 方法规模 | 方法控制分支数目 |
derivateBegin | 21 | 4 | DerivationCount | 4 | 0 |
derivateExpression | 22 | 2 | getString | 1 | 0 |
derivateTerm | 25 | 4 | setString | 1 | 0 |
derivateFactor | 17 | 4 | getAchar() | 1 | 0 |
entry2String | 15 | 4 | |||
mergeRest | 29 | 7 | |||
getTerm | 23 | 5 | |||
spiltDerivation | 32 | 8 | |||
checkRepeat | 10 | 3 | |||
DerivationCount类 | TriMergeUse类 | ||||
方法名 | 方法规模 | 方法控制分支数目 | 方法名 | 方法规模 | 方法控制分支数目 |
DerivationCount | 4 | 0 | TriMergeUse | 4 | 0 |
getString() | 1 | 0 | getInteger HashMap | 1 | 0 |
setString | 1 | 0 | getSum() | 1 | 0 |
getAchar() | 1 | 0 | setSum | 1 | 0 |
内聚与耦合情况
类视图
方法视图
第三次作业加入了求导,第一眼看求导我是绝望的,特别是老师提醒这是最难的一次作业后,但是由于我前面两次作业的结构较好,我在DeriProcess类中不断复用前面的结构,高度浓缩180行就写完了第三次作业,这种浓缩当然也导致了DeriProcess的复杂度飘红,导致了DeriProcess的耦合度较高。
当然也导致了超时........
UML类图
设计考虑
第三次作业在第二次的基础上进行迭代,由于在第二次作业中才用了自定义函数预处理的方法,第三次作业又要求若求导因子在自定义函数内,要先求导再展开,那么逼迫第三次的结构只能继续在预处理上做文章——预处理求导,由此设计了DeriProcess类来集中预处理求导问题,其中遇到了函数返回需要至少两个值的问题,由于不熟悉二元组等的用法,所以直接建类返回图方便。由此产生了DerivationMid和DerivationCount两个类。
考虑到求导三角函数时极其容易产生倍角公式可以化简的对象,所以本次加入了倍角公式的化简,为了思路的统一,还是写在了复杂度已经很高的ConsolidateProcess类里面,也同样是函数需要返回多个值的需求,加入了TriMergeUse类来返回值。
优缺点分析
优点
对输入进行函数的预处理,提前展开函数与求导,这种预处理与后面的解析耦合度较低,可以减少Bug的产生,也降低了难度。思路上也比较清晰。
多写了一个二倍角优化公式,因为考虑到求导作用于三角函数,是特别容易生成可以被倍角公式化简的对象的,结果也确实如此。
缺点
第三次作业在互测时发现一个问题,如果提前预处理函数,如果最后的输入表达式里面不带这个函数,那么这个函数可以无限代价,如果给一些特别极端的函数包含求导数据,那么就会导致MLE或者TLE。或许只有在表达式用到时进行解析是一个合理的方法。
还是老生常谈的化简问题,即使我的主化简模块已经很复杂了,倍角公式还得硬着头皮往里面写。不然也没别的地方写,这算是从第一次就没有规划好优化一直积累下来的问题,越积累越头疼。
测试
分析自我程序Bug
中测和互测均未测出bug,强测TLE了一个点,还是挺猝不及防的,因为我的程序是带有熔断处理的,输入数据运行9s后就会自行熔断,输出当前最优解。就算真的是TLE,理论上应该是输出空串,然后判WA,确实是不太理解这个,难道熔断没用?T_T
后面和史学长,叶学长,荆学长交流发现,首先确实是我的程序TLE了,这里的TLE是这个数据在评测机的9s内我的一级优化都没有跑到,没有结果,输出了空串。但确实是在9s熔断了,只不过可能由于一些后续的垃圾进程回收机制,导致了“表面上”的TLE,这次也是被第二次的作业强测情况吓到了,不敢改了,即使我知道运算速度可能会有点问题......
主要问题就是在求导Deriprocess类的derivateFactor方法这里,我为了迎合三角函数的求导,对于dx(x**7)都会用乘法法则展开七次,其实这个只需要直接展开一次就好。这导致了x*22就会展开22次,时间消耗巨大,我TLE的那个点就是因为这些指数太高了。
由于是超时,所以出现了bug的方法和未出现Bug的方法再代码行和圈复杂度上没有必然关系,甚至我的代码行和圈复杂度还低于未出现bug的方法,毕竟我当时候就是为了减少代码量才用了这个低效率求导方法,确实是害怕言多必失,码多必错。
分析他人程序Bug
同样,我利用我强化版的评测机对同屋的房友的程序进行了测试,有效性还是比较强的,因为这回的评测机数据是纯随机生成的。
发现大多数人的错点都在与求导和函数的互相嵌套上,一层还可以,两层就不行了。
还有一位同学的错点在于算(cos(x)*sin(x))**2都会报错,这就是没有好好做测试,然后写新的内容把旧的也写崩了。
当然,本次我也针对部分房友的代码结构构造了一些特殊的样例,例如我在用评测机测评的时候发现有一位同学的程序抛出了异常,但我测评机的数据代价太大了,我就去了解了一下这位同学的代码结构,发现他在处理求导的时候不具备处理dx(-())这种形式的能力,这种形式输入就会抛出异常,所以我特殊构造了dy(-(cos((y**2-y*8))))这种数据,成功找到了他的一个Bug。
Part 4 架构设计体验
第一次作业
第一次作业是我核心架构的产生之处——递归下降,我利用递归下降的方法层次化解析按照文法输入的字符串,然后再在每一层编写对应的toString,将解析内容真正展开。至此,正确性任务就已经完成。
核心架构如下:
之后是优化的架构:主要就是分为项间的合并和项内的合并,先进行项内合并,再进行项间合并。
第二次作业
加入了三角函数,将其加入了核心架构的因子部分,归一化处理,之后对于自定义函数我为了避免出错在核心架构的上层加了一层预处理,先对函数进行展开操作,之后再由核心架构进行处理:
之后是优化的架构:迭代于第一次的优化架构,在项内合并部分加入一些诱导公式的化简,在项间合并加入平方和和诱导公式的合并。
第三次作业
加入了求导操作,迭代于第二次的思路,预处理到底,继续将求导进行如同自定义函数般的预处理,也就顺理成章写出来了。
优化迭代于第二次的架构,只是在项内优化多写了一个方法用于倍角公式的优化。
至此,迭代完成,得到了最后的架构。
Part 5 自动化测试
第一次作业
第一次作业的自动化测试比较简单,按照文法用类似于递归下降的方式随机构造数据即可,并且这样就已经大规模覆盖了评测,十分有用。
第二次作业
第二次作业的自动化评测则开始上难度了,首先自定义函数的生成就比较难以处理,最后采用了同时生成提供给java(即写好的作业)的字符串,生成提供给python(Sympy化简)的字符串,保证这两个字符串等价,后者只是前者的函数展开形式,成功构造了评测。
但还有一个问题就是由于三角函数的化简比较特殊,比如sin(0),cos(0),sin**2+cos**2的情况,纯靠随机很难随机出这种化简情况,所以需要写一个常量池,专门存一些这些特殊例子,生成的时候随机加入,提高对于化简的测试强度。
第三次作业
第三次作业的自动化评测依照第二次评测的思路,同样是生成两条等价的字符串,对于求导的处理和第二次对于自定义函数的处理是一样的。可以把求导看成一种特殊的函数。
但同样有一个问题就是,为了提高测试强度,我们需要构造出函数套函数,求导套函数,函数套求导的这些形式,需要特殊修改原有生成数据的规则。
Part 6 单元总结
作业
有一位大佬学长曾经说过:OOpre难度也就图一乐。我当时候是不信的,第一单元结束之后,我想我也可以叉着腰说一句:相比于OO,OOpre难度也就图一乐QwQ。
当然,我并不是说OOpre不好,相反,如果没有OOpre的入门与引导,我想我的第一单元难度将提升几个量级。我也不能在总结之时,可以以较为轻松的心态回顾整个单元。
第一单元的表达式展开与化简,核心思路是递归下降,更核心的思路是面向对象。对象与结构,抽象层次,类的编写都是我需要在面向对象中学习的思想。在写这三次作业中,确实是感受到了编写程序过程中需要人的耐心,严谨与一丢丢的灵感。
耐心帮助程序不断前进,严谨帮助避免bug的产生,一丢丢灵感帮助攻克思路难关。我想这是我在OO第一单元学到的精神上最宝贵的经验,对我以后的编程道路也一定大有帮助。
展示
我进行了OO的第一次展示,其实本人还是有点社恐的,但我决心要踏出舒适圈,大胆展示,培养自信,锻炼表达能力,所以在王子铭学长发出相关通知的时候权衡再三进行了报名,也认真准备了一波,最后也是比较成功的,也确实不得不承认,我因此确实得到了进步,大一的我但凡展示必须拿文案,手都在抖,这回展示全程没有看文案,以一种自信的态度讲完了15分钟,我由此感受到了我自己的成长。
测试
在CO中,我就已经开始对我的CPU进行全自动测试,我可以自信的说:跑过我的评测程序的CPU就没有不能通过课上强测的,这也给了我OO自动化评测的自信。
但,人生往往事与愿违,我满心欢喜的拿着第一次迭代作业强测互测都未找出bug的情况下进入了第二次迭代作业,在经过自己评测机几十万条的验证后,拿到的却是强测挂5个点的“好成绩”,这让我开始深深反省自动化评测和自己写作业时的问题,发现自己确实在第二次的评测机构造中有所缺陷,导致挂我的那个BUG在我的评测机中出现的概率相当于中彩票,所以也没有测出来。
所以在第三次我狠狠加强了随机程度,租了一台服务器的算力进行24H测试,也借此修了一些大规模数据下才会出现的bug,然后第三次没错,TLE了一个点.............
虽然评测机或者说第一单元的自动化测试给我带来了巨大的打击,让我甚至有了想放弃继续做评测的想法,毕竟看着别人不做评测都比我错的少,有的时候真的不知道我做评测的意义在哪里。这让我有一种投入无收获的挫败感。
但有时候也在想,我确实不能否认评测机是有用的这一事实,毕竟我借此修了不少Bug(虽然有的bug强测可能测不出来),在这种情况下,如果不自己做评测机,有没有可能我连互测都进不了呢?(笑)。
所以我还是会坚持下去,求一个问心无愧,尽力而为。
下一单元,加油!