oo第一单元总结报告
OO第一单元总结报告
第一单元主要是完成了读入表达式并进行求导输出的任务,在作业中将其分为读入和处理两个部分,分别建立相应的类。
读入部分建立了FuncReader类(相当于工厂),其中内置了一些read方法,用来解析表达式的不同部分,各read方法之间递归调用,违法就返回null;这部分主要采用面向过程的思想,直接按步骤走就可以。
处理部分首先建立了Func接口,以实现求导和化简功能。然后针对x、sin(x)、cos(x)、c分别设计了基本函数类:PowerFunc,SinFunc,CosFunc,ConstFunc。接着针对组合情况:多个函数相加,多个函数相乘,两个函数复合;分别设计了AddFunc,MultFunc,CompoundFunc。其中AddFunc和MultFunc为了便于化简,采用了Map<Func, BigInteger>的形式,保证了同类项的系数和并和同类项的指数合并。各函数求导和化简方法都是返回一个新的函数,即把这些函数类看成不变类来使用。
输出部分直接在各个函数类中覆写toString方法即可。
基于度量的程序结构分析
类的属性表格
各缩写含义如下:
度量 | 意义 | 英文名称 |
---|---|---|
Dcy | 直接依赖的数量 | Number of dependencies |
B | 可能的bug数 | Halstead Bugs |
v(G) | 本质循环复杂度 | Essential Cyclomatic Complexity |
Cons | 构造器数量 | Number of constructors |
LOC | 代码行数 | Lines of Code |
IF_NEST | if语句的深度 | Conditional nesting depth |
LOOP_NEST | 循环语句的深度 | Loop nesting depth |
CONTROL | 方法控制语句数目 | Number of Control Statements |
NAAC | 新增的字段数 | Number of attributes added |
NOAC | 新增的方法数 | Number of operations added |
NOOC | 覆写的方法数(除接口) | Number of operations overridden |
CSA | 类属性数目 | Class size (attributes) |
CSO | 类方法数目 | Class size (operations) |
ISO | 接口方法数 | Interface size(operations) |
相应的类属性如下:
Class | B | Cons | Dcy | LOC | NAAC | NOAC | NOOC | CSO |
---|---|---|---|---|---|---|---|---|
AddFunc | 1 | 1 | 3 | 145 | 1 | 6 | 3 | 10 |
CompoundFunc | 0 | 1 | 6 | 62 | 2 | 2 | 3 | 6 |
ConstFunc | 0 | 1 | 1 | 40 | 7 | 3 | 3 | 7 |
CosFunc | 0 | 0 | 4 | 26 | 1 | 2 | 3 | 5 |
FuncReader | 1 | 1 | 8 | 219 | 3 | 9 | 0 | 10 |
Main | 0 | 0 | 2 | 16 | 0 | 1 | 0 | 1 |
MultFunc | 2 | 1 | 3 | 203 | 2 | 7 | 3 | 11 |
PowerFunc | 0 | 0 | 2 | 23 | 1 | 2 | 3 | 5 |
SinFunc | 0 | 0 | 2 | 23 | 1 | 2 | 3 | 5 |
Interface | ISO |
---|---|
Func | 2 |
可以看到读入部分的FuncReader类有较多的依赖,因为要分情况调用各个函数的构造方法;而其他函数类的依赖大多是在求导和化简方法中,因为求导化简之后会出现新的函数类,需要调用其他类的构造方法。
相应的方法属性如下:
ooMetrics | 周一 | 29 3月 2021 10:15:01 CST | |||
---|---|---|---|---|---|
Method | CONTROL | IF_NEST | LOC | LOOP_NEST | v(G) |
AddFunc.AddFunc() | 0 | 0 | 3 | 0 | 1 |
AddFunc.add(Func,BigInteger) | 1 | 1 | 10 | 0 | 3 |
AddFunc.derivative() | 4 | 1 | 16 | 1 | 5 |
AddFunc.equals(Object) | 2 | 2 | 11 | 0 | 3 |
AddFunc.getMap() | 0 | 0 | 3 | 0 | 1 |
AddFunc.hashCode() | 0 | 0 | 4 | 0 | 1 |
AddFunc.isEmpty() | 0 | 0 | 3 | 0 | 1 |
AddFunc.simplify() | 15 | 2 | 62 | 2 | 15 |
AddFunc.size() | 0 | 0 | 3 | 0 | 1 |
AddFunc.toString() | 6 | 2 | 27 | 1 | 7 |
CompoundFunc.CompoundFunc(Func,Func) | 0 | 0 | 4 | 0 | 1 |
CompoundFunc.derivative() | 1 | 1 | 12 | 0 | 3 |
CompoundFunc.equals(Object) | 2 | 2 | 12 | 0 | 4 |
CompoundFunc.hashCode() | 0 | 0 | 4 | 0 | 1 |
CompoundFunc.simplify() | 5 | 1 | 21 | 0 | 10 |
CompoundFunc.toString() | 0 | 0 | 5 | 0 | 1 |
ConstFunc.ConstFunc(BigInteger) | 0 | 0 | 3 | 0 | 1 |
ConstFunc.derivative() | 0 | 0 | 4 | 0 | 1 |
ConstFunc.equals(Object) | 2 | 2 | 11 | 0 | 3 |
ConstFunc.getVal() | 0 | 0 | 3 | 0 | 1 |
ConstFunc.hashCode() | 0 | 0 | 4 | 0 | 1 |
ConstFunc.simplify() | 0 | 0 | 2 | 0 | 1 |
ConstFunc.toString() | 0 | 0 | 4 | 0 | 1 |
CosFunc.derivative() | 0 | 0 | 7 | 0 | 1 |
CosFunc.equals(Object) | 0 | 0 | 4 | 0 | 1 |
CosFunc.hashCode() | 0 | 0 | 4 | 0 | 1 |
CosFunc.simplify() | 0 | 0 | 4 | 0 | 1 |
CosFunc.toString() | 0 | 0 | 4 | 0 | 1 |
FuncReader.FuncReader(String) | 0 | 0 | 5 | 0 | 1 |
FuncReader.readAddSub() | 2 | 1 | 11 | 0 | 5 |
FuncReader.readBlank() | 1 | 0 | 5 | 1 | 4 |
FuncReader.readDeg() | 3 | 2 | 16 | 0 | 5 |
FuncReader.readExp() | 9 | 2 | 49 | 1 | 10 |
FuncReader.readFactor() | 13 | 2 | 60 | 0 | 18 |
FuncReader.readG() | 2 | 2 | 16 | 0 | 5 |
FuncReader.readNum() | 3 | 1 | 17 | 1 | 6 |
FuncReader.readSign() | 1 | 1 | 9 | 0 | 4 |
FuncReader.readTerm() | 4 | 1 | 26 | 1 | 6 |
Main.main(String[]) | 1 | 1 | 14 | 0 | 2 |
MultFunc.MultFunc() | 0 | 0 | 4 | 0 | 1 |
MultFunc.derivative() | 6 | 1 | 28 | 2 | 7 |
MultFunc.equals(Object) | 2 | 2 | 11 | 0 | 4 |
MultFunc.getCof() | 0 | 0 | 3 | 0 | 1 |
MultFunc.getMap() | 0 | 0 | 3 | 0 | 1 |
MultFunc.hashCode() | 0 | 0 | 4 | 0 | 1 |
MultFunc.mult(Func,BigInteger) | 3 | 1 | 21 | 0 | 5 |
MultFunc.setCof(BigInteger) | 0 | 0 | 3 | 0 | 1 |
MultFunc.simplify() | 17 | 2 | 60 | 2 | 17 |
MultFunc.size() | 0 | 0 | 3 | 0 | 1 |
MultFunc.toString() | 14 | 2 | 59 | 2 | 16 |
PowerFunc.derivative() | 0 | 0 | 4 | 0 | 1 |
PowerFunc.equals(Object) | 0 | 0 | 4 | 0 | 1 |
PowerFunc.hashCode() | 0 | 0 | 4 | 0 | 1 |
PowerFunc.simplify() | 0 | 0 | 4 | 0 | 1 |
PowerFunc.toString() | 0 | 0 | 4 | 0 | 1 |
SinFunc.derivative() | 0 | 0 | 4 | 0 | 1 |
SinFunc.equals(Object) | 0 | 0 | 4 | 0 | 1 |
SinFunc.hashCode() | 0 | 0 | 4 | 0 | 1 |
SinFunc.simplify() | 0 | 0 | 4 | 0 | 1 |
SinFunc.toString() | 0 | 0 | 4 | 0 | 1 |
类图
图中列出了类的主要字段和方法,其中mult()和add()方法为可变方法,derivative()和simplify()为不变方法
- 优点:统一了接口;使用map容器便于合并同类项
- 缺点:同一表达式有多种表示方式,存在多义现象;化简时为完成拆括号的工作,仍要判断具体类型
Bug分析
Bug | 所在类和方法 | LOC | v(G) |
---|---|---|---|
读入时遇到括号未判断格式 | FuncReader类,readExp()方法 | 49 | 10 |
化简时改变了ConstFunc的值 | AddFunc类,simplify()方法 | 62 | 15 |
转化为字符串时出现(x+1)**2的情况 | MultFunc类,toString()方法 | 59 | 16 |
对比不出现bug的方法可以看出,出bug的方法圈复杂度都比较高,这也是因为这几个方法都需要对类进行特判,导致分支语句增多,进而加大了bug出现的概率。
bug检测策略
第一次作业中直接通过手写的测评机(随机生成样例)+特殊样例来找bug;
第二、三次作业中直接用特殊样例找bug,或用一个函数多次求导的结果来当做复杂测试样例;
第一次作业通过大批量的测试+0等不容易生成的边际样例来保证有效性;
第二、三次作业中手动遍历构成情况来保证完备性,并通过多次求导的样例来保证复杂性;
以上测试均为黑盒测试策略,并未结合被测程序代码结构。
重构经历总结
2、3次作业重构前后的圈复杂度分别为3.49和3.38;可以看出圈复杂度有一定的下降,但是由于第三次作业加入了合并同类项的优化部分,所以复杂度还是比较高。2、3次作业类图的结构基本一致,只是各个类的具体组织方式由List变为了Map。第一次作业由于可以直接选用基底,比较特殊,故不作比较。
心得体会
- in.hasNext()方法会阻塞!!!in.hasNext()方法会阻塞!!!in.hasNext()方法会阻塞!!!
- str.charAt()会越界!!!str.charAt()会越界!!!str.charAt()会越界!!!
- List很难化简(指二重循环还要判0,循环时改变List不太方便),而Map好化简
- python写生成器和文件操作测试最方便!!!还可以用sympy等高级库来检查
- 接口还是没用出灵魂来,这种架构感觉就像是强行类型转换了一样,具体化简情况还是要特判(当然求导就不用)
- 从一开始最好就明确哪些方法是不变的,哪些是可变的,在不变方法中最好不调用可变方法(要调用的话就要先克隆对象)
- String.startWith()方法可以方便地检查前缀(不用charAt)
- 多义性造成了化简时需要特判,如何设计一种无多义的架构?(这里已经尽量把PowerFunc和SinFunc的系数提到MultFunc这一层了)