OO第1~3次作業總結
作業1——多項式運算
基於度量和類圖分析設計
先看Metrics插件做出的復雜度分析:
乍一看沒有紅色報警,其實是因為選中某一行時會自動將該行改為黑色,無論之前是紅色還是藍色emmm
真正展開第一行時,慘不忍睹:
為什麽圈復雜度這樣大?先看看類圖:
- 【當時的設計思路】有個poly類,既是poly的數據結構(系數和冪指數的數組),又是其方法載體(能夠實現加減、打印自身信息);還有一個負責統籌規劃的計算者類,能夠分析輸入的字符串、進行多項式運算、打印錯誤信息。
- 【如今看來需要做的改動】
(1)把分析字符串合法性的工作放給poly,他應該知道自己是不是一個合格的poly。
(2)打印錯誤輸出這件事,應該另起一個類,實現功能的劃分。
(3)不要洋洋灑灑就用數組。
(4)字符串匹配這事兒,不要用狀態機來做。
- 【我為什麽當時用狀態機】這是一個令人心酸的故事QAQ
首先,我花了一上午熟練掌握了正則表達式的通用知識,打算在C程序(作業的另一個要求)中試試手,便去下載C程序所要的相關的頭文件等,結果一直用不了...大神跟我說C最好不要用正則表達式emmm 於是我想起來學姐提示過的有限狀態機,我畫了個大狀態機,反復分析論證其正確性,然後在C程序實現了。發現時間不多了趕緊跑去幹java,可能是太緊張了吧,java正則表達式教程我看了倆小時沒懂,突然失去自信...那就先穩一點用狀態機寫吧,之後就沒時間改過來了...
這個故事告訴我們,做事前一定要分輕重。
後來第一次實驗課時,要求我們會用java使用正則表達式,於是我打開被分配到的互測作業,有例子果然學起來就是快,10分鐘我就懂了...
所以同樣是緊張,為何效率如此不同...答案或許是,面向例子學習法萬歲!
- 【PS】狀態機唯一的優點(安慰自己一波),就是能在實現狀態的同時,順帶對錯誤的輸入進行準確的錯誤提示,比如,在第幾個字符處出現了錯誤,用戶輸入了什麽,而本程序期望怎樣的輸入。
- 【狀態機導致了高復雜度】既然我用了狀態機,那麽swith-case, if-else就暴增了,而McCabe圈復雜度:
認為程序的復雜性很大程度上取決於程序圖的復雜性。
單一的順序結構最為簡單,循環和選擇所構成的環路越多,程序就越復雜。
Bug分析
- 公測:通過
- 互測:我方安全,對方正則表達式爆棧
- 課下自我調試:發現poly打印自己的函數有bug——在考慮 結果多項式 的 某個term 的系數為0 的情況時,漏掉了一些情況導致的bug,後來對系數為0的term出現的位置進行了首、中、末的遍歷,來確保自己的打印輸出格式正確。
- 今晚是個平安夜。
作業2——傻瓜電梯
基於度量和類圖分析設計
先看Metrics插件做出的復雜度分析:
這次是真的沒有紅色報警~撒花~
那麽再看類圖:(自己手動調整了位置看起來 局部對稱、思路清晰,建議全屏看大圖)
- 【類設計思路】
首先明確指導書的要求:需要實現的類 包括但不限於Elevator, Floor, Request, RequestQueue, Scheduler 這5個類(我承認 命名的時候去搜過翻譯...)
是否要加其他類:為了讓兩種請求有所區別,我設計了RequestER, RequestFR 兩個請求類的子類; 為了讓main函數不要放在某個其他類裏導致沒有邏輯關系,我加了Main類; 我吸取第一次作業的教訓,將打印錯誤輸出抽出來作為一個Err類。
- 程序開始了,字符串輸入進來,誰來處理呢?為了讓Floor類有事幹(哭),我把parseStr分別放在了Floor類以及和它對應的Elevator類裏面,也就是說樓層和電梯都能夠判斷自己接收的請求是不是合格的,同時也能夠根據 合格與否 的判斷結果,來構造FR或ER類型的請求並返回這個請求。
- 【那麽問題1來了】
判斷是否為”合法請求“這件事,所需要的條件不僅僅是內部信息(即輸入的一行字符串),還需要外部信息(即:1. 這條請求是否是第一條請求,如果是需要滿足請求時間為零 2. 這條請求的時間輸入順序是否正確,因為我們要求這堆請求的請求時間應該是非遞減的)。
當一個面試者知道自己能力和顏值已經合格的時候,面試官還會把這個面試者和其它面試者進行比較,”比較“這件事就是外部條件吧,那麽我們可以有兩種方式來判斷這個面試者是否內外兼修地合格:
A. 面試官勤奮些,把 自我判定內部條件為合格 的面試者拿出來,做做比較。
B. 面試者的工作全面些,也就是把 其他面試者的信息等 也交給 這位面試者,讓面試者自己去判斷 自己是否同時滿足內、外部條件。
脫離這次作業,從更加抽象的角度來講,我覺得這兩種方法各有利弊。 A似乎更貼合現實,使得上層面試官看起來更加統籌規劃、更有權力的樣子。 B似乎把 “判定合格”這件事包裝得更好,不用把“內、外條件判定”這件事 進行分離(可是這樣的話,總覺得面試官是不是沒有存在的意義了...)
我無法做出判斷,不過根據本次作業的實際考慮,我選擇了A。
- 程序繼續,把合格的請求挑出來放到隊列裏面去,然後應該做什麽呢? 讓調度器去 刪除同質請求 得到真正的請求隊列、然後讓電梯運動。
- 【那麽問題2來了】
調度算法如何實現?根據我後來的了解,大體上有這麽2類思路:
A. “先知”式的,只面向請求隊列,對請求隊列進行一定的掃描,得出真正要執行的請求隊列 以及 其中每個請求 的開始執行時間和執行結束時間,然後跟傻瓜電梯說,你就按著我這個計劃來動就行。(為什麽叫先知?先看看生活現實:隨著時間的推進,一方面電梯在運動,同時,請求也在不斷地到來。而“先知”式調度, 是 坐了時間穿梭機去未來,把所有請求都拿來分析,然後穿梭回過去,跟電梯說你應該怎麽怎麽動。總的來說,就是交互性不強。)
B. “模擬”式的,從現實生活得到啟發,模擬時間推進 或 模擬電梯爬樓.
筆者寫第2次作業時,只想到了A,然後就動手了,效果還不錯。而且我很懶,覺得電梯既然那麽傻,幹脆不給他運動的方法了,直接讓調度器跟他說:你就把我傳給你的參數按照某個格式打印出來就行,都不用動的...
- 【問題3和問題1有點像】
刪除同質請求這件事,我放給了Request類及其子類,但是又遇到內外部條件的問題。
如果 r2 是 r1 的同質請求,那麽首先需要滿足這個時間條件: r1的請求時間 <= r2的請求時間 <= r1的執行結束時間。一開始我把這個時間條件當成外部條件,由Scheduler判斷 r2 r1是否滿足時間條件,再由 r1 判斷 r2 是否滿足 請求類型、目標樓層、請求方向 等內部條件,這樣就把 “判斷同質” 這事兒拆分成了兩部分,當時就是感覺心裏有點小疙瘩,忍一忍就過去了...
後來我做第三次作業的時候,受到一些同學的啟發,覺得完全可以把時間條件做成內部條件,也就是請求類增加一些屬性,這樣就可以實現功能的包裝了。
反過來去思考問題1,如果要實現這樣的功能包裝,或許要把請求隊列傳進來給Request,這樣Request才能知道自己在隊列中的位置 以及 隊列中其它請求的情況,也才能判斷自己是不是一個真正合格的請求,但是顯然我們不應該讓 碗裏的豆豆 直接知道 這個碗的信息以及碗裏裝的其它豆豆的信息 。而且如果真的實現了這樣的功能包裝,RequestQueue就真的成為 傻瓜式純容器 了(哭)。
【PS】聰明的你或許已經發現,我的設計 可擴展性 有點弱,到第三次作業就不得不大換血了...
Bug分析
- 公測:通過
- 互測:我方安全,對方沒有交readme被我報了2個bug
- 課下自我調試:沒有什麽可說的bug
- 今晚是我的平安夜。
作業3——“耍點小聰明”的電梯
基於度量和類圖分析設計
先看Metrics插件做出的復雜度分析:
一共紅了兩處,分別是 McCabe圈復雜度 和 嵌套塊深度,摘錄出來,發現是 A Little Smart Scheduler 出了問題:
作業1的分析中,我對McCabe圈復雜度進行了一定的說明,回顧一下:
它認為程序的復雜性很大程度上取決於程序圖的復雜性。
單一的順序結構最為簡單,循環和選擇所構成的環路越多,程序就越復雜。
那麽這個 A 圈復雜度 和 B 嵌套塊深度 的區別,我覺得是,前者兼顧了廣度和深度兩方面的復雜檢測,後者專註於 深度的復雜檢測。有點像B能推出A,A不能推出B的關系。
那為什麽這兩個度量值會報警呢——耍點小聰明的ALS_Scheduler類裏面,由於具有 掃描隊列來取出主請求、掃描隊列來得到能夠被捎帶的請求、掃描隊列來刪除被捎帶的請求的同質請求、掃描隊列來刪除主請求的同質請求 這4個掃描遍歷,以及 配套的許多判斷條件,所以這兩個度量值 直接爆表。(盡管我在課下de完bug之後,嘗試著把其中一些塊抽取出來包裝成方法)
類圖如下:(筆者將類的位置進行了調整,方便和作業2的類圖進行很好的對比)
- 【思想大換血】
一開始,我打算沿襲第二次作業的“先知”式調度,但是卻發現,在遍歷請求隊列的時候,主請求的執行結束時間是動態變化的(如果有請求會被它捎帶的話),{ 每次一發現可以被捎帶的請求R,就必須刪除掉R的同質請求,更新 主請求的執行結束時間,然後再次從頭遍歷請求隊列 },然後重復執行上述{ }的內容,直至沒有可以被捎帶的請求了。
這麽一聽,好像不過是稍微復雜了些而已。但是有個問題,當電梯處於STILL狀態時,被捎帶的條件就變得不大一樣了,所以必須記錄電梯在 什麽時候 在 哪個樓層 為了捎帶而停下來開關門,然後我就懵了,這個信息要如何記錄,怎樣記錄才能方便我查詢。突然失去信心。
於是我便和學霸交流了下,發現學霸模擬電梯爬樓,意識到其中的幾個大優點:
- 和模擬時間相比,不會出現 “由於輸入的請求時間過大 而導致程序運行時間變大” 的現象。(這個現象其實可以通過某種辦法消除掉)
- 沒有 “先知”式調度 的 “一找到被捎帶請求就要再次從頭遍歷” 的問題,以及由此帶來的輸出順序問題。
- 在處理STILL狀態時的捎帶判斷時,十分方便,不需要什麽數據結構來記錄。
- 模擬電梯爬樓,每次執行完一條請求,就讓電梯運動並打印運動狀態,十分方便,不用再拿數據結構去存這條請求的執行結束時間等,而且方便調整輸出順序。
- 【類設計變更】聽了我的設計思路變化,對比於作業2的類設計圖,聰明的你應該已經知道我的類設計變化了:
- 電梯沒有那麽傻了,給它增加了 幾個運動的方法 以及 一個時間屬性。
- 耍小聰明調度器 繼承了 直男癌調度器,但是並沒有用到 start[] end[] 數組,這兩個數組在第二次作業中記錄的是 每個真正有效請求 的 開始執行時間 和 執行結束時間;現在,由於電梯裏面記錄了時間,而且程序是在每次處理完一條請求後,就打印輸出,所以這兩個數組就沒有存在的意義了。
- 其它適應性的小變化。
Bug分析
- 公測:通過
- 互測:我方安全,對方公測過了但被我另找出了3個bug
- 課下自測:輸出順序有問題,這也是大家最容易忽略的一個問題吧。
- 今晚還是我的平安夜~
其它總結
發現bug的策略
- 解剖指導書。我遍歷了幾遍指導書,而且自己用筆把一條條規定都簡單列了出來,用紅筆標記不同條列之間的關系以及易忽略的點。然後進行一些深入思考,比如指導書說到電梯可能的幾個狀態的時候,我就畫了個狀態機出來。深入了解指導書是前提。
- 給自己程序打過的補丁,也可能是別人容易漏掉的。
- 不同的設計方式會有不同的易錯點,如果你抽到的代碼的設計方式和你不同,可以找找其他使用這個設計方法的同學,問問他們課下de出來了什麽bug。
- 討論區和微信群裏大家的討論也都一條條列出來了,並和3合並。
- 與你親愛的同學交換測試樣例。
- 把問題抽象到一定層次,以更加宏觀的視角去看待,就更容易發現問題所在。比如我第三次作業的時候,就畫了個時間軸,以最復雜的捎帶情況為基準來分析,然後遍歷所有可能出現的情況。同時,這個時間軸也很方便我構造測試樣例。(在這裏表示羨慕大佬 只需要動動腦子 就可以模擬出電梯運動情況,反正對我來說,每看到一個測試樣例,我基本都要畫個時間軸,不過越畫越快了嘿嘿嘿)
還可以改進的地方
- 加入枚舉的使用。
- 通過讓方法 拋出異常 來更好地包裝方法。比如,如果傳入的A滿足條件,請返回關於A的某個具體的內容,如果傳入的A不滿足條件,就拋出異常。
總之,我現在的設計狀態是,必須把設計的大體思路、以及某些實現細節,都想清楚了,偽代碼也寫得比較細致了,我才會開始碼代碼,而且想清楚之後 寫代碼賊快,也便於debug。而且,在設計和思考的時候,腦子處於及其活躍的狀態,這個時候容易想到各種你之後可能想不到的細節,盡管這個過程比較痛苦。另外,我會請給我指點迷津的大佬次飯的,再次感謝~
預祝大家 習得OO,策馬歸來~
OO第1~3次作業總結