第一单元作业总结
一.程序结构分析
第一次作业
第一次作业还完全没有面向对象的概念,仅有的常数和幂函数两种情况还被我和合并成一个Term类处理了。可以看到复杂度较高的分别是多项式转字符串、解析读入系数、项转字符串三个方法。
其中项转字符串确实是因为需要分类讨论的情况比较多导致复杂度高,不过感觉应该也可以有提取共同步骤的化简处理方法,但是复杂度下降会比较有限,除非强行拆成多个类分别解析项的系数和次数。
解析读入系数也是因为情况确实比较多,而多项式转字符串主要是为了进行优化增加了复杂度。
Method | ev(G) | iv(G) | v(G) |
---|---|---|---|
Poly.Poly() | 1 | 1 | 1 |
Poly.add(Term) | 1 | 2 | 2 |
Poly.derive() | 1 | 2 | 2 |
Poly.getPoly() | 1 | 1 | 1 |
Poly.toString() | 6 | 7 | 8 |
PolyComputer.PolyComputer() | 1 | 1 | 1 |
PolyComputer.getCoef(String,String) | 5 | 5 | 6 |
PolyComputer.getIndex(String,String) | 3 | 3 | 3 |
PolyComputer.main(String[]) | 1 | 1 | 1 |
PolyComputer.parsePoly(String) | 1 | 3 | 3 |
PolyComputer.replacePoly(String) | 1 | 1 | 1 |
Term.Term(BigInteger,BigInteger) | 1 | 1 | 1 |
Term.changeCoef(BigInteger) | 1 | 1 | 1 |
Term.changeIndex(BigInteger) | 1 | 1 | 1 |
Term.derive() | 1 | 3 | 3 |
Term.getCoef() | 1 | 1 | 1 |
Term.getIndex() | 1 | 1 | 1 |
Term.toString() | 11 | 7 | 13 |
Class | OCavg | WMC | |
Poly | 2.6 | 13 | |
PolyComputer | 2.17 | 13 | |
Term | 2.71 | 19 |
第二次作业
第二次作业增加了三角函数,其实是运用面向对象方法的好机会。但是我对于这种思想还是不够理解,觉得为了化简起来方便,把常数、幂函数、三角函数都放到了一个类里。(现在想来其实用多态可以把toString
做的更漂亮,复杂度更低)。
而且不知道为什么,我明明都想到每一项可以用 系数 幂函数 cos函数 sin函数的乘积表示了,却没有直接使用四元组的方式表示一项,而是使用了数组存了四个Term
。这样的结果就是我需要设置大量方法来读取Term
中的指数信息,导致代码行数激增,程序臃肿难看。
复杂度较高的代码还是沿用了作业1的toString
造成的,还有重写compareTo
的时候一时没想到更简单的排序方法,所以采用的笨方法罗列的情况过多。
Method | ev(G) | iv(G) | v(G) |
---|---|---|---|
Feature.Feature(BigInteger,BigInteger,BigInteger) | 1 | 1 | 1 |
Feature.compareTo(Object) | 7 | 4 | 7 |
Feature.getI1() | 1 | 1 | 1 |
Feature.getI2() | 1 | 1 | 1 |
Feature.getI3() | 1 | 1 | 1 |
Feature.getOneFeature1() | 1 | 1 | 1 |
Feature.getOneResult() | 1 | 1 | 1 |
Feature.getOneResult2() | 1 | 1 | 1 |
Poly.Poly(BigInteger,BigInteger,BigInteger,BigInteger) | 1 | 1 | 1 |
Poly.Poly(Feature,BigInteger) | 1 | 1 | 1 |
Poly.Poly(String) | 2 | 8 | 11 |
Poly.compareC(Poly) | 1 | 1 | 1 |
Poly.derive() | 2 | 7 | 8 |
Poly.getC() | 1 | 1 | 1 |
Poly.getF() | 1 | 1 | 1 |
Poly.getI() | 1 | 2 | 2 |
Poly.judgeCon() | 1 | 3 | 3 |
Poly.judgePos() | 1 | 1 | 1 |
Poly.toString() | 8 | 9 | 11 |
PolyComputer.deleteSpace(String) | 1 | 1 | 1 |
PolyComputer.getTerm() | 1 | 1 | 1 |
PolyComputer.judgeFormat(String) | 1 | 1 | 1 |
PolyComputer.judgeIndex(String) | 3 | 3 | 4 |
PolyComputer.judgeSpace(String) | 1 | 1 | 1 |
PolyComputer.main(String[]) | 1 | 4 | 4 |
PolyComputer.replacePoly(String) | 1 | 1 | 1 |
Polylist.Polylist() | 1 | 1 | 1 |
Polylist.add(TreeMap |
1 | 3 | 3 |
Polylist.derive() | 3 | 4 | 5 |
Polylist.getTerm() | 1 | 1 | 1 |
Polylist.judge_Change_or_not(Poly,Poly,Poly) | 1 | 1 | 1 |
Polylist.merge(Poly,Poly) | 3 | 1 | 3 |
Polylist.parsePoly(String) | 1 | 5 | 5 |
Polylist.toOne() | 1 | 7 | 7 |
Polylist.toString() | 5 | 5 | 6 |
Term.Term(BigInteger,String) | 1 | 1 | 1 |
Term.Term(String,String) | 1 | 1 | 1 |
Term.getIndex() | 1 | 1 | 1 |
Term.judgeMinusOne() | 2 | 1 | 2 |
Term.judgeNeg() | 2 | 1 | 2 |
Term.judgeOne() | 2 | 1 | 2 |
Term.judgePos() | 2 | 1 | 2 |
Term.judgeZero() | 2 | 1 | 2 |
Term.setIndex(String) | 2 | 2 | 4 |
Term.toString() | 5 | 3 | 6 |
Class | OCavg | WMC | |
Feature | 1.75 | 14 | |
Poly | 3.18 | 35 | |
PolyComputer | 1.57 | 11 | |
Polylist | 3.33 | 30 | |
Term | 2.2 | 22 |
第三次作业
第三次作业由于难度陡然升高,再采用原来那种层次不清晰的写法,不仅容易出现问题,实现起来也很不清晰。于是我查看了往年的博客并借鉴了学长的思路构造类之间的层次关系,这样实现出来的类图相比起来就简洁而清楚了很多。
但是由于没有采用工厂模式的方法,而是把识别表达式和构造项的两个过程混杂在了一起,导致Parse
类复杂度还是有些高。如果让parse
只完成解析的工作,而把创建具体项的工作统一转交给Factory
感觉会更好。
还有就是因为我选择解析的方法比较原始,所以大部分wf的情况都只能在main
中通过特判来识别,这也导致了Main
复杂度的飙升。然而当时能想到的解析方法只有这么以一种,所以也没有什么办法。
Method | ev(G) | iv(G) | v(G) |
---|---|---|---|
Constant.Constant(BigInteger) | 1 | 1 | 1 |
Constant.add(Constant) | 1 | 1 | 1 |
Constant.derive() | 1 | 1 | 1 |
Constant.getConstant() | 1 | 1 | 1 |
Constant.multiply(Constant) | 1 | 1 | 1 |
Constant.toString() | 1 | 1 | 1 |
Cos.Cos(Factor,BigInteger) | 1 | 1 | 1 |
Cos.derive() | 2 | 3 | 3 |
Cos.toString() | 3 | 2 | 3 |
Main.deleteSpace(String) | 1 | 1 | 1 |
Main.js(String) | 5 | 3 | 7 |
Main.judgeFormat(String) | 6 | 1 | 6 |
Main.judgeIndex(String) | 5 | 3 | 6 |
Main.judgeSpace(String) | 1 | 1 | 1 |
Main.main(String[]) | 3 | 6 | 6 |
Main.replacePoly(String) | 1 | 1 | 1 |
Mul.Mul(ArrayList ) | 1 | 1 | 1 |
Mul.Mul(Factor,Factor) | 1 | 1 | 1 |
Mul.Mul(Mul,Mul) | 1 | 1 | 1 |
Mul.derive() | 1 | 4 | 4 |
Mul.getList() | 1 | 1 | 1 |
Mul.multi(Factor) | 1 | 1 | 1 |
Mul.multi(Mul) | 1 | 1 | 1 |
Mul.toString() | 5 | 3 | 5 |
Parse.parseCos(String) | 2 | 3 | 3 |
Parse.parseFactor(String) | 6 | 6 | 6 |
Parse.parseMul(String) | 4 | 6 | 10 |
Parse.parseNum(String) | 2 | 1 | 2 |
Parse.parsePlus(String) | 4 | 5 | 13 |
Parse.parsePlusFactor(String) | 2 | 2 | 2 |
Parse.parsePow(String) | 4 | 4 | 4 |
Parse.parseSin(String) | 2 | 3 | 3 |
Plus.Plus(ArrayList ) | 1 | 1 | 1 |
Plus.derive() | 1 | 2 | 2 |
Plus.toString() | 2 | 5 | 6 |
Pow.Pow(BigInteger) | 1 | 1 | 1 |
Pow.derive() | 2 | 2 | 2 |
Pow.toString() | 3 | 1 | 3 |
Sin.Sin(Factor,BigInteger) | 1 | 1 | 1 |
Sin.derive() | 2 | 3 | 3 |
Sin.toString() | 3 | 2 | 3 |
Class | OCavg | WMC | |
Constant | 1 | 6 | |
Cos | 2.33 | 7 | |
Main | 3.14 | 22 | |
Mul | 1.88 | 15 | |
Parse | 4.62 | 37 | |
Plus | 2.67 | 8 | |
Pow | 2 | 6 | |
Sin | 2.33 | 7 |
二.程序bug分析
- 第一次作业
因为第一次作业相对比较简单,所以没有出现bug
- 第二次作业
第二次作业的bug主要是粗心造成的,具体来说是在优化方法中的两行,一行忘记随着函数改动修改变量名,一行放在了错误的大括号内。
第一个bug是在优化的过程中,发现优化和求导有重用的部分,所以提取出来作为单独的一个函数。结果提取的过程中有一行没有修改成新函数的变量名,而是沿用了原来在求导中调用的全局变量,导致没有通过报错检查出来。
而且这两个bug的触发的情况非常特殊,写完优化距离截止提交只剩下一个小时的时间,这一个小时中我的测评机和我自己出的数据都没能发现(在强测中也只有一个点测出了这个bug)。
从第二次作业中反映出来的主要问题就是,如果决定优化,一定要留出充足的测试时间,不然如果优化出了bug还不如不优化。
- 第三次作业
吸取了第二次作业的教训,加上作业难度较高,这次我完全放弃了优化,试图以这种方式保证正确率,然而却没能成功。
这次的bug又是出在一个很细节的地方,以至于改动不超过一行10个字符。在拆分表达式的过程中,由于我采用了一种非常笨的方法,根据加减号拆出每一项,所以对于* -1
这种情况如果不特判的话会错把1后面的部分拆成另外一项。对于**-1
,我把**
换成了^
进行了特判,却忽略了上面这种情况。
归根到底还是因为第三次作业我采取的解析表达式方法不好,但是因为冥思苦想只想到这么一种解决方案,所以也只能硬着头皮往下做了。这种拆分方法就导致了大部分wf的情况都需要我在解析表达式之前进行特判,之后才能进行去空格和符号替换等预处理工作,让我花费了大量时间和提交次数才通过中测。结果最后还是遗漏了上面这种情况没有考虑到。
三.发现别人bug采取的策略
- 第一次作业
由于第一次作业相对比较简单,涉及的情况不是很多,所以在自己写程序的时候我就基本列出了各种情况的测试数据用于检验自己的程序。互测时直接使用这些数据来测试别人的代码。
我也尝试了一下阅读别人的代码寻找bug,结果发现读了很久才能读懂,而且没发现什么问题,感觉效率有些低,不如直接通过数据找bug。
- 第二次作业
这次作业的复杂度相比起第一次就明显上升,而且涉及到存在优化的问题,肉眼很难判断结果是否正确,所以我尝试搭建了自己的评测机。但是可能同屋的同学都太强了,我评测机跑了很久也没有找出任何人的bug(最后互测结束时,除了我因为愚蠢的bug被暴打以外,其他七个人也就被找出一个bug),可能想测出bug还需要修改一下随机生成数据的边界,让生成的数据更有效才行。
- 第三次作业
因为没有时间研究评测机的构建,这次作业又不得不回归手动出数据。我主要还是结合了自己写程序时的经验,把一些需要特殊处理的情况结合在一起手动构造测试数据。结果发现效果反而出乎意料地好,(也可能是因为之前两次感觉都在a屋,同屋大佬没有bug,这回感觉在c屋,同屋同学bug比较多)。我随意编造的数据在本地测试时查出同学bug的次数占到了90%以上,最后一共提交的7个测试数据共命中21人次,远胜过我之前评测机的测试效果。如果不是碍于同学情面我觉得找出更多这样的测试数据并不难,但也从一个方面说明”人工攻击“因为具有针对性,有时候可能比随机生成效率更高。
四.应用对象创建模式
三次作业写完了感觉对于对象创建模式我还是没能特别掌握,所以可以想见在写作业的时候我也不可能使用对象创建模式了。前两次作业由于类相对简单,使用二元组、四元组其实就足够了;第三次作业中其实可以使用工厂模式完成对象的构造。但是因为当时我采用了“倒序”的方式,先完成了数据类和相关方法的构建,后编写的输入解析类,迫于时间压力,提取表达式和创建数据类的方法就混杂在一起写了,导致复杂度明显升高。
具体实现时,其实只需要在Parse
类中找到对应项的内容后,把String
传递给Factory
类,再由Factory
完成数据对象的构造即可,这样就实现了创建对象的过程对外完全封装。
五.对比和心得体会
相比起优秀代码,我的程序明显还是缺乏面向对象的思想,这一点在我三次作业都需要进行比较大的改动上体现的很明显。说明我编写的程序不够模块化,或者说高内聚低耦合,因而缺乏重用性,一旦需求改变只能推倒重来。这也是我在设计的时候就缺乏清晰的思路,经常是想到哪就写到哪的直接后果。几位同学对于对象创建模式应用熟练,表达式化简也很巧妙,最可贵的是类之间的关系和方法调用都十分清晰,这都是值得我学习的。
第一个单元的作业过程很痛苦,但是完成作业最后通过测试的时候也就获得了更多的成就感。感觉oo的思想十分抽象,而面对的作业又十分具体,怎么平衡好两者之间的矛盾,使之达到和谐统一,是我未来面对的最大问题。完成作业虽然重要,但是更重要的是体会设计的思想,而这一点我现在可能还没入门。希望第二单元作业完成的时候,我能对面向对象的思想有更深入的认识!