BUAA OO Unit 1

HW 1-1

题目要求与分析

本次作业,需要完成的任务为简单多项式导函数的求解。

实现上,本次作业将所有因子统一归纳为 Monomial 的规范形式。

在数据结构上,本次作业采用 HashMap 存储求导结果,便于合并同类项的实现。

UML 类图

利用 procession 画出程序的 UML 图如下:

其中,Polynomial 类主要对以 +, - 方式连接的表达式求导;而 Monomial 类主要对以 * 连接的项求导。两者的求导结果均存储再 HashMap 中。

度量分析

由于本次作业的要求相对简单,因此代码的耦合度不高,这点也能从 UML 图看出。

第一次作业的代码行数如下:

bug

本次作业在强测与互测中均未测出任何 bug。

性能

由于第一次作业有性能的最优解,且性能的优化方法较为简单,因此本次作业的性能分为满分。

注:性能优化方法

  1. 合并同类项
  2. 优化系数为 0 的情况
  3. 优化指数为 1 的情况
  4. 特殊优化:x**2 -> x*x

测试

主要采用黑箱测试。

构造了基于 python 的测评机,实现了自动生成测试样例,并测试打包后的 jar 程序,再将结果与 sympy 计算后的结果进行对比的功能。

运行效果如下:

通过自动测评机与特殊测试样例的构造,发现屋内两人的 bug。

  • 同学 1 在指数位数较大时会抛出异常,原因在于错误的使用正则表达式,即用 [0-9]{0, 9} 判断指数,人为对指数的位数做出限制。
  • 同学 2 的问题同样出在指数上,由于提前完成了后面的作业内容,未针对第一次的输入格式做出调整,因此在指数超过 10000 时便会输出 WRONG FORMAT!

HW 1-2

题目要求与分析

本次作业,需要完成的任务为包含简单幂函数和简单正余弦函数的导函数的求解。

第二次作业相较于第一次作业在复杂度上有了大幅提升,表达式因子的加入使得第一次作业的架构已经无法适应第二次作业的要求,于是决定重构。

根据指导书的建议和网上的资料,第二次作业采用了工厂模式,构建了因子的接口 Factor,实现因子的求导与标准形式的转化;又按照工厂模式构建了因子生成工厂 FactorFactory,其功能在于根据不同输入格式的因子生成 Factor 类。

此外,第二次作业沿用了第一次作业的思路,将所有因子翻译为标准形式 Term,并构建继承 HashMapTermMap,便于存储求导结果,也方便性能优化中合并同类项的实现。

从代码的整体架构上来看,这次作业已经比第一次更加具备面向对象的特点了,但在局部细节的实现上,第二次作业还保留着面向过程的处理方式。如:由于不需要判断 WF,因此在获得输入后依旧沿用第一次的处理,用大量 replaceAll() 将输入处理为相对规范的形式,这在第二次作业中是可行的,但在第三次作业的 WF 判断中就不再适用了。

UML 类图

利用 procession 画出程序的 UML 图如下,其中,类间关系与方法、属性的定义均有注释:

其中,Factor 为根据每一种函数建立的求导接口,而 PolyMono 则是分别根据加减与乘法规则建立的求导类。

此外,还建立了方法类 ParenthesisParser,便于执行去括号、拆分项、拆分因子等操作。

由 UML 图可以看到,第二次作业的代码的结构比第一次复杂,运用了接口与堕胎,更能体现面向对象的思想。

度量分析

利用 Jetbrain 插件 MetricsReloaded 对代码进行度量分析,其中,各项指标的含义如下:

度量 全称 含义
CogC Cognitive Complexity Calculates the Cognitive Complexity of each non-abstract method. The metric is similar to Cyclomatic Complexity, but is intended to explicitly measure understandability, which can be quite different from testability. Cognitive Complexity is increased with each control structure used and is higher the more nested control structures are.
ev(G) Essential Complexity Calculates the Essential Complexity of each non-abstract method. Essential Complexity is a graph-theoretic measure of just how ill-structured a method's control flow is. Essential Complexity ranges from 1 to v(G), the Cyclomatic Complexity of the method.
iv(G) Design Complexity Calculates the design complexity of a method. The design complexity is related to how interlinked a methods control flow is with calls to other methods. Design complexity ranges from 1 to v(G), the cyclomatic complexity of the method. Design complexity also represents the minimal number of tests necessary to exercise the integration of the method with the methods it calls.
v(G), OC, WMC Cyclomatic Complexity Calculates the Cyclomatic Complexity of each non-abstract method. Cyclomatic complexity is a measure of the number of distinct execution paths through each method. This can also be considered as the minimal number of tests necessary to completely exercise a method's control flow. In practice, this is 1 + the number of if's, while's, for's, do's, switch cases, catches, conditional expressions, &&'s and ||'s in the method.
  1. Cognitive Complexity & Essential Complexity & Design Complexity

  2. Cyclomatic Complexity

有以上各图可以看出,第二次作业的复杂度主要体现在工厂类 FactorFactory 和方法类 ParenthesisParser 类,其原因在于以上两个类均使用了较多的逻辑判断语句,如 if-else 等。

第二次作业的代码行数如下

从代码行数可以看出,第二次作业与第一次相比有明显的行数增长,类的数量也显著上升。

bug

本次作业在强测中未测出 bug,而在互测中被测出一个 bug,原因在于对 -+- 的处理有误,在 main 函数中加入三个符号相连的处理即可解决。

性能

