軟件開發丨關於軟件重構的靈魂四問

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在軟件工程學中重構就是在不改變軟件現有功能的基礎上,通過調整程序代碼改善軟件的質量、性能,使其程序的設計模式和架構更趨合理,提高軟件的擴展性和維護性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"摘要"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在本文中,您會了解到如下的內容:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"先添加新功能還是先進行重構?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"重構到底有什麼價值?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"如何評判這些價值?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"重構的時機是什麼?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"如何進行重構?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"1. 先添加新功能還是先進行重構?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"問題:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"官方資料,重構分析1.0版中。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有兩頂帽子,一個是添加新功能,一個是重構"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"添加新功能時,你不應該修改既有代碼,只管添加新功能,重構時你就不能再添加功能,只管改進程序結構。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一次只做一件事情。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這兩個是否有矛盾,以哪個爲準?前面有些可信材料版本不一,有的還要互相打架,是否可以統一一下?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"回覆:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"關於添加新功能和重構是否矛盾的問題,是先添加新功能還是先進行重構?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們要做的是觀察這兩個事情哪個更容易一些,我們要做更容易的那一個。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"就是你"},{"type":"text","marks":[{"type":"strong"}],"text":"不能一下子同時做這兩件事情"},{"type":"text","text":"。因爲同時做兩件事情,會導致你工作的複雜度提升,容易出錯。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一般而言,重構會改變程序的設計結構改動相對來說比較大。但是因爲沒有功能方面的添加,所以對應的測試案例我們不需要進行修改,那對我們來說,只要能夠使得現有的重構修改能夠滿足我們的業務測試案例就可以了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"添加新功能意味着我們要添加對應的測試案例,以保證我們新的功能是可測的。這部分的修改一般會依託現有的程序結構,改動起來相對比較少,並且修改容易鑑別。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"在絕大多數正常情況下,我們一般是先添加功能,提交完成以後,再新的修改需求中對代碼進行重構。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從大的方向上來說是分兩步走的,這兩個任務不能混爲一談。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一次只做一件事情,一次提交只包含一個任務,這是爲了避免在工作中人爲的增加複雜度,這個複雜度包含代碼修改,審查,測試等各個方面。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"避免複雜度的上升,是我們在軟件開發過程中時刻要謹記的一個原則"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"俗話說,一口喫不成胖子,心急吃不了熱豆腐。做事情要一步一個腳印,穩紮穩打,步步爲營。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"2. 重構的價值和評判效果"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"問題:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"哪種類型的代碼重構是高價值的?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1. 在網上跑了這麼多年也沒啥問題,爲什麼要動他?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2. 重構前後功能又沒啥變化,當前收益是啥?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3. 若是提高可維護性,可擴展性的話,怎麼評判效果呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"回覆:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這是關於重構價值和評判結果的問題。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這幾個問題問的都很好。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"我們來看第1個問題,就是\"在網上跑了這麼多年也沒啥問題,爲什麼要動\"的問題?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏的關鍵點就在於到底有沒有問題。是不是說在客戶那邊客戶看不到問題,就算是沒問題。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當然不是的,在我們軟件開發當中,在交付給客戶以後,客戶那邊看到的是黑盒,他不知道我們內部的邏輯存在多少的漏洞。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果我們的內部邏輯存在很多的漏洞。假設偶然某一天,某個客戶發現了一個漏洞,它可以通過這一個漏洞進入到我們的系統內部,這樣進入我們的內部,會發生什麼樣的狀況,我們可以自己想象。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在公司的內部發言中專門提到了UK對我們產品的一個評價,外層是銅牆鐵壁,內層是很脆弱的,客戶或者黑客一旦進入到我們的內部以後,他就可以爲所欲爲了,從這一點上來說,我們一定要對我們現有的代碼進行重構,以避免這樣的問題。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"我們再來看第2個問題。重構前後功能又沒啥變化,當前收益是什麼?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"重構最大的收益是解決如下的問題:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"代碼太多重複問題,單個函數體或者文件或者攻城過大的問題,模塊之間耦合度太高的問題等等。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"以上問題歸根結底就是一個問題,就是複雜度過高的問題。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現在來談一談複雜度的問題,軟件開發中的複雜度當然是越低越好。一般談到複雜度,我們可能想到了各種邏輯上的複雜度,設計上的複雜度,實際上在軟件過程中複雜度涉及到方方面面,我們來看一下,具體有哪些方面我們需要注意複雜度的問題。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"第一是命名規則"},{"type":"text","text":"。先舉個例子,我定一個變量叫word。有的人喜歡把它寫成wd。這個就增加了這個變量定義的複雜度,你從wd很難明白,這個變量是word的意思。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不管是變量的命名還是函數的命名,我們都希望看到名字,我們應該能夠理解這個變量或者函數大體是關聯到什麼樣子的事情。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以謹慎的使用縮寫是避免命名規則複雜度提高的重要前提。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"第二是程序邏輯的複雜度"},{"type":"text","text":"。線性順序執行的複雜度爲1, 出現分支以後要乘以分支的個數。分支可以是條件判斷也可以是循環。所以儘可能的避免分支的出現是降低程序邏輯複雜度的重要手段。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果程序分支不可避免,要儘可能的把程序分支放到最高的邏輯層。這樣做的目的是爲了避免在下層處理的時候出現發散式的分支。發散式的分支會急劇的增加程序的複雜度。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"複雜度越高,程序越難維護,複雜度超過一定程度,人類程序員是無法處理的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"第三是架構設計的複雜度"},{"type":"text","text":"。架構設計涉及到模塊設計和系統設計。要儘可能的把一些公用的模塊或者子系統抽取出來,比如安全相關的,日誌相關的,工具相關的等等,這些公用的功能可能會被所有其他的業務模塊或系統所調用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在調用這些公用功能的時候,越簡單越好,並且調用者不需要關心具體的內部實現,只需要知道如何使用就可以了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這樣做的目的是讓程序員專注到業務代碼的設計上來。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"第四是系統部署的複雜度"},{"type":"text","text":"。系統部署包含幾個不同的階段如開發階段,測試階段和生產階段。不管是哪個階段,部署的步驟越少越不容易出錯。有些系統天然的需要很多指令的配置,如果是這樣的情況,需要編寫一個批處理的文件來簡化外部使用者的部署步驟,把多個步驟變成一步。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"與部署相關聯的還有集成部分。如果能夠實現自動化或者從模板中創建那是非常好的狀態。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"第五是測試的複雜度"},{"type":"text","text":"。測試分白盒測試和黑盒測試。白盒測試的複雜度直接關聯着代碼層級的複雜度,代碼層級的複雜度越高,當然白盒測試的複雜度也就越高。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"白盒測試需要注意的一個重要問題是不要使白盒測試這部分的代碼脫離實際業務代碼的設計。也就是說白盒測試它的依附對象就是我們實際的業務代碼,從架構設計上說是一個附屬層,不要試圖在這裏使用什麼軟件設計藝術或者所謂的編程藝術。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這種代碼的風格就是簡單直接,複雜度線性化。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"黑盒測試的複雜度來自於業務需求分析。要有非常清晰的文檔說明,需要對測試步驟和預期結果寫的非常清楚。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"第六是技術的複雜度"},{"type":"text","text":"。技術的發展趨勢一般是越發展越簡單,功能越強大。那麼在設計和開發的過程中,要避免使用老舊的技術。關於技術框架的選擇,要提前做好調研。前端選什麼框架,要不要選擇某些UI庫,後端選什麼框架,要不要選擇某些程序庫,原則上是爲了簡化我們的學習過程,提高開發效率,增強整個項目的可維護性。需要具體問題具體分析。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"第七是隊伍結構的複雜度"},{"type":"text","text":"。隊伍構成一定要短小精悍,人多不一定好辦事。像亞馬遜提倡的是兩張披薩團隊,意思是說整個團隊兩張pizza就能喫飽。大體估算就是10人左右的一個隊伍。當然這只是一個參考指標。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"整個隊伍的目標一定要明確。所有的人都向着那個目標邁進,分工可以不同,但是目標一定要一致。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"目標+分工是隊伍成功運作的關鍵。具體來說就是把目標分成多個任務,每個任務裏又可以分成小任務,那所有的人都去做對應的任務,自己讓自己忙起來,而不是別人讓你忙起來。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"我們現在來看一下第3個問題,就是如何評判重構效果的問題。在上面的分析中,我們已經瞭解了重構的目標和最大的收益,就是複雜度的降低。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那麼對應的,就是代碼的重複率大大降低了,單個函數體或者代碼文件或者工程過大的問題不存在或者減少了,模塊之間的耦合性降低了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"再進一步說,就是關於代碼的可維護性和可擴展性上,我們需要關注這麼幾點:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"一是代碼的可讀性"},{"type":"text","text":",我們看到現有的代碼就應該可以理解代碼作者的意圖是什麼,這樣我們在修改bug的時候就更容易把握。比如函數,類或者組件的功能要單一化,命名要友好,要刪除一些誤導性的註釋,對於一些沒用的代碼,要毫不客氣的拋棄。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"二是設計模式的可參考性"},{"type":"text","text":"。設計模式的好處就是提供一種可以追尋的代碼擴展軌跡,新的功能可以遵循這種軌跡模板進行添加,從而獲得複雜度線性增長的效果。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"三是白盒測試的完善性"},{"type":"text","text":"。儘管我們有非常強大的測試團隊,對於黑盒測試方面有很多的經驗和心得,但是現在我們有很多項目缺乏白盒測試案例,這使得開發者在進行重構的時候,面臨非常尷尬的境地。沒有充分的白盒測試案例,重構工作會舉步維艱,有一種瞎子摸象的感覺。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現在就說一下白盒測試這一部分。測試的框架應該在項目開始階段或者重構開始前搭起來。等部分代碼成型的時候,逐步的添加必要的測試案例。測試案例的選取可以按照環形複雜度的計算方法來確定,也可以根據集成測試對應的用戶需求來確定。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"與代碼相關的測試,一般有單元測試,集成測試和系統級的測試。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"單元測試"},{"type":"text","text":",一般被認爲非常繁瑣。單元測試的繁瑣主要體現在測試案例的選取上, 如果使用全覆蓋方式來選取測試案例的話,會產生大量的測試代碼,以後維護起來也是一個負擔。如果採用環形複雜度來選取測試案例的話,會產生適量的測試代碼,但是環形複雜度的計算也是一個很大的時間開銷。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"集成測試"},{"type":"text","text":"跟客戶的實際業務需求相關。在這個過程中需要理清接口的輸入與輸出,以及運行路徑,然後據此來設計測試案例,寫出測試案例代碼。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"開發人員一般不會拒絕寫集成測試。因爲她帶來的好處是實實在在的,會極大的提高你的開發效率和調試效率。尤其是對於無界面的程序接口尤爲重要。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"系統級測試"},{"type":"text","text":"是大系統中子系統之間的集成測試。這個主要包含兩個方面:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一個方面是有界面的自動化測試,通過這樣的測試架構來模擬人類用戶的使用過程,同時增加一些隨機性的行爲,試圖能夠找出系統的一些漏洞。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另一種是無界面的測試,體現在多個服務系統之間的調用上或者類似瀏覽器自動化框架的使用上。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一套完整的測試系統,可以幫助工程師提高開發效率,減少以後系統維護和重構的成本。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從測試的緊迫性上來說,集成測試最爲必要,系統間的測試有時候使用手工測試通過一些測試工具來代替。單元測試可以有很廣闊的討論空間,這部分要具體問題具體分析。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"3. 重構的時機"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"問題:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"關於重構時機的說法,正確的是?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"添加功能時,重構能夠使得未來新增特性時更快捷、更流暢"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在修復錯誤時,應該聚焦問題本身,不建議重構,可以避免引入新的問題"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"專家Review時重構,能夠傳遞經驗,改善設計,避免或減少代碼持續腐化"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"回覆:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"關於重構的時機問題,現在我們有三個選項,我們就分別分析一下這三個選項。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"第1個選項是說在添加功能的時候進行重構"},{"type":"text","text":"。這個選項的主要問題就是一個提交包含了多個任務。這屬於人爲的增加工作的複雜度。第1個缺點是會增加工作的難度,使得本來可以用工作量1解決的問題,變成了工作量2和3。第2個缺點是增加了代碼審查的難度。本來你的提交中描述的是添加功能,結果發現裏面的代碼修改大部分與此描述無關。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"所以第1個選項排除。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"第2個選項是說在修復錯誤的時候應該聚焦問題本身"},{"type":"text","text":",不建議重構,以避免引入新的問題。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"聚焦是點睛之筆。我們在做任何事情的時候,都不要忘記初心,集中精力攻克問題,不要分心。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"所以第2個選項是正確的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"第3個選項是說專家在審查代碼的時候再重構"},{"type":"text","text":"。這裏面的"},{"type":"text","marks":[{"type":"strong"}],"text":"最關鍵問題是專家可能並不瞭解代碼的業務需求和應用場景。"},{"type":"text","text":"他們能夠看到代碼存在不好的味道,但在不瞭解業務場景的情況下,讓專家進行重構會帶來很大的風險。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"所以第3個選項也不正確。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"4. 如何進行重構?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"問題:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如何正確的進行重構?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"回覆:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面我們來看看如何進行重構。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"簡單的代碼重構我們都比較熟悉"},{"type":"text","text":",比如說你通過工具就可以做一些整理,如變量重命名,函數抽取,類創建等等。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"現在比較頭疼的一個話題就是對老產品的重構,一些老產品涉及到上千萬行,上億行的代碼。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"關於老產品整改的問題。如果只是縫縫補補的話,可能起不到化繁爲簡的目的。其實做類似這種工作的話,有一個比較可行的方案。就是把現有的產品當做一個成型系統也就是現有運行的產品,不要做大的改動,頂多就是修改bug。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然後以這些成型的系統爲基準,去寫新的系統。相當於參照一個大的白盒就寫一個小的白盒,這樣新的小的白盒質量上肯定比大的白盒性能上要有優勢。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這樣子按部就班去做的話,就會比較靠譜。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有朋友會說上面的做法是重寫,字面意義上沒錯的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"實際上不矛盾。"},{"type":"text","marks":[{"type":"strong"}],"text":"區別就是重構的方式應該從下往上還是從上往下。比如說我們現在大部分的重構都理解爲從下往上來做。也就是感覺這個文件裏頭有壞代碼的味道,然後就改這個文件,這樣做是沒有問題的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"比如現在有些教練遇到的問題,就是發現上下文不是很清晰,這個代碼爲什麼要這麼寫?爲什麼一個文件有1萬行或者3萬行,這個來龍去脈不是很清楚。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個時候可能就需要從整個子模塊來進行一個自上而下的分析。梳理出這個子模塊的功能需求是怎樣的,需要有多少個公共接口?內部公共接口的實現方式是不是應該像目前這樣的?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一個文件能夠寫成1萬行或者3萬行,肯定是有一定歷史原因的,絕大程度是由於全局把握的編程能力不夠造成的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"像這種情況,如果從這個文件本身去做重構的話,難度非常之大,但是如果從上往下,從模塊的整個設計角度來做重構的話,可能就容易一些。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"對於這樣的龐然大物,最好的辦法就是分而治之"},{"type":"text","text":"。首先要確定系統的功能邏輯點,針對這些邏輯點,要編排好對應的檢測點,也就是說等我們完成了重構以後,我們得確保我們的重構是沒有問題的,這些檢測點就是做這個的,我們可以理解成集成類的測試。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這些集成類的測試一定要確保可以在當前未重構之前的系統上正常運行。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有了這個設施以後,我們就可以開展我們的重構工作。重構的方法有很多,比如採用比較好的工具,函數和變量的命名改變,調用方式的改變等等。這些是在現有代碼的基礎上進行的重構。這裏我們重點說一下重寫的方式來實現重構。所謂重寫呢,就是另外開闢一套代碼底座。甚至可以選用不同的編程語言。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"這種情況下重構首先要重用已有的業務邏輯,實現針對業務邏輯集成測試100%的通過率。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"具體不管採用哪種方式都要"},{"type":"text","marks":[{"type":"strong"}],"text":"一個模塊一個模塊的進行推進"},{"type":"text","text":"。驗證完成一個是一個,千萬不能急於求成,試圖一次性的把某些問題搞定。如果出現很多次失敗,有可能會消磨掉你的自信心。所以一定要一點一點的往前推進,始終是在進步當中。採用了這種方式以後,不管當前的系統有多麼的龐大,你只要堅持做下去,就一定能夠把重構工作徹底完成。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個時候需要做的具體步驟可以參考如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"1. 根據功能需求定義公共接口。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"2. 根據公共接口寫出測試案例代碼。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"3. 這個時候可以按照測試驅動開發的理念去填充代碼。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"4. 代碼可以從現有的代碼中抽取出來。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"5. 在抽取的過程中進行整理重構。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這樣,這個子模塊完成以後,就可以嘗試去替代現有的子模塊,看看能不能在整個系統中安全的運行。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"對於整個系統來說,我們又可以分成很多個子模塊。然後又可以對各個子模塊各個擊破,最終完成對整個系統的重構。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果一開始對整個系統進行重構的話,也是可以從自上而下的角度來看的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"比如說開始的時候先把所有的子模塊看成一些佔位符,假定他們已經完成他們的接口了。那對於整個系統來說,它本身就是一個子模塊,屬於提綱挈領的那個模塊。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個過程,"},{"type":"text","marks":[{"type":"strong"}],"text":"從字面意義上可以理解成重寫,實際上,它也是一個重構的過程"},{"type":"text","text":",因爲我們肯定會重用這個系統本身的一些現有代碼和現有的邏輯。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面我們是假定系統在已經完成的情況下進行的重構,其實重構可以貫穿於軟件開發的始終。軟件開發的首要目標是實現業務邏輯,能夠解決客戶的問題。這個目標實現以後,我們就要追求代碼的乾淨度,複雜度能夠降到最小,當前的技術能夠用到最先進。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"所以只要有機會,我們都應該對代碼和設計進行重構。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"結語"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文針對收到的幾個關於重構方面的問題作了回答,側重點各不一樣,希望能夠給存在相同困惑的朋友們"},{"type":"text","marks":[{"type":"strong"}],"text":"有所啓示"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://bbs.huaweicloud.com/blogs?utm_source=infoq&utm_medium=bbs-ex&utm_campaign=other&utm_content=content","title":""},"content":[{"type":"text","text":"點擊關注,第一時間瞭解華爲雲新鮮技術~"}],"marks":[{"type":"strong"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章