OO第一單元做業已所有完成,爲了使這一單元的做業可以收穫更多一點,我回憶起我曾經在計算機組成課設中,常常咱們會寫一些實驗報告,常常以此對實驗內容反思總結。在咱們開始下一單元的做業以前,我在此對OO第一單元進行整體性的反思總結,請各位助教和同窗們批評指正!python
類 | 類總代碼規模 | 類屬性個數 | 類方法個數 |
---|---|---|---|
"MainClass" | 8 | 0 | 1 |
"Polynomial" | 150 | 10 | 4 |
"Term" | 36 | 2 | 5 |
方法 | 控制分支數目 | 方法規模 |
---|---|---|
"MainClass.main(String[])" | 0 | 6 |
"Polynomial.Polynomial(String)" | 1 | 10 |
"Polynomial.diff()" | 3 | 13 |
"Polynomial.initialize(Matcher)" | 6 | 29 |
"Polynomial.printDiff()" | 12 | 56 |
"Polynomial.standardize()" | 6 | 29 |
"Term.Term(BigInteger,BigInteger)" | 0 | 4 |
"Term.compareTo(Term)" | 4 | 16 |
"Term.getCoe()" | 0 | 3 |
"Term.getPow()" | 0 | 3 |
"Term.setCoe(BigInteger)" | 0 | 3 |
"Term.setPow(BigInteger)" | 0 | 3 |
類 | 類總代碼規模 | 類屬性個數 | 類方法個數 |
---|---|---|---|
"Expression" | 157 | 11 | 9 |
"MainClass" | 20 | 0 | 1 |
"Term" | 206 | 7 | 21 |
方法 | 控制分支數目 | 方法規模 |
---|---|---|
"Expression.Expression()" | 0 | 5 |
"Expression.Expression(String)" | 0 | 5 |
"Expression.addTerm(Term)" | 0 | 3 |
"Expression.diff()" | 1 | 6 |
"Expression.getDeriFunc()" | 0 | 3 |
"Expression.getPrimFunc()" | 0 | 3 |
"Expression.parse()" | 1 | 10 |
"Expression.printDiff()" | 7 | 35 |
"Expression.standardize()" | 12 | 60 |
"Expression.trim(String)" | 0 | 5 |
"Expression.validCheck()" | 0 | 9 |
"MainClass.main(String[])" | 2 | 18 |
"Term.Term()" | 0 | 3 |
"Term.Term(String)" | 8 | 29 |
"Term.addFactors(Expression)" | 0 | 3 |
"Term.compareTo(Term)" | 8 | 28 |
"Term.diff()" | 6 | 37 |
"Term.getCoe()" | 0 | 3 |
"Term.getCosPow()" | 0 | 3 |
"Term.getEksPow()" | 0 | 3 |
"Term.getFactors()" | 0 | 3 |
"Term.getSinPow()" | 0 | 3 |
"Term.isCosine(String)" | 0 | 5 |
"Term.isDigit(String)" | 0 | 5 |
"Term.isEks(String)" | 0 | 5 |
"Term.isSine(String)" | 0 | 5 |
"Term.setCoe(BigInteger)" | 0 | 3 |
"Term.setCosPow(BigInteger)" | 0 | 3 |
"Term.setEksPow(BigInteger)" | 0 | 3 |
"Term.setSinPow(BigInteger)" | 0 | 3 |
"Term.similarTo(Term)" | 3 | 12 |
"Term.toCosine(String)" | 1 | 9 |
"Term.toDigit(String)" | 1 | 10 |
"Term.toEks(String)" | 1 | 9 |
"Term.toSine(String)" | 1 | 9 |
類 | 類總代碼規模 | 類屬性個數 | 類方法個數 |
---|---|---|---|
"MainClass" | 45 | 0 | 2 |
"factor.Coefficient" | 45 | 1 | 7 |
"factor.Cosine" | 73 | 6 | 6 |
"factor.Eks" | 54 | 4 | 7 |
"factor.FactorFactory" | 56 | 9 | 5 |
"factor.Sine" | 73 | 6 | 6 |
"factor.SubExp" | 53 | 1 | 7 |
"main.Expression" | 182 | 12 | 8 |
"main.Term" | 123 | 12 | 9 |
方法 | 控制分支數目 | 方法規模 |
---|---|---|
"MainClass.bracketLayer(String)" | 6 | 28 |
"MainClass.main(String[])" | 1 | 15 |
"factor.Coefficient.Coefficient(String)" | 0 | 3 |
"factor.Coefficient.equals(Factor)" | 1 | 7 |
"factor.Coefficient.getDiff()" | 0 | 6 |
"factor.Coefficient.getPow()" | 0 | 4 |
"factor.Coefficient.getSubFac()" | 0 | 4 |
"factor.Coefficient.getType()" | 0 | 4 |
"factor.Coefficient.print()" | 0 | 4 |
"factor.Coefficient.toDigit(String)" | 1 | 10 |
"factor.Cosine.Cosine(BigInteger,Factor)" | 0 | 4 |
"factor.Cosine.Cosine(String)" | 3 | 16 |
"factor.Cosine.equals(Factor)" | 2 | 10 |
"factor.Cosine.getDiff()" | 1 | 16 |
"factor.Cosine.getPow()" | 0 | 4 |
"factor.Cosine.getSubFac()" | 0 | 4 |
"factor.Cosine.getType()" | 0 | 4 |
"factor.Cosine.print()" | 1 | 7 |
"factor.Eks.Eks(String)" | 1 | 6 |
"factor.Eks.equals(Factor)" | 1 | 7 |
"factor.Eks.getDiff()" | 0 | 7 |
"factor.Eks.getPow()" | 0 | 4 |
"factor.Eks.getSubFac()" | 0 | 4 |
"factor.Eks.getType()" | 0 | 4 |
"factor.Eks.print()" | 1 | 7 |
"factor.Eks.toEks(String)" | 1 | 9 |
"factor.FactorFactory.getFactor(String)" | 6 | 25 |
"factor.FactorFactory.isCosine(String)" | 0 | 5 |
"factor.FactorFactory.isDigit(String)" | 0 | 5 |
"factor.FactorFactory.isEks(String)" | 0 | 5 |
"factor.FactorFactory.isSine(String)" | 0 | 5 |
"factor.Sine.Sine(BigInteger,Factor)" | 0 | 4 |
"factor.Sine.Sine(String)" | 3 | 16 |
"factor.Sine.equals(Factor)" | 2 | 10 |
"factor.Sine.getDiff()" | 1 | 16 |
"factor.Sine.getPow()" | 0 | 4 |
"factor.Sine.getSubFac()" | 0 | 4 |
"factor.Sine.getType()" | 0 | 4 |
"factor.Sine.print()" | 1 | 7 |
"factor.SubExp.SubExp(ArrayList
|
1 | 6 |
"factor.SubExp.SubExp(String)" | 0 | 11 |
"factor.SubExp.equals(Factor)" | 1 | 7 |
"factor.SubExp.getDiff()" | 0 | 7 |
"factor.SubExp.getPow()" | 0 | 4 |
"factor.SubExp.getSubExp()" | 0 | 3 |
"factor.SubExp.getSubFac()" | 0 | 4 |
"factor.SubExp.getType()" | 0 | 4 |
"factor.SubExp.print()" | 0 | 4 |
"main.Expression.Expression()" | 0 | 5 |
"main.Expression.Expression(String)" | 0 | 5 |
"main.Expression.addTerm(Term)" | 0 | 3 |
"main.Expression.diff()" | 1 | 6 |
"main.Expression.getDeriFunc()" | 0 | 3 |
"main.Expression.getPrimFunc()" | 0 | 3 |
"main.Expression.parse()" | 7 | 30 |
"main.Expression.print(int)" | 8 | 52 |
"main.Expression.standardize()" | 0 | 50 |
"main.Expression.validCheck()" | 1 | 11 |
"main.Term.Term()" | 0 | 5 |
"main.Term.Term(String)" | 9 | 29 |
"main.Term.addFactors(Factor)" | 0 | 3 |
"main.Term.compareTo(Term)" | 4 | 16 |
"main.Term.diff()" | 6 | 30 |
"main.Term.getCoe()" | 0 | 3 |
"main.Term.getEksPow()" | 0 | 3 |
"main.Term.getFactors()" | 0 | 3 |
"main.Term.setCoe(BigInteger)" | 0 | 3 |
"main.Term.setEksPow(BigInteger)" | 0 | 3 |
"main.Term.similarTo(Term)" | 2 | 10 |
從三次做業的數據能夠看出,類的個數不斷增長,代碼總規模顯著增長;類的方法個數、屬性個數變化較少。這符合做業題目中,功能不斷迭代增長,而處理的基本流程不變的特色。git
方法的平均行數也不斷減少,代碼的可讀性有所增長。正則表達式
筆者使用了IDEA內置的MetricsReloaded插件進行度量,其中主要包括如下幾個參數(摘自學長博客):編程
ev(G)基本複雜度,是用來衡量程序非結構化程度的,非結構成分下降了程序的質量,增長了代碼的維護難度,使程序難於理解。所以,基本複雜度高意味着非結構化程度高,難以模塊化和維護。實際上,消除了一個錯誤有時會引發其餘的錯誤。bash
Iv(G)模塊設計複雜度,是用來衡量模塊斷定結構,即模塊和其餘模塊的調用關係。軟件模塊設計複雜度高意味模塊耦合度高,這將致使模塊難於隔離、維護和複用。模塊設計複雜度是從模塊流程圖中移去那些不包含調用子模塊的斷定和循環結構後得出的圈複雜度,所以模塊設計複雜度不能大於圈複雜度,一般是遠小於圈複雜度。架構
v(G)圈複雜度,是用來衡量一個模塊斷定結構的複雜程度,數量上表現爲獨立路徑的條數,即合理的預防錯誤所需測試的最少路徑條數,圈複雜度大說明程序代碼可能質量低且難於測試和維護,經驗代表,程序的可能錯誤和高的圈複雜度有着很大關係。模塊化
從圖中能夠看出,三次做業的平均複雜度呈降低趨勢,也剛好證實了筆者在對三次做業進行迭代時,對代碼結構不斷重構並解耦的過程。函數
能夠看到,超標的方法是Polynomial.initialize()和Polynomial.printDiff()。前者由於負責了所有的字符串解析工做而較爲臃腫,後者涉及了複雜的格式處理,於是不夠簡潔。性能
在此須要特別注意的是輸出(print)方法,簡化表達式(standardize)方法以及Term的構造方法均出現了複雜度超標的現象。學習
輸出複雜度超標的緣由,主要是在輸出時對係數和指數是0,±1等特殊狀況作了較爲複雜的判斷;尤爲是前兩次做業,筆者沒可以將複雜的輸出邏輯簡化到項(Term)的層面進行,所以使得表達式的輸出模塊較爲臃腫。
簡化表達式涉及到對方法參數(爲一個引用)所指向實體的修改,所以使得耦合度大幅上升。筆者在開始構造這一模塊時並無想到這樣作會帶來危險;通過學習後發現,最好的處理方法仍是將獲得的優化後的表達式做爲返回值返回,而不是直接對引用實體進行修改,這樣能夠避免後期做業中,遞歸表達式對外層表達式作不指望的修改。
這一單元的第二次做業中,自做聰明的我在Term等子類的建立邏輯所有放在了構造方法裏面,這樣就能夠直接經過一個new Term(input)操做直接獲得表達式須要的項(也正因如此,我沒可以認識到工廠方法的優點)。但在閱讀了學長的博客以後,我發現了這樣作的問題:在《阿里巴巴Java開發手冊》中有這樣的描述:【強制】構造方法裏面禁止加入任何業務邏輯,若是有初始化邏輯,請放在init方法中。進一步查閱資料後,我才明白,這樣作會給後續的依賴、擴展形成困難,第三次做業的開發也證明了這一點,我不得不經過方法重載來實現遞歸表達式的Term建立。
第三次做業我便改用了FactorFactory函數,用工廠方法模式建立須要的因子。此時Term的構造方法耦合度明顯降低(但FactorFactory工廠方法略微臃腫)。
前兩次做業內容較少,筆者沒有采用抽象類繼承或接口實現的方法。第三次做業中,因爲使用了FactorFactory的工廠方法模式,所以使用了Factor接口,共有Coefficient(常數因子)、Eks(基本變量x)、Sine(sin函數)、Cosine(cos函數)、SubExp(子表達式)五中因子類實現了Factor接口。接口的實現深度爲1。
第一次做業中,我使用了三個類。MainClass只負責輸入輸出,Polynomial負責對多項式進行解析和求導,Term類的示例爲多項式中的項對象。
我認爲此次做業中成功之處在於:
失敗之處在於:
第二次做業大致繼承了第一次做業的架構,主要爲Term方法添加了SinPow和CosPow兩個屬性,併爲之添加了相應的計算邏輯和化簡邏輯。
我認爲此次做業的成功之處在於:
失敗之處在於:
第三次做業進行了較大的結構變更,但具體的實現過程和第二次相似。爲了求穩,不少優化優化過程也被去除。
此次做業的成功之處在於:
失敗之處在於:
第一次做業中,公測和互測均未出現bug,筆者本身也沒有檢查出bug。
第二次做業中,公測和互測均未出現bug,但筆者本身在互測階段再次發現bug,是在三角函數輸出結果的次冪爲-1時的格式錯誤。例如:當生成的表達式爲sin(x)**-1*cos(x)時,程序將-1誤判爲cos(x)的係數進行化簡,獲得了sin(x)**-cos(x)的結果。
第三次做業中,公測和互測均出現bug,和第二次做業相似,分別是輸入和輸出時發生的格式錯誤。在內層表達式解析時,筆者經過去掉輸入字符串的前三個和後三個字符(「({}」和「){}」)來獲得內層嵌套,但忽略了先後空格問題,在輸入以後加上.trim()便可解決。
總而言之,三次做業均未出現設計層面的問題,但在細節處理上不夠謹慎,在往後的工程中須要更加當心,並在開發階段作充足的魯棒性測試。
本單元的互測環節中,我主要經過黑箱測試發現bug。我首先使用了基於bash批處理的評測機,結合python的xeger庫生成測試數據進行處理。但評測機的自動評測均沒有出現bug,說明你們都完成了最基本的功能設計。
所以,我採用手動構造測試數據集以達到遍歷樣例空間的目的。手動構造的測試集沒法遍歷全部可能的錯誤,但經過率先遍歷邊界數據、在數據合法的範圍內取各種極端值、構造數據優化的樣例以找出優化漏洞的方式能夠找出大多數可能存在的錯誤。
構造樣例時,我也用到了白箱模型的思想。我將在下文結合發現其餘同窗的bug說明。
在評測機沒有測出任何錯誤的狀況下,我採用了各類極端數據和邊界數據進行測試。
發現兩個同窗各出現一個問題。同窗A在簡化以-1爲係數的數據時,沒有刪除對應的乘號。這一點能夠在其代碼中看出端倪:在進行優化時只作了將±1及以前可能的**刪除,而沒有考慮到±1可能做爲求導所得結果的係數而存在。同窗B出現了在項數超過300時,棧溢出的錯誤。(後文會對各類因正則表達式而致使的棧溢出進行分析)
第二次互測中,個人重要改進是使用bash腳本對互測屋內的同窗實現同步測試。這樣作的好處有三:輸入一次樣例可以獲得7我的的結果,節省了不少時間。另外,經過七我的之間的結果對比很容易發現是否有人出現bug,尤爲在難以化簡或結果不直觀時,這一方法實用性很強。對你們的優化結果進行對比也可以看出在輸出優化方面所採用的的策略。我在第二次做業的討論區詳細說明了bash腳本進行批量測試的方法,歡迎你們閱讀。
本次測試發現兩個bug,同窗A出現的bug爲x**10000*x**10000,題目所述的「冪函數指數絕對值不超過10000」指的是輸入格式,並不是最終結果,這屬於沒有看清要求所形成的錯誤。同窗B出現的bug爲x*sin(x)**-1 + x*cos(x)**-1,和第一次做業的狀況相似,在化簡的過程當中想固然地認爲指數不可能出現-1,由於x**0求導直接得0,然而忽略了求導乘法公式的存在。
第三次做業中,同窗們採用的實現方式多樣,很難對全部人的實現細節逐一分析。但從黑箱測試的結果來看,你們的程序廣泛對輸入合法性的判斷存在必定問題,不少同窗(包括我本身)出現了不應出現的Wrong Format。說明沒有對空格可能的出現位置作充分的分析,徹底依賴正則表達式對正確性的判斷是不可靠的。
本次測試發現諸多bug,有關輸入格式判斷的問題涉及不少naive的錯誤,在此不一一枚舉。另外,有兩位同窗的程序出現了嚴重的超時問題。主要緣由是使用了過大規模的正則表達式,出現了50行的正則字符串和11個Pattern類,這樣進行匹配相比會出現很大問題。第二位同窗和其餘同窗的思路有所不一樣,他經過主動識別錯誤輸入的方法檢測錯誤,而其中使用了不少.*,反覆的正則回溯形成了超時問題。兩位同窗的代碼處於隱私考慮不在此公佈了。
在第一次做業中,我使用了Polynomial類做爲多項式類,在Polynomial類中經過new Term()語句生成所須要的Term類(項),並儲存在ArrayList中。項有係數和指數兩個Biginteger字段,Polynomial類依據這兩個變量對項進行操做。這樣的設計成功地分理出了本題的核心數據:係數和指數,但把過多的業務邏輯交給了Polynomial類,Polynomial類的方法較爲複雜。
第二次做業的對象建立方式和第一次相同,類間的依賴關係也相同,只是在Term中添加了SinPow和CosPow兩個字段,並分別添加了相應的化簡和計算操做。
第三次做業涉及到表達式的嵌套,即因子Factor中包含表達式Expression的現象。兩個類的相互調用使得上述方法再也不適用。
我開始使用工廠方法模式建立對象,經過實現Factor接口完成對各類因子的統一,在SubExp表達式因子中建立Expression對象,實現表達式的遞歸建立。
我認爲優秀代碼都有值得我學習的如下共性:
代碼結構清晰:優秀代碼廣泛對代碼進行合理的分包和類的劃分,類之間的耦合度很低,易於進行功能的複用和添加。我認爲我在這一方面已經有所進步,但仍然須要多加練習。
各個類功能簡單明確:優秀代碼的各個類都比較精簡,能夠直觀地看出不一樣模塊針對的是哪一需求。這一方面我特別須要向優秀的同窗學習,本身在編程時沒有可以提早對需求進行充分分析,致使不少地方出現了令我本身都費解的雙構造方法,假若再沒有明確的註釋,代碼可讀性將很低。
命名嚴謹而易懂:優秀代碼中的命名規則符合駝峯原則,變量名依據真實功能取英文全稱,基本不會出現理解上的二義性。反觀互測中出現的其餘代碼,雖然大致作到了按代碼風格命名,但在一些不重要的變量上取名隨意,出現不少諸如s一、s2的簡單符號,甚至有所有使用漢語拼音作變量名的現象。我認爲本身可以基本作到代碼風格的嚴謹,但在功能添加和保持命名風格的統一方面,仍然有進步空間。
OO編程和初學C語言有很大的不一樣,在完成一個小型的工程中學習各方面的知識,這可能將是在從此很長一段時間的學習和工做中我都要經歷的模式。在這種狀況下,原來的學習方式再也不適用。一次做業的完成時間,就永遠不能夠預料。不少時候會由於一個小問題,一會兒就耽誤了不少時間,這是最痛苦的;因此說efficiency的提升對有效學習相當重要。
祝各位在從此的學習中有所進步!