北航2022面向对象第一单元:表达式解析和化简

北航2022面向对象第一单元:表达式解析和化简

1. 发现的典型问题

1.1 对象深拷贝

在使用对象时,应该尤其注意对象的属性是否在各种操作下都保持不变。特别是那些管理其他对象的对象。如果两个容器类储存了相同的对象引用,其中一个修改时,会把另一个容器中的对象一起修改,从而导致不可知的后果。

就这三次做的作业而言,时间上的要求不高,因此可以把所有管理数据的类全部设置成不可变的。或者获得一个新的对象,必须完全地拷贝原来对象的全部信息,包括容器内部的对象必须是地址不同的。

1.2 字符串提取和解析

比较好的字符串解析方法是递归下降,这样就不用在一个方法内处理所有的问题,一个方法只用处理一种运算即可,逻辑比较简单和清楚。但是这样就必须知道目前顶层是哪一种运算。

可以使用栈来判断顶层的运算。比如要判断该层是否是加法,可以遍历字符串,如果遇到左括号就压栈,遇到右括号就弹栈,只有遇到加号,而且此时栈为空时,才表示这个加号在顶层。

同样的方法可以用来分割函数的参数。函数参数是用逗号分割的,而且参数可以是其他函数的调用。我听到一种做法处理函数参数,就是把每两个逗号作为分隔符,每组分别判断是否是分隔符。这样时间开销相当大,而且分开的字符串形式复杂,不好判断。可以使用栈的方法解析函数参数字符串。如果遇到逗号,而且此时栈为空,就说明这个逗号一定是顶层的,可以作为分隔符。

1.3 加减号和正负号的判断

在第一次作业时,很多同学遇到了问题,不知道怎么区分加减号和正负号。在第一次作业时,我用到的是无脑判断。如果遇到一个 '+' 号,而且此时栈为空,就直接递归解析符号左右的字符串。如果左右都是合法的字符串,就可以认为这是个加号。

这种做法应该是没有逻辑问题的,根据递归分析,错误的字符串会在底层被识别,把错误信息逐层上传回来。但是在第二次作业中,因为这种方法我被hack了两次,然而不是报逻辑错误,而是超时。这是因为输入的字符串非常长,每个项都有一个加号和一个正号。这样我处理每个项,都必须递归分析整个字符串。这种方法的时间复杂度是指数函数,字符串稍微长一点耗时就非常长了。因此我被迫做了特判,就是字符串的结尾不能是 '+' ,'-','*' 号,否则直接返回字符串非法。因为输入的字符串一定是合法的,每次递归下降都是有效操作,这样就保证了每次操作的时间开销都是必要的。

2. 分析自己的程序

2.1 数量度量

指标:

指标名 作用对象 含义 方向
LOC 类/方法 代码行数 越小越好
LCOM 类中内聚度的缺乏,值越小说明内聚度好,符合高内聚要求 越小越好
FANIN 扇入表示调用该模块的上级模块的个数,值越大表示复用性好。 越大越好
CC 方法 圈复杂度,值越大说明程序代码质量低,且难以测试和维护 越小越好

第一次作业

类:

类名 简介 LOC LCOM FANIN
ExpFactory 用来构造表达式 154 0.625 0
Expression 表达式 102 0 3
Term 表达式中的项 62 0.286 1
Main 主类 10 -1 0

方法(只记录主要方法):

类名 方法名 简介 LOC CC
ExpFactory createExp 创建表达式 45 11
createExpByTerm 创建项 46 12
createExpByFactor 创建因子 32 7
simplifyStr 简化字符串 3 1
Expression toSimpleStr 简化字符串 28 7
power 乘方 18 4
multiExp 乘表达式 18 4
subExp 减表达式 4 1
negate 取负号 5 2
addExp 加表达式 14 3
addTerm 添加一个项 9 2
Term toSimpleStr 简化字符串 40 13

第二、三次作业

因为第二、三次作业的设计思路基本一样,所以用第三次作业来说明

类:

类名 简介 LOC LCOM FANIN
Main 主类 47 -1 0
AddSubFunc 加减函数 23 0 3
Func 函数基类 117 0 10
FuncFactory 用来构造函数 227 0 2
FuncLib 自定义函数库 105 0 2
MulFunc 乘法函数 18 -1 2
NumFunc 常元函数 19 0 4
OpFunc 正负函数 19 0 2
PowerFunc 指数函数 23 0 2
SumFunc 求和函数 64 0.5 2
TriFunc 三角函数 23 0 3
VarFunc 变元函数 23 0 6
Output 用来输出的类 150 0 3
StrUtil 字符串工具类 37 -1 3
Term 输出类的组成单元 157 0 2

方法(只记录主要方法):

