北航oo第一单元博客作业
一、基于度量的程序结构分析
-
第一次作业
-
代码度量
-
UML类图
-
第一次作业分析
第一次作业要求我们为解析表达式并化简,其中表达式由项通过加减法连接,项由因子(变量,常量,表达式)通过乘法连接。在刚看到第一次作业时我是一头雾水的,完全没有思路,不知道从何开始下手,某种意义上来说第一次作业其实是我三次作业中做得最痛苦的一次。于是我的选择是跟着训练题的思路,先解析表达式,将输入的表达式转化为后缀表达式的形式,再根据后缀表达式输出最终拆掉括号后的结果。因此本次作业便分为了解析表达式和输出最终结果两部分。
-
解析表达式
解析表达式的部分我遵循了训练题中代码的思路,利用Lexer类和Parser类不断读入表达式,将输入的表达式-项-因子依次从顶向下地解析,解析模块也自顶到下递归调用。而这样子的做法最大的好处在于可以处理嵌套的括号,无论嵌套了几层括号都可以按照运算顺序输出我们想要的后缀表达式,从而简化了完成后面两次作业的步骤。
-
输出最终结果
输出最终结果的部分我是通过CalExpr类来实现的,将解析后得到的后缀表达式输入至CalExpr类中,按照运算顺序依次计算(其中计算多项式乘法时调用了Poly类中的静态PolyMult方法,计算完成后由于运算结果没有化简相当复杂,于是我又设计了simplify方法和combine方法来实现运算结果的化简,利用HashMap<String, BigInteger>的数据结构存储了每个项的系数和变量指数,将变量指数完全相同的项进行合并,最终再遍历hashmap即可得到最终的结果。
-
总结
由于这是第一次作业,所以在一开始实现的过程中我并没有考虑的十分清楚,写出来的代码也比较混乱,从度量分析中也可以看出CalExpr类的复杂度很高,同时还耦合了多项式乘法计算的方法,就更加复杂了,没有能够实现一个简单清晰的输出方法,这是后面需要改进的地方。
-
第二次作业
-
代码度量
-
UML类图
-
第二次作业分析
第二次作业的要求在第一次作业的基础上增加了自定义函数、求和函数和三角函数,要求我们对这三种函数进行解析并最终输出不含括号的表达式。为了处理新增的三种函数,我新增了一个Function类,其包含CusFunction、SigmaFunction和TriFunction三个子类,分别对应三种函数。第二次作业的实现方法我选择的是先使用ParserFunction类对自定义函数和求和函数进行替换,得到不含这两种函数的表达式后再如同第一次作业一样进行解析表达式和输出最终结果两个部分的处理,因此整个第二次作业大致分为了三个部分。
-
预处理函数
对于求和函数的展开,将求和函数中的起始位置、终止位置和求和表达式分别存入SigmaFunction类的start、end、expr变量中,在输出时调用toString方法,将expr变量中的i用replaceAll替换成相应的常数值,再通过循环将每项加起来,最终便可以直接返回一个表达式因子。
对于自定义函数的代换,我使用了两个ArrayList分别存储函数中的变量名和实参的值,在输出时依然是通过replaceAll将每个变量替换为对应的实参,值得注意的是如果先将别的字母以x的函数代换进表达式后再代换x的值就会出错,因此我选择先将x换为别的字母避免引发错误。
-
总结
第二次作业与第一次作业相比仅是多了对三种函数的处理,因此也只需要我第一次作业设计的架构的基础上增加对三种函数的预处理即可,预处理完后得到的表达式便与第一次作业中直接输入的表达式一样再经过解析表达式和输出最终结果两个部分的处理即可得到最终化简后的表达式,后面两个部分的处理与第一次作业完全相同,也令我在第二次作业中的工作量大大下降了。但本次作业的不足之处在于,由于表达式的计算沿用了第一次作业中CalExpr类中的方法,仍然产生了过于复杂,复杂度太高的问题。而别的类和方法的复杂度都在可接受范围之内。
-
第三次作业
-
代码度量
-
UML类图
-
第三次作业分析
第三次作业的要求在第二次作业的基础上增加了嵌套因子,即在自定义函数的实参中可以嵌套别的自定义函数,在三角函数的因子中也可以嵌套表达式因此,并且三角函数内的因子也需要化简。对于第三次作业,我依旧是采取先将输入的表达式转化为后缀表达式的因子,再对后缀表达式进行处理输出的策略,这么做的好处就在于后面两部分依旧可以沿用之前的代码,仅需要在第一部分预处理函数的代码中进行适当的修改即可。而本次作业中在预处理函数部分主要做出了循环替换求和函数和自定义函数,以及将三角函数作为一种运算的改变。
-
循环替换求和与自定义函数
第二次作业中由于自定义函数中不存在嵌套因子,因此只需要进行一次替换将自定义函数和求和函数替换为普通表达式即可进入后面的处理。在第三次作业中支持自定义函数中嵌套别的函数,只需要加入一个循环判断替换后的表达式是否含有自定义函数和求和函数,若依旧含有自定义函数和求和函数则继续进行函数的替换,直到输出的表达式中不含有自定义函数和求和函数即可。
-
增加三角运算
第二次作业中的三角函数由于形式简单,不需要进一步化简,于是我将一个三角函数作为了一个字符串整体,在运算时将三角函数作为整体进行运算即可。但是第三次作业中三角函数中的因子可以十分复杂,并且还需要对其进行化简,因此我将三角函数作为了一种运算如加减一样加入到后缀表达式中,在进行后缀表达式计算时再将三角函数和它内部的因子组合起来,并且组合之前先将因子进行化简,这样就实现了第三次作业的要求。
-
总结
第三次作业依旧是在前面作业的基础上完成的,没有进行大规模的重构,这也是前面两次作业的架构设计的好带来的便利,让第三次作业可以比较轻松地完成了。
二、bug分析
-
第一次作业:
第一次作业由于做的时候头脑最不清醒,因此也是bug最多的一次作业,主要的bug在于我一开始没有想清楚应该把每个因子的指数用什么表示,并且放在哪个类中,导致了很多bug,将这个bug解决后就没有什么别的问题了。
-
第二次作业:
第二次作业在强侧和互测中测出来了两个bug。
-
一个是三角函数内出现正负号时会引起bug,在用正则表达式匹配项与项间的加减号时也会匹配到三角函数内的加减号,解决办法是先将三角函数内的运算符号用别的字符代替,最后再替换回来即可。
- 另一个是符号的bug,在自定义函数出出现连续的加减号时会输出一些奇怪的结果,解决办法是在替换完自定义函数和求和函数后用正则表达式将多个连续的加减号替换成单个的加减号。
-
第三次作业:
第三次作业在互测中被测出来了两个bug、
-
一个是sum中的大数bug,解决办法是将sum里面的起始和终止位置换成BIgInteger类型。
- 另一个是sum函数里面的i取负数引起的bug,由于之前都没有考虑i取负数的情况,因此在替换时都没有在i两侧加括号,这样的话遇到i**2这种数据就会出错,解决办法是替换i时在两侧加上括号。
三、Hack策略
我的hack策略一般是将自己写代码过程中遇到的一些bug记录下来,看一些别人会不会具有同样类似的bug。其次就是一些边界的数据,超出int范围、超出long范围的数据和负数等等容易产生bug的数据。并且在设计测试数据时最好是根据别人代码中写的冗长混乱的部分进行针对性的hack,一般逻辑清晰的部分不容易产生bug,而混乱的部分是最有可能出现bug的地方。
四、第一单元作业心得体会
做第一单元的作业前,还没有将pre作业完全的消化理解,这就导致了一上来就需要写一个较大规模的作业时完全没有思路,不知道从何开始下手,在完成第一次作业时耗费了大量的时间,还不能写出架构良好、逻辑清晰的代码,多亏有了训练题的指导,在逐步的学习过程中也有了思路,虽然整个的过程非常痛苦,但收获也是相当的多,后面的两次作业由于有了第一次的基础,自己也有了对应的思路和架构设计,能够在第一次作业的基础上进行迭代开发。但第一次作业中依旧暴露出了非常多的问题,没能很好地运用抽象层次与设计模式,主要依赖类的组合和条件判断来解决问题,面向对象的思想也需要在后面的每次作业练习中不断巩固深化。经过了第一单元的学习和作业练习,我也初步了解了面向对象程序设计的一些基本的思想和方法,并迭代开发了一个”冗长“的Java程序,经过3次的迭代开发,也让我充分感受到了从一开始就设计一个好的架构的重要性。