OO_第一单元总结

前言

  第一单元的题目是表达式化简,涉及因子:
|
|--变量因子
|  |--幂函数
|  |--三角函数
|  |--自定义函数调用
|  |--求和函数
|--常数因子
|  |--带符号整数
|--表达式因子
.  |--'('表达式')'

  项由因子相乘构成,表达式由因子相加/减构成。
  大致要求:展开所有括号(除三角函数括号),由答案长度判定性能。

一.整体思路简介

1.结构分析

  程序结构如下

│ MainClass(主类)
│ AnswerParse(化简表达式)
│ Atom(形成答案的基本原子,形式:系数*x**指数*三角函数*三角函数*...)

├─Factor
│ Factor(所有因子的父类)
│ Expr(表达式)
│ Term(项)
│ Number(带符号整数)
│ Trig(三角函数)
│ Var(幂函数)

├─Math
│ AddSub(加减法)
│ Mul(乘法)

└─Parse
  ParseFirst(分析输入表达式,替换求和函数以及自定义函数)
  SumParse(求和函数替换)
  Def(保存自定义函数)
  DefParse(自定义函数替换)


2.思路简介

对传入表达式进行解析
1.替换自定义函数以及求和函数(ParseFirst)
2.分析表达式,依加减号拆分为项(Expr.analysis())
3.分析项,依乘号拆分为因子(Term.analysis())
4.因子转换为Atom类型,并在term、Expr进行计算合并
5.化简输出


3.类图

image
image


4.化简思路

  • 在分析计算时,于atom级合并表达式成为“系数*x**指数*三角函数*三角函数*...”的最简形式,在加减类和乘法类里,最大限度合并可以合并的项,例如在加减类,把除了系数外其他元素相同的项合并等。
  • 输出结果,按系数从大到小排列,优先输出系数为正的数据,省略加号
  • x**2更改为x*x,sin(0)、cos(0)分别化简为0、1,将指数为0的因子化简为1
  • 我对于三角函数公式化简比较谨慎,考虑到部分同学性能优化导致答案出现问题,或者程序TLE,我在观察了强测的答案后,最终选择仅采用sin(x)**2+cos(x)**2 = 1的化简方法,具体实现为把cos(x)**2更改为1-sin(x)**2,将一个项变为两个项,代入结果,最后进行加减法合并化简。
  • 由于cos()函数为偶函数,可以考虑将()内的因子负号去掉

  在化简部分的确做得不够好,但是在保证正确性的前提下,最终结果还不是很差。后两次作业都有三个测试点总分近80,还有很大的提升空间。希望以后的作业可以合理分配精力,努力提高一下性能分。

二.程序分析

1.度量分析

方法复杂度分析(仅展示了部分圈复杂度较大的方法)
image
圈复杂度饼状图
image
类复杂度分析
image
圈复杂度总和饼状图(类)
image


2.耦合度&复杂度分析

  关注两个指标:

  • v(G),圈复杂度,数值越大,意味着代码中错误的可能性更大,维护的成本更高。
  • iv(G),设计复杂度,数值越大,意味模块耦合度高,可读性及可维护性差。(衡量指标是模块和其他模块的调用关系)

  程序部分方法iv(G)较大,说明这些方法的耦合度高,内聚性差,模块的可重用性、移植性较差。

  • Trig.toString()方法,分析后发现这个方法是多余的,忘删了
  • Term.analysis()方法,耦合度高,因为在分析因子时,调用了各种因子类,并且在这一步尝试初步化简。
  • Term.merge()方法,将analysis得到的因子进行初次合并化简,调用了乘法运算,耦合度较高。
  • AddSub以及Mul的compute方法,多次调用atom类以及Trig类,在atom级进行运算并合并,耦合度较高。
  • Atom.toString()方法,在atom级化简,由于调用了许多因子类,耦合度较高。

  饼状图显示,v(G)较大的四个方法分别为AddSub和Mul类里的compute,Atom类里的toString,以及Term类里的analysis,原因大致同上。

  为了达到表达式最简化,Term.analysis()里使用了众多的判断条件,企图在事先分析出sin(0)、cos(0)、去掉表达式因子的括号、将指数为0的因子直接存为1,然而由于圈复杂度过大,这个部分也包含了一个很严重的bug。

三.BUG分析

