第一次作业
第一次作业内容为最基本的多项式求导,未涉及三角函数和嵌套内容,难度不大。(点击获取指导书)
UML图
程序UML图如下:
代码分析
可以看出,代码整体分为三个层次,依次为Item、Polynomial、Main,功能划分颗粒度足够细。
对于方法 Main.judge_space 和 Main.get_char 复杂度相对较高,在参考了阿里Java开发手册后,认为可以从以下几个方面提升:
- 【编程规则9.1】在使用正则表达式时,利用好其预编译功能,可以有效加快正则匹配速度。
- 【编程规则7.5】除常用方法(如 getXxx/isXxx )等外,不要在条件判断中执行其它复杂的语句,将复杂逻辑判断的结果赋值给一个有意义的布尔变量名
- 【设计规约12】系统设计阶段,共性业务或公共行为抽取出来公共模块、公共配置、公共类、公共方法等,避免出现重复代码或重复配置的情况。
公测
程序在公测阶段未发现任何bug。
互测
不参与互测。
反思
第一次任务难度较低,工程量相对不大,不到300行的代码就能实现指导书中限定的功能,优化也仅局限在合并同类项、符号合并、删除空格等处,并未为如何补充更多的功能提前做出准备,这也导致后续实验过程遇到了一些麻烦。
第一次任务是笔者第一次真正的使用java类相关功能,本次实验之前,面向对象语言笔者仅接触过C++。通过第一次实验,笔者感受到了Java类库的强大之处(BigInteger、ArrayList之类),Java类库封装了许多便捷高效的类、功能函数,所以在编写Java代码的过程中,笔者逐渐形成了一种新的思维方式,若遇到新的功能需求(例如字符串“+001”转换为相应的数字1),首先考虑前人是否已经实现过这样的功能,之后在库中查找相应的类、函数,而非最初学习C语言时的一切功能从0开始。指导书中反复强调的“不要重复造轮子”,某种程度上也是对Java普遍存在的前人栽树现象的肯定吧。但是由于未涉及继承、接口等内容,因此本次实验只能认为是笔者初步感受Java的魅力的一个大胆尝试,无论是IDEA还是Java,还有太多的东西等待着笔者去探索。
正则表达式是本次实验一个比较关键的内容,输入字符串需要依据正则表达式进行正确性比对和项的划分。在本次实验中,多项式格式较为简单,可以使用正则表达式进行匹配,在第三次实验中,涉及到多项式嵌套的情况,正则表达式便不再适用了(也许依然适用,只是笔者未找到正确的打开方式)
第二次作业
第二次作业是第一次作业功能上的补充,在第一次作业的基础上,实现三角函数求导的功能,三角函数的形式能且仅能为sin(x)及cos(x),同时运算规则增加了乘法规则。难度依然不是很大,但是在优化过程中笔者出现了致命的失误,导致强测过程出现了问题。(点击获取指导书)
UML图
第二次作业,笔者的程序结构如下:
层次关系相较于第一次作业,笔者增加了 MultDivPolynomial 这一层次,将多项式由乘法运算符划分为多个子多项式,再针对每个子多项式进行如同第一次作业的求导操作。
代码分析
可以看到,第二次试验中,仍有部分方法复杂度较高,主要集中在输入字符串处理和输出结果优化的相关函数,例如 Polynomial.dealResult 、 Main.judge_space 、 Main.judge_sincos 、 Main.judge_char 等方法。主要原因是第二次作业发布前期过多的时间忙于其他的事务,导致属于OO的时间被压缩。
公测
第二次作业中,笔者的程序公测出现了一个bug。后来检查,是优化过程产生了形如1x的错误形式,导致部分测试点出错,优化前的输出结果未发现bug。问题出现在 Polynomial.dealResult 方法,该方法将优化前的求导结果字符串依据一定的规则进行缩短,例如删除所有的"*1",这一优化方法本身就极具危险性,在笔者编写代码的过程中就已经意识到这里可能会出现比较严重的问题,最合理的做法是对表达式类的 PrintPoly 方法进行优化,而非对已有的字符串结果进行再加工。但是由于时间原因,笔者不得不采用这种不科学的方式赶上进度。
互测
不参与互测。
反思
第二次作业相较第一次作业已经出现了难度上的升级,无论是层次结构还是优化难度都相较第一次作业变得更为复杂。(例如三角函数优化过程中需要考虑sin(x)^2+cos(x)^2可以化简为1的情况)
由于另一方面的压力提升,导致本次作业未能达到预期效果,测试过程未发现1x这类错误,实在是不应该的,在之后的实验中,我需要重视测试的过程,尽可能覆盖所有测试的类型。
第二次作业整体架构都不尽合理,本着尽可能减少工作量的心理,很多有必要修改或规范的地方都未能实现。例如优化过程中,若沿用第一次作业较为科学的做法,应在每个多项式类的输出方法中进行优化。但为了赶进度,不得不将优化部分单独拿出来,并且是对优化前的字符串进行操作,不安全且不科学。其次,联系第三次作业,在第二次作业中,更好的做法是将加法、乘法操作列为组合类,组合类也将有自己的求导方法,从而实现递归向下求导的架构,便于第三次实验沿用。
第三次作业
第三次作业是第二次作业的升级,在第二次作业的基础上,需要满足函数嵌套规则的求导,例如sin((x*x))。题目看上去相关性很大,但是由于前两次作业设计上的缺陷,以及正则表达式的失效,导致我第三次作业不得不重构代码。(点击获取指导书)
UML图
笔者在本次作业中第一次尝试使用了Java接口和继承功能,不再使用前两次作业的层次关系,定义顶级父类 Item ,运算规则定义为类 CombinItem ,其子类有 MultCombinItem 、AddCombinItem 、SubCombinItem 、NestedCombinItem 。求导时递归调用运算规则成员的求导函数,实现递归下降求导。
代码分析
可以看出,相较于第二次作业,性能方面已经有了很大的提升,整体功能相对零散,耦合状态也还不错。递归下降分析输入字符串的函数规模略显庞大,有 Top.genNextSym、Top.Factor
公测
第三次作业在公测阶段未发现任何bug。
互测
不参与互测。
反思
第三次作业相较于前两次作业首次应用了继承和接口,按照课程组的意思,这三次作业应该是上下呼应的,但是个人感觉第三次作业与前两次作业断层较大。第三次作业解题思路在指导书中已经明确给出——递归下降+继承,虽然提到了正则表达式,但是周围同学并没有人使用正则表达式实现字符串匹配(可能是因为正则表达式着实不适合处理嵌套格式),求导时各个类的求导方法各司其职,这是前两次作业都不曾涉及到的,也就导致,当第三次作业加入继承、接口这一系列新的工具之后,整个代码的结构都发生了翻天覆地的变化。如果不是事先深入了解过Java并且考虑到后续多项式嵌套的形式,很难在前两次作业中很有远见的构建出递归下降的架构。
第三次作业虽然笔者的程序在公测阶段未被发现任何bug,但是依然有一些可以改进的地方:
- 和第一次作业一样,部分函数仍能继续细分
- 由于前期探索是否可以维持现有架构实现新的功能,后续发现原有架构不太具有可行性,耽误了部分时间。当然新的架构考虑清楚之后,加上指导书、讨论区的指点,编码过程还是很行云流水的。
总结
以上三次作业,让笔者一步步认识到面向对象的魅力,以及Java的强大之处,学到了许多从未接触到的知识:
- 代码规范十分重要,Tab、变量命名、每行长度、方法长度等都是编码过程中需要留意的细节
- 规划当下架构时,需要考虑到未来可能补充的附加需求,为自己的代码留有余地,能进能退
- 代码完成后,充分多角度的测试同样必不可少,安全性与可靠性是一个优秀程序不可或缺的一个点
- 不要重复造轮子!不要重复造轮子!不要重复造轮子!重要的事情说三遍
最后,个人认为这三次作业还不够面向对象(或者说为了面向对象而面向对象),暂且视为新手初次面对Java的Hello World练习,关于继承、接口等使用的必要性及优越性还未能充分体现,期待在后续作业中进一步感受架构设计、面向对象的乐趣。
其他
笔者在学习Java面向对象过程中,使用的工具为IDEA,一经使用便深深爱上了它,觉得Eclipse和IDEA相比真的毫无人性,因此想在此分享几个IDEA的惊喜之处。
一键格式修改
在编写代码过程中,笔者经常会忽略某些括号后面的空格(好的,我知道这不是什么好习惯),留到程序调通之后一并修改,例如下图 isNum() 后应有一个空格:
最初笔者都是依据提示一处处修改空格,直到遇到了以下快捷键:
Ctrl+Alt+Shift+L
在IDEA中使用这样一个快捷键,可以自动调整大多数格式问题(空格不规范问题自然是小菜一碟)
使用后的效果,一键清爽:
统一修改名称
在编码过程中,笔者偶尔会对变量采用错误的命名格式(好的,我知道这也不是什么好习惯),例如对于变量名首字母大写,不满足格式要求:
修改的时候我会先修改定义处的变量名称,例如将上图中第一个Poly修改为poly,之后依据报错信息逐个修改,操作起来十分不科学。从网上了解到IDEA具有统一修改名称的功能
使用快捷键:
Shift+F6
效果如下:
IDEA会自动匹配所有与第一个Poly相对应的变量调用,当修改第一个Poly(红色方框)名称时,后续所有的Poly都会同步做出修改(绿色方框)
Plugins下载
本条针对因为网络原因无法在IDEA内部进行插件下载的情况
在安装插件过程中,笔者遇到了网络连接错误的问题,无法显示可下载的插件,或者无法完成下载过程,这个时候可以选择从官网下载插件到本地,再使用如下途径:
以CheckStyle为例,直接在搜索栏搜索即可找到相应插件,将对应版本压缩包下载到本地即可
注:官网下载的插件为zip格式,无需解压,直接在IDEA中添加即可放心使用