北航面向对象2022第一单元作业总结

BUAAOO第一单元作业总结

本博客是对2022面向对象第一单元作业的总结,主要内容包括本人的作业设计,作业过程中发现的问题及对问题的分析,以及小灵感和大隐患(优缺点)。作为整个oo课的第一单元,让我在这里做一个总结,为掺杂着些许遗憾的舟车劳顿之旅画上一个暂时的句号。

hw_1

第一次作业的目标相对简单,我们只需要实现完成单变量多项式的括号展开,其中因子只包含了变量因子(幂函数),常数因子,表达式因子三类。并且由于括号层数限制,至多只有一层嵌套。

程序结构

以下是我的第一次作业的程序结构,通过类图展示:

 

在第一次作业的建模中,我没有太注意类之间的关系,还是沿袭了老一套做法。相比于助教在实验中给出的逐层解析,我的运算逻辑显得有些隐晦难懂,这也导致了我在第二次作业中遇到了棘手的问题,而不得不小范围重构。

其中,MainFunction类作为函数启动和调用的主体,Expression类作为表达式存储以及处理的单位,Term类作为项的单位,而Factor将常数和幂函数统一起来,构成了底层因子类。为了使得调理相对清晰一点,我把每个类有关的处理方法,单独拿了一个类出来进行编辑。但事实证明了这种形式化的类处理并不能让程序的可延展性变好。(在第二次作业中吃亏)

总的来说,虽然看起来第一次作业,我的结构方面设计存在一些问题(上面已经指出),但是我想到了一些处理方法,和一些优秀同学的思路类似,并且后续也都得到了很好的应用,在此我也想介绍以下这些内容的精妙之处。

1. 因子逐层递归下降:第一次作业有一个很大的门槛在于,项的因子是由‘*’号相互连接的,但是这其中可能会有套着括号的表达式因子在暗处埋伏你,让你的常规存储策略一筹莫展。对此,我初步构建起一个想法,便是由上至下逐层把一个初始的表达式,一步步分解成如下形式。

//表达式 -> 项(若干)
//项 -> 底层因子(幂函数以及常数) + 表达式

//final 项 -> 底层因子(幂函数以及常数)

由于表达式总是会拆成项,而项总会拆成新的因子,因而,存在位于最底层的项,它的内部只含有底层因子,这样,嵌套起来的括号就形同虚设了,我们可以直接捣入内部获取底层因子。在后期,这个逐步下降的模式,也为我由下至上获取结果提供了方便。

2. 正负号处理:在第一次作业指导书发下来以后,这个正负号的存在当真让我伤了脑筋,因为你不知道什么地方表达式,项,带符号常数因子的符号就会堆起来埋伏你。为了“一劳永逸”,我研究了以下符号规律,最后决定在表达式处理的空白符删除操作之后,就立马把符号的问题全部解决掉。具体实现如下:

// search for '+' || '-'
// if the last char is '*' (钻了个小空子,乘号和指数后,若有,则一定是正负号,并且单独成行)
//   '+' : throw away ; '-' : use '!' instead (代表此因子的符号,后续会谈到符号处理)
// else
//  combine with the '+'/'-' before to get the compute sign(此为项与项之间的运算符,由于个数补丁,尝试与前面的符号进行合并)

//tips : be significantly careful about the boundary (边界问题是永远的痛)

3. 符号规定:在上一步确定了正负号以后,我们就会有符号的处理等着我们。这里我采用策略是,给表达式因子,项因子,底层因子,均设立符号属性。在自底向上的过程中,运算到项这一层时,便会把所有隶属于它的表达式因子以及底层因子的符号交予它,并与自己的符号一起做一个运算,这样,就可以确定它最后的符号。在最后的合并之前,只需要针对这些符号做一点小小的处理,就能解决问题了。

度量分析

以下是对程序中类及其方法度量分析:

 

 

在类分析中,很明显地发现主函数,ExForm函数,TermAnalyse函数的各项复杂度明显超标了,在扩展性和维护性上明显不足,这也给第二次作业的重构埋下了隐患。

 

 

 

 

在方法分析中,也明显可以看到,复杂度比较高的类中,有这样一些函数的非结构化程度很高,圈复杂度大,也印证了前面的想法。

