1. 程式人生 > 其它 >BUAA_OO_2022 第一單元總結

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牽一髮動全身。之後的設計還是應該細緻拆分,高內聚低耦合!!!