上個月個人有機會重構正在開發的一個系統代碼,在完成后團隊使用中還有效果的情況下,覺得有必要將總結一下。
簡介
這個系統功能是一個工業設計軟件,通過一些參數的配置,自動生成客戶所需要的模型。采用C#開發,winform系統,該系統現為單機版,不存在服務端開發。其代碼實現主要是模型生成,數據庫參數讀取都相對簡單,現階段代碼量不是很大,配置界面大概有十個左右,業務操作代碼估計有一萬五左右吧。開發模式主要是敏捷開發,前期對系統進行簡單設計,具體詳細設計主要是由編碼人員實現。
原因
- 由於該系統正在開發中,不易系統新增功能,甚至有些功能無法新增;
- 核心功能修改困難,只有開發自己可以修改;
- 該系統是公司計划的一個產品,產品負責人希望保證其質量;
- 團隊技能提升方向。
處於以上原因,團隊成員與團隊負責人,產品負責人,以及請其他技術骨干人員,大家一起討論覺得現階段代碼有必要重構。初步的計划是,不影響整體項目計划,我們原開發人員繼續完成可以完成的任務,重構由團隊負責人,與一C#技術資深人員(其他部門,十年左右開發)完成 。這里就有必要說說我們的這位團隊負責人了,反正現在也不是我的主管了!):,他以前是做java的,自身對技術也比較感興趣,現在剛剛做團隊主管,經常把一些PMP中的原話搬出來,自己不負責開發,不做分析,不做設計,不寫代碼,但是在專案的過程中,經常對開發很感興趣。
好了,還是言歸正傳,話說最后決定有這兩位大神對系統進行重構,一位是相對我們這些全身投入的“豬”來說的“雞”,一位是從其他部門調來的大神,每天支援我們專案半天左右。二位就開始了他們的重構之踐,每天打電話請資深人員來我們辦公室,二位在同一台電腦前,開始了結對重構,一個向另一個講解專案需求,另一個向這個分析應該怎么設計,應該怎么實現。就這么一個知道需求,一個只懂技術的兩個人一周過后,系統基本與原來一樣。為什么會這樣呢?最后還是我們團隊負責人說,他每天要對另個人講解需求,而且還包括部分行業知識,還不如他自己做呢,但他自己又對具體實現和C#不清楚,不明白不敢修改,而且還要不停的詢問當時代碼開發人員,效率效果都不好。
所以最終還是決定讓我來重構,因為我對現階段系統的實現都比較清楚,沒有十分也有七分吧。我對當前的重構內容評估的時間七人天,最后給了我兩周的時間,包括最后將現在開發的任務整合進重構后的系統代碼中。
目標
由於是自己第一次正式的做重構,所以我當時給自己制定的目標是:
- 使該系統新增現功能方便;
- 處理當前系統難以修改的部分;
- 多向其他人員詢問,這樣是否可以易於編碼?
步驟
這里就該主要的總結一下,自己在本次重構過程中究竟做了哪些了。這里有有必要說一下VS的一個快捷鍵 “CTRL +K,R ”,該快捷鍵幫助我找出一個屬性,方法或者類在哪些地方被調用,對后面的重構省了很多功夫。而且有SVN版本管控使我很容易的找到上一個正確的版本,並比較修改了哪些代碼。
步驟一:先找出系統中復雜度最高的幾個類。
我的定義是修改最頻繁,代碼行數最多,其他類引用該類最多。初步划分下來主要有三部分,暫且定義為A、B、C吧。A為界面操作部分,B為A對應之業務操作類,C為另一主要業務部門,在編寫代碼中我主要負責的C中的部分實現。
步驟二:找到了需要重構的部分,接下來就要確定從哪里入手。
針對上面的ABC三部分,我第一感覺是將C排在了最后,因為當時主要是由我個人完成的,我對里面的代碼質量還是有信心的(雖說最后仔細看代碼結構還是不理想),而且每個人都不喜歡改自己的代碼,認為修改即是對之前的否定,我個人心里也算有點這方面的意識吧,雖說一直在調整這種認識;第二覺得應該最先處理的是B部分,因為現有大部分新增功能都與B相關,而且認為B完成后也易於修改A部分。所以最終就從B部分開始啦!
步驟三:確定了具體從哪里入手后,就要徹底仔細分析該部分。
B部分復雜不是由於引用該類的地方多,直接引用該類只有A的主要一個界面,這里為什么說是直接引用呢?這就要提到A部分了,因為在A的主界面中B是作為一個公用屬性,而且在主界面上new一個子界面時穿的參數是this——即主界面全部信息。好了還是說B吧,B復雜主要是由於修改頻繁和代碼行數較多,該類當時行數記得好像大概有近2000行吧,而且還在增加,修改頻繁的主要是B里面的一個主要方法Mb,而且該方法基本上只有編寫人員他一個搞的懂內部關系,其他人只能望文觀止。
步驟四:具體做法。
我在重構的時候具體的方法大概有:由於沒有測試代碼,所以較小比較確定的修改只是保證其編譯通過,就可以進行下一項了,較大的修改,就需要簡單手動測試下。
- 去除一些無用的注釋,例如當時開發人員注釋掉的一些方法或段落,現在用不上的;
- 移除除沒有被用到的屬性,方法,類,以及一些空的cs文件。這里的類和cs文件可能是超前設計的產物;
- 只在該類中用到的屬性,方法,降低起訪問級別,改為private;
- 該類中修改最頻繁的那個方法Mb,復制至新的類中,並取合適的類名稱,連同其所有子方法,以及用到的屬性字段。修改調用該方法的代碼,使其使用新的類中的方法;
- 檢查上面方法中的子方法與屬性,原類中是否還有其他方法使用,若無直接刪除,否則先暫時保留。分析有使用共同屬性的方法與需要調整的方法Mb之間是否有聯系;
- 去除掉與方法Mb相關的操作后,B類此時剩余代碼大概就只有1000行左右。也就是每一個復雜的類里面總能找到一個與之對應的方法同樣復雜;
- 接下來就是對方法Mb再一步進行重構,方法復雜主要是由於方法里面判斷比較多,解決的方法就是用多態替換判斷,將不同類型處理方式放在不同的類中,然后將這個方法改為一個工廠方法;
- 做完這些測試通過后,我覺得B部分就可以先放一放了,接下來就是A部分,因為AB關系較密切;
A的處理方式:
- 開與B中的基本相似,除去無用代碼與注釋
- 然后修改界面之間的傳值問題,將this整個界面傳遞改為需要什么傳什么
- 若子界面中的數據影響主界面,則用委托事件替代之前的直接調用主界面中的方法
- 建立每個子界面的業務操作類,從B中移除主要是數據讀取
- 將每個界面上的數據新建一個對象,運用單例模式保存數據,減少之間的參數傳遞
- 子界面與子界面之間的關系都通過主界面間接觸發
- 提取每個界面的公有接口,重置,保存,判斷數據正確性,判斷數據是否被修改
C的處理方式
C復雜主要是由於開始的時候只有一種處理方式CA,然后慢慢的又出現另一個處理方式CB。也就是之前只有一個一個基類CA,然后又加了一個基類CB,但是寫CB這個類的人又是完全參照CA寫的,造成二者間有許多重復的操作,而且為了模仿增加了一些不必要的操作。然后出現這種情況了,當時我們討論盡然大部分一樣那就在抽取一層CAB,是CA和CB分別繼承CAB,好么,最后這樣弄完以后就是全部代碼在CAB類中大概有1000行左右,然后CA、CB有兩三百行左右。
我的處理方式就是和其他人先討論這兩種處理方式,最后決定就使用CB這種方式,好,由於CA之前是我寫的,CB是另外一人寫的,但是我比較清楚。我就將CAB重寫了一遍,去掉CA與CB,將類中的屬性分離只另個類中,這個類的對象作為CAB的一個屬性包含在內,方法里面的一些類型判斷全部刪除,留給子類自己處理,最后好像代碼也只有三百多行似的。
現狀
重構的任務當時也在兩周之內完成,達到團隊的預期把。距現在也有一個月時間了,在這一個月里代碼中又新增了許多功能,工作中也發生了許多事。代碼整體上還算整潔,每個類的代碼基本上都還保持在500行左右,但是也有時常有需要改善的地方,我個人有個習慣,就是沒事的時候看看自己別人寫的代碼,覺得需要調整的就調整了,覺得別人哪里寫的不好的,也會找本人談談。我發現我們團隊的主要問題是在完成功能時忽略代碼質量,而且就算覺得部分代碼放在這里或這樣寫不好,但是不知道應該放哪,這時在不知道的情況就只好暫且先這樣,然后就是永遠這樣。個人覺得我們在面向對象中還是應該先做好類的封裝,然后在談繼承多態,就像我之前介紹設計原則——單一職責,封裝是最基礎最實用,對封裝性最直觀的就是方法、類行數,以及類中的屬性方法數量。
反思
其實寫這篇博客的初衷,希望將自己重構的方法,以及對代碼質量的認識分享出來的,但是寫着寫着就跑到記錄上去了,唉又跑題啦!不過還好,這張的題目叫重構之踐,多點實踐,下次重構之見時再寫個人的認識見解吧。