第一单元总结
作业总结
一程序结构分析
因为在此之前并没有接触过 java 语言或者是与之相似的语言, 而不用说 OO 的概念, 所以一切都是全新的. 开始的时候脑子并没有任何面向对象的概念, 类这个概念仅仅建立在 C 语言的某个 .c 文件之上, 简单地认为 java 中的类就是 C 中的 .c 文件. 所以第一次作业几乎没有什么类划分, 仅有的四个类更像是 C 中的四个函数, 而不是四个类. 第二次作业的时候毫无疑问地走向了重构道路, 虽然仍然没有摆脱面向过程的编程方法, 但开始对类的概念有一些自己的想法, 开始尝试着划分类, 并用类之间的交互来实现需要的功能. 第三次作业中, 类的数量和种类大大增多, 同时类的划分界限也明确起来, 并不是一味地当成函数集合来用. 很明显, 这三次并不那么令人愉快的作业经历大大地加深了我对面向对象思想的理解. 下面是三次作业的详细分析:
三次作业类图
三次作业度量
名词说明:
OCavg:
Calculates the average cyclomatic complexity of the non-abstract methods in each class. Inherited methods are not counted for purposes of this metric.
类平均圈复杂度, 继承类不计入
WMC:
Calculates the total cyclomatic complexity of the methods in each class.
类总圈复杂度
ev(G):
Calculates the essential complexity of each non-abstract method. Essential complexity is a graph-theoretic measure of just how ill-structured a method's control flow is. Essential complexity ranges from 1 to v(G), the cyclomatic complexity of the method.
非抽象方法的基本复杂度, 基本复杂度是一个图论度量理论, 用来度量一个方法的控制流结构有多差, 基本复杂度的范围是 1 到 v(G)
iv(G):
Calculates the design complexity of a method. The design complexity is related to how interlinked a methods control flow is with calls to other methods. Design complexity ranges from 1 to v(G), the cyclomatic complexity of the method. Design complexity also represents the minimal number of tests necessary to exercise the integration of the method with the methods it calls.
设计复杂度, 度量方法控制流与其他方法之间的耦合程度, 设计复杂度的范围是 1 到 v(G)
v(G):
Calculates the cyclomatic complexity of each non-abstract method. Cyclomatic complexity is a measure of the number of distinct execution paths through each method. This can also be considered as the minimal number of tests necessary to completely exercise a method's control flow. In practice, this is 1 + the number of if's, while's, for's, do's, switch cases, catches, conditional expressions, &&'s and ||'s in the method.
圈复杂度, 度量每个中不同路径执行的数量
度量结果:
第一次:
Class metrics:
class OCavg WMC com.lm.Merge 6.0 6.0 com.lm.Main 6.0 6.0 com.lm.Item 2.857142857142857 20.0 com.lm.IfLegal 4.0 8.0 Total 54.0 Method metrics:
method ev(G) iv(G) v(G) com.lm.PolyGetter.pureOperators() 1.0 11.0 11.0 com.lm.PolyGetter.PolyGetter(String) 1.0 1.0 1.0 com.lm.PolyGetter.getItems() 1.0 3.0 3.0 com.lm.PolyGetter.delSpace() 3.0 3.0 4.0 com.lm.Merge.merge(Item[]) 5.0 5.0 6.0 com.lm.Main.main(String[]) 2.0 6.0 6.0 com.lm.Item.setExp(BigInteger) 1.0 1.0 1.0 com.lm.Item.setCoff(BigInteger) 1.0 1.0 1.0 com.lm.Item.Item(String) 1.0 6.0 7.0 com.lm.Item.getItem() 3.0 6.0 7.0 com.lm.Item.getExp() 1.0 1.0 1.0 com.lm.Item.getCoff() 1.0 1.0 1.0 com.lm.Item.derive() 1.0 3.0 3.0 com.lm.IfLegal.judge() 6.0 4.0 8.0 com.lm.IfLegal.IfLegal(String) 1.0 1.0 1.0 Total 29.0 53.0 61.0 Average 1.9333333333333333 3.533333333333333 4.066666666666666 第二次:
Class metrics:
class OCavg WMC com.mypkg.PolyGetter 6.0 6.0 com.mypkg.Main 5.0 15.0 com.mypkg.Item 2.4166666666666665 29.0 Total 50.0 Average 3.125 16.666666666666668 Method metrics:
method ev(G) iv(G) v(G) com.mypkg.PolyGetter.parse(String) 6.0 4.0 7.0 com.mypkg.Main.merge(ArrayList) 4.0 7.0 7.0 com.mypkg.Main.main1() 2.0 8.0 8.0 com.mypkg.Main.main(String[]) 1.0 2.0 2.0 com.mypkg.Item.setSinExp(BigInteger) 1.0 1.0 1.0 com.mypkg.Item.setPowExp(BigInteger) 1.0 1.0 1.0 com.mypkg.Item.setCosExp(BigInteger) 1.0 1.0 1.0 com.mypkg.Item.setCoff(BigInteger) 1.0 1.0 1.0 com.mypkg.Item.itemDerive() 1.0 1.0 1.0 com.mypkg.Item.Item(String) 3.0 11.0 12.0 com.mypkg.Item.Item(BigInteger,BigInteger,BigInteger,BigInteger) 1.0 1.0 1.0 com.mypkg.Item.getSinExp() 1.0 1.0 1.0 com.mypkg.Item.getPowExp() 1.0 1.0 1.0 com.mypkg.Item.getItem() 1.0 9.0 9.0 com.mypkg.Item.getCosExp() 1.0 1.0 1.0 com.mypkg.Item.getCoff() 1.0 1.0 1.0 Total 27.0 51.0 55.0 Average 1.6875 3.1875 3.4375 第三次:
Class metrics:
class OCavg WMC com.mypkg.Sin 6.333333333333333 19.0 com.mypkg.Production 3.875 31.0 com.mypkg.Pow 3.3333333333333335 10.0 com.mypkg.Main 4.2 21.0 com.mypkg.Int 1.0 3.0 com.mypkg.Cos 6.333333333333333 19.0 com.mypkg.Conbination 22.0 66.0 Total 169.0 Average 6.035714285714286 24.142857142857142 Method metrics:
method ev(G) iv(G) v(G) com.mypkg.Sin.Sin(String) 1.0 12.0 12.0 com.mypkg.Sin.print() 1.0 6.0 6.0 com.mypkg.Sin.derive() 1.0 8.0 8.0 com.mypkg.Production.Production() 1.0 1.0 1.0 com.mypkg.Production.print() 1.0 14.0 14.0 com.mypkg.Production.derive() 1.0 11.0 11.0 com.mypkg.Production.add(Sin) 1.0 1.0 1.0 com.mypkg.Production.add(Pow) 1.0 1.0 1.0 com.mypkg.Production.add(Int) 1.0 1.0 1.0 com.mypkg.Production.add(Cos) 1.0 1.0 1.0 com.mypkg.Production.add(Conbination) 1.0 1.0 1.0 com.mypkg.Pow.print() 1.0 3.0 3.0 com.mypkg.Pow.Pow(String) 1.0 3.0 3.0 com.mypkg.Pow.derive() 1.0 4.0 4.0 com.mypkg.Main.pureString(String) 1.0 11.0 12.0 com.mypkg.Main.nextIndex(String,int) 4.0 4.0 6.0 com.mypkg.Main.main1(String[]) 4.0 4.0 4.0 com.mypkg.Main.main(String[]) 1.0 2.0 2.0 com.mypkg.Main.bracketMatch(String) 3.0 3.0 5.0 com.mypkg.Int.print() 1.0 1.0 1.0 com.mypkg.Int.Int(String) 1.0 1.0 1.0 com.mypkg.Int.derive() 1.0 1.0 1.0 com.mypkg.Cos.print() 1.0 6.0 6.0 com.mypkg.Cos.derive() 1.0 8.0 8.0 com.mypkg.Cos.Cos(String) 1.0 12.0 12.0 com.mypkg.Conbination.print() 1.0 9.0 10.0 com.mypkg.Conbination.derive() 1.0 9.0 10.0 com.mypkg.Conbination.Conbination(String) 22.0 51.0 66.0 Total 57.0 189.0 211.0 Average 2.0357142857142856 6.75 7.535714285714286
优缺点分析:
从上面的类图和度量表看到, 我在这几次作业中并没有使用继承和接口, 一方面是因为我觉得自己对于继承和接口的概念不够熟悉, 怕乱用写出 BUG, 加大自己的工作量, 另一方面, 我对于接口的作用确实不理解, 也不会用(直到现在仍然是认为接口只是形式上规定了继承于他的类应该有哪些方法, 而和我不用接口分别在各个类中去实现这些方法并没有什么实质区别, 仅仅是形式上而已).
从三次作业的度量值分析可以看到, 我几次的代码都比较病态, 尤其是到了第三次, 几乎所有的代码都集中到了 Conbination 类, 而且在 Conbination 类中的构造方法也是占据了极高的比例, 方法, 类之间的耦合度极高, 这些都暴露出来我编程思想不够严谨的问题, 我希望在以后的学习中努力改变自己这种病态的代码结构.
从三次类图的递进发展来看, 类渐趋于完善, 可以看到进步也是存在的. 这三次作业最大的收获就是对面向对象思想的理解得到了极大程度地加深, 尤其是看到第三次作业提示构造表达式树, 有一种豁然开朗地感觉. 其次, 则是正则表达式, 在 OO 课开课之前多次听说正则表达式地大名, 但都是好像很叼地样子, 觉得应该不是一个初学者地我能掌握地东西, 所以没怎么看. 但是经过三次作业地磨砺, 我觉得自己对正则表达式已经有了一定的了解, 开始觉得正则表达式很难, 接触之后又觉得正则表达式就那么回事, 挺简单的, 然后大正则爆栈, 开始了解一些和正则相关的深入一些地知识, 如: DFA, NFA, 回溯等概念. 又开始觉得正则是个博大精深的东西. 相信在经历了 OO 之后, 我一定能很好地运用正则.
二程序BUG分析
第一次作业
第一次作业一共两个已知 BUG , 分别是:
超大整数陷阱: 我在比较两个超大整数的时候, 想当然地调用了 intValue() 方法, 将他们转换 int 型数据比较, 未考虑超大整数溢出, 结果出错了. 应该调用 Compare() 方法进行超大整数之间的比较.
大正则陷阱:试图用一个超长的正则表达式匹配整个表达式, 结果爆栈. 正确方法应该是分项匹配. 下面贴出大正则反例片段:
Pattern q = Pattern.compile("^" + "\\s*[+-]?\\s*(([+-]?\\d+)" + "|(([+-]|[+-]?\\d+\\s*\\*)?\\s*x\\s*(\\^\\s*[+-]?\\d+)?))" + "(\\s*[+-]\\s*((([+-]|[+-]?\\d+\\s*\\*)?\\s*" + "x(\\s*\\^\\s*[+-]?\\d+)?)|[+-]?\\d+))*" + "+" + "\\s*$"); Matcher n = q.matcher(line); if (n.find()) { return true; } Pattern p = Pattern.compile("^" + "\\s*[+-]?\\s*(([+-]?\\d+)" + "|(([+-]|[+-]?\\d+\\s*\\*)?\\s*x\\s*(\\^\\s*[+-]?\\d+)?))" + "(\\s*[+-]\\s*((([+-]|[+-]?\\d+\\s*\\*)?\\s*" + "x(\\s*\\^\\s*[+-]?\\d+)?)|[+-]?\\d+))*" + "\\s*$"); Matcher m = p.matcher(line); return m.find();
第二次作业
第二次作业未发现 BUG
第三次作业
第三次作业发现一个潜在 BUG ( 未被测出来, 但确实存在 ), 就是老僧长谈地深层递归爆栈, 目前并没有想到什么好的解决办法. 另一个已知 BUG 是 sin(- 1) 会判对, 原因是我在处理原始字符串地时候采用了穷举带符号数被空格截断地情况, 但是穷举不够充分, 仅考虑到了幂指数, 以及最外层的整数系数和常数项, 未考虑到整数作为因子出现嵌套的情况, 故错.
三Hack体验
第一次作业因为超大整数溢出直接没进互测, 没有任何体验....
第二次作业没有被炸, 体验良好....
第三次被炸了几个点....
总结几次互测的体验, 发现 BUG 高发区集中在输入格式处理上面, 大正则爆栈, 情况考虑不够完善等. 另外很多同学的 BUG 是由于优化导致的, 以至于将优化方法或者类注释掉就通过了....另外有一点要说的是, 希望提交间隔可以适当缩短一点, 毕竟有的数据点自己验证起来也不好验证, 特别是经过一系列恒等变换之后.
四建模重构
关于重构, 初步的想法是构建一个运算接口, 运算接口包括求导, toString(), 简单化简(仅仅是同类项合并, 指数合并等)方法. 然后构建 Sin Cos Pow Com Mul 类继承运算接口, 构建表达式树, 实现链式求导, 指导书上说构建嵌套类, 但我不知道怎么构造, 只能通过在 Sin Cos Pow 内部使用嵌套标记来表示该对象是否嵌套了下一级, 希望在之后公布的优秀代码种学会如何构建嵌套类. 至于化简, 随缘吧...