BUAAOO 第一单元总结
程序结构分析
第一次作业
现在看来第一次作业真的是相当的友好,只有幂函数的求导,优化也几乎不用考虑太多。
我第一次作业就用了三个类:
-
主类MainClass用于输入输出操作,这里有一个小细节我觉得还是值得一提,就是我是采用的多行输入
while (scanner.hasNextLine())
,这样做的一个好处就是在调试的时候可以连续地调试,而不是仅仅调试一个数据 -
字符串处理类StrProcess用于处理输入数据以及提取项,由于这次作业并没有要求格式检查,于是我对输入进行了预处理,包括去空格(逐字符判断)以及将连续的+-号转换为单个的符号,如下所示
private final String trueAdd = "(\\+\\+\\+)|(\\-\\-\\+)|(\\-\\+\\-)|(\\+\\-\\-)" + "|(\\+\\+)|(\\-\\-)";
strStandard = strStandard.replaceAll(trueAdd, "+");
-
项类Term作为单位存储每一个项的信息,由于任意一项都可以被转换为一个通项:coe * x ^exp,所以Term的属性就只需要coe和exp,再配置一个求导方法就基本完整了
类图以及度量分析:
从中可以见得StrProcess.StrProcess方法较为复杂,实际上我把一个功能全都在一个方法中实现了,导致里面有很多while和if的嵌套,会大大增加BUG的出现概率。
整体的架构就是用了一个treeMap存所有的项,用exp作为key,在存入的过程中就进行了同类项合并并排序,最后的输出就只需要逐项求导并用+-组合起来就完成了。
这次的架构实话说是比较简单的,优点就是清晰易操作,很顺利地帮我解决了这次作业,可是缺点也有,当时做完就觉得下次必然需要重构了,因为没有考虑WF,可扩展性也一般,事实也如此,第二次我果然推倒重来了。
第二次作业
第二次作业加了嵌套,这让我困扰了很久,正则表达式几乎就是用不了了,虽然听了dalao们介绍的递归下降分析法很是心动,但是单个字符处理的话我觉得我能写到抓狂,最终我还是偷了一次懒,钻了没有WF的空子,还是像第一次作业一样将字符串进行了预处理。
这一次作业我一共有八个类:
-
主类MainClass与第一次大致相同,多了一点字符串处理
-
简单因子类Sin,与幂函数类似,存系数和指数,系数本意是用来存正负号的,实际上没有太大的必要。
-
简单因子类Cos,与Sin类似
-
简单因子类Pow,即幂函数,这个是不带系数的,也就是专门存x^coe的类
-
因子求导类FactDer,用于实现因子的求导,因子一共有上述的三个简单因子再加上常数因子和表达式因子,由于常数因子求导得0,故我没有专门设置一个类来存,表达式因子的求导也是直接通过递归的方式实现,也就没有必要再实现一个类。该类的作用对传进的因子判定类型,再调用对应的类的求导方法即可得到因子的求导结果
-
项求导类TermDer,用于实现项的求导,传进一个项,首先是将其拆分成各个因子(通过括号外的单独的*拆分)存入一个容器,再利用乘法求导法则并调用因子的求导类即可得到项的求导结果
-
表达式求导类ExpDer,用于实现表达式的求导,传进一个表达式(可以是输入,也可以是表达式因子),首先是将其拆分成各个项(通过括号外的非*后面的正负号拆分)存入一个容器,逐项求导再求和即可
-
字符串预处理类StrProcess,将输入的表达式去空格,替换连续的正负号得到易于后续处理的标准表达式
类图以及度量分析:
这次是FactDer.FactDer和TermDer.Term.Der两个求导类过于复杂,主要原因是我在里面进行了类型的判断,即各种类型因子的解析,并且有几种类型是直接在这里进行求导的,实际上是相当差的设计,结构的层次感完全丢失了。
第二次作业我起初的设想是先不考虑优化,全用字符串进行传递,再对最后的结果单独做一次优化,结果却是我做完第一步之后得到正确性的保证了(优点也就是正确性能够很简单就得到保证),就开始摸鱼了(阿巴阿巴,底层人民真实写照
所以就结果而言性能分丢了好多,同类项合并去括号什么的都没怎么做,但我觉得这种解耦的思路还是不错的(尽管因为本人的偷懒而破产
第三次作业
第三次作业对于我而言主要是多了一个格式检查,对于三角函数的嵌套就是将括号里的再进行一次因子求导,所以幸运的是这次没有重构,加一个格式检查类就ok了,这里我把Sin和Cos类都直接删了,把括号里的因子提取出来直接处理,但实际上这是不太符合OO思想的。
类图以及度量分析:
由于本次作业还是沿用的第二次作业的架构,所以这次作业的方法行数和复杂度和上次差别不大。
BUG分析
第一次作业
第一次作业我被hack的全是同质BUG,是我在字符串预处理去除多余正负号的时候想当然了一次,我的原代码是这样的:
private final String trueAdd = "(\\+\\+)|(\\-\\-)";
private final String trueSub = "(\\-\\+)|(\\+\\-)";
strStandard = strStandard.replaceAll(trueAdd, "+");
strStandard = strStandard.replaceAll(trueSub, "-");
错误在于这个正则表达式的替换并不是循环进行的,即不能保证会把连续的三个符号替换成一个符号,修改也很简单:
private final String trueAdd = "(\\+\\+\\+)|(\\-\\-\\+)|(\\-\\+\\-)|(\\+\\-\\-)" + "|(\\+\\+)|(\\-\\-)";
private final String trueSub = "(\\-\\-\\-)|(\\+\\+\\-)|(\\+\\-\\+)|(\\-\\+\\-)" + "|(\\-\\+)|(\\+\\-)";
通过对比复杂度:
显而易见,复杂度高的往往容易出BUG,关键还是没有分好结构,并进行单元测试
第二次作业
由于第二次作业没有考虑优化,实现求导的思路非常简单清晰,所以我并没有被hacked到,只是性能分白给得有点多
第三次作业
就对于正确输入的求导结果而言是没有出现正确性方面的错误,但是我在格式检查的地方还是出现了BUG,没有判断出形如()的错误,实际上是相当白给的错误
Hack策略
由于没有搭建评测机,所以本人采用的都是较为原始的办法
-
看代码,这是一项特别重的体力活(看了一次就打消这个念头了),看他人的代码主要可以看边缘情况,毕竟简单的错误也过不了中测,看他们对于特殊情况的处理是怎样的,例如对连续正负号的处理
-
盲测,手搓一份覆盖比较广的数据,当然这就是一项脑力活了,尽可能多地去覆盖每一个分支可以大大增加hack成功的可能性
最终我也只是在第二次作业的时候hack到了别人三个点,还只是随手交的一份盲测数据,可能是分组较次的原因,到第三次甚至都没hack别人(年轻人少一点戾气,和平相处不好吗
重构经历总结
重构真的很难受,不用重构真的很快乐
第二次作业我就进行了重构,原因非常显然,正则表达式用不了了,这真的令人感到挺绝望的,一时不知道该如何面对递归这玩意,递归下降分析法又没有比较好的学习资料,最终想到的就是逐项逐因子地拆分,思路还是比较简单的,并且最大的好处就是对于第三次作业几乎不需要改动
但愿以后能够摆脱重构的折磨 ( ̄﹃ ̄)
心得体会
做这个单元的作业的时候感觉作业就是推背感巨强,周三发作业,基本上我都是在周六当天肝完的,在动手之前还得仔细地构思好久,时间的紧迫是我感到压力的来源。至于难度,第一次作业不是特别难,但是我当时pre还没做完(这也导致我感到时间严重不足),连正则表达式都不会用,第二次作业由于加上了递归就感觉难度提升了好多,但是由于第二次作业较为良好的构架,第三次作业的工作量很少。从最终结果看来,其实就是构思的问题,只要想到了一个比较可行的架构,那么后面都是非常自然而又轻松的事了。
良好编程思维习惯的养成:通过这个单元的学习,我深刻体会到了面向对象编程带给我们的诸多好处(减少迭代作业的工作量他不香吗),Checkstyle的检查也让我开始注重代码的“美观”,Crtl+Alt+L,代码突然就变得整洁起来,这大大改善了编程的体验。
另外一点感悟就是这门课实际上更加偏重于自学?没有像COOS那样的教程供我们参考,谁知识点学得越多做作业越得心应手,这一点我认为对于自学积极性不是特别高的同学(比如我)不是特别友好,希望之后能够有所改变吧。
总而言之,OO第一单元让我收获颇丰,下个单元继续努力。