1. 程式人生 > 其它 >面向物件第一單元總結回顧

面向物件第一單元總結回顧

OO第一單元總結回顧

目錄

1.前言

OO第一單元的作業圍繞表示式化簡。考慮到增量式開發需要程式具有良好的可拓展性,三次作業都採用遞迴下降的方法,表示式、項、因子逐級分析,再針對不同層次採用不同的化簡、合併策略。下面對各次作業展開分析。

2.各次作業分析

2.1第一次作業

Part 1. 基本思路

第一次作業要求讀入一行表示式,展開其中的所有括號後輸出。資料限制包括:括號深度最多一層,指數不為負數等。效能分數則根據輸出字串的長度決定,越短越高。

第一次作業建立的遞迴下降架構是後續作業的基礎,參考了實驗課提供的遞迴下降例程,本人的程式類圖如下:

綠色框中的類用於解析表示式,橙色框中的類用於構成表示式樹。

程式對錶達式的處理步驟如下:

  1. 首先以字串形式的表示式建立Lexer物件,並由Lexer物件建立Parser物件;
  2. 呼叫Parser物件的parseExpr()方法,後者遞迴呼叫parseTerm()方法,parseFactor()方法,建立表示式樹,並返回建立的Expr物件;
  3. 呼叫Expr物件的delPower()方法,將所有指數大於1的表示式化簡為多個表示式相乘;
  4. 呼叫Expr物件的delBracket()方法,使用乘法分配律展開括號;
  5. 呼叫Expr物件的merge()方法,將表示式的各項同類項合併,並輸出化簡後的表示式。
Part 2. 優化點

由於作業一實現相對容易,我轉而在優化上投入了大量時間,儘可能地達到最佳效能。採取的優化策略包括:

  • 指數為0的項化簡為數字1;
  • 合併所有同類項;
  • 輸出時,引數為正數的項優先作為首項輸出,並省略其正號;
  • 輸出時,指數為1的項不輸出指數;
  • 輸出時,引數為1且含有變數x的項不輸出引數;
  • 輸出時,指數為2的冪函式,不輸出x**2,轉而輸出x*x
Part 3. 類方法分析

使用MetricsReloaded工具分析各類的方法,得到如下結果:

以上圖片截取了複雜度最高的方法。分析表明,Expr類的merge()方法複雜度最高,這是因為該方法同時完成合並同類項和輸出結果的任務,任務過重,屬於設計時的缺陷;Parser類的parseFactor()方法複雜度較高,這是因為該方法需要同時考慮Number類,Variable類和Expr類的解析;Term類的delPower()方法複雜度較高,這是因為該方法特判了指數為0的情況,比較繁瑣。

Part 4. 類分析

使用MetricsReloaded工具分析各類,得到如下結果:

分析表明,Parser類的平均圈複雜度最高,這是因為Parser類在解析表示式、項、因子時需要考慮許多細節問題,例如表示式是否包含指數等;Expr類和Term類的平均圈複雜度,總圈複雜度都很高,這是因為這兩個類的方法大多比較複雜,同時,兩個類方法過多,任務過重,也導致了總迴圈複雜度較高。

2.2 第二次作業

Part 1. 基本思路

第二次作業要求在第一次作業的基礎上增加了三種新的因子:三角函式、自定義函式和求和函式,程式功能拓展為展開所有的自定義函式和求和函式,並展開除三角函式呼叫的括號以外的所有括號。同時,第二次作業不再限制括號深度。

第二次作業的實現中,我增加了TriFunc類用於表示三角函式,增加了CustomFuncManage類和CustomFunc類分別用於管理和處理自定義函式,增加了SumFunc類用於處理求和函式。此外,為了滿足三角函式帶來的複雜化簡需求,增加了MergeTerm類和MergeFactor類用於同類項合併和化簡。

程式類圖如下(紅色方框中的類為本次新增的類):

