OOBeiHang Unit1 Report

To Simplify Expression !

 

一、整体架构与拓展过程

整体架构分为 读取、解析、合并三个部分。

 

1. 读取部分:

读取部分主要包含类Lexer、Paser、FunctionFactor、FunctionList、Function、Sum。这一部分架构如下图:

(1)函数的获取:

Function类为自定义函数类, FunctionList用于记录所有的自定义函数。FunctionFactor作为函数因子,建立起Paser与Function的桥梁。类似于荣老师课上讲过的学生选课的案例,将学生、课程单独建类,再建立选课的这种连接类,这里FunctionFactor就是中间连接类。 ​ 在获取时,para存放调用参数,function存放所用函数,方法exFunction返回函数代入后的表达式形式。Function类主要部分为函数名name,表达式value(String),以及方法getAnswer利用传入参数再次调用解析求得表达式结果。此处,getAnswer即为解析过程,由于需要代入参数,parseFactor方法需要重写。解析过程见下一部分。 ​ 该三个类的设计目的是将函数的获取独立出来,减少主过程的复杂程度。

(2)求和的获取:求和获取类似于函数部分。

(3)其他的获取:采用训练中的递归下降方法。

 

2. 解析部分:

解析部分的目的是建立起抽象的表达式树,Paser类作为获取与解析的桥梁。该部分主要包含类Paser、Expr、Term等。整体结构如下:

中心结构为 Expr - Term - TrianglePower+Expr。另外,ExprPower作为中间结构出现,Triangle作为次级结构。 将所有的因子划分为两类:表达式类、因子类。表达式类递归定义,因子类统一定义为

优点是可以将所有类型因子划归至单一类型,降低了结构的复杂度,便于管理。

缺点是由于过度的统一性丧失了很多结构的性质,是的方法的实现变得复杂。

特别的,对于函数以及求和式中,仍采取此结构建立起表达式树。不同点在于,在解析过程中,采用边解析边建树的方式。Lexer.curToken中含有y等,则直接将参数代入。

 

3. 合并部分:

合并依赖于表达式结构中的方法实现。结构如下:

主要由两类方法实现:upItem() 化简,mulItem() 乘法。

 

4. 拓展过程:

拓展过程主要体现在结构上面:基本结构由第一次的FactorPower变为可以包含三角函数的TrianglePower。

第二次到第三次改动极少,只在func和sum的获取处稍作改变。

 

二、结构分析与度量分析

由于拓展主要为两部分,所以我们主要分析第一次代码与第三代码。

第一次作业

可以发现程序主要流程上的类Lexer、Paser圈复杂度(OCavg)都偏高。

在方法分析中,可以发现复杂度高的类中,往往其主体方法复杂度极高,非结构化程度也高,是的测试与拓展变得困难。

第三次作业

 

 仍然延续了之前的问题,由于Sum中分工不明确,所有的都由一个类实现,导致复杂度超标。而函数方向由于方法调用较为混乱,出现来回调用的情况,所以复杂度同样超标。此外,可以发现在拓展过程中,为了尽量不改变之前的代码,导致新增加的类为了迎合之前的结构产生了很多复杂的处理。

方法分析中问题类似第一次。

拓展心得:

拓展过程中主要有两个原因,其一是第一次作业代码的可拓展性不够高,这个主要由于我在设计中没有关注拓展,只考虑到如何实现当前要求。其二是过分的畏惧改动之前的代码,应该结合重构与拓展,整体拓展,拓展性差的部分重构。比如此次的Paser类,为了尽量保证第一次作业中的结构,我在Sum、Function的解析中重写了Paser的方法,导致出现了三个Paser,既增加了复杂度,也容易出现错误。

 

三、测试方法与bug分析

 

第一次作业:

基础功能测试、复杂表达式的基本功能测试 、特别测试 与 边界测试:

其中,空白、符号、次方指将所有可以加入该元素的地方均加入,并搭配测试,如图Venn图所示。

互测:

发起hack: 1/21 正确率(5%)

收到hack:1/23 正确率(4%)

bug:

边界检查(忽略了式子最后可以加空格)

反思:边界检查应当出现在所有while类过程中,此外,测试时也应考虑全面,对于测试的边界。

 

第二次作业:

基础功能测试、

迭代测试:测试Work1中所测试的样例

新测试:

互测:

发起hack: 3/12 正确率(25%)

收到hack:5/18 正确率(28%)

bug:输出错误:当三角函数内因子等于0时,不予输出。 (错误原因:在三角函数内部直接调用原先的toString,化简不输出0)

反思:在测试时要将特殊点带入所有功能点和特别点测试。且此次未充分把握形式化表述,做了很多不需的工作,且忽略了很多一些的工作。

 

第三次作业:

测试思路为将1作为因子代入2。

互测:

发起hack: 8/18 正确率(44%)

收到hack: 0/12 正确率(0%)

bug:深浅复制:忘记考虑函数参数中的深浅复制。

反思:当格式修改后,要即使修改测试思路,避免漏过测试点。

bug分析:

bug出现的类有:Lexer、MainClass、FunctionFactor。而同时,这三个类的圈复杂度也是最高的。可见圈复杂度高往往会表示有较高的bug风险

 

四、反思总结

 

重构与拓展并非不可结合

  在第一次到第二次的拓展过程中,我为了避免引入bug,尽量保持原有部分代码不变。然而事实证明,为了保持,引入了过多元素,直接造成了架构的臃肿与脆弱。后面的结果也证实了为了避免引入bug的保护措施起到了反效果。

   事实上,每一次设计能够良好的适应下一次的拓展只存在一定概率,尤其是在短期时间内完成。提高拓展性需要在设计时引入相关的思考,比如在设计三角函数类triangle时思考到是否可能拓展三角函数内因子的格式,或者思考是否可能拓展函数命比如tan等;然而在一定时间内,很难思考的全面,也很难与下一次的任务吻合的很好。所以,不应过度自信于高度的可拓展性,在恰当的情况下,进行一个类或者一个流程的部分重构是最好的选择。

  希望再下一次作业的过程中,能够更进一步的研究出何时应该选择重构,对什么进行重构的问题。

 

设计先于实现,测试同需规划

  在接收到任务的时候,一定要全面进行设计与分析。然而事实上根据我第一次作业的经历包括之前编写程序的实践甚至是上学期计组的学习来看,问题往往在于我们错误的认为自己已经设计完备。也许原因是设计过于粗略,或者思考不够严密。在下一次作业中,我的改正计划是固定出一段时间用于设计,比如半天,或者四个小时,通过增加时间反推设计的严密。

   设计需要设计什么?通过这一次作业,包括老师的建议,我认为设计应该完成UML类图,包括基本方法、属性、关系,甚至是流程的进行。也许不至于能在脑子里运行一遍,但也要考虑是否能容纳一些奇奇怪怪的数据。另一方面,设计也要兼顾任务要求。完善理解任务要求是高效的必要条件。 

  在下一次作业的过程中,要努力探索设计完成的标志。究竟设计到什么程度是足够的?这需要我进一步去探索。

  测试等同于架构,测试数据集的设计也很重要。在本次作业中我使用Venn图来关联可能的坑点构造测试数据,在下一次作业中,希望能进一步拓展完善这种测试的方法。

 

最后,革命尚未成功,同志能需努力。

 

五、结语

“正因为你在玫瑰上花费了时间,才是你的玫瑰如此重要。——《小王子》”

 

posted @ 2022-03-24 20:03  PangRJ  阅读(46)  评论(2编辑  收藏  举报