第二次作业性能的优化相比第一次复杂很多,且没有一个标准的求解方法。在第二次作业中我只进行了最基本的优化,如系数为 0,指数为 1 的优化。不过因为存储方式为 HashMap,会自动合并同类项,因此出乎意料的在强测中拿到了 99+ 的分数,可见架构对于性能提升的重要性。

测试

由于表达式相对与第一次的复杂度有了很大提升,因此要对以前的评测机生成代码部分做出较大改动,且考虑到 sympy 在处理三角函数情况时可能无法得出正确的结论,因此本次互测主要采用了构造特殊样例,手动测试的方法。主要是懒。

在互测中发现屋内三个人的 bug,分别为:

  • 同学1 在处理多个 x 连乘时效率会显著下降,在 x 达到一定数目的时候会出现 TLE
  • 同学2 在遇到 0* 项的时候会判断为 WRONG FORMAT!
  • 同学3 在对 -sin(x)*(-cos(x)) 求导时结果中会出现 *--1,为格式错误

HW 1-3

题目要求与分析

本次作业,需要完成的任务为包含简单幂函数和简单正余弦函数及其嵌套组合函数的导函数的求解。

本次作业相对与第二次作业主要增添了三角函数嵌套和 WF 判断。

关于三角函数嵌套,只是改变了继承 Factor 类的 SinCos 的求导和存储方式,以及 Exponent 类的部分属性,因此整体架构还可以沿用第二次作业,只是增添了 TrigonometricMap 类来存储三角函数括号内的因子,之所以用继承与 HashMap 的结构来存储,主要也是考虑到性能优化的问题,用 HashMap 存储比较容易合并同类项,可以有效的提升性能。

关于 WF 的判断,建立了新的异常类型 WrongFormatException ,在遇到格式错误时抛出该异常,并在 main() 函数中用 try-catch 捕获异常,并输出 WRONG FORMAT!。由于可能导致 WF 的情况较多,且无法继续沿用第二次作业中 replaceAll 的方式判断,因此第三次作业采用了层层解析的方法,在每一层判断是否出现格式错误。并且将所有对字符串的处理和解析的静态方法写在了包 parser.util 中,方便调用。不过,由于并没有学习递归下降,因此在利用有限状态机来解析各类因子,在实现方式上相较于递归下降可能更复杂。

UML 类图

利用 procession 画出程序的 UML 图如下,其中,类间关系与方法、属性的定义均有注释:

从 UML 图可以看出,本次作业的架构基本沿用了第二次作业,只是在三角函数和 WF 处理上增加了一些方法和类,说明代码的可复用性相比于第一次有所提升。

度量分析

  1. Cognitive Complexity & Essential Complexity & Design Complexity

  2. Cyclomatic Complexity

    由上面各图可以看出,第三次作业的整体复杂度相较于第二次作业有提升,主要体现在 ParenthesisParser 类的各种因子解析方法中,由于采用了状态机对因子进行解析,因此涉及到的选择语句等较多,逻辑较为复杂。

第三次作业的代码行数如下:

可以看到,行数最多的是 ParenthesisParser 类,主要实现各类因子的解析和 WF 的判断,在实现方式上由于采用了有限状态机,因此涉及到大量了逻辑判断,逻辑复杂度较高,代码行数也较长,且总体而言,WF 的处理方法更偏向于面向过程,这点虽然在实现上并没有正确性问题,但不够优雅,还需要进一步优化与改进。

bug

本次作业在强测中测出了一个 bug ,原因在于在遇到连续嵌套的括号而内部又为空时不会识别出 WF,而会默认其为 0。抛开现象看本质,导致该错误的原因在于在解析表达式因子时只检查了括号的匹配程度等,而没有对这种状况做出判断。

在互测中,未被测出任何 bug。

性能

和第二次作业类似,第三次作业也没有固定的最优解,且由于涉及三角函数嵌套,对其的优化会更加复杂,因此在特殊处理系数为 0,指数为 1 的情况之后,也没有做进一步的优化,但由于存储方式会自动合并同类项,因此在性能上的得分普遍在 99 分以上。

测试

本次作业中,采用构造特殊样例的方式来对同屋人的代码进行测试。

由于不允许输入格式错误数据,也对输入长度有所限制,因此可以测试的样例十分有限。重点测试多层嵌套,多个 x 连乘,多项相加,三角函数多层嵌套等情况。

在互测中,发现屋内一人的 bug,即在处理多层括号嵌套时运行时间明显变长,在嵌套至一定程度时会出现 TLE。

总结

第一单元的三次作业围绕同一个题目,即多项式求导,三次作业的需求逐渐增加,代码也逐渐扩展。

通过三次作业,我加深了对于面向对象思维的理解,在第二次作业开始时我对代码进行了重构,用接口和多态实现了工厂模式,从面向过程转化为面向对象的思维,并且在第三次作业中沿用了第二次作业的架构,说明该架构具有一定的可扩展性。不过,从度量分析也可以看出,在一些局部处理上还保留着面向对象的特点,如对因子的解析、拆分等。

第一单元的学习也让我认识到一个好架构的重要性,一个好的架构不但可以良好的实现功能的扩展,保证正确性,也对优化性能很有帮助。因此,在开始写码之前,有一个清晰的思路和设计是很重要的。带着清晰的头脑写码,效率和正确性会大大提高。

此外,正确理解需求文档也很重要,不能因为文档较长而不去仔细研读,最终会为自己的粗心大意付出代价。

最后,要在公测开始前自己的代码做足测试,而不是靠强测与互测发现问题。

posted @ 2021-03-27 19:36  Ericaaaaaaaa  阅读(151)  评论(1编辑  收藏  举报