BUAA-OO-2021 第一单元总结

在上这个课前的一点点理解

编程范式

OOP(Object Oriented Programming)是一种编程范式。许多号称多范式的编程语言都有对面向对象的支持。

OOP的流派

其中C++的面向对象允许多继承、多态、以模板为特征的泛型化;Objective-C单继承、以传递消息实现函数调用、动态绑定、重载不允许同名而参数个数相同参数类型不同。前者为Simula67学派,后者为Smalltalk学派。OC可以一定程度上与Java类似地有动态反射能力(C++Dlang等其它语言的编译期反射不计)。

AOP

AOP(Aspect Oriented Programming)可以视为OOP的延续。面向切面编程可以隔离降低业务逻辑的耦合性,提高可重用性。通过代理程序织入代码,类似python的装饰器,但对性能有少许影响。

选用Java在课程中的优点

JavaGC很适合让学生专心到OOP当中而非繁杂的内存操控(诸如内存分配、回收、锁、内存对齐、内存定域性、内存碎片、内存扩散等),对比如C++Allocatorpmr抑或是shared_ptrunique_ptr来说可以从底层中解脱出来。常用的JVM(hotspotVMopenj9VM)对此方面有较为全面的工作,包括JIT等都对于刚接触编程的学生来说性能较为友好。而C#CLI(CLRMono)则略逊于它,乃至Stream APILINQ的性能,C#的移植性也是最近才达成的(.Net 5),故抛开语言的优雅Java可以说更适合本次教学。第三方库在本次课程中不计,则按下不表。

关于编程原则

对象是实体,类则为对象的抽象。OOP拥有三大核心特性继承性、封装性、多态性。而一些后起之秀如Rust之流,以trait的类型系统为主而非传统的类与对象,亦能达成一些面向对象的特性(封装)。但作为一种思想,究竟是更清晰还是更啰嗦,需要程序员自主的判断。同时在写代码时,也更注重于“对修改封闭,对扩展开放”,以及DRY原则等。

第一次作业

类图

classDiagram Main class Main{ consumeExpr() consumeItem() consumeFactor() }

第一次作业比较简单,于是直接使用过程式的方式更为直观,提交的代码只有一个文件、低于100行(包括空行)。

代码度量

(注:ev(G)表示方法的结构化程度。iv(G)表示方法的调用紧密程度。v(G)表示圈复杂度。OCavg代表类的平均循环复杂度。)

  • 代码统计

    Source File Total Lines Source Code Lines Source Code Lines[%] Comment Lines Comment Lines[%] Blank Lines Blank Lines[%]
    Main.java 98 90 92% 0 0% 8 8%
    Total: 98 90 92% 0 0% 8 8%
  • 类复杂度

    class OCavg OCmax WMC
    Main 5.20 9 26
    Main.Pair 1.00 1 1
    Total 27
    Average 4.50 5.00 13.50
  • 方法复杂度

    method CogC ev(G) iv(G) v(G)
    Main.consumeExpr() 5 1 3 4
    Main.consumeFactor() 4 2 4 5
    Main.consumeItem() 3 1 3 4
    Main.diff(Entry<BigInteger,BigInteger>) 9 1 5 5
    Main.main(String[]) 24 1 7 9
    Main.Pair.Pair(BigInteger,BigInteger) 0 1 1 1
    Total 45 7 23 28
    Average 7.50 1.17 3.83 4.67

其中Main中的OCavg较高是因为在判定token时连续的equals所致,因为在checkstyle允许下这样会比switch块的代码更短。

BUG分析

本次作业暂无BUG。

互测分析

自己所写的对拍程序没有找到同房间中程序的错误。后来猜测可能有对指数大小未用BigInteger的可能,故通过手写测试数据与查看源代码的方式确认了该BUG于房间中存在。

第二次作业

类图

classDiagram FuncWrapper <-- Op FuncWrapper <-- Num FuncWrapper <-- Var Op <-- OneOp Op <-- TwoOp OneOp <-- Sin OneOp <-- Cos TwoOp <-- PlusOrSub TwoOp <-- Mul TwoOp <-- Pow PlusOrSub <-- Add PlusOrSub <-- Sub FuncWrapperHelper FactorHelper class FuncWrapper{ getString() Stream~String~ diff() FuncWrapper simplify() FuncWrapper isZero() boolean isOne() boolean isNegOne() boolean } class Num{ getVal() BigInteger add(Num) Num substract(Num) Num multiply(Num) Num negate() Num } class OneOp{ getData() FuncWrapper } class TwoOp{ getLhs() FuncWrapper getRhs() FuncWrapper } class Pow{ isOrdinary() boolean} class FuncWrapperHelper{ negativeFunc(FuncWrapper) FuncWrapper smartSimplify(FuncWrapper) FuncWrapper }

