Stitch11752

Unit 1 Summary

Unit 1 Summary

by Mike Liu


代码分析

Homework 1

Preview

第一次作业较为简单,只用了3个class,分别是:

  • Term 用来存储一项
  • TermMap 用来存储所有项
  • TermSet 原本用来存储排序后的项,后发现算法有误,不再用set存储;该类只进行主程序的识别项和输入输出工作。

静态分析

  • 方法分析

    Method BRANCH CONTROL ev(G) EXEC iv(G) LOC RLOC v(G)
    Term.Term() 0 0 1 2 1 4 4.82% 1
    Term.Term(BigInteger,BigInteger) 0 0 1 2 1 4 4.82% 1
    Term.Term(String,String) 0 0 1 2 1 4 4.82% 1
    Term.add(Term) 0 2 3 3 1 12 14.46% 3
    Term.calcDerivative() 0 1 2 2 1 6 7.23% 2
    Term.compareTo(Term) 0 0 1 1 1 4 4.82% 1
    Term.equals(Object) 0 1 2 2 3 7 8.43% 3
    Term.hashCode() 0 0 1 1 1 4 4.82% 1
    Term.positiveCoef() 0 0 1 1 1 3 3.61% 1
    Term.toString() 0 6 2 14 4 30 36.14% 8
    TermMap.addTerm(BigInteger,BigInteger) 0 0 1 1 1 3 12.00% 1
    TermMap.calcDerivativeAll() 0 0 1 3 1 6 24.00% 1
    TermMap.printAll() 0 2 1 6 4 12 48.00% 4
    TermSet.main(String[]) 0 8 1 21 8 55 68.75% 9

    可以看出,主要复杂度均集中在主函数和toString()部分,其余操作较为简单。

  • 类分析

    Class CSOA DIT LCOM LOC NAAC NOAC NOIC NOOC OCavg
    Term 26 1 1 83 3 4 8 3 2
    TermMap 17 1 1 25 2 3 11 0 1.67
    TermSet 23 1 1 80 10 1 11 0 9
  • 类图

Homework 2

Preview

