BUAA OO Unit 1 总结

赵泽文-22371150 学生 2024-03-23 19:55:26

BUAA OO Unit 1 总结

最终架构

img

架构分析:

  • MainClass 为程序入口,读入字符串输入,将其交给 preProcessor 进行预处理。处理后的 input 交给 Parser 解析表达式。在自定义函数定义的行,input 则被交给 FunctionUtils,解析自定义函数定义。

  • Lexer 负责将输入进行词法分析,拆分成一个个不同类型的 Token,并将其返回给 Parser。

  • Parser 按照语义及层级顺序,解析出 Expr,Term, Factor
    由于多种 Factor 都有整数指数属性,所以我是将 Factor 作为父类,
    各种 Factor 作为子类。

  • TokenType 是 Token 类型的一个枚举类。

  • Poly 是多项式类,是进行多项式计算,化简,以及转化为字符串的类,有属性 TreeSet units.

  • Unit 是单项式类,或者说基本项类,在每次作业中含义不同。内部进行基本项的计算化简,toString。

  • Expr, term, Factor 及其子类均实现了 toPoly()方法,都能够转化为 Poly,以实现统一。这些类的 toString 方法也都是直接重写为toPoly().toString() .

    • 基于度量来分析自己的程序结构

      • 度量类的属性个数、方法个数、每个方法规模、每个方法的控制分支数目、类总代码规模
      • 计算经典的OO度量(可使用工具),分析类的内聚和相互间的耦合情况
      • 画出自己作业的类图,并自我点评优点和缺点
        • 注意1:不要使用IDEA等工具“无脑”逆向生成类图
        • 注意2:需要配文字来解释每个类的设计考虑
    • 架构设计体验

      • 结合三次作业的迭代介绍自己的架构如何逐步成型
        • 若经历过重构,则可通过类图或度量数据来对比重构前和重构后的程序结构,并介绍重构体验
        • 自定一个新的迭代情景,并说明你的最终设计在这个新迭代情景上的可扩展性
    • 分析自己程序的bug

      • 分析未通过的公测用例和被互测发现的bug:特征、问题所在的类和方法,为什么会出现这样的问题?你能否通过更好的设计避免这样的问题?
      • 对比分析出现了bug的方法未出现bug的方法代码行和圈复杂度上的差异,并思考可以通过怎样的方式来降低方法的复杂度
    • 分析自己发现别人程序bug所采用的策略

      • 列出自己所采取的测试策略及有效性,并特别指出是否结合被测程序的代码设计结构来设计测试用例
    • 分析自己进行的优化

      • 你做了什么优化,怎么做到的
      • 你的优化能否保证你代码的简洁性与正确性?如果能,为什么。如果不能,思考可能的解决方案
    • 心得体会

      • 本单元学习的心得体会,真情实感
    • 未来方向

      • 你觉得可以如何修改第一单元的课程,让大家更好的进行第一单元的学习?

复杂度分析

Class metrics

img

爆红并不多,似乎还不错?
Unit 类的 WMC 较高, 毕竟我在其中放了很多方法。

note: WMC 是 Weighted Method Count 的缩写,也称为权重方法计数。它是一种度量对象内部复杂性的度量指标,用于衡量一个类中方法的数量和复杂性。
WMC 的计算方法是将每个方法的复杂度(通常使用 McCabe 圈复杂度)加总起来得到一个总和,即为该类的 WMC 值。WMC 值越高,表示该类的方法更多且相对较复杂,可能需要更多的测试和维护工作。
通过计算 WMC 值,可以帮助我们了解一个类的复杂性、维护难度和可测试性等方面的信息。通常情况下,较高的 WMC 值可能意味着较高的复杂性和耦合性,可能需要进一步重构来使代码更加清晰和可维护。

方法复杂度分析

爆红的方法
| method | CogC | ev(G) | iv(G) | v(G) |
| :------------------- | :--- | :---- | :---- | :--- |
| Lexer.tokenNext() | 16.0 | 7.0 | 8.0 | 15.0 |
| Parser.parseFactor() | 6.0 | 2.0 | 4.0 | 11.0 |
| Poly.addUnit(Unit) | 8.0 | 4.0 | 5.0 | 6.0 |
| Poly.compareTo(Poly) | 4.0 | 4.0 | 2.0 | 4.0 |
| Poly.toString() | 5.0 | 4.0 | 4.0 | 5.0 |
| Unit.mul(Unit) | 4.0 | 4.0 | 3.0 | 5.0 |

