在平常項目開發中,被衆人說爛了的重構究竟是什麼?在我見過的不少開發者的眼中,對重構的理解,就是推翻之前的軟件,從新花時間設計架構一個和界面如出一轍的東西!有這種想法我認爲是很危險的,在效率爲王的當今社會,這樣的人力資源浪費是不被社會和商業所接受的,索性咱們此次認真探討一下到底什麼纔是真正的重構?在正式開始以前,咱們先來看一下大神是如何理解重構的。前端
重構(名詞): 對軟件內部結構的一種調整,目的是在不改變軟件可觀察行爲的前提下,提升其可理解性,下降其修改爲本。程序員
重構(動詞): 使用一系列重構手法,在不改變軟件可觀察行爲的前提下,調整其結構。編程
在過去,不少人使用重構這個詞來指代碼清理。其實,重構的關鍵在於 運用大量微小且保持軟件行爲的步驟,一步步達成大規模的修改。每一個單獨的重構要麼很小,要麼由若干小步驟組合而成。所以,若是有人說他們的代碼在重構過程當中有幾天甚至更長時間不可用,基本上你能夠肯定,他在作的事情不是重構。架構
若是沒有重構,程序的內部設計會隨着時間的增長逐漸腐敗變質。在國內不少科技公司都已速度爲王,公司爲了能早點上線,壓迫縮短開發時間,開發人員爲了早點下班回家少加班,不少開發人員在改bug修改代碼時,常常沒有徹底理解程序的架構設計,就臨時修補程序,因而今天張三改一下明天李四改一下,因而代碼逐漸失去了本身的結構。長此以往,程序員愈來愈難經過閱讀代碼理解原來的設計,因而代碼就越愈發腐敗得更快,最終淪爲一個誰也不敢動的項目。函數
在項目的初期,複雜度和代碼腐化程度還未達到峯值的時候,一開始開發進展會很快,這會給公司領導層一種錯覺,以爲真正的開發速度就應該這樣,應該一直會保持下去。可是當發展到必定階段後,想要添加一個新功能時,須要的時間會比以前的時間要長不少,開發人員須要花更多的時間去思考,去考慮如何把新功能塞進現有的代碼庫中,避免因改一個地方而出現牽一髮而動全身的尷尬境界。整個項目的代碼庫看起來就像是在補丁上再補上補丁,須要像考古同樣才能弄明白整個系統是如何工做的,這些負擔不斷地拖慢新增功能的速度,到最後程序員實在忍無可忍時就會提出「要不,咱們從新作一個新版本放棄如今這個項目吧?」。架構設計
那麼,到底一個項目代碼腐化到什麼地步,咱們應該開始重構呢?在前輩馬丁.福勒大神的《重構》一書中,早已經總結出了實用的重構的24個契機,讓咱們一一來細數對照一下,看看本身在編寫代碼時,是否有犯過這些錯誤。設計
一個好的命名,能讓讀者一眼就清楚代碼的意思,整潔代碼中最重要的一環是從好的命名開始。現實中不少人不肯意給程序更名,以爲不值得花費這個時間,但好的名字能節省將來用在猜謎上的大把時間,因此當項目中出現神祕命名時,即是重構的開始。關於代碼有意義的命名請參閱《代碼整潔之道》第二章 有意義的命名。代理
在優秀的開發者心中,踐行着事不過三的原則,即一段代碼在三處以上的地方用到時,即是開始重構的時候,三不是一個絕對值,準確的說,若是你在一個以上的地方看到相同的代碼結構,便應該當即開始重構代碼。不少新手開發者在寫代碼時,喜歡經過拷貝粘貼的方式進行開發,這會給後期其餘人維護和修改代碼形成更多的沒必要要的麻煩。代碼規範
有大佬曾講過,但凡是一個函數若是超過50行以上的代碼,就應該開始考慮進行重構它。根據我以往的開發經驗,粒度越小的函數,會活的最長最好,他們都遵循着單一職能原則。當函數過長,複雜度就會越高,理解就越難,因此不少開發者喜歡在長長的函數中,加入更多的註釋來解釋程序,這是一個錯誤的作法,過多的註釋內容更容易干擾代碼的閱讀和理解。因此當咱們感受須要用註釋來講明代碼邏輯的時候,咱們就應該把須要註釋的內容單獨抽象出來,寫到一個單獨的函數中去,並命一個直觀的好名字。code
曾經我在編寫Angular1的代碼時,在控制器中常常須要注入不少依賴對象,在回調函數中須要傳遞大量的參數,致使常常本身手抖或者順序不對致使一些BUG。過長的參數列表會常常使人產生迷惑,容易讓人犯錯,咱們能夠把多個參數合併成一個對象,經過傳遞對象的方式減小過長的參數列表,從而讓代碼更簡潔已讀。
在項目中使用全局數據,容易形成數據的污染與衝突。在不少的代碼規範中,對全局數據的使用都是明令禁止的,全局數據從代碼庫任何一個位置均可以修改它,這使其排查BUG的時候異常的困難。在實際開發中能夠對全局數據進行抽象封裝,在使用到數據的地方進行手動導入操做。
對於弱類型開發語言Js,可變數據的問題很容易就發生,在一處更新數據後,卻沒有意識到軟件的另外一處指望着徹底不一樣數據類型的數據,因而就改出了一處BUG,這種狀況發生在很罕見的狀況下,因此要找出故障的緣由也會更加的困難,因此如今Ts開始在前端技術圈愈發流行起來。
在軟件開發之初,咱們但願軟件可以更容易被修改,一旦須要修改,咱們就能當即跳轉到系統的某一點,只在該位置作修改便可完成。若是某個模塊常常由於不一樣的緣由在不一樣的方向上發生變化,發散式變化就出現了。
分散的修改相似於發散式變化,正好又相反。若是每遇到某種變化,你都必須在許多不一樣的類內作出許多小修改,那麼所面臨的問題就是分散的修改,在這種狀況下,你的代碼散步在多個地方,你不但很難找到它們,也很容易錯過某個重要的修改。這時候你須要進行封裝和抽象,或者搬移某些代碼到同一個模塊中,以達到集中修改的地步。
接受過面向對象編程的開發者在編寫代碼時,或多或少都會想到代碼要高內聚、低耦合、開放封閉等。但有時你會發現,一個函數跟另外一個模塊中的函數或者數據交流格外頻繁,遠勝於在本身所處模塊內部的訪問,這就是代碼的依戀情結。咱們能夠經過搬移這部分代碼到所訪問模塊的內部,或者咱們再抽象一層,將函數分解爲多個較小的函數,分別放置在不一樣地點。
曾經在維護他人代碼時,常常看到在不少地方相同的字符串、相同的參數散落在代碼的各個角落裏,使其讓人很難維護這樣的代碼。咱們只須要將這些散落在處處的數據,進行抽象,將它們提煉到一個獨立的對象中,再經過導入對象的方式訪問這些數據便可,這樣就能夠幫咱們下降不少重複的內容,使其修改代碼時也不會再出現分散的修改。
在一些代碼腐化的項目中,基本類型偏執是很常見的問題,好比價格能夠用浮點數類型進行表示,座標範圍能夠封裝爲對象進行展現,實際倒是用字符串進行存儲和展現。字符串彷佛成了萬能的數據類型,徹底能夠將這些類型偏執的代碼替換爲正確的類型進行展現。
在新手編寫的代碼中,當遇到條件判斷或者分支判斷時,大量重複嵌套的if和重複的switch是常見的處理問題的方法。這些重複的switch的問題在於,當你想增長一個選擇分支時,就必須找到全部的switch,一一進行修改。對於重複的分支判斷,咱們能夠利用多態
來取代條件表達式,這樣會讓代碼更優雅。
在大量的業務場景中,循環語句都是沒必要要的,不少新人喜歡寫大量的for循環語句,來實現需求。實際上咱們能夠利用管道
來取代循環,好比使用filter、map、forEach等方法,幫助咱們更快地看清被處理的元素以及處理他們的動做。
在項目開發中,爲了支持變化促進代碼複用,每每咱們會進行代碼的抽象與封裝,但每每這時候很難掌握抽象的度,致使過渡封裝。可能一個方法名字和實現代碼看起來如出一轍,可能一個抽象出來的類根本就是一個簡單的函數等,這就形成了代碼的冗餘。咱們能夠經過合併的方式,將過分抽象的代碼給釋放出來,從而減小代碼的冗餘。
一樣,過分設計的代碼中,一定會出現各類各樣的特殊狀況,用來處理一些特別罕見或非必要的一些事情,實際上這些特殊的狀況只會讓系統更難維護和理解,因此在設計之初就考慮清楚若是確實能用上那麼這麼作是值得的,若是用不到,就只會讓代碼更加容易出現壞味道。
在多人編寫的項目中,也許你有看到過他人,在代碼中定義某個臨時字段,但這個字段只在特定的狀況下才會使用到,若是代碼沒進入分支,這樣的臨時字段就很容易讓人產生誤解。咱們能夠將這些臨時的字段,抽象到一個專門的類當中,而後把這些字段和相關的代碼都搬移到這個類中,再在須要的地方調用該類中的方法便可。
在使用他人編寫的代碼獲取數據時,咱們查看其邏輯時,發現你訪問的對象,是代理的另一個對象,另一個對象又再請求另外一個對象時,這時過長的消息鏈就出現了,若是你着急修復一個BUG,趕上這樣的代碼也許會讓你抓狂。當消息鏈過長時,咱們能夠經過抽象和搬移這些代碼到一個類中,來截斷過長的消息聯來縮短代碼的深度,使其代碼的可讀性大大提升。
面向對象編程的特徵之一就是封裝,封裝每每會伴隨着委託。但也許你看到過某個類的接口有一半的函數都委託給其餘的類進行實現的話,這就是過分運用封裝了。這時候咱們應該移除中間人代碼,直接和真正的實現對象進行交互。
在實際開發中,不少人都貫徹着模塊的高內聚,但內聚後勢必就會增長模塊間大量的交換數據,這會增長模塊間的耦合。若是兩個模塊間一直存在着私下的交換數據,那麼咱們就有必要找出兩個模塊間共同數據,抽象爲二者的數據中介,把這種交換行爲放在明面上。
臃腫的代碼中,最大的功臣莫過於一個超大的類。一個類須要作不少的事情,天然就會出現不少的字段,重複代碼也就接踵而至,代碼開始混亂並最終走向死亡。對於這種大類,咱們須要運用抽象能力,對這些代碼進行提煉,相同的業務提煉到相應的類中,公共的內容提取到超類中。
在迭代過多個版本的代碼中,殊途同歸的類格外的多,大致上看你們彷佛都作着同樣的事情,僅有一些細微的差異,好比一些參數的不一樣等。這些類充斥着大量重複的代碼,咱們能夠經過改變函數的聲明將函數的參數變得一致,再將幾者進行合併,若是出現重複代碼,咱們再將他們抽象爲超類進行補償。
在一些有經驗的程序員中,他們出於方便管理的考慮,會把一些零散的數據,放到一個專門的純數據類中,這樣這個類就擁有一大堆的字段,以及用於訪問這些字段的函數。但這些純數據類,除了用於訪問讀寫這些字段外,就一無可取,咱們應該把處理數據的行爲,從其餘地方搬移到純數據類中。
在維護一些有繼承關係的代碼時,常常發現,一個子類繼承了父類,得到了父類的全部的函數和數據,但子類卻只從父類哪裏取得一個值,父類其餘的函數和值就被子類給拒絕了。這意味着繼承體系設計的錯誤,咱們須要爲這個子類新建一個兄弟類,再把用不到的函數和值推給那個兄弟類,這樣父類就持有全部子類共享的東西了。
優秀的代碼,自帶解釋性。但實際上大部分註釋的存在都是由於代碼很糟糕,因此不得不加一些註釋內容,便於進行補充說明。因此,當你感受須要寫註釋時,就須要先嚐試進行代碼重構,運用提煉函數和改變函數聲明的手法,試着讓全部的註釋都變得不少餘。
以上就是代碼的24種壞味道理論篇,在下一篇文章中,將正式開始進行演練和實踐!