1.作业bug分析

  在前两次作业中,未发现bug。


  在第三次作业中,在互测时发现了一个bug————忘记在sumParse把i的替换值两边加上括号。这个bug导致对于i=-1,i**2的类型的替换出错,错误结果是-1**2 = -1,而正确的答案是(-1)**2 = 1。
  由于我在本地生成的随机数据不包含sum,导致这一个点没有被测试到,这也是我测试做得不够完善的原因。


  另外一个强测以及互测都没有被发现的bug,就在圈复杂度高达42的Term.analysis()里。
  重写了三角函数类的equals方法后,我判断的两个三角函数equals是指数相等,三角函数类型相等,三角函数括号内的元素相等。
  在Term.analysis(),我想判断一个三角函数组里面是否包含一个三角函数,和指定的另一个三角函数类型、括号内元素相等,可是判断代码写成了:
if(trig.contains(trig)){...}
这就导致,判断的条件加了一个指数相等,把某些符合条件的数组略去了,直接引发了后面乘法模块的错误。
  实际上,较短的随机数据无法测出我的bug,我在本地随机生成的十几万条数据长度都限制在了35,调整为50,就能发现错误的样例。


2.自动化测试

  主要借助了python的sympy库来进行测试,在本地生成测试数据,利用自己的数据和sympy化简结果进行对拍。
  但是sympy不能化简带前导零的整数,而且对于一些三角函数里面带两层括号的较长的输出,也会显示不匹配,只能靠人眼比对,目前不清楚为什么。
  借鉴讨论区后,找到了可以一次对拍多人的方法。后面在互测阶段,进行黑盒测试的时候会轻松点。


3.总结

  • 本地测试,代码努力做到全覆盖
  • 充分做边界数据的测试

四.架构设计体验


1.迭代

  • 第一次,从0开始构建代码时最为艰难,甚至思考了好久都没敢开始,一直在想思路,现在的架构其实是开始就想出来的,但也是在否定完后面所有的设想后,才选择了最初的思路。第一次设计架构的时候,将表达式因子单独作为一个类提出,程序不支持多层嵌套。
  • 第二次,在第一次的基础上,将表达式因子类和表达式类合并。增加了三角函数,自定义函数和求和函数调用,发现了HashMap的离奇之处。就像助教解释的“将对象【放入 HashSet】或【作为 HashMap 的 Key】时,一定要注意保持这个对象的【不可变性】——即:放进去之后就不能修改这个对象。否则整个哈希表的结构就会产生混乱,引发一些非常奇怪的 bug,而且这个比较难调试”。
  • 第三次,在第二次的基础上,将所有的HashMap换为ArrayList,增加了sin(x)**2+cos(x)**2 = 1 的优化。

  这三次作业起码没有经历重构,还是节省了不少的时间。


2.讨论课及课堂知识

  • 注意类的设计,只把这个类该有的属性和方法赋给它。在第二次迭代的时候,把数学运算从term和expr类里面抽离出来,另外设计了加减运算类和乘法运算类。
  • 对于题面给的要求,进行合理地变通。例如把**换为^,把小x换为大X等,的确可以很方便处理。

3.第一单元完结体验

  开始迟迟没有定下具体思路,周二一整天,都在不断地否定中,进度为0,其实还是很慌的,担心自己第一次作业就寄掉,后来,最终确定思路,并把它实现的那一刻,十分开心。整体上看,很好理解题意,设计程序的时候思路可以更清晰,也可以省去很多bug,但是想得太多也不行,真正开始写代码,才能找到真正的难点所在,也才能更好理清思路。
  第二次作业,大中午昏昏沉沉中一直在找一个bug,感谢助教开了个测试点,de出了一个十分傻的bug,我觉得以后还是清醒时debug比较好。
  暴力替换求和函数和自定义函数真的很容易出现bug,高耦合、圈复杂度高的代码段往往是bug集中点。对容器的理解不够深,导致我出现了循环中删元素,写出的代码和自己的设想不一致等bug。
  第一个月的OS任务相对轻松,我可以将大部分的时间投入到OO里面,之后随着OS的任务加重,希望我能提高效率,利用好时间,更好平衡两门专业课。
  总之,要学的还很多,要努力像优化程序一样优化自身,像修复bug一样不断自省。

posted @ 2022-03-25 00:10  eiang  阅读(75)  评论(0编辑  收藏  举报