有關係統重構那些事兒程序員
所謂「外事不決問谷歌,內事不決問百度,房事不決問天涯」,重構這個東西,通常還算是內事,因此我先百度了一把,百度百科上面是這麼解釋重構的:重 構(Refactoring)就是經過調整程序代碼改善軟件的質量、性能,使其程序的設計模式和架構更趨合理,提升軟件的擴展性和維護性。算法
聽起來比較玄乎,很是專業化。說白了,就一句話:在不改變系統功能的前提下,調整系統內部結構,優化系統性能,讓這個系統可以更好的知足客戶需求,同時,但願重構完成以後,這個系統可以多蹦躂幾年。編程
重構是個什麼事兒,前面已經講得很清楚了。沒有讓系統變得更漂亮,也沒有讓用戶可以用到的功能有增長,從外在表現層面上看,這就是個戳白子玩意兒啊。有那個時間有那個精力,你很差好作我要的需求,你玩重構?是在拿着我給你的錢不當錢吧?設計模式
有嗎?天地良心!!!程序員們的腦子裏面0和1比較多,還沒太多那種浪費老闆的錢的齷齪心理。我們作這個事情,實際上是爲了省錢!架構
從軟件的整個生命週期上面分析,軟件太好養了啊!你買個汽車還得隔段時間去作個保養,看看哪一個地方要加點機油,剎車片是否是磨損的太厲害了 之類的吧?軟件就沒有那麼多的毛病了,一不會發生物理磨損,二不會由於用的次數多了要換輪胎啥的。可是,爲何軟件不是研發出來了以後就可以一直用下去任何問題不出呢?框架
道理很簡單,需求變動、設計不當是兩個最大的罪魁禍首。函數
先說說需求變動的問題:一款軟件老是爲了解決某個特定的需求而產生的。套用一句很俗的話:時光飛逝,日月如梭。時代是在不斷的發展的,客戶的業務場景、需求目標人羣也是不 斷在發生變化的。有些需求相對來講穩定一些,好比說BOSS,套個三戶模型就可以吃上不少年飯了;可是有的需求就變化的比較劇烈了,甚至消失,或者轉化成 別的需求了,好比說移動互聯網視爲經典的「永遠是BETA版」,各類試錯讓猿類們有各類砸鍵盤摔鼠標的想法。然而你可以不跟着變嗎?固然,考慮到時間、成 本、運營策略等各種因素,不是全部的需求變化都要在軟件系統當中體現。可是你一直覺得你的永遠是業界最牛屎的,目空一切的話,你會發現摩爾定律會很快在你的身上發揮做用。SO,軟件系統必須根據業務場景、運營策略、用戶反饋等各方面的信息進行不斷的需求變動來知足用戶需求,須要進行各類修修補補以保持本身的生命力。佈局
好了,問題就出來了:最開始在設計系統的時候,架構這塊無論哪一個公司都是會投入大量的精力,內部資深的架構師、工程師一塊兒討論,什麼高內聚、低耦 合,知足將來10-30年的架構可擴展需求都考慮到了。按說這樣應該可以避免掉不少問題吧?實際狀況還真不是那樣。從我入行這些年來看,可以抗過五年不作 大修的系統,架構已經算是很是牛屎的了。爲何會出現這樣的狀況?第一個,你們看待事情的眼光都是有侷限性的,即使是資深的專業人士,也會存在過分設計、設計不足、耦合不合理、可擴展空間留存不足等問題;第二個,隨着時間的推移、業務場景的變動,需求是會不斷進行調整的。那好,原來已經作好的功能須要修改,原來沒有的功能須要新增,原來可能還存在一些藏起來的問題須要修復。加上可能存在的「不近人情的甲方」之類的威逼進度,在實現過程當中不可避免的出現了一些違反最初 的設計架構的事情。第三個,人員的變動問題,可能研發這個系統的骨幹人員離職了,可是交接不完整或者是交接內容過多致使被交接人一會兒沒法接受那麼多知 識,或者是新來的人員對架構理解不夠深刻,也一樣會致使違反架構設計思想的事情發生;第四個,固然也是最坑爹的:沒有文檔沒有註釋。有個哥們笑言程序員最 討厭的四件事:寫註釋、寫文檔、別人不寫註釋、別人不寫文檔。文檔和註釋其實就是個知識的傳承,沒有這兩個東西,違反架構設計思想的事情發生根本不足爲奇。原本一扇牆好好的,今天打個洞,明天鑽個孔,一段時間以後,就只能用一個成語來形容這個系統了:千瘡百孔。BUG越修補發現越多,系統變得愈來愈難以維護,新的需求愈來愈難以實現,人員流失也不斷增長,對新需求的支持能力愈來愈差,甚至制約了業務的發展。而後你們把算盤子拿出來噼裏啪啦一扒拉,新需求的開發成本還超過開發新系統的成本了,這個時候就是這個軟件系統GAME OVER的時候了。性能
再說說設計的責任。設計上面的問題無非兩種,一種叫作過分設計,一種叫作設計不足。學習
所謂的過分設計,就是代碼的靈活性和複雜性超過所需,或者是設計了一些用戶根本就用不到的功能。一個緣由是溝通不順暢,因爲在實際的項目開發當中,是每一個人負責一塊東西,看似分工明確,可是會使每一個人都在本身的小天地工做而不關注別人是否已經完成了本身所需,最終致使大量的重複代碼。另外個緣由是業務理解力不足,或者是需求溝通存在問題,將不須要建父子表的作了父子表,將不須要後臺管理維護的東西作成可維護的。用一個詞來形容那就是:費力不討好。
而設計不足產生的緣由就海了去了,大部分系統出現問題都是這種問題。總結了一下,主要是如下幾個緣由:
1) 時間不足,沒時間或者抽不出時間,或者時間不容許
2) 知識不足,不知道要怎麼作才符合好的軟件設計理念
3) 進度壓力,程序員被要求在既有系統當中快速的添加新功能
4) 戰線過長,程序員被迫同時進行多個項目
長期的設計不足,會使軟件開發的節奏變成「快,慢,更慢,不知道怎麼下手」,一個可能的演變過程以下:
系統的1.0版本很快就交付了,代碼質量談不上好,可是還好,咱們的架構仍是能夠的,這些問題後面能夠慢慢修改和完善;
系統的2.0版本也交付了,在作新需求的時候發現原來有些東西作得真不怎麼樣,讓此次的版本多費了不少功夫;
系統的3.0版本又要交付了,兄弟們,形勢比較艱苦,你們咬咬牙,原來的那些功能暫時先無論了,重點保障此次版本的內容,保障關鍵功能點,千萬千萬不能出問題。
又來了一堆新需求!!!全部小組長過來開會,評估下要怎麼玩兒。。。
前面也說了,系統重構不是浪費投資人的錢的一個事兒,那在沒有引入新功能、知足新需求的狀況下,重構可以帶來些什麼好處?
第一個:持續修正和改進軟件設計。
全部優秀的設計都是不斷修改而成的,修改意味着從新審視。
應該說,重構和軟件設計是一個互相促進的過程。在重構過程當中,你會逐漸發現,原來看起來很優秀的設計,其實有可能只是一個合理的解決方案。隨着對業 務的熟悉加強,對整個系統的把控加強,設計將更加有針對性,更加合理。若是沒有重構的話,程序設計會逐漸逐漸變成一匹脫繮的野馬,沒法掌控,或者變成一團 看似有序實際無序的亂麻,不知後續如何下手。重構其實就是在整理代碼,讓各類帶着發散傾向、偏離航向傾向的代碼,老實起來,本分起來。
第二個:加強代碼的可讀性。
任何傻瓜都會編寫計算機能理解的代碼,好的程序員可以編寫人可以理解的代碼。
一種狀況是垃圾代碼過多:曾經有個研發小組長跟我鼓吹,他寫的一個類達到了3000行之巨,做爲新人的我佩服的是五體投地。後面一睹廬山真面目,一 個類裏面就一個方法,裏面有大約30個業務處理邏輯是相同的,只是其中某個參數的值不同而已。我非常啼笑皆非:你丫就不嫌敲鍵盤敲得爪子疼嗎?結局就是 我花了半個小時,優化成3個方法,加上簡單的註釋,也不過100來行的代碼。另一種狀況是代碼晦澀難懂:有些人作事很快,分配給他的任務噼裏啪啦很快就 可以搞定,可是代碼很是難以閱讀。參數命名從a1到a100,方法命名從f1到f10,不是加密勝似加密。像這樣的代碼,讓閱讀他代碼的人就一個感受: 暈。
在一個軟件的生命週期當中,維護代碼的猿類應該以批來計算。可是猶如以前說的註釋和文檔的狀況同樣,猿類們在編寫代碼的時候每每是寬以律己嚴以律 人。編碼的工做不只僅是寫代碼那麼簡單,爲了讓代碼讓別人更容易理解,咱們須要在進行軟件編碼的過程當中作不少額外的、看似跟編碼無關的一些事情。好比說簡 明扼要的註釋,複雜、獨特狀況下的備註說明,清晰的排版佈局,一看就知道含義的命名。在重構過程當中,這些都是須要重點關注的。若是發現代碼中的髒亂差問 題,必需要下狠刀子,我砍我砍我砍砍砍!
第三個:抓出代碼當中的臭蟲
溫故而知新。
猿類們應該對這樣的狀況深有感觸:本身寫的代碼,隔個三五個月,若是不去看註釋,會對程序邏輯出現不甚理解的情景。在重構過程當中,會逼迫你理解原先所寫的代碼,思考原來設計的合理性,發現其中的問題和隱患,構建出更優秀的設計和代碼。
第四個:提高編程的趣味性、成就感和效率
軟件80%的核心功能,經過20%的代碼實現。
程序猿是一種很奇怪的動物,他們願意坐在電腦面前用幾十個小時來解決一個問題來享受那麼三五分鐘的成就感。當你發現一個問題異常複雜的時候,每每不是問題自己形成的,而是你用錯了方法,走錯了路。拙劣的設計每每致使臃腫的編碼和讓人轉的迷迷糊糊的業務邏輯。改善設計、改進編碼風格,均可以有效的提高開發速度。好的設計和代碼質量是成功的一半,更是成功的關鍵因素。在一個有缺陷的設計和混亂代碼的基礎上的開發,業務表面上進度較快,可是本質上是延後對設計缺陷的發現和錯誤的修改,也就是延後了開發風險,最終要在開發的後期付出更多的時間和代價。而研發進程中的重構,雖然在當前會減緩速度,可是帶來的後發優點倒是不可低估的。要知道,項目的維護成本是要遠高於開發成本的。
應該說,程序猿這個集體是最喜歡重複發明輪子和最討厭重複勞動的一羣人。
寫代碼這個事情,比較忌諱重複勞動。寫一段代碼,第一次的話,直接寫就好了;第二次,可能皺下眉頭,但仍是能夠繼續寫,或者翻一下之前的代碼COPY過來;第三次還碰到,那說明重構的時機已經成熟了。若是是同一個類當中有相同的代碼塊,請將它提煉成一個私有方法;若是是不一樣類當中有相同的代碼,請將它提煉成一個公共類中的公共方法。
代碼寫出來了以後,除非出了問題,不然是不多會有人會回頭看的。因此,發現代碼看不順眼的狀況通常出如今兩個場景:新增功能、修改BUG。
修改BUG的時候進行代碼重構是比較好理解的,在理順原來的代碼邏輯以後,發現哪一個地方有漏洞而引起了問題,順帶着進行一把重構。而爲何新增功能的時候也會引起重構呢?
通常來講,新增功能的重構只會發生在經驗豐富的程序員身上。所謂「年輕人只管完成功能,老頭子們才考慮性能和效率」的意思就是在這裏。老程序員們對業務理解更深刻,對代碼邏輯處理更爲透徹,因此在新增功能的時候,也可以順帶着就把原來的東西進行一番調整。
提起這四個字,我感受這個是我一輩子的痛。
代碼審查作的最好的應該是對日外包的公司,幾乎全部出去的代碼都會進行審查。尤爲是涉及到關鍵業務邏輯的,甚至是開項目組審查會進行代碼審查。可是國內公司作這個的,太少太少,有的公司是有這樣的規定,可是流於文字。
其實代碼審查應該是整個開發環節當中至關重要的一個環節,這個環節的重要性程度應該說僅次於需求和設計。固然,代碼走查是一個對團隊成員要求較高的事情,一個是人力要求,團隊當中至少要有兩我的對於同一個模塊比較熟悉,不然的話就發現不了存在的隱患和邏輯錯誤;第二個是時間要求,要可以作到天天更新的代碼天天可以審查。
代碼審查主要是要考慮幾個事情:第一,創建統一編碼規範,統一編碼風格。軟件研發到了目前這個年代,早就脫離了做坊式、土匪軍的作法,玩的都是團隊做戰、集羣攻擊了。一個軟件公司,若是連統一編碼規範都沒有,確定也談不到統一編碼風格了。第二,簡化代碼邏輯,便於後續員工接手。在代碼審查過程當中,對於一些複雜邏輯、超長方法、巨大類都可以挑出來,並解決掉,讓後續新員工看到的是風格統1、邏輯簡單的代碼。第三,發現問題,查漏補缺。代碼當中的邏輯錯誤、隱患等在審查的時候就發現出來,以避免後面在用戶使用過程當中報個空指針,拋個錯誤堆棧什麼的。
在進行代碼重構的第一步永遠都是相同的:爲被重構的代碼編寫一組可靠的自動化測試代碼,覆蓋原有代碼可能出現的任意場景。人在進行測試的時候,可能因爲各類各樣的緣由,會犯一些錯誤,而機器執行的代碼,是避免犯錯的好方法。
應該說,OCP(開發-封閉原則)是全部編碼人員都須要遵循的一個準則,只有遵循了這個準則,纔有可能寫出比較優秀的代碼。
所謂OCP實際上是由兩組詞語組成的,一個叫作Open For Extension,意思就是咱們開發的代碼,對於功能的擴展,是比較開放的,這也就意味着一旦咱們的系統發生需求變動的時候,咱們能夠快速的對軟件的功能進行擴展,使其知足新的需求;另一個叫作Close For Modification,意思就是對於軟件代碼的修改應該是封閉的,也就是說,咱們的代碼在修改的時候,是不能影響原有功能的使用的。
我的見解,後面這句更加劇要,尤爲是在進行系統重構的時候。
在進行重構的時候,永遠都不建議把全部代碼都調整完成以後,再進行測試。小步快跑的研發方式其實並非敏捷開發的專利,而是適用於各種軟件開發應用中的一個基礎準則。小步快跑的設計思想體現了簡單、快速反饋的特色,也更加符合重構應用的場景。咱們在優化的時候,就是對系統當中的某個細胞當中存在的病毒進行肢解,換句話說,今天只管今天的精彩,至於明天有什麼,咱們花個三五分鐘想一下就好了,不是今天的重點工做內容。
重複代碼包括功能性重複和代碼徹底重複。代碼徹底重複通常都是程序員們偷懶使用複製粘貼引起的。功能性重複代碼則有較多的緣由,解決他們的訣竅就在於「提取公因式」(來源於《重構與模式》一書)。
1) 類層次中不一樣子類存在明顯或不明顯的重複→造成Template Method
2) 子類中的方法除了對象建立以外其餘實現方法都相似→用Factory Method引入多態建立→使用Template Method去除更多重複
3) 構造函數包含重複代碼→鏈構造函數
4) 單獨的代碼處理一個對象或者一組對象→Composite替換
5) 類層次多個子類都實現了本身的Composite,並且實現可能徹底相同→提取Composite
6) 對象處理方法的區別僅在於接口不一樣→經過Adapter統一接口
7) 條件邏輯處理空對象,並且相同的空邏輯在整個系統中都是重複的→引入Null Object
巨大類每每是類抽象不合理的結果,類抽象不合理就下降了代碼的複用率。過長的方法因爲包含的業務邏輯過於複雜,錯誤概率將直線上升,代碼可讀性直線降低,類的健壯性很容易被打破。所以,在看到巨大類或者巨大方法的時候,基本上能夠判定這個是存在問題的,須要進行優化處理。
從我的工做經驗上面提供一些數字供參考:類的長度不超過3000行,方法的長度不超過120行(差很少就是一個屏幕高,建議值是10-20行)。
可能有的同窗會擔憂性能問題,實際上,這個問題是不存在的。首先,沒有三分三,哪敢上梁山?優秀的設計人員都不會對代碼進行不成熟的優化;第二,業內已經有人作了性能剖析,發現將許多小方法串起來的性能開銷微乎其微,在毫秒級,因此不會成爲性能瓶頸;第三,性能上面存在問題,通常都須要經過重構或者改變算法來提高性能,放棄小方法的原則是不大可取的。
邏輯控制複雜是引起代碼問題的最重要的緣由,也是後續接手代碼維護人員最頭痛的點。
咱們在進行維護型系統的開發的時候,總會碰到一個事情:代碼越寫越多。好像不這麼寫,不弄這麼多的控制邏輯,這個功能就沒辦法實現了。可是其實解決問題的方法有不少,作IT的人也都不是傻子,咱們徹底有能力找到更好、更優秀的方案來作處理。
舉個造橋的例子,頤和園的17孔拱橋是很是漂亮的,可是呢,那個橋只能在園子裏面用,只能做爲景觀存在,來艘稍微大點子的船,這個橋就把他攔住了。可是趙州橋呢?整個橋很簡單,就一個孔,不影響通航,洪水來了也基本對他沒有太大的影響。因此,屹立千年而不倒。這個就是個簡單惟美的設計的典型,也是咱們學習的對象。
在接口的開放和使用上面,參數、代碼、數據的暴露實際上是比較講究的。讓客戶、第三方開發者看到不過重要或者有間接重要性的代碼,會增長系統的風險,同時,會增長接口的複雜度。
一個不恰當暴露的例子:爲了研發簡單,某個項目團隊將所有參數都放到了一個公開的類當中,這樣的話,想何時用就何時用,想要什麼參數就有什麼參數,想在哪裏用就在哪裏用。還好這個是作內部管理系統的項目,沒有向外部暴露任何接口,知道代碼的也就那麼幾我的,公司裏面也沒幾我的懂程序設計的,因此沒有引起災難。這樣作是嚴重違背了面向對象的原則的,將代碼框架變成了高度中央集權制度。而事實上,中央政府是不能替代地方政府作好一切的,地方政府也不可能爲了屁大點事情就去找中央政府請示一下。既然是面向對象的編程,那就要遵循屬地原則,本身可以解決的事情,不要鬧那麼大動靜。
前面也說過有關注釋和文檔的事情,註釋的出現主要是要解決兩個問題,第一個是說明這段代碼是幹什麼的,第二個是在複雜邏輯、特定邏輯處理、複雜算法時,說明爲何要這麼作。
總的來講,中國的程序員們廣泛存在的問題就是重視功能實現,輕視代碼註釋。這個很好理解,畢竟前者更能帶來成就感,然後者更加表現爲工做量、體力活。有些人甚至鼓吹一點:好的代碼是不須要註釋的。確實是這樣,可是可以寫出徹底不須要註釋的優秀代碼的人畢竟仍是極品當中的奇葩,通常人仍是作不到的。而人的記憶曲線降低的坡度是陡得嚇人的,過個兩三個月,本身寫的代碼要去補註釋,估計也很難想起當時爲何要這麼玩了。因此,必要的註釋仍是有必要在編碼的時候加上的。
同時還要注意註釋冗餘的問題,這個出現的狀況比較少,可是仍是存在。之前咱們有個規範,註釋代碼(不含方法說明和類說明)的部分,不能超過整個代碼的20%。
關於註釋,還必須注意註釋的正確性。什麼也比不上放置良好的註釋來的有用;什麼也比不上亂七八糟的註釋更有本事搞亂一個模塊;什麼也不會比陳舊、提供錯誤信息的註釋更有破壞性。