BugAndHack

在第一次作业中,虽然有些部分的实现有些不那么面向对象,但是还是勉强过去了强测。但在互测中被hack就是另一回事了。说实话一整天自己被hack烂的时候心态都裂开了(bushi)。

本次作业在hack中出现了一个bug,问题出在化简的过程中,虽然符号考虑周全了,但是面对+0的时候,我没有采取输出,但-0的存在被我忽视掉了,结果导致了结果末尾输出“-”的尴尬情况。这让我意识到了字符串处理完毕后,输出中很有可能存在考虑不周全,从而导致bug出现的状况。

而我在这次作业中慌了神,也没有良好的互测策略,最终没有在同组其他人身上发现bug。

 

hw_2

第二次作业一发出直接给我干懵了,新增了三角函数,自定义函数以及求和函数三个重量级选手。由于第一次我太依赖于固定统一的幂函数factor类型,导致我在思考数据存储的时候花费了大量时间,基本上重新构造了整个数据体系。最后我选择新建triFunc类做三角函数存储,factor存储幂函数部分以及三角函数部分,再新建Funtion类存储函数结构,Functiontrf做函数转换;新建SumDemo做求和处理。

程序结构

以下是我的第二次作业的程序结构,通过类图展示:

在第二次作业中,我对架构进行了一个相对大的调整,也花了我比较多的时间,主要是想把括号和函数因子的处理做的相对系统一些。

在图中各个部分已经把内容和功能写的比较清晰了,对各类的含义这里就不再赘述。

这里我对结果归纳这一部分有一个比较深刻的思考,最后才能在解析完所有因子的基础上,成功解决整个部分的计算问题,我想对这个部分做一个分享:

  在我的体系结构中,最底层的包括幂函数(含常数)因子,以及三角函数因子,他们构成了我们系统的计算单元,就是说,解析到最底层就是一个个的计算单元。解析完毕以后,我们需要自下而上的进行计算。这里的计算逻辑如下:

//自上而下的解析
//
表达式 -> 项(若干) //项 -> 底层因子(幂函数以及三角函数)(区别于第一次) + 表达式 //final 项 -> 底层因子

//自下而上的计算
//原则:
//1. 结果项交给表达式因子用一个项的数组存储
//2. 基础项化成结果项的步骤:内部因子放入结果项,结果项与各个表达式因子相乘 -> 若干个结果项

//final 项 -> 结果项:(只含有底层因子)
//表达式因子 * 表达式因子 -> 表达式因子 * (项 + 项 + ···)
//项 * 表达式因子 -> 项 * (结果项 + 结果项 + ···)
//项 * 结果项 (把底层因子放到两个新数组分别保管,构成新的项)-> 结果项 (即)
//···
//基础表达式 拥有所有的结果项求和的形式 -> 输出

 

度量分析

以下是对程序中类及其方法度量分析:

 

可以发现,我们的类的整体OC明显下降,因为我们做了许多对象之间的协调工作,让每个类更好地各司其职了。但是整体复杂度有上升,这是因为我们的程序实现功能更加复杂了。

 

 

可以发现,我们的程序在构造上的复杂度比第一次好了一些,问题主要出在输出和解析上,它们的循环度太高,分析原因主要是出在面向过程上,还没有彻底地全面切换到面向对象的意识上。

BugAndHack

在第二次作业中,自己对程序的构造思考了很久,也加以了相当规模的重构,因而有些手忙脚乱。事实证明了,强测中,有三个点指向了我的一个相同小问题,让我直接定点爆破了。可以说在好不容易结束了困难工作以后的松懈,直接击破了我的美好幻想。(>_<)顺带一提,这个bug是sum函数内i做替换时,我使用了课上同学推荐的捕获组,然后由于括号的问题,识别不到i,直接re。

在hack的过程中,我尝试了课程组说的从代码到bug,确实很好用(就是有些代码看起来很费劲)。我最后通过sum函数,以及自定义函数,发现了同组的三个bug,分别指向i**2的表达式指数问题,由大数加到小数时取得0的问题,以及BigInteger的问题。但由于未开放相应的互测功能,所以我实际上没有在平台上成功hack(线下hack成功,乐)。

hw_3