lexer 的 tokenNext()用于获取下一个 token,其中有大量判断结构,尽管我已经做过重构优化,但其控制结构复杂度还是难以控制。
parseFactor 倒是有优化余地,因为我对一些比较短的 Factor 类型的解析是直接放在 parseFactor 里的,可以拉出来单独一个方法。

note:

  • CogC(全局复杂度):CogC 表示方法的复杂性,可以通过计算方法内的 McCabe 圈复杂度(Cyclomatic Complexity)来衡量。较高的 CogC 值表示方法的控制流程复杂,可能需要更多的测试和维护工作。
  • ev(G)(边界边数):ev(G) 表示方法的边界边数,即方法内的边界路径个数。较高的 ev(G) 值表示方法中存在多个控制路径,可能导致更高的复杂性和测试工作量。
  • iv(G)(内部边数):iv(G) 表示方法内部的边数,即方法内部控制结构之间的直接连接数。较高的 iv(G) 值表示方法内部控制结构之间的连接较多,可能导致更高的复杂性。
  • v(G)(边界边数与内部边数之和):v(G) 表示方法内的控制结构总数,即 ev(G) 和 iv(G) 之和。较高的 v(G) 值表示方法内控制结构较多,可能导致更高的复杂性。
modulev(G)avgv(G)tot
project2.051546391752577199.0

代码量分析

1711186515554

不算空行和注释, 848 行,似乎不算太多?
最长的是 Unit,这是因为我在 Unit 里放了太多优化和简化输出的内容了。
其次就是 Poly 了

架构设计体验及 bug 分析

第一次作业

上机训练万岁! hyggge,神!
上机教会了我:将指导书中要求的形式化表达,直接翻译成了一层层的递归下降的类
hyggge 的博客教会了我 Poly 和 Mono 这样的架构,实现各层级计算、输出的统一。
让我了解到后面会改基本项,所以提前将 Poly 和 Unit 的功能解耦。
基本项:
coe*x^exp

第二次

新增自定义函数和指数函数。
很炸裂,忙了三天,我中测一个点也没过。
最后五六个小时,硬是被一个 exp(x)难住了,输出是 1,一直是 1.
调试的时候却出现了变化,竟然每次都不一样。
最后才发现,我的 toString 方法里使用并改变了未经深克隆的对象,导致调试的时候调用 toString 改变了对象,从而结果不一样

exp 这个只需要改一下基本项:
coe*x^exp*e^poly
添加类 ExpFactor。
我这里还改了个类名,从 Mono 到 Unit
自定义函数定义:由 FunctionUtils 完成,内用 Parser 解析等号前的函数名和参数列表,并与函数定义式(字符串)一起放进一个 MyFunction 对象里。
自定义函数调用:ParseFactor 中解析实参列表和函数名,利用 FunctionUtils 里的方法进行虚参到实参的字符串替换,将替换后的字符串进行 Expr 解析,创建 FuncFactor 对象。
FuncFactor 的 toPoly 直接使用 Expr 的 toPoly 即可。

因为挂了,所以在 bug 修复的时候我有大把时间进行了一些重构。

  1. Poly 的 units,本来是从 exp 到 coe 的 Map,我改成了 unit 的一个 TreeSet,这就需要实现 Poly 和 Unit 的 Comparable 接口。
    但是这两个类相互包含,要进行比较就要相互调用对方的 compare 方法,递归下去,岂不是停不下来?转念一想,实际上是有递归边界的。毕竟嵌套的层数有限。
  2. 听同学说指数可能超范围, 我直接丧心病狂地在项目文件中搜索 int,看哪个不顺眼(实际上只改了几个)就直接改成 BigInteger。
  3. 控制结构优化,tokenNext 方法直接超行数了,checkstyle 都过不了, 于是我将大部分简单的符号放进 Map 里,建立字符到 TokenType 的映射,这样结构就有优化空间了。

第三次

自定义函数的嵌套

第二次作业已经自动实现了,因为是虚参到实参的字符串替换,替换后进行 parseExpr,其中若有自定义函数调用,又会自动解析。

