BUAA_OO_2022 第一單元總結
BUAA_OO_2022 第一單元總結
1.架構設計
架構流程:
輸入 -----> 預處理 -----> 拆項建樹 -----> 合併 -----> 化簡 -----> 輸出
主體架構:
我的第一單元三次作業的核心思路都是建立二叉樹,以操作符為結點,以兩個運算元作為子節點。
以“ x ^ 3 + ( x ^ 2 + x + 1) * ( x + 2 ) ”為例,進行拆項建樹:
- 第一次作業的操作符只有 + 和 *,葉結點也只能為 x 或 常數,拆項一拆到底。
- 第二次作業,操作符不變,但是葉結點擴充至表示式因子、三角函式因子等 。
- 第三次作業中操作符增加了 **, 解決冪指數較大遞迴多次的問題。
合併時,自底向上,通過每個操作符,將子結點連結起來。
在第一次作業中,Variable類和Constant類聊勝於無,功能集中在Expression類和Operator類中(面向物件,面向過程!)
在第二次作業中,正式步入面向物件。對於求和函式和自定義函式,採用了預處理替換的方式,簡單直接但不利於後續迭代開發。
在第三次作業中,將自定義函式和三角函式都作為因子處理,並在相應的類中,代換後作為表示式處理,建立分支樹。
細節實現:
-
建樹:
採用遞迴的方式建樹,依次尋找 +、*、 **三個操作符
public void buildTree(String expr) { if(getPlus(expr) > 0) { ... buildTree(expr.subString(getPlus(expr))); } else if(getMul(expr)) { ... buildTree(expr.subString(getMul(expr))); } ... }
-
化簡:
對於以加號/減號分隔的每一項:
-
以BigInteger型別的constant儲存係數。
-
以int型別的power儲存x的冪指數。
-
以HashMap<String, Integer>型別的triangle儲存三角函式及其指數,之後對三角函式降冪排列。
-
以HashMap<String, BigInteger>型別的coefficient儲存這一項。
再通過對三角函式內部表示式降冪排列,每一項構造出如下形式。
3*x**2*-1*cos(x**2)*sin(x**2)**2 constant = -6; power = 2; triangle.put("cos(x**2)", 1); triangle.put("sin(x**2)", 2); String term = -x**2*sin(x**2)**2*cos(x**2); coefficient.put(term, constant);
通過coefficient索引表示式,獲取係數進行合併。
2.程式結構度量
方法複雜度:(僅列出較為重要的類)
方法 | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
Function.getSpilt(String) | 14.0 | 1.0 | 6.0 | 7.0 |
Sum.getSpilt(String) | 14.0 | 1.0 | 6.0 | 7.0 |
Simplify.getSplit(String) | 14.0 | 1.0 | 6.0 | 7.0 |
Expression.getPower(String) | 9.0 | 3.0 | 5.0 | 6.0 |
Expression.buildBranch(String) | 8.0 | 1.0 | 9.0 | 9.0 |
Simplify.merge() | 8.0 | 1.0 | 5.0 | 5.0 |
Expression.getPlus(String) | 8.0 | 4.0 | 5.0 | 6.0 |
Operator.polyMul(String, String) | 7.0 | 1.0 | 5.0 | 5.0 |
Constant.Constant(String) | 7.0 | 1.0 | 4.0 | 4.0 |
Triangle.Triangle(String, HashMap) | 7.0 | 1.0 | 6.0 | 6.0 |
Simplify.triangleSort(HashMap) | 7.0 | 1.0 | 5.0 | 5.0 |
Simplify.powerSort() | 6.0 | 1.0 | 4.0 | 4.0 |
Expression.getMul(String) | 6.0 | 4.0 | 3.0 | 4.0 |
Expression.getPos(int, String) | 4.0 | 1.0 | 3.0 | 4.0 |
Simplify.getPower(String) | 3.0 | 3.0 | 2.0 | 3.0 |
Expression.powerType(int, String, int) | 3.0 | 1.0 | 4.0 | 4.0 |
Operator.getAnswer() | 3.0 | 1.0 | 4.0 | 5.0 |
Triangle.getPower(String) | 2.0 | 2.0 | 2.0 | 2.0 |
Sum.Sum(String, HashMap) | 1.0 | 1.0 | 2.0 | 2.0 |
Constant.getBase(String) | 1.0 | 1.0 | 2.0 | 2.0 |
MainClass.main(String[]) | 1.0 | 1.0 | 2.0 | 2.0 |
Constant.getPower(String) | 1.0 | 1.0 | 2.0 | 2.0 |
Function.Function(String, HashMap) | 1.0 | 1.0 | 2.0 | 2.0 |
Average | 4.09 | 1.19 | 2.89 | 3.12 |
相較於遞迴下降方法,我建樹的思路的方法複雜度和類複雜度的數值都較高,因為在建樹的過程中,使用了很多的if-else語句去進行分支判斷,從而判斷拆分的每一項屬於哪種型別或因子。
類複雜度:
類 | OCavg | OCmax | WMC |
---|---|---|---|
Constant | 1.83 | 4.0 | 11.0 |
Expression | 4.1 | 10.0 | 41.0 |
Function | 2.4 | 7.0 | 12.0 |
MainClass | 1.5 | 2.0 | 3.0 |
Operator | 3.33 | 7.0 | 20.0 |
Simplify | 4.9 | 15.0 | 49.0 |
Sum | 2.4 | 7.0 | 12.0 |
Term | 1.0 | 1.0 | 4.0 |
Triangle | 2.2 | 6.0 | 11.0 |
Variable | 1.0 | 1.0 | 4.0 |
Average | 2.93 | 6.0 | 16.7 |
對於自定義函式、三角函式類、括號類,需要緊密依賴Expression類,繼續拆項,出現了和表示式類自圈的情況,類似於狀態機如下圖。導致這幾個類的耦合度很高,程式碼複雜度也很高。
類圖
3.個人bug分析
第一次作業
編號 | bug描述 | bug位置 | 總行數:未修復/修復 | 複雜度:未修復/修復 |
---|---|---|---|---|
1 | 化簡處理時,直接replace("+1", ""),忽略11x的情況 | 表示式類 | 619/625 | 2.46/2.48 |
2 | 新增結點出錯,導致ArrayList索引越界 | 表示式類 | 619/625 | 2.46/2.48 |
3 | 括號處理異常,會把-(x-x)處理為(-x-x) | 表示式類 | 619/625 | 2.46/2.48 |
4 | 乘法處理錯誤,兩個項相乘,中間直接加上了* | 操作符類 | 619/625 | 2.46/2.48 |
第一次作業的bug主要是出在括號項的處理上,括號項的乘法以及括號項前的正負號沒有得到很好的處理。
第二次作業
編號 | bug描述 | bug位置 | 總行數:未修復/修復 | 複雜度:未修復/修復 |
---|---|---|---|---|
1 | 如f(x)=x**2,f(-2)帶入時未給2帶上括號 | 主類 | 606/614 | 3.24/3.24 |
2 | 未注意冪有前導0的情況,直接使用了replace("x**0", 1) | 主類 | 606/614 | 3.24/3.24 |
3 | 誤以為冪次不超過10,使用replace("**1", "")的方式替換 | 化簡類中 | 606/614 | 3.24/3.24 |
第二次作業的bug主要是出在化簡和自定義函式的處理上。化簡時,草率的將x0和x1兩種情況直接化簡,未考慮前導零以及冪指數超過10的情況。自定義函式bug的修復較大,在三角函式類中需要處理冗餘的括號以及內部化簡,之前的設計是隻處理內部常數的冪函式的化簡,修復後三角函式內部作為expression處理。
第三次作業
編號 | bug描述 | bug位置 | 總行數:未修復/修復 | 複雜度:未修復/修復 |
---|---|---|---|---|
1 | 未考慮s < e的情況 | 求和函式類 | 791/795 | 2.93/2.95 |
2 | 求和函式中-i的處理有誤,負號會被直接忽略 | 求和函式類 | 791/795 | 2.93/2.95 |
3 | 輸出格式有誤,sin(sin(1)*cos(1))缺少括號 | 三角函式類 | 791/795 | 2.93/2.95 |
第三次作業的問題主要是忽略了求和函式 s < e的情況。
總體分析
總體而言,行數和圈複雜度的對比,也說明程式的bug主要還是集中在一些細節上。實際上,三次作業的大部分bug,都是在不斷優化(卷效能分)中產生的,但是一些細節沒有考慮周到,導致了bug的出現。
4.互測策略
在對自己的作業進行測試時,主要歸納出了以下幾個測試方向。
-
正負號。
-
空格、縮排。
-
冪指數前導0。使用如x**002這類資料測試。
-
大數。求和函式中的s、e容易被忽略。
-
求和函式s > e。這是指導書中的細節,規定了s > e 時,求和結果應為0。
-
求和函式的i替換。求和函式對錶達式中的i替換時,會把sin中的i也替換掉。
此外,如f(x)=x**2,f(-2)這種型別也很容易出現問題。但是在互測中不允許出現自定函式,這類情況只能用於自測。
5.心得體會(自刀)
-
不要刻意卷效能分!!!絕大部分的bug都是因為卷效能分產生的(為了20分效能分丟80分功能分顯然虧本)。 -
拒絕面向物件!!!第一次作業,成功被我寫成了面向過程,程式碼可讀性以及結構的魯棒性極差。(不能理解為什麼面向過程的程式碼複雜度最低) -
為程式碼的後續迭代做好準備!!!在第二次作業中,我將求和函式和自定義函式在預處理時替換,並將冪函式和三角函式放在用一個類中。在第二次作業限制較多的情況下,這樣做完全是可以的,但是卻不利於後續迭代,導致第三次作業幾乎是完全重構。
-
遵循高內聚低耦合的原則!!!耦合度高的程式碼塊在debug時堪稱災難~~~程式碼的高內聚做的也不好,經常性一個方法幾十行,導致方法的複雜度很高,debug牽一髮動全身。之後的設計還是應該細緻拆分,高內聚低耦合!!!