一、基于度量的程序结构分析
基于度量的程序结构分析,主要运用IntelliJ里面自带的Diagram功能生成类关系图,以及Metrics插件进行复杂度分析。
其中复杂度分析相关指标含义如下:
Complexity Metrics(复杂度分析)
这部分我们需要使用的主要是方法和类的复杂度分析。
方法的复杂度分析主要基于循环复杂度的计算。循环复杂度是一种表示程序复杂度的软件度量,由程序流程图中的“基础路径”数量得来。
-
ev(G):即Essentail Complexity,用来表示一个方法的结构化程度,范围在[1,v(G)]之间,值越大则程序的结构越“病态”,其计算过程和图的“缩点”有关。
-
iv(G):即Design Complexity,用来表示一个方法和他所调用的其他方法的紧密程度,范围也在[1,v(G)]之间,值越大联系越紧密。
- v(G):即循环复杂度,可以理解为穷尽程序流程每一条路径所需要的试验次数。
对于类,有OCavg和WMC两个项目。
- OCavg:类的方法的平均循环复杂度。
- WNC:类的方法的总循环复杂度。
Dependency Metrics(依赖度分析)
依赖度分析度量了类之间的依赖程度。有如下几种项目:
- Cyclic:指和类直接或间接相互依赖的类的数量。这样的相互依赖可能导致代码难以理解和测试。
- Dcy和Dcy:计算了该类直接依赖的类的数量,带表示包括了间接依赖的类。
- Dpt和Dpt:计算了直接依赖该类的类的数量,带表示包括了间接依赖的类。
第一次作业
类图:
度量表:
第一次作业仅仅是实现一个幂函数表达式的求导,所以本次作业我仅仅设计了一个幂函数表达式对象包含系数和指数数组,用正则表达式判断输入合法之后,再用正则表达式将每一项匹配构造出来,即可生成表达式对象,正则表达式如下:
String regChkItem = "[\\+\\-]*\\d*\\**[x]*(\\^+[\\+\\-]*\\d*|\\d*)";
String regConstant = "[\\+\\-]{0,2}\\d+";
String regNumItem = "[\\+\\-]{0,2}\\d+\\*x";
String regItem = "[\\+\\-]{0,2}x";
String regItemPow = "[\\+\\-]{0,2}x\\^[\\+\\-]?\\d+";
String regNumItemPow = "[\\+\\-]{0,2}\\d+\\*x\\^[\\+\\-]?\\d+";
生成对象之后按照求导法则进行求导,以及可以通过扫描合并系数相同的项,在输出的时候正项置于前面,负项置于后面,系数为0或者指数为1或0的情况进行格式省略,以达到优化输出的目的。
优点:生成对象时仅仅记录幂函数最关键的特征:幂数和指数,便于求导、化简
缺点:扩展性不强
第二次作业
类图:
度量表:
第二次作业在第一次作业的基础上加入了三角函数sin、cos,沿用第一次作业的思路,发现表达式的每一项都可以表示成a*x^b*sin(x)^c*cos(x)^d的形式,所以提取其最关键的特征系数a、幂指数b、sin指数c、cos指数d即可完成表达式对象的构造。第二次作业本来可以在第一次作业的基础上直接加入sinNum和cosNum数组属性,但为了更具层次性和逻辑性,抽象多了一层Item,使得更为简洁。
此次作业也是用正则表达式提取每一项然后进行构造,正则表达式如下:
String regItem = "[\\+\\-]{1,2}" +
"(([\\+\\-]?\\d+)|" +
"(x\\^[\\+\\-]?\\d+)|" +
"(x)|" +
"((sin|cos)\\(x\\)\\^[\\+\\-]?\\d+)|"
+
"((sin|cos)\\(x\\))){1}" +
"((\\*[\\+\\-]?\\d+)|" +
"(\\*x\\^[\\+\\-]?\\d+)|" +
"(\\*x)|" +
"(\\*(sin|cos)\\(x\\)\\^[\\+\\-]?\\d+)|" +
"(\\*(sin|cos)\\(x\\)))*";
提取出来后求导即可,此次作业仍可进行有限度的优化,在输出的时候正项置于前面,负项置于后面,系数为0或者指数为1或0的情况进行格式省略,也可以判断幂指数和三角函数指数相同时,进行同类项合并,但是由于三角函数的特殊性,一些基于三角函数公式的合并的实现会有难度。
优点:提取出表达式的主要特征,方便构造表达式及求导
缺点:提取表达书主要特征的方法对于无嵌套的表达式比较实用,若表达式有嵌套则难以扩展
第三次作业
类图:
度量表:
第三次作业加入三角函数嵌套规则之后,前两次作业的思路难以借用,因为拥有嵌套的情况下,表达式不能用一条式子表示,得调用递归才能识别。笔者一开始尝试构建AST树来进行运算,但是发现一些WRONG FORMAT!的情况难以判断,导致错误的输入也能得出结果,以及对于x*-1或x^-1这种系数或者指数为有符号数的情况笔者未能在有限的时间内想出比较好的解决方法。遂进行代码重构。经分析发现,输入的表达式可以视为每一个乘积项和的形式,而每一个乘积项可以看成每一个单项相乘的形式,而单项可能为常数项、幂函数项、三角函数项以及表达式和三角函数嵌套项,其中表达式和三角函数又可以视为乘积项和的形式,如此便形成了递归嵌套。一开始的思路是先按照和->积->单项->嵌套...的思路将每一项分开形成树的结构存储,再进行求导操作最终返回结果。但是实际在写的过程中,发现这样写的复杂度很高,水平有限,于是就面向过程编程了一波,直接在按层次分开每一项的过程中递归求导返回结果。所以PlusItem MuilIem SingleItem的属性直接置为求导结果,在输入解析表达式的过程中就进行嵌套求导操作,然后一层一层返回结果。
优点:将一般的处理输入--生成对象--求导--输入结果的过程简化成输入--求导--输出结果,实现简单
缺点:面向过程编程,未能体现面向对象的妙处,而且可拓展性不高
二、BUG分析
第一次作业
第二次作业
前两次作业因为正确的表达式可以用正则表达式描述,所以不会出现WRONG FORMAT!误判的情况,在输入正确的情况下,求导也比较容易实现,因此在强测和胡测的过程中没有被发现BUG。
第三次作业
sin( 1)是正确的输入却被误判成WRONG FORMAT! ,原因是在判断sin|cos(+ 1)三角函数内部的带符号数符号和数字有空格的非法输入情况时,判断+-的次次数为0 or 1,导致出错,次数为1即可改正。
这个错误是出现在用正则表达式对错误类型进行判断时,正则表达式填写错误造成的。所谓成也正则,败也正则,第一第二次作业可以用正则表达式描述正确的输入,确实万无一失,但是第三次作业正则表达式无法描述一个表达式的时候,只能用正则表达式来描述单个项,递归下降。在使用正则表示式时不宜将其写得过长,否则会出现一些缺漏或者造成爆栈的风险。
三、互测方法
由于笔者不会使用对拍器进行数据测试,一般在互测的时候,都是先用笔者撰写代码时采用的数据对测试代码进行测试,然后再深入阅读代码理解逻辑结构看是否有BUG。
优点:针对性高
缺点:阅读时间多和阅读难度大
四、总结反思
在构造对象和设计架构的时候,应该首先分析要生成什么对象,以及分析对象的特征和属性,进而得到需要什么方法,才能满足需求。