一、作业分析
第一次作业
与后两次作业相比,第一次作业非常简单,仅要求对由常数项和幂函数组成的多项式求导。但由于缺少面向对象编程经验,我在这次作业中栽了不少跟头。
(1)度量分析
在第一次作业中,我还没有适应JAVA语言面向对象的coding风格。可以看到,我的第一次作业只有一个主类One,所有方法都堆在一个类中。这也导致了我的程序的各类复杂度都相当爆炸,总之是一次惨痛的教训。
(2)bug分析
首先,最重要的一点:
不要特判!!
不要特判!!
不要特判!!
由于寒假摸鱼的缘故,我对JAVA的很多功能都不甚了解,这个问题在这次作业中充分暴露出来了。
String matchB = str.replaceAll("[\t ]*[\+-][\t ]*[\+-][\t ]+\d", "w"); matchB = matchB.replaceAll("[\+-][\t ]*[\+-][\t ]*[\+-]", "w"); matchB = matchB.replaceAll("\d[\t ]*x", "w"); matchB = matchB.replaceAll("x[\t ]*\d", "w"); matchB = matchB.replaceAll("\^[\t ]*[\+-][\t ]*[\+-]", "w"); matchB = matchB.replaceAll("\d[\t ]+\d", "w"); matchB = matchB.replaceAll("[\+-][\t ]*\*", "w"); matchB = matchB.replaceAll("x[\t ]*\*", "w"); matchB = matchB.replaceAll("(xx)|(\^[ \t]*x)", "w"); matchB = matchB.replaceAll("\^[ \t]*[\+-][ \t]+\d", "w"); matchB = matchB.replaceAll("\^[ \t]*[+-]*[ \t]*\d*\*", "w"); matchB = matchB.replaceAll("[\t ]", ""); if (matchB.length() == 0) { matchB = "w"; } String matchC = matchB.replaceAll("\*x\^", ""); String matchD = matchC.replaceAll("\d\*x", "1"); String matchE = matchD.replaceAll("[\+-][\+-]?(\d|(x\^?))", ""); matchE = matchE.replaceAll("x\^?", "");
这是我第一次作业checkstyle部分的代码,当时我还不能熟练使用正则表达式来检查代码格式。
特判最大的弊端就是对编程者提取错误信息的能力提出了很高要求,一旦编程者考虑的错误情况不全,就极易在检查格式这一步上栽跟头。
此外,由于第一次作业中采用了朴素的特判处理作为checkstyle的方法,我的这一次作业程序的扩展性极差,第二次作业只能推倒重来,这也需要引以为戒。
第二次作业
第二次作业的内容是对包含简单幂函数和简单正余弦函数的导函数的求解,在这次作业中,我将上一次作业的代码推倒重建,代码风格开始逐渐偏向面向对象而不是面向过程。
(1)度量分析
在第二次作业中,我开始尝试使用多个类处理不同任务,但是有部分类依旧显得过于臃肿。这是我还没有面向对象化的体现。
我的整体思路是在CheckStyle类中完成格式检查,在Poly类中完成求导计算和输出,Main类的功能则是整体调度。可以发现,我的思路依旧是将类作为一个大型函数来使用,其本质还是面向过程而非面向对象。各个部分也没有做到高度分工,尤其是Poly类,同时需要完成求导计算、化简和输出这三个任务。这也导致了Poly类的复杂度特别大。
(2)bug分析
我吸取了第一次作业的教训,采用了正则表达式作为checkstyle的工具,在提取项的过程中也将正则表达式作为搜索关键词使用。
Pattern p = Pattern.compile("^[ \t]*[+-]?[ \t]*[+-]?[ \t]*" + "((([+-]?\d+)|(x([ \t]*+\^[ \t]*[+-]?\d+)?)" + "|(sin[ \t]*\([ \t]*x[ \t]*\)" + "([ \t]*\^[ \t]*[+-]?\d+)?)" + "|(cos[ \t]*\([ \t]*x[ \t]*\)" + "([ \t]*\^[ \t]*[+-]?\d+)?))" + "([ \t]*\*[ \t]*(([+-]?\d+)" + "|([ \t]*x([ \t]*\^[ \t]*[+-]?\d+)?)" + "|([ \t]*sin[ \t]*\([ \t]*x[ \t]*\)" + "([ \t]*\^[ \t]*[+-]?\d+)?)" + "|([ \t]*cos[ \t]*\([ \t]*x[ \t]*\)" + "([ \t]*\^[ \t]*[+-]?\d+)?)))*)" + "([ \t]*[+-][ \t]*[+-]?[ \t]*" + "((([+-]?\d+)|(x([ \t]*\^[ \t]*[+-]?\d+)?)" + "|(sin[ \t]*\([ \t]*x[ \t]*\)" + "([ \t]*\^[ \t]*[+-]?\d+)?)" + "|(cos[ \t]*\([ \t]*x[ \t]*\)" + "([ \t]*\^[ \t]*[+-]?\d+)?))" + "([ \t]*\*[ \t]*(([+-]?\d+)" + "|([ \t]*x([ \t]*\^[ \t]*[+-]?\d+)?)" + "|([ \t]*sin[ \t]*\([ \t]*x[ \t]*\)" + "([ \t]*\^[ \t]*[+-]?\d+)?)" + "|([ \t]*cos[ \t]*\([ \t]*x[ \t]*\)" + "([ \t]*\^[ \t]*[+-]?\d+)?)))*))*+[ \t]*$");
这是我checkstyle时使用的超大正则,经过实际检验并不会爆栈。大正则的好处是比较直观,缺点则是编写和debug时会比较痛苦,只能说要慎用。
private String strin; private Map<String,String> match = new HashMap<String,String>(); private Map<String,String> result = new HashMap<String,String>();
我新建了两个hashmap变量用来保存项的内容,第一个保存求导前的内容,第二个保存求导结果。这样做的好处是便于合并同类项,可以把项的内容作为保存的关键词,项的系数作为保存的内容。
我的这次作业中并没有出现bug,但由于我未考虑诸如sin(x)^2+cos(x)^2这类情况的化简,导致我的性能分比较低。
第三次作业
第三次作业在前两次作业的基础上加入了嵌套函数的定义,尽管这类嵌套仅限定在正余弦函数中,但这仍给编程带来了相当大的挑战。
(1)度量分析
在这次作业中,我使用CheckStyle类检查代码格式,Calculator类完成求导计算,Print类完成化简和输出,Poly类作为存储,Main类整体调度。可以看到,代码复杂度相较前两次有了明显降低。部分类的复杂度较高的原因可能是我定义了比较多的正则表达式变量的原因。
(2)bug分析
我这次作业的bug主要来自于正则表达式的编写错误。由于本次作业的结构与之前有较大差异,我重构了之前的正则表达式,不慎有一处括号写错了地方,导致在checkstyle部分产生了很严重的误判。
二、debug经验
我debug的主要方法还是人工查bug(不会写评测机的泪),效率很低,但对我个人编程能力有很大的提升。尤其是在阅读代码风格很好的代码时,会让人有眼前一亮的感觉。
我习惯先顺着别人的思路将他的程序按流程过一遍,从而分析他的程序在哪一步可能会出问题。这种方法对于找bug而言效率很低,但是在阅读学习他人程序时可以排上用场,靠人眼评测机来理解作者想要表达的意图,并将其1转化为自己的知识。
三、反思
第一单元的三次作业,主要目的是给我们“练手”,逐渐培养面向对象的编程风格。因此,模块化编程就十分重要了,我现在习惯在coding前先理一遍整体思路,将各个部分需要实现的功能规划清楚,在一步步完善各个部分,最后将这些部件“组装”成一个完整的程序。