程式的類主要分為三個部分:綠色框中的類用於解析表示式,橙色框中的類用於構成表示式樹,藍色框中的類用於表示式化簡。

程式的執行步驟與第一次作業類似,此處僅指出不同點:

  1. 在呼叫Parser類的方法解析時,parseFactor()方法增加了對三角函式、自定義函式和求和函式的解析,其中後兩者會直接處理字串,返回替換後的表示式
  2. 為了減輕Expr類和Term類的負擔,並實現三角函式合併,化簡工作交由MergeFactor類和MergeTerm類完成,兩個類都重寫了equals()hashCode()方法,用於判斷兩項是否可以合併。
Part 2. 優化點

本次作業的優化策略相較於第一次作業,主要體現在增加了三角函式的合併。

Part 3. 類方法分析

以上分析僅截取了複雜度最高的部分方法。分析表明,Parser類的parseFactor()方法和Lexer類的next()方法複雜度較高,這是因為隨著因子種類的變多,兩個方法不可避免地需要增加分支來判斷新的因子;Expr類的print()方法和printFunc()方法為了減少字串長度,設定了各種條件語句來匹配可以優化的地方;Term類的delPower()方法和MergeTerm類的MergeTerm()方法因為根據不同因子型別採用不同的化簡指數和合並策略,所以複雜度較高。

Part 4. 類分析

分析表明,Parser類, Lexer類, Expr類, Term類, MergeTerm類的平均圈複雜度較高,大都因為這些類需要對不同因子分別判斷和處理。

2.3 第三次作業

Part 1. 基本思路

第三次作業要求在第二次作業的基礎上增加巢狀因子,允許任何因子充當三角函式和自定義函式呼叫的因子。簡單來說,程式需要能在解析表示式時遞迴解析另一個表示式。

為了支援三角函式巢狀因子,我增加了TriExprFunc類。該類繼承TriExpr類並支援三角函式內含表示式因子。我的程式類圖如下(紅色方框中的類為本次新增的類):

程式的類主要分為三個部分:綠色框中的類用於解析表示式,橙色框中的類用於構成表示式樹,藍色框中的類用於表示式化簡。

Part 2. 優化點

由於本次作業優化較為困難且容易出錯,本次作業未進行優化。

Part 3. 類方法分析

以上分析僅截取了複雜度最高的類方法。第三次作業類方法分析結果與第二次作業非常接近,在此不再論述。

Part 4. 類分析

類分析結果與第二次作業類似,新增的TriExprFunc類複雜度較低,在此不再展開論述。

3 優缺點分析

三次作業的實現方法優缺點並存,分析如下:

優點:

  1. 採用遞迴下降方法,結構清晰,易於拓展;
  2. 設計的類邏輯合理,符合人類思維;各個類分工合理,基本做到高內聚低耦合;
  3. 採用了繼承和介面實現,增加了程式碼複用性和可讀性;
  4. 採用多種方法提升效能。

缺點:

  1. 部分類和類方法任務過重,缺乏分工,複雜度較高;
  2. 正確性和效能的實現相互分離,分別考慮,缺乏整體性。效能優化的程式碼非常冗餘;
  3. 未找到既能保持低複雜度,又能支援多種因子解析的方法。

4 Bug分析

4.1 自己作業的Bug分析

第一次作業中,我因為BNF描述閱讀不仔細,誤以為常數因子處於int範圍,導致Parser類的parserFactor()方法出現Bug,並花了較多時間發現這一問題。

第二次作業中,我本人並未發現Bug,卻在強測中被測出一個Bug:由於追求效能分,我在Expr類的merge()方法中將部分三角函式化簡為sin(x*x)cos(x*x)的形式,然而這種形式並不符合BNF描述,所以未通過部分測試點。

第三次作業中,同樣因為BNF描述閱讀不仔細,我漏掉了自定義函式呼叫中,自定義函式名和'('中間的空白符,導致Parser類的parserFactor()方法執行時錯誤。

