度量分析
HW1
UML图
各个类方法,分支和行数
类 | Class | Method | branch | line(695) | LCOM方法内聚缺乏度 | FANIN类的扇入 | FANOUT类的扇出 |
---|---|---|---|---|---|---|---|
Term | 1 | 6 | 10 | 29 | 0.333333333 | 0 | 0 |
Simplify | 2 | 8 | 54 | 107 | 0.857142857 | 0 | 0 |
Power | 1 | 4 | 8 | 15 | 0.5 | 0 | 0 |
Parser | 1 | 4 | 26 | 52 | 0 | 0 | 0 |
Num | 1 | 4 | 8 | 16 | 0.5 | 0 | 0 |
MainClass | 1 | 3 | 2 | 23 | -1 | 0 | 0 |
Lexer | 1 | 6 | 34 | 31 | 0 | 0 | 0 |
Expr | 1 | 6 | 14 | 37 | 0.333333333 | 0 | 0 |
根据这个表可以看到Simplify方法的内聚缺乏度过高,该类的内聚性不高。现在回想起来,当时我设计时就出现了问题,这个类的设计初衷是把一个去掉多余括号的表达式,重新拆分并化简。这个想法使我的代码对hw2新增因子exp出现了很强的不适应性,而且也没有使用拆分复杂度到细小的类的思想。这个架构设计我下文会讲述。
每个方法
Type | MethodName | LOC(代码行数) | CC(圈复杂度) | PC(方法中传参个数) |
---|---|---|---|---|
Expr | Expr | 5 | 1 | 0 |
Expr | addIndex | 3 | 1 | 1 |
Expr | addKind | 3 | 1 | 1 |
Expr | addTerm | 3 | 1 | 1 |
Expr | toString | 29 | 6 | 0 |
Expr | simplify | 14 | 3 | 2 |
Factor | addIndex | 0 | 1 | 1 |
Lexer | Lexer | 4 | 1 | 1 |
Lexer | getNumber | 8 | 2 | 0 |
Lexer | getIndex | 16 | 4 | 0 |
Lexer | meetX | 3 | 1 | 0 |
Lexer | next | 17 | 5 | 0 |
Lexer | peek | 3 | 1 | 0 |
MainClass | main | 17 | 1 | 1 |
MainClass | exprDelete | 10 | 2 | 1 |
Num | Num | 5 | 1 | 2 |
Num | addIndex | 3 | 1 | 1 |
Num | simplify | 15 | 4 | 0 |
Num | toString | 8 | 2 | 0 |
Parser | Parser | 3 | 1 | 1 |
Parser | parseExpr | 17 | 4 | 0 |
Parser | parseTerm | 12 | 3 | 1 |
Parser | parseFactor | 45 | 7 | 0 |
Power | Power | 3 | 1 | 1 |
Power | addIndex | 3 | 1 | 1 |
Power | addKind | 3 | 1 | 1 |
Power | toString | 20 | 5 | 0 |
Simplify | Simplify | 3 | 1 | 1 |
Simplify | operate | 43 | 7 | 0 |
Simplify | mulOperate | 48 | 9 | 1 |
Simplify | mulPrintf | 23 | 6 | 2 |
Simplify | addPrintf | 36 | 6 | 1 |
Simplify | compare | 3 | 1 | 2 |
Simplify | getIndex | 10 | 2 | 1 |
Term | Term | 3 | 1 | 0 |
Term | addFactor | 3 | 1 | 1 |
Term | addKind | 3 | 1 | 1 |
Term | getKind | 3 | 1 | 0 |
Term | toString | 21 | 4 | 0 |
Term | ExprMult | 14 | 3 | 2 |
圈复杂度较高的是Expr的toString()方法,和Parser类的parseFactor方法,和Simplify的绝大部分方法。久其原因是我在该toString方法内进行了部分化简和合并运算,没有另起一个方法或类。而Simplify类究其原因是我的设计动机:把一个表达式字符串重新拆分并化简,出现了问题。我没有在Term类和Expr类提供专门的化简方法。这是因为我认为Expr和Term类可以调用Simplify方法,进行化简。实时证明Simplify的化简方法不专一而且不适配Expr类内部的输入,后续的迭代证实了这个错误。
HW3的基本情况和HW1类似。而且因为HW2和HW3相比架构变化不大,没有列出它的UML图和度量分析。
HW3
UML类图
HW3可以看到,本来Factor不是Term类的接口,但是参考实验代码,这样设计可以方便求导,求导后返回值是Term类型,更容易承载求导后的结构。对我来说,这是一种架构的改变,也是一种思想的改变。当然当时我没有意识到这一点。
各个类方法,分支和行数
类 | Class | Method | branch | line(695) | LCOM方法内聚缺乏度 | FANIN类的扇入 | FANOUT类的扇出 |
---|---|---|---|---|---|---|---|
Term | 1 | 10 | 48 | 102 | 0.4 | 0 | 0 |
Simplify | 2 | 10 | 92 | 186 | 0.888888889 | 0 | 0 |
Power | 1 | 6 | 8 | 20 | 0.333333333 | 0 | 0 |
Parser | 1 | 6 | 46 | 92 | 0 | 0 | 0 |
Num | 1 | 6 | 8 | 20 | 0.5 | 0 | 0 |
MainClass | 1 | 4 | 6 | 39 | -1 | 0 | 0 |
Lexer | 1 | 6 | 46 | 43 | 0 | 0 | 0 |
FuncStatic | 1 | 3 | 3 | 0 | 0 | 0 | |
Func | 1 | 7 | 10 | 45 | 0.285714286 | 0 | 0 |
Expr | 1 | 10 | 52 | 126 | 0.3 | 0 | 0 |
Exp | 1 | 5 | 11 | 0.4 | 0 | 0 | |
Derive | 1 | 5 | 8 | 0.4 | 0 | 0 |
每个方法
Type | MethodName | LOC(代码行数) | CC(圈复杂度) | PC(方法中传参个数) |
---|---|---|---|---|
Type | MethodName | LOC(代码行数) | CC(圈复杂度) | PC(方法中传参个数) |
------------ | ------------- | ----------- | ---------- | ------------- |
Derive | Derive | 3 | 1 | 1 |
Derive | toString | 4 | 1 | 0 |
Derive | addIndex | 2 | 1 | 1 |
Derive | derive | 4 | 1 | 0 |
Derive | clone | 3 | 1 | 0 |
Exp | Exp | 3 | 1 | 1 |
Exp | addIndex | 2 | 1 | 1 |
Exp | toString | 4 | 1 | 0 |
Exp | derive | 6 | 1 | 0 |
Exp | clone | 4 | 1 | 0 |
Expr | Expr | 5 | 1 | 0 |
Expr | mergeExpr | 12 | 3 | 2 |
Expr | addIndex | 4 | 1 | 1 |
Expr | addKind | 3 | 1 | 1 |
Expr | addTerm | 3 | 1 | 1 |
Expr | toString | 33 | 6 | 0 |
Expr | simplify | 24 | 5 | 2 |
Expr | deleteExp | 50 | 11 | 3 |
Expr | derive | 30 | 5 | 0 |
Expr | clone | 8 | 2 | 0 |
Factor | addIndex | 0 | 1 | 1 |
Factor | derive | 0 | 1 | 0 |
Factor | clone | 0 | 1 | 0 |
Func | Func | 5 | 1 | 1 |
Func | addIndex | 2 | 1 | 1 |
Func | addFactor | 3 | 1 | 1 |
Func | creatExpr | 27 | 3 | 0 |
Func | toString | 10 | 2 | 0 |
Func | derive | 8 | 2 | 0 |
Func | clone | 7 | 2 | 0 |
FuncStatic | addAll | 3 | 1 | 1 |
Lexer | Lexer | 4 | 1 | 1 |
Lexer | getNumber | 8 | 2 | 0 |
Lexer | getIndex | 16 | 4 | 0 |
Lexer | meetX | 3 | 1 | 0 |
Lexer | next | 33 | 9 | 0 |
Lexer | peek | 3 | 1 | 0 |
MainClass | main | 27 | 2 | 1 |
MainClass | exprDelete | 10 | 2 | 1 |
MainClass | sort | 9 | 2 | 1 |
Num | Num | 5 | 1 | 2 |
Num | addIndex | 3 | 1 | 1 |
Num | simplify | 15 | 4 | 0 |
Num | toString | 8 | 2 | 0 |
Num | derive | 5 | 1 | 0 |
Num | clone | 3 | 1 | 0 |
Parser | Parser | 4 | 1 | 1 |
Parser | parseExpr | 17 | 4 | 0 |
Parser | parseTerm | 12 | 3 | 1 |
Parser | parseFactor | 64 | 13 | 0 |
Parser | factorExpr | 17 | 2 | 0 |
Parser | makeFunc | 22 | 4 | 0 |
Power | Power | 3 | 1 | 1 |
Power | addIndex | 4 | 1 | 1 |
Power | addKind | 2 | 1 | 1 |
Power | toString | 13 | 3 | 0 |
Power | derive | 15 | 3 | 0 |
Power | clone | 3 | 1 | 0 |
Simplify | Simplify | 3 | 1 | 1 |
Simplify | operate | 57 | 9 | 0 |
Simplify | change | 10 | 3 | 2 |
Simplify | mulOperate | 61 | 11 | 1 |
Simplify | mulPrintf | 39 | 8 | 4 |
Simplify | addPrintf | 37 | 6 | 1 |
Simplify | compare | 3 | 1 | 2 |
Simplify | getIndex | 10 | 2 | 1 |
Simplify | deleteExp | 50 | 11 | 3 |
Term | Term | 3 | 1 | 0 |
Term | addFactor | 3 | 1 | 1 |
Term | addKind | 3 | 1 | 1 |
Term | getKind | 3 | 1 | 0 |
Term | toString | 28 | 6 | 0 |
Term | ExprMult | 25 | 5 | 2 |
Term | deleteExp | 50 | 11 | 3 |
Term | derive | 22 | 5 | 0 |
Term | addIndex | 2 | 1 | 1 |
Term | clone | 8 | 2 | 0 |
架构设计体验
HW1架构可以用这张类图表示
MainClass是程序入口,内部使用Lexer类遍历输入的表达式,Parser通过使用Lexer,逐渐按表达式(Expr),项(Term),因子(Factor)的层次结构递归构造,最后返回一个Expr类的因子,最后调用Simplify类,化简toString()后的Expr。
Simplify simplify = new Simplify(result);
result = simplify.operate();
这个架构参考了课程组提供的代码。但是我也有思考不周的地方,对于带指数的表达式因子,我是把指数存入表达式因子内部,把化简因子的任务交给了表达式类,破坏了Expr类的简洁性,提高了求导迭代的难度。同时,我对Simplify类的定义:处理一个经过处理的不带括号的表达式,影响了我之后的迭代,最终使Simplify类不堪重负,而且也未被了递归下降法和层次分析。
HW3和HW1相比,多出的几个因子,只是在HW1的基础上新建几个类,在使用Factor接口。比较重要的实现是Exp即指数函数,主要的变化是Simplify类,里面的部分方法被重写,方法之间的调用关系也被改变。HW2新添的指数函数因子对我的整个代码设计是一个很大的打击。因为exp在化简后还留有括号,括号内出现的+和这个因子是一体的。这是一场灾难。
例如:
for (String string : expr.split(“\+”))
这样的代码划分表达式是很脆弱的,而且和解析表达式的递归下降法有很强的割裂感,也可以看成是不成熟的正则表达式。严格说这是因为自己偷懒,没有为Expr和Term等类设计专门的化简方法,使得本应承担合并同类项工作的Simplify类做了一些额外的工作。
同时,对新增指数函数exp的不适配的主要原因也可能是自己第一次作业架构设计,没有把Factor作为Term类的接口,处理项内的表达式乘法的时候是利用字符串拼接,而不是重新构造部分层次关系,再统一使用toString()方法。
Term的toString()方法部分内容:
String termOne = sb.toString();
String termTwo = iter.next().toString();
…
sb = ExprMult(termOne,termTwo);
最后,关于我怎么处理指数函数exp。使用字符串的replace方法,把exp替换成形如a1a的标识语句,建立HashMap的映射机制,化简处理后再替换回去。这种字符串处理,使第二次作业出现CPU运行时间过长。望各位引以为戒。
以上是我的架构设计和不足之处。悲观的说,自己的架构不适用任何一种处理后会出现新的带括号因子的扩展。
程序bug
HW1,自己的主要bug是对Term类的toString()方法处理失误。简单来说,如果整个项有表达式而且为负,我只会简单的在表达式前加一个‘-’号,显然这是错的。当时设计时,我虽然考虑到了带负号的情况,但是没考虑到Term内表达式因子带负号,仅仅简单处理了负号。这种设计情况的缺失,好的方法是利用自动化评测或者尽可能构造所有情况的样例。
HW2,自己的bug是CPU运行时间过长,原因是Expr类toString()方法的表达式乘表达式即指数运算,产生了一个十万级的字符串,这时使用字符串构造方法及其耗费时间。这个问题出在架构设计和实现思路上,这只能通过经验或者好的架构设计来解决。
Type | MethodName | LOC(代码行数) | CC(圈复杂度) |
---|---|---|---|
Expr | toString | 33 | 6 |
Term | toString | 21 | 4 |
很明显,出现bug的方法,代码行过长,圈复杂度过高,其他方法圈复杂基本不超过2,证明这些方法职责划分不清晰,功能过多,设计不合理。可以把方法内不同功能的代码划分出去创建新的单一功能的方法。 |
发现别人程序bug策略
手动构造一些特殊样例,但是基本无效,没有阅读代码进行白盒测试。
放到自动评测机上跑。
自己的优化
hw1优化有认真写
数字之间进行运算
同类项合并不包括指数函数
不同项排序,避免负项在首位
相同指数函数合并没有公因数提取,鸵鸟战术
通过字符串处理进行优化,但是在某种程度上老的处理影响了自己的重构改进。
我的优化没有保证自己程序的简洁性,比如自己只想到总的Simplify类,没有想到一些分开的处理方法,比如exprSimplify,termSimplify。相信这样处理不会让某个类过于庞大,也容易理清自己的思路。 正确性得到了一定保障。优化过后本来不能过的测试点通过了。没有数据。好奇怪
心得体会
首先,我第一次使用递归下降法解析字符串,同时也体会到层次化设计的优越之处。第一次看课程组的代码感到醍醐灌顶,发人深思。这种设计架构使复杂的结构层次化,简单化。学到了新知识。其次,单元一可能是我大学以来第一次认真使用编辑器自带的调试工具。之前用printf法解决程序bug,很舒适。但是这个单元使用了递归的方法处理内容,使用调试工具打断点,一步一步看还是很有必要的。最后,自己的第一单元还是有点遗憾的。没有实现搭一个自己评测机的想法。HW1自己的开头不是很好,限制了自己的思想。指数等细节处理不到位。最后的化简没有认真做,有1*出现等。现在回想自己为了实现HW2的功能改HW1的代码也花费了很多时间和心态,效果也不理想,期望自己之后的学习实践中可以提升能力、收获经验。
未来方向
可以优化一下oolens公众号的内容。