OO_BLOG_UNIT1
OO_U1_blog
1. 作业思路
1.1 第一次作业
- 第一次作业任务为完成简单多项式导函数的求解。
构建思路
- 由于本次作业中只需要考虑幂指数因子的求导,因此第一次作业中我建立了Poly多项式类与Item项类两个类。在Poly类中实现顶层的存储,在Item类中实现项的存储、求导与输出等方法。读入时使用Hashmap便于合并同类项,求导输出的存储结构为ArrayList。
层次结构
![U1HW1](https://img2020.cnblogs.com/blog/2197936/202103/2197936-20210328192701385-1657972016.png)
度量分析
- 第一次作业因为结构简单,复杂度均在正常范围内。
method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
Total | 33 | 14 | 34 | 35 |
Average | 2.36 | 1.00 | 2.43 | 2.50 |
完成情况
-
在第一次作业中对于一些基础的信息还很生疏,比如不知道可以使用BigInteger.ONE和BigInteger.ZERO,因此建立了两个多余的静态常量。
-
第一次作业的互测与强测都非常惨,原因是一个输出简化时竟然直接忽略了系数为0指数不为0的因子的bug,写了直接替换掉+0这样的愚蠢代码,导致三个强测点挂掉。原因一方面是因为自己并没有进行自动化测试,同时也没有在自己测试的阶段重视测试。后来反思发现其实求导和输出阶段也应该使用Hashmap,并在输出函数里特判系数,而不是全部无脑输出再无脑粗暴替换。
-
整体而言,第一次作业完成时间较短,开始逐渐了解和学习面向对象的编程思维。
1.2 第二次作业
- 第二次作业任务为完成包含简单幂函数和简单正余弦函数的导函数的求解。
构建思路
- 主要分为Factor因子类、Term项类、Der类以及Merge类。数据存储方面,主要是Factor类与Term类用于存储,Pow类和Expr类继承Factor类,Tri三角函数类、Var变量类与Const常量类继承Pow类。Der类用于求导、InputParse类主要用于数据读入与初始表达式构建;由于第二次作业使用ArrayList进行表达式存储,于是构建了Merge类用于项内因子合并。
层次结构
![U1HW2](https://img2020.cnblogs.com/blog/2197936/202103/2197936-20210328192849275-490304539.png)
度量分析
-
以下列出第二次作业中度量分析复杂度较高的方法。
-
Element类中setMark()方法由于需要为各个词法分析单元初始化Mark值标识非结构化程度较高;Merge类中mergeTerm(), mergeFactor()以及lnTerm()方法的非结构化程度较高。由于在Merge类lnTerm方法对于Term类中因子类型的判断,使用了大量的for循环与if-else语句,导致其耦合度以及圈复杂度较高。
Method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
Element.setMark() | 11 | 1 | 10 | 11 |
Merge.mergeTerm(Expr) | 6 | 4 | 3 | 4 |
InputParse.parseFactor() | 2 | 6 | 6 | 6 |
Merge.mergeFac(Term) | 13 | 6 | 6 | 6 |
Merge.lnTerm(Term) | 21 | 8 | 13 | 13 |
Total | 119 | 90 | 120 | 136 |
Average | 2.09 | 1.58 | 2.11 | 2.39 |
完成情况
-
第二次作业由于没有思路,所以动手开始的时间比较晚。在思路构建的阶段通过博客了解了递归下降的知识,恰好周五讨论课班里两位大佬分享了递归下降的构建思路,于是采用了这种思路开始构建。因为自己对于递归下降的掌握不熟练、开始实现前框架并不明晰以及陡然上升的作业量,从周六开始构建在周天凌晨极限完成。
-
第二次作业的强测挂了三个点,第一个bug是项内判断第一个因子时特判过于复杂;第二个bug是debug是在Expr表达式类中写了一行printTerm()忘记删除,导致递归死循环。debug的时候以为这个TLE的问题修复不了了,后来找到zsz同学交流,他帮我找到了这个愚蠢的bug。
![U1HW2_2](https://img2020.cnblogs.com/blog/2197936/202103/2197936-20210328193013864-1119945200.png)
2.3 第三次作业
- 第三次作业任务为完成包含简单幂函数和简单正余弦函数及其嵌套组合函数的导函数的求解。
构建思路
-
本次作业核心结构类似第二次作业,功能上主要为字符串预处理、求导、输出三个层次。字符串预处理以及异常判断在Process类中实现;求导在Derive类中实现;Print输出在Basic类中实现。本次作业增添的非法性判断沿用递归下降的结构与思路在Lexer以及Parse类中完成判断并抛出异常。
-
关于优化,由于重构采用了Hashmap同类项的合并非常便捷。同时我还对项内只有一个表达式因子的情况进行了处理,在设计中因子求导返回项。当求导后的表达式因子(简记为tmp)仅有一个因子时,将tmp内该因子加入Term返回;tmp.size()不为0时,将表达式因子加入newTerm;当tmp.size()为0时设置sign为true,以标识该项仅有系数且设为0。
// factorDer()
case 1:
Term newTerm = new Term();
Expr tmp = exprDer((Expr) toderFactor);
if (tmp.size() == 1) {
for (Term t : tmp.getExprMap().keySet()) {
newTerm.setRatio(newTerm.getRatio().multiply(newTerm.addTerm(t)));
}
} else if (tmp.size() != 0) {
newTerm.add(tmp);
} else {
newTerm.setSign(true);
newTerm.setRatio(BigInteger.ZERO);
}
return newTerm;
- Copyable接口:由于作业中有乘积求导、链式求导的需求,因此通过Copyable接口,在Factor类及其子类,以及Term类中实现copy()方法,实现了深拷贝。具体实现方法参考了一篇博客。
![U1HW2_2](https://img2020.cnblogs.com/blog/2197936/202103/2197936-20210328194222508-483445570.png)
层次结构
- 由于类的内部方法与第二次作业相似,因此以思维导图的形式列出层次结构。
![U1HW2_2](https://img2020.cnblogs.com/blog/2197936/202103/2197936-20210328193051624-1904122990.png)
度量分析
- 以下列出第三次作业中度量分析复杂度较高的方法。
- 由于Term的termParse()方法中调用了Element类、Const类以及Term类的多个方法,因此耦合度较高;Term类在输出时由于是通过特判Factor因子类型,直接调用Factor子类的print方法,因此耦合度较高,优化结构时可以通过将这一部分放在Factor类中实现,同时也能避免相似部分在Tri三角函数类中的重复。
- termParse以及Term的print方法由于对于Factor因子的特判,写了多个case的switch-case以及大量的if-else结构,圈复杂度偏高。Tri三角函数类的print方法实际类似Term的print,v(G)也较高。
- 回顾实际debug的过程,圈复杂度较高的方法的确多次因为各种细节进行了修改。反思在之后的方法实现中应该进行必要的拆分与重构,使整体也更加面向对象而不是面向过程。
Method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
process.Element.setType() | 4 | 1 | 1 | 12 |
basic.Tri.print() | 14 | 2 | 6 | 13 |
process.Parse.crtCos() | 3 | 4 | 1 | 4 |
process.Parse.crtSin() | 3 | 4 | 1 | 4 |
basic.Expr.equals(Object) | 4 | 4 | 2 | 5 |
process.Parse.crtConst() | 4 | 4 | 3 | 4 |
basic.Term.equals(Object) | 5 | 4 | 3 | 6 |
der.Derive.factorDer(Factor) | 8 | 4 | 7 | 8 |
basic.Tri.equals(Object) | 6 | 5 | 4 | 7 |
process.Parse.expParse() | 10 | 5 | 5 | 7 |
process.Parse.exprParse() | 6 | 6 | 2 | 6 |
process.Parse.factorParse() | 1 | 6 | 6 | 6 |
process.Parse.termParse() | 27 | 8 | 11 | 15 |
basic.Term.print() | 19 | 8 | 12 | 17 |
Total | 186 | 146 | 172 | 235 |
Average | 2.27 | 1.78 | 2.10 | 2.87 |
完成情况
- 第三次作业在第二次作业的基础上完成,在此之前因为有不少同学都使用了递归下降的思路,所以通过讨论区、私聊等形式交流讨论推进的相对比较顺利,没有因为自己的思路局限而一直卡死。因为第二次作业极限完成的悲惨经历,第二次作业较早动手开始重构,主要因为想要便于合并同类项因此重构改为使用Hashmap。本次作业核心部分实现的时间并不长,但是因为构建过程中对于Hashmap的不熟练、求导模块以及输出模块的特殊情况大量特判疏漏导致debug耗时很长。
- 本次作业强测阶段没有测出bug,互测测出两个bug,一个是表达式输出字符串拼接的bug,另一个是三角函数因子嵌套因子的特判冗杂导致漏判嵌套因子为三角函数时指数的相等判断。
2. 心得体会
2.1 关于重构
反思
-
本单元我重构了两次。从第一次作业到第二次作业,因为出现了三角函数因子和乘积形式求导,因此进行了第一次注定的重构;从第二次作业到第三次作业,因为在第二次作业合并同类项部分实现的很痛苦,于是想到了放弃ArrayList使用自己并不熟练但是很香的Hashmap(事实证明真的很香),同时将第二次作业中写的冗杂的代码进行一些精简。
-
完成作业后再次回顾自己的作业,发现仍旧存在一些可以在重构时就优化好的问题。比如输出部分,作业中对于类似sin(Factor)和项的输出的情况,我是在Tri三角函数类中特判Factor再分别按照不同情况输出,项Term的输出同样也是先特判再输出,但这两部分实际上可以完全类比Derive求导部分,直接判断丢进factorPrint(),写一次switch-case判断因子类型即可,这样降低写错的概率以及出错后debug的难度(卑微.jpg)。
体会
- 第一次重构的过程在实现中体会到了Parse,第二次重构在重复性实现读入的部分加入合法性判断,进一步加深了对于递归下降的理解,同时一些结构上的改变使得代码更加面向对象、可读性更强。
2.2 关于测试
- 从计组到OO,测试的关键性越来越显著。
反思
- 三次作业的互测中我均被hack出了1-2个同质bug,第一、二次作业都挂掉三个强测点,第三次在同学的测试帮助下侥幸过了。但在debug阶段我发现每一次的错误其实都是很细节的问题:第一次作业的+0无脑替换、第二次作业debug时多写了一行termPrint()忘删导致递归死循环、第三次作业里的outStr拼接写成str拼接,类似的问题其实在写代码的时候完全可以避免,要通过后期读代码de掉这样的bug几乎不可能。
-
后来想了想,三次作业出bug的地方都是大半夜不怎么清醒的时候写的(我直接爬),所以反思就是:测试很重要哇,还有就是恰好饭、睡醒了再写作业,效率和正确率都会更高。
-
我在第二次作业中借鉴讨论区的思路,用Python写了一个简易的测评机,但是它实际上只能进行制造测试数据、验证计算结果是否正确,暂且不说随机的产生的数据比较弱,很难覆盖全所有的情况,我所写的测评机还出现过制造的数据异常等bug。bug一个个fix完测完之后测试的强度仍旧不够,我就没有在第三次作业里对其进行优化和使用了。整体上自测与互测都是通过针对形式化描述尽可能的自己手动构造与测试。
小收获
- 第二次作业中同room里的Saber同学分package的细节,让我在第三次作业里改善了代码的框架。无论是构建阶段还是后期回顾,按照功能将多个类分在一个package下会使结构清晰,如下图为第二次和第三次作业的对比。
体会
-
我理解互测阶段主要的目的是从room里同学的构建框架学习,也就是通过读代码来学习一些知识,但是实践的时候因为别人的代码短时间内实在是难以看懂,而且看完每一个同学的代码根本不实际,因此互测就变成了黑箱测试,完全数据驱动,所以感觉自动测试工具几乎就成了互测阶段必备的。但是写完作业的时候所剩的时间并不充裕,实现、完善一个自动化评测机对于我而言其实并不实际。因此我在互测时采用了将自己在写代码debug时用到的样例的方法。而且到了第三次作业,完成作业本身已经花费了我较多的精力,因此我参与互测的意愿并不强烈了,想法几乎就是:反正得不了多少分,还不如休息一天emm,事实上也是这么做的emm。
-
总之,对于互测,我的感受是学会写自动评测机的确是必需的,不过对我而言现学现实现、优化并不实际,需要事先掌握一些知识才能更好地在完成作业的同时实现评测机。
2.3 关于讨论
-
UNIT1作业的完成让我感受到交流与讨论的重要性。
-
第一次作业主要是通过搜索一些基础的Hashmap的方法等做完了,讨论与交流的部分并不多。不过通过看讨论区的提问和回答,看到了一些可以在之后作业中改进的细节。
-
第二次作业因为难度陡然上升,自己看了几篇有关递归下降的博客之后思路还是很混乱,导致迟迟没有设计。恰好第二周是研讨课,课上班里有两位大佬都分享了递归下降的相关知识和设计思路。深夜和舍友在宿舍沟通和交流之后理清了思路,当天晚上把框架写了出来。后期debug的时候TLE的bug起初以为是解决不了的递归的缺陷,但是在讨论区看到交流以及与同学私信沟通后,发现了输出部分的问题。
-
- 在第三次作业漫长的debug过程中,美丽善良智慧的舍友小杨分享了一篇关于深拷贝的博客,救我过了一个深坑;在我提交了五六次的时候弱测始终有两三个点过不了,有两位非常热心的同学帮我跑了测试,找到了我的亿点点bug。弱测第三个点貌似卡住了不少人,直到周天晚上九点多的时候还有几位同学在私信我解决掉弱测第三个点的可能坑点。
-
当然,我认为所有的交流和讨论要建立在自己充分思考可搜索的基础上,在这点上我的确需要改进。
- 啊,写第三次作业的时候我整个人精神状态实际上极度不正常,现在想想真是辛苦各位帮助我的同学了!写到这里,不禁感叹,俺真是人菜bug多。继续努力吧~~~