第三次作业需要我们完成自定义函数,三角函数,括号均允许嵌套的情况,在hw_2费劲脑筋后,这就显得非常简单了,因为我在第二次作业中已经针对嵌套问题做了很多思考,实际上最后并没有更多添加的内容。

程序结构

以下是我的第三次作业的程序结构,通过类图展示:

 

 可以发现,只添加了simplifier类中的新化简函数,以及三角函数内的嵌套处理新函数。

度量分析

 

在第三次的架构中,我们进一步让程序各个类的复杂性下降了,这在OCavg里得到了明显的体现。

 

由于没有过多的更改,可以发现相比于第二次作业的方法复杂度没有太大变化。但好消息是增加嵌套并没有让复杂度上升,坏消息是也没办法做太多优化,仍然维持在一个较差的复杂度水平。

BugAndHack

在第三次作业的测试中,我讲究了互测策略,基于第二次互测的经验,我直接采用按点构造的方法,成功巨量hack同房间的人。主要发现的问题还是在sum函数中,与第二次的问题基本一致,做到了总共8次有效hack,并且针对了3个不一样的问题(一刀多人×)。

在自己的程序中,由于对新加的程序有一些自信,但在强测中还是败下阵来,一方面是hw_2的历史遗留问题,参数输入有误,一方面是函数循环嵌套以后,没有对表达式做最新更新,导致数组内容变化,i的指向有误而忽略变量。总之一行代码就修复的问题,让我追悔莫及测试上的问题了,这让我对自我测试有了深深的反思(真诚)。

另外值得一提的是,三次BugAndHack让我顺利认识到了bug的出现与复杂度问题的关联。我相信大多数人在本单元中,有两个方面都是下足了功夫在处理。一个是parse部分,一个是output部分。很容易预见,这两个部分的复杂性稍有不慎,就会突破天际(我就是反例×)。我在强测前测试自己的bug时,就狠狠地盯着output去括号的简化部分做了巨量测试,发现了一堆问题(虽然还是没发现完)。这也说明了复杂性与bug出现频率的关系。(以后也需要在这个方面尽量下功夫去避免bug的出现)

架构设计体验

在第一单元作业中,我深深感受到了架构设计的重要性。从第一次作业到第二次,实际上有非常多的内容增加,但并没有本质上的变化。但我由于过于专注幂函数的构造,导致新的内容来临时直接措手不及,不得不想办法重构。在第二次作业进行完善的处理以后,我对自己的架构有了比较清晰的认识,因而可以比较顺利地过渡到第三次作业的嵌套,可以说是吃到了架构的甜头,一步到位了。

总而言之,一个良好的初始架构是非常重要的。在面向对象的世界里,一些具体的内容理应被更加抽象化,从而更好地融入到对象中,使得可扩展性上升、以及复杂度的下降。这样,即使在面对三角函数、自定义函数、求和函数的增量开发时,也不会慌了手脚,能逐步在既有的内容上稳步前进。

心得体会

第一单元,我算是在煎熬与痛苦中获得新生了。一方面,没想到的上手门槛(指第一次的作业和pre的巨大反差)让我直接对开学产生了怀疑;另一方面,在强测和互测中被干碎也让我有点小痛苦。但是我觉得这不是我裹足不前的理由。

面对上手的困难,我及时想到了办法撑过了第一次的设计,并且对后续的架构进行了假想,并成功走好了第二次到第三次的架构升级。面对互测的失利,我参考了hack的策略,一步步到最后成为刀人能手(bushi),让我意识到测试和找特定问题的方法与途径。这些实际上都是我成长的见证,也是我反思自我的良药。

应该来说,我非常感谢oo课的设计与安排,它让我意识到了设计上的格局是一件多么重要的事情,实现一个任务,并不是心浮气躁地无脑冲锋,应该是在思考周全以后小心大胆地反复求证。在实现功能以后,也要沉得住气,反复斟酌自己易出错之处的逻辑关系。oo课的第一单元让我认识到了这些,我认为这比单纯的分数要来的重要的多,它会成为我后面课程的奠基,让我慢慢深入面向对象的下一个世界。

 

posted @ 2022-03-22 22:38  2037hanzhe  阅读(214)  评论(3编辑  收藏  举报