一、第一单元作业回顾
系列一作业分为三周进行,都是表达式求导,难度渐进。
第一次实现的是简单幂函数的求导,第二次加入了sin和cos两种三角函数,第三次实现了三角函数内的嵌套以及引入了表达式因子。
主要学到的东西有:
IDEA的操作使用(包括checkstyle/statics/MetricsReloaded等插件的使用)
正则表达式的使用
Biginteger
java中字符串处理
Arraylist的使用
继承与接口
java建立二叉树
递归分析的思路
二、程序结构分析
1.第一次作业
(1)类图
(2)规模
第一次作业的规模并不算大。
(3)复杂度
Complexity metrics | 星期二 | 26 三月 2019 21:18:27 CST | |
---|---|---|---|
Method | ev(G) | iv(G) | v(G) |
DeriPolynomia.main(String[]) | 7 | 24 | 24 |
Term.addx(BigInteger) | 1 | 1 | 1 |
Term.deri() | 1 | 1 | 1 |
Term.getTerm1(String) | 1 | 1 | 2 |
Term.getTerm2(String) | 1 | 1 | 1 |
Term.getTerm3(String) | 1 | 1 | 1 |
Term.getTerm4(String) | 1 | 1 | 2 |
Term.getTerm5(String) | 1 | 1 | 1 |
Term.getc() | 1 | 1 | 1 |
Term.getx() | 1 | 1 | 1 |
Term.numDeal(String) | 2 | 2 | 4 |
Term.out() | 1 | 11 | 11 |
第一次作业可以明显看出我还停留在面向过程的思路,main方法一main到底,输入的处理直接都放在了main方法中,而没有新建一个类或者方法,这样导致我的main方法难以调试,容易出错。
2.第二次作业
(1)类图
(2)规模
第二次作业的规模比第一次变大了,但仍然可以接受。
(3)复杂度
Complexity metrics | 星期二 | 26 三月 2019 21:15:57 CST | |
---|---|---|---|
Method | ev(G) | iv(G) | v(G) |
Deri.checkS(String) | 4 | 10 | 13 |
Deri.deal() | 8 | 16 | 16 |
Deri.deri(Term) | 1 | 1 | 1 |
Deri.getList() | 1 | 1 | 1 |
Deri.getList2() | 1 | 1 | 1 |
Deri.main(String[]) | 1 | 9 | 10 |
Term.addChang(BigInteger) | 1 | 1 | 1 |
Term.addCosC(BigInteger) | 1 | 1 | 1 |
Term.addSinC(BigInteger) | 1 | 1 | 1 |
Term.getChang() | 1 | 1 | 1 |
Term.getCosC() | 1 | 1 | 1 |
Term.getFlag() | 1 | 1 | 1 |
Term.getSinC() | 1 | 1 | 1 |
Term.getTerm(BigInteger,BigInteger,BigInteger,BigInteger) | 1 | 1 | 1 |
Term.getTerm(String) | 10 | 9 | 10 |
Term.getTerm1(String) | 14 | 14 | 14 |
Term.getxdC() | 1 | 1 | 1 |
Term.out() | 5 | 14 | 18 |
第二次作业在耦合度上比起第一次作业有所进步但仍然复杂度很高,我将对于输入的预处理放在了两个函数中checkS进行合法性的判断,deal进行split前的预处理,预处理之后进行项的处理,我把它放在了getTerm和getTerm1两个函数中,而没有再继续向下细分,这就导致我在处理项的过程中无法根据输入项的类型准确定位bug点,比较麻烦。
3.第三次作业
(1)类图
我把所有的运算符和因子都归并到Node类之中,并在Node类中实现了Ob对象,对于运算符,我只通过setType方法加以标识,而对于幂函数因子、三角函数因子、常数因子,我则实例化其对应Node中的Ob对象。
而在求导的时候,对于运算符Node,由于他最后返回的是几个符号和左右孩子求导结果的组合,因此我直接在Node类中实现dif接口完成此类求导,而对于因子类的Node,他返回的应是一个给予自身属性运算之后的新的Node,因此我又在Ob中预留了dif函数,让我的各因子——Chang/Mi/Tri去继承Ob类,实现各自的dif函数,这样的命名其实容易导致混淆,以后要加以注意。
(2)规模
第三次作业的规模较大,是一个不小的挑战。
(3)复杂度
Complexity metrics | 星期二 | 26 三月 2019 21:06:46 CST | |
---|---|---|---|
Method | ev(G) | iv(G) | v(G) |
Chang.dif() | 1 | 1 | 1 |
Chang.getI() | 1 | 1 | 1 |
Chang.setI(BigInteger) | 1 | 1 | 1 |
Chang.setI(String) | 1 | 1 | 1 |
Chang.setI(int) | 1 | 1 | 1 |
Deal.get(char[]) | 1 | 1 | 1 |
Deal.getEx(char[]) | 2 | 4 | 6 |
Deal.getFa(char[]) | 10 | 10 | 10 |
Deal.getNum(char[]) | 1 | 1 | 3 |
Deal.getTe(char[]) | 2 | 4 | 6 |
Deal.getcFa(char[]) | 2 | 6 | 6 |
Deal.getsFa(char[]) | 2 | 6 | 6 |
Deal.getxFa(char[]) | 2 | 3 | 3 |
Deal.preDeal(String) | 1 | 28 | 31 |
Deal.rpl(String) | 1 | 1 | 1 |
Main.main(String[]) | 1 | 3 | 3 |
Main.wrong() | 1 | 1 | 1 |
Mi.dif() | 1 | 1 | 1 |
Node.Node() | 1 | 1 | 1 |
Node.Node(BigInteger) | 1 | 1 | 1 |
Node.Node(char) | 2 | 2 | 10 |
Node.dif() | 2 | 2 | 7 |
Node.fdif() | 1 | 1 | 1 |
Node.getData() | 1 | 1 | 1 |
Node.getLchild() | 1 | 1 | 1 |
Node.getRchild() | 1 | 1 | 1 |
Node.getType() | 1 | 1 | 1 |
Node.hdif() | 1 | 1 | 1 |
Node.mdif() | 1 | 1 | 1 |
Node.out() | 2 | 14 | 22 |
Node.out_r() | 1 | 11 | 12 |
Node.pdif() | 1 | 2 | 2 |
Node.setData(Ob) | 1 | 1 | 1 |
Node.setLchild(Node) | 1 | 1 | 1 |
Node.setRchild(Node) | 1 | 1 | 1 |
Node.setType() | 1 | 1 | 4 |
Node.setType(char) | 2 | 2 | 7 |
Node.setType(int) | 1 | 1 | 1 |
Tri.dif() | 1 | 2 | 2 |
Tri.getType() | 1 | 1 | 1 |
Tri.setType(int) | 1 | 1 | 1 |
第三次作业我第一次使用了接口和继承,事实上由于对于这两个概念的了解不够深入,导致我的继承关系显得比较混乱,但总归是在不同的类之间建立了一定的逻辑关系。并且可以看出,虽然第三次作业在难度和代码量上比起前两次作业都有一个质的提升,但是我的方法的复杂度并没有随之有一个很大的变化,这也得益于继承和接口的使用,也正是在这一次作业开始,我觉得自己开始对于“面向对象”的思维方式有了新的认识。
至于方法复杂度,preDeal我复用了部分前两次作业的代码,虽然比较复杂但对于我个人而言理解并没有问题,但如果是在团队开发的过程中,这样的方法自然是要尽量避免的。
三、bug分析
1.第一次作业
没有在公测与互测中发现bug
2.第二次作业
在优化输出的过程中,为了实现将‘1*其他因子’这么一个式子中的‘1*’忽略,我先把整个式子输出到一个数组,把这个数组转成字符串,再把所有的‘1*’抹掉,却忽略了判断被抹掉的‘1’是不是真的是独立的因子,导致‘x^11*5’中的‘1*’也被抹掉,就这么一个bug,导致我被扣将近四十分,可谓损失惨重。
3.第三次作业
在读取因子的时候,如果碰到运算符+或-之后,我就直接将之后的内容作为常数因子进行处理,忽略了之后是表达式因子的情况,因此对于‘-(x)’这样的表达式我会报错WF,导致强测和互测中都出现了bug。
四、互测策略
我在互测之中一般按三步进行:
1.利用自己在构造代码过程中发现的容易出错测试集进行测试。
2.阅读代码,尝试找出bug
3.利用脚本进行大量测试
个人测试集测试
这一步直接利用自己在构造代码时准备的测试集进行测试,其中大部分其实是我自己的代码在完善过程中出现bug的测试点,事实证明大家易犯的错误实际上是类似的,第一次作业和第三次作业我都在这一步发现了同屋其他人的bug。
阅读代码
对于同时测试6、7个人的互测而言,阅读代码实际上有着不小的难度,我只在第一次作业,代码规模较小的情况下通过阅读代码的方式发现了一位同学的bug,在后两次作业的互测过程中都没有发现bug,这一方面是因为时间有限,没有办法非常细致地阅读每位同学的代码,另一方面也有着先入为主,被别人代码引导了思路的原因。
但阅读代码也不仅仅是为了寻找bug,也可以使我们得到其他同学代码的启示,我在第一次作业处理输入时是采用的‘匹配-替换-再匹配’的模式,而在互测过程中,我发现有不少同学采用的是‘先进行符号处理,再用+进行split,再分别处理各个项’的方式,这给我的第二次作业的处理提供了很大的帮助。
脚本测试
利用bash脚本进行批量测试,再对于输出结果进行肉眼比对,有同学在得到输出结果之后利用matlab或者sympy进行比对,很遗憾没能掌握这样的测试方法。
五、Applying Creational Pattern
对于java设计模式我了解的并不是很多,通过大致查阅资料我发现自己的设计有的地方具有抽象工厂模式的特征,有的地方又有着建造者模式的特征,总的来说前三次作业我都有一种埋头去干的感觉,在顶层设计上并没有提前做好准备,显得比较混乱,这也导致我在debug上花费了大量的时间,这一点需要我在之后的作业中进行改进。