第二次作业实际上已经接近第三次作业的架构。对于FuncWrapper有一个抽象类,其中操作、变量x、常量继承之,而操作又派生出一目运算、二目运算(指接受的参数个数)。此时已拥有了完整的功能,但对简化部分还较为薄弱。于是在此基础上引入了FuncWrapperHelper用于激进的化简,增加了诸如合并同类项、重排等功能。同时将取负操作删除,取而代之的是乘以-1,以得更简洁的表示。

另一个亮点在于本项目不使用简单的toString,而改为getString,返回一个由Stream<String>类似链表连接起来的字符串流,将生成字符串的时间复杂度从理论O(n^2)降为O(n)。

graph TD A(+) B(x) C(+) D(x) E(+) F(x) CONST(1) A-->B A-->C C-->D C--... n个-->E E-->F E-->CONST

代码度量

  • 代码统计

    Source File Total Lines Source Code Lines Source Code Lines[%] Comment Lines Comment Lines[%] Blank Lines Blank Lines[%]
    Add.java 36 32 89% 0 0% 4 11%
    Cos.java 28 24 86% 0 0% 4 14%
    FuncWrapper.java 22 15 68% 0 0% 7 32%
    FuncWrapperHelper.java 174 167 96% 0 0% 7 4%
    Main.java 196 186 95% 0 0% 10 5%
    Mul.java 73 68 93% 0 0% 5 7%
    Num.java 59 46 78% 0 0% 13 22%
    OneOp.java 11 9 82% 0 0% 2 18%
    Op.java 4 4 100% 0 0% 0 0%
    PlusOrSub.java 6 5 83% 0 0% 1 17%
    Pow.java 78 71 91% 0 0% 7 9%
    Sin.java 38 32 84% 0 0% 6 16%
    Sub.java 38 34 89% 0 0% 4 11%
    TwoOp.java 17 14 82% 0 0% 3 18%
    Var.java 21 17 81% 0 0% 4 19%
    Total 801 724 90% 0 0% 77 10%
  • 类复杂度

    class Ocavg OCmax WMC
    FuncWrapperHelper 5.83 10 35
    Main 4.00 12 36
    Mul 3.40 7 17
    Pow 2.50 6 15
    Sub 2.00 6 15
    Add 1.75 4 7
    Cos 1.25 2 5
    Sin 1.17 2 7
    FuncWrapper 1.00 1 3
    Num 1.00 1 12
    OneOp 1.00 1 2
    Op 1.00 1 1
    PlusOrSub 1.00 1 1
    TwoOp 1.00 1 3
    Var 1.00 1 4
    Total 156
    Average 2.23 3.60 10.40
  • 方法复杂度

    method CogC ev(G) iv(G) v(G)
    FuncWrapperHelper.simplifyExpr(PlusOrSub) 17 2 9 12
    FuncWrapperHelper.simplifyItem(Mul) 17 6 8 13
    FuncWrapperHelper.negativeFunc(FuncWrapper) 10 7 7 7
    Main.parseExpr() 8 1 4 6
    Mul.simplify() 8 7 5 9
    Pow.getString() 7 5 6 9
    ... ... ... ... ...
    Total 119 110 137 181
    Average 1.70 1.57 1.96 2.59

通过复杂度的分析,FuncWrapperHelper中的代码复杂度偏高,因为对于化简使用了繁杂的逻辑,不适合人阅读与修改。而部分Mul类中亦显示复杂度较高,则是因为对于一些特殊情况的化简、生成字符串使用了专门的方法,增加了代码的复杂程度。

BUG分析

本次作业暂无BUG。

互测分析

又一次,自己所写的对拍程序未能发现错误。后来经过排查,又是数据范围给小了,并找到了一个披着BigInteger外衣而用着Long的程序,遂成功hack一次。但本房间其实还存在着另一个错误,本人在互测阶段没有发现。

第三次作业

类图

classDiagram FuncWrapper <-- Op FuncWrapper <-- Num FuncWrapper <-- Var Op <-- OneOp Op <-- TwoOp OneOp <-- Sin OneOp <-- Cos TwoOp <-- PlusOrSub TwoOp <-- Mul TwoOp <-- Pow PlusOrSub <-- Add PlusOrSub <-- Sub FuncWrapperHelper FactorHelper class FuncWrapper{ toString() diff() simplify() } class FuncWrapperHelper{ negativeFunc() smartSimplify() } class FactorHelper{ expand() }

第三次作业的架构与第二次作业差别不大。在递归下降时添加了WRONG FORMAT的判断。同时额外引入了FactorHelper,本来是用来作有限域上因式分解的尝试,但因较为复杂故只有了展开括号的功能。

