本文是BUAA OO課程Unit1在課程講授、三次作業完成、自測和互測時發現的問題,以及傾聽別人的思路分享所引起個人的一些思考的總結性博客。本文第二部分介紹三次作業的設計思路,主要以類圖的形式展現,並有簡單的優劣分析;第三部分為程式代碼複雜度的分析(二、三兩部分為基於度量的對自己程式結構的分析);第... ...
一、摘要
本文是BUAA OO課程Unit1在課程講授、三次作業完成、自測和互測時發現的問題,以及傾聽別人的思路分享所引起個人的一些思考的總結性博客。本文第二部分介紹三次作業的設計思路,主要以類圖的形式展現,並有簡單的優劣分析;第三部分為程式代碼複雜度的分析(二、三兩部分為基於度量的對自己程式結構的分析);第四部分為對自己、對他人程式的測試、DEBUG、Hack的思考;第五部分是分析作業中可以應用對象創建模式的可能性,和重構的思考。
二、開發設計思路
1.程式類圖展示
第一次作業
第二次作業
第三次作業
2.簡單分析
這三次作業我基本上採用了完全不同的方法,感覺到其實非常有趣並且很有收穫。
第一次作業的時候我的所有代碼都在一個.class文件內,設計了一個沒有什麼意義的主類Derivation,包含了一個多項式類和一個單項式類。這種設計除了在文件管理上不容易丟失文件以外基本沒有什麼好處。第二次和第三次作業中代碼才越來越面向對象。第三次中有一些體現抽象工廠的設計,使用了Factor介面和Item抽象類,顯然第三次作業的程式更具有可擴展性。實際上我在第二次作業中就有意識地在降低模塊的耦合以方便單元測試。
第二次和第三次的作業實際上採用了不同的設計思路,主要在於第二次是為了提高對輸出優化處理的可擴展性,第三次是為了提高支持輸入的項的類型的可擴展性。第二次中的對象保存的是對一個多項式的各種處理方法,而第三次的對象保存的是多項式的各種組成成分。這兩個程式一個在怎麼處理產品的方法上有較好擴展性,一個在能處理的產品種類上有較好擴展性。此外,第三次作業我嘗試直接使用字元串來保存數據,無意中使用了Creative Pattern。前兩次作業中,會牽涉到對象的拷貝等,容易發生錯誤,某些對象的方法可能不具有可再現性,引入不安全性並且不利於單元測試;第三次作業中,由於沒有計劃做詳細的優化,使用了直接進行字元串操作的簡化設計,但非常不利於優化。此外,前兩次作業使用的容器還較為傳統(定長數組),不利於利用現有的包進行合併同類項優化而自己寫了優化的方法,雖然實現簡單,但是也浪費了空間資源。
三、程式結構分析
1.度量分析
第一次作業
第二次作業
第三次作業
從三次作業來看,代碼結構的基本複雜度、模塊複雜度和圈複雜度呈現明顯的逐次下降趨,體現出我的程式更加結構化、模塊化,這有利於代碼的理解和結構化、模塊化的維護、測試和功能擴展;各模塊之間耦合度降低,有利於模塊的隔離、維護和復用;程式獨立路徑的數量也在減少,則出現錯誤的幾率會降低,亦有利於測試和維護。
四、測試、DEBUG與互評
1.個人BUG分析
第一次作業:
- 引發錯誤的數據:“+x+x+x...”(大量“+x”構成的長輸入)
- 引發的錯誤:正則表達式匹配棧溢出錯誤
- 沒有測出的原因:本地使用JDK版本比評測機高,該版本正則表達式並不會發生這樣的匹配導致棧溢出的錯誤,所以雖然本地也測試過這種數據,但是並不會引發錯誤
- 錯誤的代碼:
123 "|\\d+)[ \t]*)*[ \t]*";
- 修改後的代碼:
123 "|\\d+)[ \t]*)*+[ \t]*";
- 分析:使用在匹配中使用獨占模式即可使得程式在對整個輸入進行匹配後不進行回溯,從而避免棧溢出的錯誤。
第二次作業:
- 引發錯誤的數據:“x^2*cos(x)^6*sin(x)^7+x*cos(x)^6*sin(x)^7”
- 引發的錯誤:優化時引入了計算錯誤
- 沒有測出錯誤的原因:測試不充分
- 錯誤的代碼:
47 boolean canOptimizeWith(Monomial m) { 48 return ((this.power.equals(m.power) && 49 (this.sinPower.equals( 50 m.sinPower.add(new BigInteger("2"))) 51 && m.cosPower.equals( 52 this.cosPower.add(new BigInteger("2")))) 53 || (this.cosPower.equals( 54 m.cosPower.add(new BigInteger("2"))) 55 && m.sinPower.equals(this.sinPower.add(new BigInteger("2"))))))); 56 }
- 修改後的代碼:
48 return (this.power.equals(m.power) && 55 this.cosPower.add(new BigInteger("2")))
- 分析:判斷兩項是否滿足優化條件的方法錯誤,邏輯太複雜導致的括弧錯誤。
第三次作業:
- 引發錯誤的數據:cos((-x^+7*x^7))^+1
- 引發的錯誤:對象調用了自己的方法導致迴圈調用,引發棧溢出錯誤
- 沒有測試出的原因:測試不充分
- 錯誤的代碼:
47 public void optimize() { 48 if (super.getData().matches(".*\\^0*1")) { 49 setData(getData().replaceAll("\\^0*1", "")); 50 } else if (super.getData().matches(".*\\^0*")) { 51 setData("1"); 52 } 53 }
- 正確的代碼:
47 public void optimize() { 48 if (super.getData().matches(".*\\^0*1")) { 49 setData(super.getData().replaceAll("\\^0*1", "")); 50 } else if (super.getData().matches(".*\\^0*")) { 51 setData("1"); 52 } 53 }
- 分析:我的設計中getData時要優化,優化的時候要調用父類的getData,如果調用自己的,則會迴圈調用。
2.互測BUG分析和Hack技巧
我互測中遇到的BUG主要是對數據的輸入處理的問題,例如未判斷正負號使用是否正確就先去除了重覆的正負號,或者在正則表達式中使用“\s”從而引入了不允許的特殊字元的可能性,或者某些位置應該允許輸入多個空格只允許了單個。還有對不完整的輸入,比如“+x+”,"45*"等沒有進行判斷。第三次作業中,還有同學沒有使用BigInteger處理數據,或者對指數為000000000000000000001的輸入進行了報錯,說明這方面沒有處理得當。還有對於如“x*+1”的乘法內含有帶符號整數因數處理錯誤的情況。
主要通過閱讀代碼針對性測試和構造測試數據盲測結合進行測試。JUnit的Series能夠排列組合生成一些數據,同學分享的隨機生成字元串也能生成數據,但主要還是人工設計的邊界條件數據或者特殊情況數據更容易測出錯誤。在時間允許的情況下,閱讀代碼是最有效的找到別人BUG的方法。當然,自己設計時積攢或出錯過的測試數據也有使用的價值。
3.對提高DEBUG效率的思考
What Information |
More Information | Less Information |
Trail |
Step by step |
Conditional Breakpoints |
Code |
The whole code |
Coverage |
Value of variables |
Variables |
Watches/Evaluation |
Exception |
Throw an exception |
... |
Anything |
Hiearchy + |
Hiearchy - |
我認為DEBUG也是值得思考的事情。DEBUG是根據提供的信息尋找所寫代碼中錯誤並改正的過程。通過IDEA和Eclipse所支持的豐富的功能,我們可以輕易的獲得包括代碼覆蓋、變數值、代碼軌跡等信息,通過增加和減少信息結合自己程式的設計結構,可以大大減少鎖定bug的時間。利用條件斷點實現分級的DEBUG信息輸出也是有價值的DEBUG方法。
五、對象創建模式的應用思考
第一次作業中,我沒有實現多項式類,將多項式當做多個單項式存儲在數組中進行處理,並編寫相應的各種方法。可以重構,設計多項式類,僅包含HashMap類,而且由於多項式求導後還是多項式,求導方法可以使用new的方法返回一個多項式。
第二次作業中,也沒有實現多項式類。仍可設計多項式類,各種多項式處理器與主函數的數據交流也可以重構為對象創建的方法,每次返回一個new多項式,可以使得各種求導、優化、合併同類項、根據三角函數公式優化等方法可以獨立地開發和測試。
第三次作業我已經有使用工廠模式的影子,仍有很多可以改進的地方。可以完成部分優化工作,這裡就需要註意equals方法的重寫以支持合併同類項。此外,在我設計中使用了替換字元以屏蔽括弧內正負號和乘號的處理方法來應對多層嵌套,分割後再將字元換回,這個有很大的不安全性,也可以重構,將這種乘積和單項式匹配方法(即遞歸地根據+、*分割輸入)改為expression tree對象的處理,改為工廠模式,可以減少正則表達式的使用和避免對輸入數據的改動引起的不安全性。