新增求导

这次的上机训练的架构我本来也是想照抄的,但是在每个类中都写一个求导方法似乎并不符合我的初衷。我建立一个 Poly 和 Unit 类,就是为了能统一计算,方便管理。
所以我选择在 Poly 和 Unit 里直接求导。
基本项的导数是一个 Poly:
coe*exp*x^(exp-1)*exp^poly+coe*x^exp*e^poly*dx(poly)
Poly 的导数是 units 中 unit 导数的和(利用 add()方法)。

自定一个新的迭代情景,并说明你的最终设计在这个新迭代情景上的可扩展性

比如添加求和函数,三角函数等,我就可以并且新增对应的函数 Factor,在基本项上添加对应的部分,在 Unit 的 mul()方法中添加一个新的 Factor 对应的 mul 方法。

我的优化:

  1. 正项先输出,省个正号。
  2. expFactor 中有指数函数的指数 poly,poly 中的 units 由于 TreeSet 的保序性,能够保证相同指数的项被合并。
  3. exp((x))与 exp(x),这里坑点是 exp(-x)不对

优化的优化

尽管我实现了上述优化,但是复杂得难看。
第三次作业过程中我又将 Unit 的 toString 进行了简化。

分析自己程序的 bug

  • 分析未通过的公测用例和被互测发现的 bug:特征、问题所在的类和方法,为什么会出现这样的问题?你能否通过更好的设计避免这样的问题?
    从某种意义上来说,我公测没错过————毕竟要么公测没错,要么中测没过。
    毕竟 bug 修复时的数据点给了我很大启发。

    在这里我还是重点分析我第二次挂的原因。
    我在 Poly 的 toString()中,为了将正项先输出,首先将 units clone 了一下(问题就在这),然后遍历新的 units,有正项就输出,并将这个正项系数置 0,后续再输出剩余项。
    然而 units(当时)是 Map,或者后来的 Set,clone 方法不是深克隆,增删没问题,改容器中的对象就不行。
    最痛的事情,就是自己能测出 bug,却 de 不出来。尤其是每次输出结果不一样,让我很慌。

  • 分析自己发现别人程序 bug 所采用的策略
    一方面,我尝试卡边界,构造嵌套层数比较多的数据,但是很容易就超出了 cost 的要求。
    另一方面,我利用自己的数据生成器和别人的评测机,进行大量测试.
    我测出过好多次同学的 tle,实际上数据并不复杂,指数也在 6 那里,但是经过精简,cost 没问题了,程序确又能正常跑了。
    所以到最后也没成功 hack 一次。

心得体会与未来方向

  • 1000多行,还是很有成就感的。
  • 第二次作业中测没过打击挺大的。
  • idea debug功能挺好用的。
  • 重写Object的方法要谨慎
  • clone要分深克隆浅克隆
  • 优化需谨慎,扣分悔莫及
  • 类的解耦,方法的解耦

oopre中其实可以下放更多接口相关内容

类复杂度完整版