總結本次作業中我的程式碼出現的Bug,可以得到如下經驗:

  1. 形式化表述一定要逐字逐句仔細閱讀。
  2. 追求效能分的同時一定要確保正確性不受影響。

同時,經過對比,我發現出現Bug的地方均是圈複雜度較高的地方(Expr類的merge()方法,Parser類的parserFactor()方法),由此可見圈複雜度較高的程式碼可能會對程式設計師造成干擾,這體現了追求低複雜度的意義。

4.2 發現的同屋Bug

我在本單元中自行設計了評測機,用於自測和互測。評測機通過python實現,分為三個步驟:測試樣例隨機生成、執行jar包獲得程式輸出、比對程式輸出和正確輸出。在實現中,我使用了python的sympy包用於表示式化簡和比較,使用了subprocess包用於執行jar包。

通過評測機自動測試,我找到了同屋其他同學的一些Bug,列舉如下:

  • 第一次作業:解析部分錯誤,導致形如1*-1這種乘法接負數的表示式出錯;
  • 第二次作業:為了追求效能,導致當正確輸出為0時不輸出;
  • 第二次作業:解析部分錯誤,導致形如sin(-1)這種因子為負數的三角函數出錯;
  • 第三次作業:支援的資料範圍不滿足要求,sum(i,s,e,t)中的s和e不支援超長整數。

通過本次互測,可以總結出以下經驗:

  1. 通過評測機測試遠比人工測試更快速,有效;
  2. 同學們的錯誤主要集中於解析錯誤等,其根因在於形式化表述的解析過程不嚴謹,可見仔細閱讀形式化表述的重要性。

5 架構設計體驗

5.1 架構成型過程

由於第一次作業就採用了拓展性強的遞迴下降方法,在本單元中,我基本上沒有需要重構的地方,可以直接針對需求增添新的功能模組。為了支援第二次作業,我增加了TriFunc等新的因子類和MergeTerm等新的合併類;為了支援第三次作業,我增加了TriExprFunc因子類用於三角函式巢狀因子,架構的成型過程循序漸進、連續一致。

5.2 心得體會

由於我上學期修過《Java程式設計》一課,Java使用比較熟練,因此本次作業中語言使用方面的問題幾乎沒有出現。與之相對,思路上的問題則讓我覺得比較吃力。

第一次作業中,考慮到後續迭代開發,我決定使用遞迴下降演算法。儘管課程組在課上實驗提供了遞迴下降的模板程式碼,我依然花費了較多時間才得以理解。而從理解到自己能熟練使用,又需要更深入地學習。不過,由於第一次作業整體難度較低,最終還是較順利地完成了。為了保證作業的正確性,我用剩餘的時間寫了一個評測機,較嚴謹地評測了自己的程式碼,最終在強測中取得滿分。第一次作業的順利完成很大地增長了我的信心。

第二次作業中,我遇到的難點在於深拷貝和equals()方法重寫。由於第二次作業不再限制括號深度,我的程式碼不可避免地需要使用深拷貝;同時,為了合併同類項,又不得不重寫equals()方法。為了解決這兩個問題,我搜了許多網上的資料,學習了討論區和助教們提供的許多方法,最終得以解決這兩個問題。第二次作業給我帶來的收穫就在於提高了自己蒐集資料,解決問題的能力。

第三次作業中,我在程式碼實現上基本沒有遇到阻礙,很快就完成了迭代開發,但卻被一個弱測點困擾了很久。這次作業的debug時間尤其漫長,我使用了各種方法,包括逐字逐句與第二次作業對比、使用各類自己的樣例測試,最後使用評測機測試都沒能解決問題。無奈之下只得自己重新一點一點看指導書和程式碼。最後發現問題出在形式化表述漏看了一個空白項。這次作業再次提醒我審題的重要性。

總而言之,OO第一次作業中,我收穫了許多,也取得了不錯的成績。希望能在第二單元再接再厲!