在准备着手第二次作业时,已经明显感觉到第一次作业的代码结构层次不足,在该基础上扩展比较艰难;同时也预感到第三次作业会涉及到函数嵌套,因此对代码进行了完全重构,并且留下了扩展余地(为三角函数均保留了subFunc成员。

静态分析

  • 方法分析

    Method CONTROL ev(G) iv(G) v(G) LOC
    MainClass.main(String[]) 1 1 2 3 13
    StringProcessor.readin() 11 7 10 16 57
    Cosine.Cosine() 0 1 1 1 4
    Cosine.Cosine(BigInteger) 0 1 1 1 3
    Cosine.Cosine(BigInteger,Expression) 0 1 1 1 4
    Cosine.Cosine(BigInteger,Term) 0 1 1 1 4
    Cosine.compareTo(Cosine) 1 2 1 2 6
    Cosine.equals(Object) 1 2 2 3 6
    Cosine.getDerivative() 1 2 1 2 13
    Cosine.getPower() 0 1 1 1 3
    Cosine.sameFunc(Cosine) 0 1 2 2 3
    Cosine.toString() 2 3 1 3 10
    Cosine.xOnlyMult(Cosine) 0 1 2 2 3
    Sine.Sine() 0 1 1 1 4
    Sine.Sine(BigInteger) 0 1 1 1 3
    Sine.Sine(BigInteger,Expression) 0 1 1 1 4
    Sine.Sine(BigInteger,Term) 0 1 1 1 4
    Sine.compareTo(Sine) 1 2 1 2 4
    Sine.equals(Object) 1 2 2 3 6
    Sine.getDerivative() 1 2 1 2 13
    Sine.getPower() 0 1 1 1 3
    Sine.sameFunc(Sine) 0 1 2 2 3
    Sine.toString() 2 3 1 3 10
    Sine.xOnlyMult(Sine) 0 1 2 2 3
    Xpow.Xpow() 0 1 1 1 3
    Xpow.Xpow(BigInteger) 0 1 1 1 3
    Xpow.Xpow(int) 0 1 1 1 3
    Xpow.compareTo(Xpow) 1 2 1 2 4
    Xpow.equals(Object) 1 2 1 2 6
    Xpow.getDerivative() 2 3 1 3 11
    Xpow.mult(Xpow) 0 1 2 2 3
    Xpow.toString() 3 4 1 4 12
    Expression.Expression() 0 1 1 1 2
    Expression.Expression(Term...) 0 1 1 1 3
    Expression.add(Expression...) 1 1 2 2 5
    Expression.add(List) 5 4 5 5 15
    Expression.add(Term...) 0 1 1 1 3
    Expression.equals(Object) 4 5 2 5 17
    Expression.getDerivative() 1 1 2 2 8
    Expression.merge() 0 1 1 1 5
    Expression.mult(Expression) 1 1 2 2 7
    Expression.mult(Term) 2 2 2 3 10
    Expression.simplify() 24 9 10 17 59
    Expression.toString() 6 4 4 6 15
    Term.Term() 0 1 1 1 6
    Term.Term(BigInteger,BasicFunction...) 9 6 1 7 21
    Term.add(Term) 1 1 2 2 5
    Term.addable(Term) 3 4 9 13 10
    Term.compareTo(Term) 6 7 1 7 23
    Term.equals(Object) 2 3 11 16 15
    Term.getCoef() 0 1 1 1 3
    Term.getDerivative() 3 1 4 4 13
    Term.hashCode() 0 1 1 1 4
    Term.merge(Term,int) 12 10 12 14 37
    Term.merge(Term,int,ArrayList) 2 2 7 7 27
    Term.mergeable(Term) 9 10 19 28 38
    Term.mult(Term) 0 1 4 4 8
    Term.toString() 17 6 13 20 50

    可以看出,每种方法的代码量不多,职责分工比较明确。复杂度主要集中在对项和表达式的增加、化简处理,各因子的基本操作较为简洁。与此同时,给之后作业的功能扩展留下了充足空间。

  • 类分析

    Class CBO DIT LCOM NAAC LOC NOAC OCavg
    MainClass 3 1 1 0 15 1 2
    StringProcessor 8 1 1 23 88 1 15
    BasicFunction 4 1 0 0 2 0 n/a
    Cosine 7 2 1 5 66 5 1.55
    Sine 7 2 1 5 64 5 1.55
    Xpow 7 2 1 5 52 3 2
    Expression 7 1 2 1 152 8 3.75
    Term 8 1 1 8 270 9 6.07

    由LCOM值可以看出,各类的内聚性较高,类的功能分工比较清晰;最复杂也设计关系最广泛的类是StringProcessor,他负责了输入的读取和各对象的建立,拥有显著高于其他的方法复杂度。而对于多项式的计算和处理主要由Expression和Term类负责,涉及到众多方法,代码量较大。

  • 类图

Homework 3

Preview

在计算求导、项和表达式相加减层面,由于第二次作业已经留下了充足的兼容性,几乎没有作太多修改。

第三次作业更突出的问题在于输入和化简。本次作业由于涉及到递归输入,果断放弃了大正则处理,而是使用了字符串处理+局部正则的手段;同时,将类的实例化方法封装入工厂模式,进一步明确代码分工。化简部分由于精力原因,并没有注重合并同类项/三角函数优化这样的化简方法,而是侧重于括号化简、零和一的判断这样较为基础的工作;但还是在强测及互测过程中暴露了bug,造成了遗憾的失分。

静态分析

  • 方法分析

    Method CONTROL ev(G) iv(G) LOC v(G)
    MainClass.main(String[]) 1 1 2 12 3
    StringProcessor.readin() 1 2 1 7 2
    ExpressionFactory.getExpression(String) 26 15 5 62 25
    FactorFactory.expLimitJudge(BigInteger) 1 2 1 5 2
    FactorFactory.getFactor(String) 8 6 9 44 9
    FactorFactory.getNumber(String) 0 1 2 4 2
    TermFactory.getTerm(String,int) 17 12 6 45 16
    ConstVal.ConstVal(BigInteger) 0 1 1 1 1
    ConstVal.ConstVal(int) 0 1 1 1 1
    ConstVal.compareTo(Factor) 1 2 2 6 2
    ConstVal.equals(Object) 1 2 1 5 2
    ConstVal.getDerivative() 0 1 1 4 1
    ConstVal.getVal() 0 1 1 3 1
    ConstVal.hashCode() 0 1 1 4 1
    ConstVal.isOne() 0 1 1 1 1
    ConstVal.mult(BasicFunction) 1 2 2 6 2
    ConstVal.multable(BasicFunction) 0 1 1 3 1
    ConstVal.simplify() 0 1 1 1 1
    ConstVal.toString() 2 3 1 5 3
    Cosine.Cosine() 0 1 1 4 1
    Cosine.Cosine(BigInteger) 0 1 1 3 1
    Cosine.Cosine(BigInteger,Factor) 0 1 1 4 1
    Cosine.Cosine(BigInteger,Term) 0 1 1 4 1
    Cosine.compareTo(Factor) 4 5 3 12 7
    Cosine.equals(Object) 1 2 2 8 3
    Cosine.getDerivative() 1 2 1 13 2
    Cosine.getPower() 0 1 1 3 1
    Cosine.isOne() 2 3 2 6 4
    Cosine.mult(BasicFunction) 1 2 2 7 2
    Cosine.multable(BasicFunction) 1 2 1 5 3
    Cosine.sameFunc(Cosine) 0 1 2 3 2
    Cosine.simplify() 2 1 4 8 5
    Cosine.toString() 5 5 4 19 8
    Sine.Sine() 0 1 1 4 1
    Sine.Sine(BigInteger) 0 1 1 3 1
    Sine.Sine(BigInteger,Factor) 0 1 1 4 1
    Sine.Sine(BigInteger,Term) 0 1 1 4 1
    Sine.compareTo(Factor) 4 5 3 12 7
    Sine.equals(Object) 1 2 2 8 3
    Sine.getDerivative() 1 2 1 12 2
    Sine.getPower() 0 1 1 3 1
    Sine.isOne() 0 1 1 3 1
    Sine.mult(BasicFunction) 1 2 2 8 2
    Sine.multable(BasicFunction) 1 2 1 6 3
    Sine.sameFunc(Sine) 0 1 2 3 2
    Sine.simplify() 2 1 4 8 5
    Sine.toString() 5 5 4 19 8
    Xpow.Xpow() 0 1 1 3 1
    Xpow.Xpow(BigInteger) 0 1 1 3 1
    Xpow.Xpow(int) 0 1 1 3 1
    Xpow.compareTo(Factor) 1 2 2 7 2
    Xpow.equals(Object) 1 2 1 6 2
    Xpow.getDerivative() 2 3 1 11 3
    Xpow.isOne() 0 1 1 1 1
    Xpow.mult(BasicFunction) 1 2 2 7 2
    Xpow.multable(BasicFunction) 0 1 1 5 1
    Xpow.simplify() 0 1 1 1 1
    Xpow.toString() 3 4 1 12 4
    Expression.Expression() 0 1 1 2 1
    Expression.Expression(Term...) 0 1 1 3 1
    Expression.add(Expression...) 1 1 2 5 2
    Expression.add(List) 5 4 5 15 5
    Expression.add(Term...) 0 1 1 3 1
    Expression.castToBasic() 1 2 3 7 3
    Expression.castToTerm() 1 2 2 6 2
    Expression.castable() 0 1 3 4 3
    Expression.castableToTerm() 0 1 1 4 1
    Expression.compareTo(Factor) 5 6 3 15 7
    Expression.equals(Object) 1 2 1 6 2
    Expression.getDerivative() 1 1 2 8 2
    Expression.merge() 0 1 1 5 1
    Expression.mult(Expression) 1 1 2 7 2
    Expression.mult(Term) 2 2 2 10 3
    Expression.simplify() 23 9 11 48 17
    Expression.toString() 6 4 4 15 6
    Term.Term() 0 1 1 8 1
    Term.Term(BigInteger,Factor...) 0 1 1 4 1
    Term.add(Term) 1 1 2 5 2
    Term.addFactors(Factor...) 11 6 11 31 11
    Term.addable(Term) 6 7 3 19 7
    Term.castToBasic() 4 5 9 12 11
    Term.castable() 4 5 7 9 11
    Term.compareTo(Term) 6 7 4 24 9
    Term.equals(Object) 2 3 2 11 4
    Term.getCoef() 0 1 1 3 1
    Term.getDerivative() 8 1 9 22 9
    Term.hashCode() 0 1 1 4 1
    Term.merge(Term,int) 4 2 2 42 3
    Term.mergeToExp(Term,int) 1 2 4 13 4
    Term.mergeable(Term) 4 5 4 47 7
    Term.mult(Term) 5 2 5 9 6
    Term.removeNullFactors() 2 1 4 7 4
    Term.simplify() 3 1 4 14 4
    Term.toString() 22 12 10 45 19

    可以看出,除了涉及到计算和输出的方法外,方法的复杂度主要在字符串处理和表达式化简部分占了相当大的比重,体现出对于这两部分在本次作业中的地位。

  • 类分析

    Class CBO DIT LCOM LOC NAAC NOAC OCavg
    MainClass 3 1 1 14 0 1 2
    StringProcessor 5 1 1 98 2 1 2
    ExpressionFactory 7 1 1 64 0 1 18
    FactorFactory 11 1 1 83 19 3 4.33
    TermFactory 6 1 1 47 0 1 12
    BasicFunction 7 2 3 5 0 0 n/a
    ConstVal 7 3 3 46 4 7 1.42
    Cosine 9 3 1 106 5 8 2.21
    Factor 9 1 1 3 0 0 n/a
    Sine 9 3 1 104 5 8 2.07
    Xpow 8 3 2 66 5 6 1.73
    Expression 12 2 2 166 1 13 3.29
    Term 9 1 1 372 7 14 5.11

    LCOM体现出本次作业类的内聚性依然在一个可以接受的高度,各类的分工还是相对明确。最复杂的类是Term,内含了众多计算和化简的操作,是整个数据结构的中坚。FactorFactory作为三个工厂中最底层的工厂,是正则表达式的用武之地,可以说是负责了所有“脏活累活”。

  • 类图

从类图中也可以看出第三次作业数据结构和第二次作业的最大差别:Expression类本身也作为Factor,体现了本次作业的递归思想。


Bug分析

Homework 1

第一次作业由于较为简单,全程只出现了一个bug,但也是致命的、导致程序完全错误的bug。在求导之后、准备输出之前,我将所有项放入一个排序Set中,按系数由大到小排序,从而避免在一开始输出负号;但是在书写排序函数compareToequals时,将所有系数相同的项判为相同,导致系数相同的项只能输出一个,导致程序完全错误。

戏剧性的是,这样完全错误的程序通过了弱、中、强测的所有测试点,但是在互测环节中被Hack爆34次,荣幸地成为了课上被提到的反面教材。这样的结果也让我产生了对本课程评测机制地疑惑。

这次的bug反映出的问题,其实并非是思维上的不缜密,而是对Java的不熟悉。作为曾经的C++选手,本应清楚地知道sort函数效率更高、使用更方便,也应知道如何正确书写比较函数,却被Java不能运算符重载、没有友元的机制绊了跟头。这一问题也反过来作用于本次作业的整个代码结构:结构不清晰、分工不明确,也导致了第二次作业的完全重构。从这一点来看,提前掌握一门语言虽然给学习一门新的语言打下了基础,但也使得少部分先入为主的概念成为了学习的绊脚石。

Homework 2

第二次作业经过完全重构,已经和第一次作业之间没有任何的关系,也不再具有Bug的可参考性。此次作业的bug是由于处理读入字符时,字符串越界访问导致的RE,这一错误在强测和互测中都有所体现。

其实很难说是什么原因导致写出了这一bug;但是提交前没有找到此次bug的原因也是显而易见的:没有用足够的样例来测试。坦诚地说,我并不擅长也不喜欢造样例来进行测试,而是习惯将这一过程交给评测机或是已有的样例,相信自己能用尽量少的次数写对代码。从今以后,我是否会自己造更多的样例呢,很难说,因为在任何应用中,我们总是用程序正常运行中的反馈来更新改进程序,而不是在运行之前自行验证。

Homework 3

本次作业基于上次作业改进,因此保留了很多上次作业的元素。本次被测出了两个bug:第一个bug是由于保留了上次作业将x**2输出为x*x的优化,导致输出了类似cos(x*x)这样的非法内容;第二个bug是由于递归优化时没有对递归过程进行优化,导致了TLE。

对于第一个bug我不想评价,只能说导致了大量失分十分遗憾;对于第二个bug,只能说编码时完全没有考虑到时间复杂度的问题(毕竟Java本身也不是一门适合描述算法的语言,缺乏C++的灵活性和C的执行效率),以后对于涉及到算法和数据结构的问题还要多加小心。当然,从本次程序的设计结构上来看,编码时没有考虑清楚优化在整个结构中应该所处的地位,导致优化方法过于零散,也是造成此次bug的根本原因;这一问题,往往需要借助编码前期的合理设计来解决,而这在我们目前来看的编码方式中还是相对缺乏的。


发现他人Bug

在讨论作业要求的内容之前,我想先谈一谈我对互测这一环节的看法,从教学的角度提出我的一些思考。首先,将互测结果纳入成绩计算,事实上是将代码评测和学业水平评价这一属于教学组的责任卸载到了学生身上;同时,不同的学生面对不同的互测数据和他人代码,意味着必然存在的更大不公平性。而互测不需要理解他人的代码,只需要提供测试数据,这势必造成会批量生产数据的人对不会批量生产数据的人的压制;然而,批量生产数据是否是本课程的教学内容、批量生产数据的能力又是否是本课程的评价标准呢。这是我向课程组提出的几个问题。

当然,互测能够让我们学习代码,也发现更多自己程序中的bug并进行优化;同时,教学组也通过一系列手段,试图降低本部分评测中不必要的竞争性。这是我认为本机制可以被肯定的原因。

综上所述,我非常感谢在本部分为我提供出错测试数据的同学们,让我能不断改进自己的程序。同时,我不擅长阅读他人的代码,也不喜欢写数据批量生成程序去恶意Hack他人的程序。我所用的hack用例,无非是在编程过程中体会到的程序可能不正确处理的数据,用自己可能犯的错误去检测他人的程序,并不追求通过hack他人获得多少分数,也就无谓“测试策略及有效性”。


应用对象创建模式

Homework 1

几乎是面向流程地完成了对象创建,在主函数中处理字符串并调用相应构造函数。仅适用于较简单的题目要求,难以进行扩展。

Homework 2

完全不基于上次作业而进行了重构,但依然保持了大正则识别。将字符串处理和对象创建分离在另一个StringProcessor类中,但该类内部依然使用面向流程的思想,处理字符串的同时进行分支判断、构造函数调用和后续处理。

Homework 3

为表达式、项和因子分别书写了相应工厂;工厂中的创建方法只接受对应部分的字符串为参数,每个工厂中面向流程的代码量显著缩减,返回相应类型的对象,基本实现了上级处理流程和对象创建的解耦。大致流程可表示为:

st=>start: String
exp=>operation: ExpressionFactory
div1=>subroutine: divide
term=>operation: TermFactory
div2=>subroutine: divide
fac=>operation: FactorFactory
con1=>condition: is Expression?
con2=>condition: has sub-func?
ed=>end: return

st->exp->div1->term->div2->fac->con1
con1(yes)->exp
con1(no)->con2
con2(yes)->fac
con2(no)->ed

对比和心得体会

客观来说,在字符串的处理方面,大家的思路和结构都大同小异。更值得我借鉴的地方,可能在于求导和化简的部分,显然参考代码中的结构更为清晰。因为我写代码习惯想到哪儿写到哪儿,在作业周期较短、时间比较紧的条件下,难以做出优秀的前期规划和后期调整;而一些同学可能更习惯花时间去规划和调整代码,可以说互有利弊。结合我自己的情况而言,还是应该保持对代码能力的自信,并且在写代码时就想清原理、考虑周全,避免出错。

第一单元的作业,对我来说是两方面的转变:从更强调面向流程的算法体系到更强调面向对象的工程设计的转变、从更为灵活的C++到更为规范的Java的转变;如前文所提到的,既是一种技术的基础,也是一种思维的限制。同时,本单元作业也令我体会到“完全面向对象”的快感:借助不用在意底层的实现、而是用代码完全体现人类顶层思维的编程模式来解决问题。但这样的快感是基于完成本次简单缺繁复的作业并得到分数的;也许只有像从根本出发了解如何搭建一颗CPU、一个操作系统一样学习透彻程序设计的一种原理,才能真正从中得到具有满足感的收获。

posted on 2020-03-21 00:53  Stitch11752  阅读(135)  评论(0编辑  收藏  举报

导航