methodCogCev(G)iv(G)v(G)
DxFactor.DxFactor(Expr)0.01.01.01.0
DxFactor.toPoly()0.01.01.01.0
ExpFactor.clearExp()0.01.01.01.0
ExpFactor.derive()0.01.01.01.0
ExpFactor.ExpFactor(Expr)0.01.01.01.0
ExpFactor.toPoly()0.01.01.01.0
Expr.addTerm(Term)0.01.01.01.0
Expr.derive()1.01.02.02.0
Expr.Expr()0.01.01.01.0
Expr.toPoly()1.01.02.02.0
Expr.toString()0.01.01.01.0
ExprFactor.ExprFactor(Expr)0.01.01.01.0
ExprFactor.toPoly()0.01.01.01.0
Factor.derive()0.01.01.01.0
Factor.Factor()0.01.01.01.0
Factor.getExp()0.01.01.01.0
Factor.setExp(BigInteger)0.01.01.01.0
Factor.toPoly()0.01.01.01.0
Factor.toString()0.01.01.01.0
FuncFactor.FuncFactor(String, ArrayList)0.01.01.01.0
FuncFactor.toPoly()0.01.01.01.0
FuncUtils.callFunction(String, ArrayList)6.03.05.05.0
FuncUtils.defFunction(String)1.01.02.02.0
FuncUtils.getFunction(String)0.01.01.01.0
Lexer.curToken()0.01.01.01.0
Lexer.curTokenType()0.01.01.01.0
Lexer.getLeft()0.01.01.01.0
Lexer.getNumberToken()2.01.03.03.0
Lexer.getPos()0.01.01.01.0
Lexer.Lexer(String)0.01.01.01.0
Lexer.tokenNext()16.07.08.015.0
MainClass.main(String[])1.01.02.02.0
MyFunction.getDefinition()0.01.01.01.0
MyFunction.getName()0.01.01.01.0
MyFunction.getParams()0.01.01.01.0
MyFunction.MyFunction(String, ArrayList, String)0.01.01.01.0
NumberFactor.derive()0.01.01.01.0
NumberFactor.NumberFactor(BigInteger)0.01.01.01.0
NumberFactor.toPoly()0.01.01.01.0
Parser.parseDxFactor()0.01.01.01.0
Parser.parseExpr()4.01.03.04.0
Parser.parseFactor()6.02.04.011.0
Parser.parseFuncFactor()1.01.02.02.0
Parser.Parser(Lexer)0.01.01.01.0
Parser.parseSign()2.01.03.03.0
Parser.parseTerm(int)1.01.02.02.0
Poly.add(Poly)1.01.02.02.0
Poly.addUnit(Unit)8.04.05.06.0
Poly.clone()1.01.02.02.0
Poly.compareTo(Poly)4.04.02.04.0
Poly.derive()1.01.02.02.0
Poly.equals(Object)3.03.02.04.0
Poly.getUnits()0.01.01.01.0
Poly.hashCode()0.01.01.01.0
Poly.isFactor()1.01.02.02.0
Poly.isZero()0.01.01.01.0
Poly.mul(Poly)3.01.03.03.0
Poly.negate()1.01.02.02.0
Poly.one()0.01.01.01.0
Poly.Poly()0.01.01.01.0
Poly.Poly(TreeSet)0.01.01.01.0
Poly.pow(BigInteger)3.01.03.03.0
Poly.toString()5.04.04.05.0
Poly.zero()0.01.01.01.0
PowerFactor.derive()0.01.01.01.0
PowerFactor.PowerFactor(String)0.01.01.01.0
PowerFactor.toPoly()0.01.01.01.0
PreProcessor.mergeOperator(String)0.01.01.01.0
PreProcessor.preProcess(String)0.01.01.01.0
PreProcessor.trimBlank(String)1.01.02.02.0
Term.addFactor(Factor)1.01.02.02.0
Term.derive()6.01.04.04.0
Term.Term(int)0.01.01.01.0
Term.toPoly()2.01.03.03.0
Unit.add(Unit)0.01.01.01.0
Unit.clone()0.01.01.01.0
Unit.compareTo(Unit)2.03.01.03.0
Unit.couldAdd(Unit)1.01.02.02.0
Unit.derive()0.01.01.01.0
Unit.derivePoly()0.01.01.01.0
Unit.derivePower()2.03.03.03.0
Unit.equals(Object)4.03.04.06.0
Unit.getCoeSign()0.01.01.01.0
Unit.getPoly()0.01.01.01.0
Unit.getStrCount()3.01.01.04.0
Unit.getStrings()8.01.06.06.0
Unit.hashCode()0.01.01.01.0
Unit.isOne()2.01.04.04.0
Unit.isZero()0.01.01.01.0
Unit.mul(Unit)4.04.03.05.0
Unit.negate()0.01.01.01.0
Unit.one()0.01.01.01.0
Unit.onlyPoly()0.01.01.01.0
Unit.onlyPower()0.01.01.01.0
Unit.toString()9.02.04.06.0
Unit.Unit(BigInteger, BigInteger, Poly)0.01.01.01.0
Unit.zero()0.01.01.01.0
Total118.0127.0167.0199.0
Average1.21649484536082461.3092783505154641.72164948453608262.051546391752577
...全文
117 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

301

社区成员

发帖
与我相关
我的任务
社区描述
2023年北航面向对象设计与构造
学习 高校
社区管理员
  • YannaZhang
  • CajZella
  • C_ecelia
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