类名 方法名 简介 LOC CC
Func toOutput 得到输出的对象 52 12
replaceFormal 将所有变元替换为新函数 12 4
replace 将该函数节点替换为新函数 10 2
duplicate 复制 23 4
FuncFactory createFunc 创建函数 47 12
createSumFunc 创建求和函数 33 6
createTriFunc 创建三角函数 15 3
createOpFunc 创建符号函数 13 3
createAddSubFunc 创建加减法函数 29 7
createPowerFunc 创建指数函数 31 7
createMulFUnc 创建乘法函数 33 8
createVarFunc 创建变元 10 2
createNumFunc 创建常元 10 2
FuncLib addFunction 添加自定义函数形式 5 1
createCustomFunc 创建自定义函数调用 35 7
getRealParamStrings 解析实参字符串 45 12
Output duplicate 复制 7 2
getString 获得化简字符串 26 7
power 乘方 15 3
mul 乘法 22 6
sub 减法 5 1
add 加法 19 5
negate 取负 8 2
addTri 添加三角函数 10 3
addNum 添加常数 4 1
addVar 添加变量 4 1
isNumFactor 是否是常量因子 7 2
isVarFactor 是否是变量因子 17 5
Term duplicate 复制 10 3
getString 获得化简字符串 53 16
mul 乘法 30 7
isNumber 是否是常数 9 3
isPower 是否只含有变量指数 9 3
isSimilar 是否是同类项 6 2
StrUtil noBlank 去除空白 6 2
noBracket 去除两端对应的括号 29 8

2.2 发现的bug位置(第二、三次作业)

  • 笔误
    在 FuncFactory.createPowerFunc() 方法中,使用到了很大的循环,循环中的一个判断条件写错。此处代码31行,是同类方法行数第二长的。
  • 超时
    在 FuncFactory.createFunc() 中没有特判,导致递归调用超时。该方法47行,是该类方法中最长的,该方法的CC为12,也是该类方法中最大的。这导致这个函数不好调试,而且一旦出错会影响到很多地方。
  • 重写错误
    在 MulFunc.duplicate() 方法中,重写Func.duplicate(),忘记递归调用乘法两边的复制方法,导致该方法复制不完全,没有达到深拷贝的效果。

2.3 结构图

  • 第一次作业

  • 第二、三次作业

3. 测试策略

因为时间所限,只有第一次作业做了比较完全的测试。因为整个结构是比较有规律的,二、三次作业根据1.1的易错点进行测试,即可基本保证不出问题。完全测试基本上是根据形式化表述,从底向上,逐层测试。比如先测试各种因子对不对,然后构造项进行测试,最后构造表达式进行测试。

因为时间所限,我没有具体针对代码进行互测,只是随意交了一些数据进行测试,当然也没有查到别人的bug。我认为,最大的问题可能就是对象深拷贝,其次是函数多次调用和循环调用的问题。如果这两个点都是正确的,我相信其他地方也不会有很大问题。

4. 设计体验

4.1 架构迭代

因为第一次作业只有多项式,所以我采用了很简单的方法解析,专门针对多项式。然后根据助教的提醒,后期的作业需要更大范围的抽象,所以只能重构了。第二次作业因为时间不够,加上整个架构全部要重写,所以作业效果并不好。但是架构设计好了以后,为第三次作业带来了很大的方便。第三次作业在第二次的基础上,修改行数估计只有二三十行,修改时间估计一两个小时。而且在强测和互测也没有什么错误。我认为只要把易错点都检查过了,就不会有什么问题。

4.2 心得体会

第一单元是进入OO课程的第一次训练。通过三次作业,我逐步地提高设计的抽象程度,以应对增加的需求。我原先认为,第一次作业和第二、三次的衔接不够,针对第一次作业的设计结构到后面几乎都要重构,否则会变得非常复杂。但是现在我认为这也不是什么问题。因为之后确实有可能会提出各种需求,而这是在之前的作业中不能预计的。所有的远见都是有限的,不可能考虑到所有的情况。我个人认为,程序首先要满足当前的需求和性能,其次才是预留未来的需求。我觉得要抓住主要矛盾和主要方面,优先满足当前需求,该重构时就重构,我听说unix还是linux这么庞大的系统都重写了几次。

关于作业量和难度,我因为之前看过一些Java的教程,所以感觉难度不是很大,只是作业量有点多,需要平衡这个课和其他的课的时间分配。但是我也听说有的同学感觉有些难度,可能有一次作业做得不是特别好。如果我之前没有看过相关知识,可能也会感觉比较吃力。我觉得这实在是没办法的事情,想要用好一门语言,至少要知道语法和常用用法。在pre中有比较好的练习,因为pre不是强制的任务,可能有些同学就没做了。

posted @ 2022-03-23 11:09  mtr329  阅读(144)  评论(0编辑  收藏  举报