代码度量

  • 代码统计

    Source File Total Lines Source Code Lines Source Code Lines[%] Comment Lines Comment Lines[%] Blank Lines Blank Lines[%]
    Add.java 36 32 89% 0 0% 4 11%
    Cos.java 37 33 89% 0 0% 4 11%
    FactorHelper.java 41 41 100% 0 0% 0 0%
    FuncWrapper.java 36 26 72% 0 0% 10 28%
    FuncWrapperHelper.java 174 167 96% 0 0% 7 4%
    Main.java 226 216 96% 0 0% 10 4%
    Mul.java 73 68 93% 0 0% 5 7%
    Num.java 69 54 78% 0 0% 15 22%
    OneOp.java 11 9 82% 0 0% 2 18%
    Op.java 4 4 100% 0 0% 0 0%
    PlusOrSub.java 6 5 83% 0 0% 1 17%
    Pow.java 78 71 91% 0 0% 7 9%
    Sin.java 50 38 76% 6 12% 6 12%
    Sub.java 38 34 89% 0 0% 4 11%
    TwoOp.java 17 14 82% 0 0% 3 18%
    Var.java 21 17 81% 0 0% 4 19%
    Total 917 829 90% 6 1% 82 9%
  • 类复杂度

    class Ocavg OCmax WMC
    FactorHelper 11.00 11 11
    FuncWrapperHelper 5.83 10 35
    Main 4.11 12 37
    Mul 3.40 7 17
    Pow 2.50 6 15
    Sub 2.00 6 15
    Add 1.75 4 7
    Cos 1.25 2 5
    Sin 1.33 3 8
    FuncWrapper 1.00 1 6
    Num 1.00 1 14
    OneOp 1.00 1 2
    Op 1.00 1 1
    PlusOrSub 1.00 1 1
    TwoOp 1.00 1 3
    Var 1.00 1 4
    Total 176
    Average 2.32 4.19 11.00
  • 方法复杂度

    method CogC ev(G) iv(G) v(G)
    FactorHelper.expand(FuncWrapper) 27 10 9 12
    FuncWrapperHelper.simplifyExpr(PlusOrSub) 17 2 9 12
    FuncWrapperHelper.simplifyItem(Mul) 17 6 8 13
    FuncWrapperHelper.negativeFunc(FuncWrapper) 10 7 7 7
    Main.parseExpr() 9 1 5 7
    Mul.simplify() 8 7 5 9
    Pow.getString() 7 5 6 9
    ... ... ... ... ...
    Total 156 128 163 209
    Average 2.05 1.68 2.14 2.75

相比于第一次作业,代码长度因为优化的需求而膨胀较多。而代码复杂度当中FactorHelper一下子收获了更高的复杂度,这源于其会一一检查传入的类型instanceof哪一个种类,并且逻辑较为繁琐。理论上可以将expand添加到FuncWrapper基类中作为一种基础的操作,这将会使其复杂度均摊并使平均代码较短,但对修改封闭的原则故后期几乎没有修改作业二祖传下来的代码(也就是结果上来说越改越难维护了)。也就是对于架构上来说暂时并没有重构,但是会将累积的问题推到将来重构,这在完成项目时不是一个好的选择。而观察到部分的getStringsimplify也被复杂度标红,则是因为不同的FuncWrapper互相之间的解耦不完全,需要花一些代码处理特例。

BUG分析

本次作业在强测中未出错。在互测阶段被检查出错误。

关于bug的唯心主义的原因,也在于最后的修改后并未对拍导致,过于心急地提交了。实际上在hack阶段本人也通过对拍找到了自己程序的bug。关于bug的代码层面的原因,在于出错的优化。其一,本程序中选用了若干种方法操作后结果的最短者,但是除了smartSimlify处理后以外的表达式并不一定满足作业三题面的要求;其二,本程序会检测Sin函数中的内容的结果是否带有前导的负号,并将其提到Sin的外侧,而如果此时Sin还被嵌套了幂函数则会出错(因为此时幂函数的底数变成了-1*Sin,而这不符合幂函数底数的要求);其三,因为幂函数底数非常规的情况依然存在(比如(x+1)**5),故本程序还有将非常规底数展开为连乘的功能,而此时如果遇到其二则是可能造成负指数,则会因此抛出异常,导致捕捉后输出WF,而这不是所预期的。

互测分析

通过对拍程序(针对作业三的),率先顺利地找到了自己的程序的错误。然后接着找到另外三个人的BUG,并成功hack了两位。

该对拍程序虽然在前两次互测中未发挥出特别大的用处,其实是因为生成数据的设置过于宽松,实际上其设置与功能比较全面。该程序用python实现并达7.89K,包含了各种类型的数据生成并能设定数据范围、嵌套深度、数据种类、概率分布等;用sympy进行判别并能处理多种异常,可以在超时的情况下终止运算;允许从多个源代码或jar运行受测程序;允许随机或手动的数据装填;可以对多个人进行统一评测等。

单元总结

第一单元的难度对入门来说刚刚好,熟悉了Java语法和基本的面向对象知识。

对于我个人来说:一个是给自己敲响警钟,不能过于轻视作业(从而导致了被hack),不可好高骛远,还是要实打实地完成课程。另一个是熟悉了IDEA的更多内容(如checkstylediagram等),然后与OS一同了解了更多git的操作,以及更多Java的实际编码操作。总的来说,还是希望自己能更多更广地学习,认真对待作业,多多与同学交流进步。

posted @ 2021-03-28 00:39  buaa-shy  阅读(173)  评论(1)    收藏  举报