一、程序结构分析
第一次作业
思路:
本次作业比较简单,只有幂函数求导,并且不支持连乘,用了一个Mono类,Poly类,一个主类就解决了战斗
Mono类主要存系数和指数
Poly类用HashMap存储每个幂函数的系数和指数,并且在Poly类中实现了求导在主类中实现了数据的读入和处理,用大正则直接莽的
分析总结:
- 第一次作业还不太能应用面向对象的思维,也没有太考虑到扩展性,导致第二次直接重构
附上第一次作业UML图,由于构思的不好,显得就像面向过程
附上各类的的复杂度分析
- 有OCavg和WMC两个项目,分别代表类的方法的平均循环复杂度和总循环复杂度。
第二次作业
思路:
作业二因子中加入了三角函数,并且在单项式中支持因子连乘,也许已经稍微有一点摸到面向对象的边缘了。
设计处理输入类RegExp和StringProcess。RegExp中主要是分级并返回各级正则表达式,StringProcess中利用正则表达式进行字符串的分解和读取。
并且由于本次要判断字符串是否合法,不合法输出"WRONG FORMAT!",因此我自定义了一个异常类来解决,继承自Exception。
在RegExp中返回了一个整个多项式的正则表达式,如果匹配出来的长度和输入不相等则抛出一个自定义的异常(输入为空也抛出)。考虑到第一次作业可扩展性不足,这次经过理性分析后,设计了抽象类Factor,并且三角函数类Trigon,幂函数类Mono(从第一次作业copy的懒得改),单项式类Item全部继承自Factor。
Factor类有属性系数和指数,其中Item的指数恒为1,除此之外还需要实现比较系数equals()方法、输出print()方法和求导derivative()方法。
常数因子和幂函数因子均用Mono存储,常数因子的指数为0,幂函数因子的系数为0,Trigon类中有属性Type来区分sin和cos。
其中Item类中需要另写一个求导方法,因为单项式求导的结果可能不只一个单项式,返回的可能是ArrayList,个人认为这是设计的时候出现的问题,在作业三中有对求导方法进行统一。
Poly类中用ArrayList存储多个单项式,求导则令其中所有的单项式分别求导,再将返回的所有ArrayList<Item>组合成一个新的ArrayList即可。
分析总结:
化简部分做的不是很完善,只做了合并同类项和部分的三角函数化简,第二次作业的性能分不是很理想
但是第二次作业有了一定的可扩展性,第三作业并没有重构
附上第二次作业UML图(手画的关系简洁一些,更能体现编程思路和各类之间的关系)以及第二次作业各类复杂度分析
第二次代码总长近700行
第三次作业
思路:
- 由于作业三增加了嵌套表达式因子,字符串处理稍微采纳了一些递归下降的思想,设计了一个字符串处理的类Parser,在判断完空格的错误之后,进行字符串处理
只需要按照指导书所写的文法进行读取即可,最底层实现getTri()获取三角函数,getPow()获取幂函数,getConstant()获取整数等即可
例如获取三角函数代码如下(未遵循checkstyle):
public ComplexTrigon getTri() throws WrongFormatException { StringBuffer s = new StringBuffer(""); for (int i = 0; i < 3; i++) {s.append(getchar());} String type = s.toString(); char ch = getchar(); ComplexTrigon com = new ComplexTrigon(); if ((type.equals("sin") || type.equals("cos")) && ch == '(') { com.setType(type); Factor inner = getFactor(); com.setInner(inner); ch = getchar(); if (ch != ')') {throw new WrongFormatException();} BigInteger p = getExp(); com.setPower(p); return com; } throw new WrongFormatException(); }
- 由于支持表达式嵌套因子,和三角函数内可替换为任意因子,因此将多项式类Poly也归入父类Factor中,其中Factor抽象类中的derivative()方法,
改成了返回值为ArrayList,对于幂函数求导则返回一个只有一项的ArrayList,Trigon和Item类也做了对应的修改,求导部分和之前大同小异
分析总结:
- 第三次作业虽然除了性能的优化之外圆满完成,但是未能优化的原因是在考虑优化时发现,自己的设计还是出现了一定的漏洞,导致想要优化会将求导的部分近乎推翻重来
在仔细想了想作业分之后还是放弃了优化的想法,个人认为这还是前期对题目的分析不够全面,优化必然会出现在过程之中,设计不合理会导致代码优化难以实现。
附上UML(仍是手画的和IDEA自动生成的)和各类复杂度分析
第三次作业代码长度750行左右
- 可见与第二次差距不大,除了解析字符串的方法大加改动之外,其余部分均在第二次作业的基础上做一定量的修改即可,个人认为达到了一定程度上的迭代开发
二、Test与Bug
Test:
个人认为这两点可以放在一起说,因为我测试自己的bug和测试他人的bug我基本采取的是相同的策略
- 我个人采取的方法是,手动测试边界数据加自己写的评测机
评测机用的是讨论区大佬分享的用python的xeger生成字符串和sympy求导并带入x的值,得到正确答案,也可以选择与大佬对拍
之后用os库os.system(" ")
直接在python中用命令行命令调用java的jar包可以方便的输入输出重定向,个人觉得比较方便py是真滴香评测机在数据生成上不要按照题面来,这样的话普通数据太多,不利于测试效率,我个人的方法是主要是生成以0和1组成的数据
在第三次作业中,数据生成的重点主要是放在多层次嵌套写好评测机开始测试自己的程序你就可以开心的构造一些边界数据hack你自己顺带考虑优化了,
虽然后两次我完全没想着优化手写的数据更注重在多层次嵌套上,第三次作业卡了不少递归
互测的时候多人对拍测试,发现bug数据之后你可以根据评测机生成的数据自己化简成简短的数据
如助教所说,长数据有错,短数据也必然有错
Bug:
课下全面的测试才能让你课上测试更安稳,三次作业在强测和互测都没被hack出bug
但是手写的评测机在第三次作业真的立功了,强测之前帮我查出来两个bug,都是在从作业二改进至作业三时未将原方法进行改动造成的
因此说迭代开发很容易会出现我们自认为之前的方法没有问题的情况,原方法不能完全的处理新题目中的所有情况而造成bug
我们永远不能抱着我们本来的代码是正确的观念,不然即使发现bug在debug的阶段也很难找到bug所在
三、应用对象创建模式
- 第三次作业由于采取了一点递归下降的思想解析字符串,在每一层方法中基本上都会进行实例化
个人认为,此处可以改用工厂模式,可以使得字符串解析部分的代码更加简洁,思路更加清晰
例如更改后读取三角函数部分代码如下
public ComplexTrigon getTri() throws WrongFormatException { StringBuffer s = new StringBuffer(""); for (int i = 0; i < 3; i++) {s.append(getchar());} String type = s.toString(); char ch = getchar(); if ((type.equals("sin") || type.equals("cos")) && ch == '(') { ch = getchar(); Factor inner = getFactor(); ch = getchar(); if (ch != ')') {throw new WrongFormatException();} BigInteger power = getExp(); return FactorFactory.newTri(type,inner,power); } throw new WrongFormatException(); }
四、总结与反思
经过第一单元的学习,主要存在的问题如下:
面向对象的思想掌握的还不够
还不能较好的使用工厂模式等设计模式,不能较好地运用抽象层次
很多方法还是出现了重复性,或者过于冗余,还不能做到高内聚低耦合
收获与反思:
三次作业对我个人来说主要锻炼了各个容器的使用,正则表达式的使用,以及对clone的理解与使用(求导使用到)。
虽然三次作业的都保证了正确性,但在优化方面仍然有不足。优化部分确实是具有比较大的挑战性,我们要做到在优化的同时保证正确性,希望在之后能越做越好。
在最后感谢助教组和老师们的辛勤付出,感谢共同讨论、分享的同学,也希望今后的学习中能够与同学们多讨论,共同提高。
来源:https://www.cnblogs.com/Kidleyh/p/12514418.html