代碼重構原則

來源:互聯網
上載者:User

標籤:

1. 總則 
總則規定了一些大體原則,必須要作的、最需要注意的事項。也是面向目前我們的代碼中亟需解決的一些問題: 
(1)標頭檔、源檔案布局混亂,直接影響編譯效率 
(2)有編程規範,但遵守的很差 
(3)過長函數 
(4)大量重複代碼 

1.1 源檔案 
源檔案原則: 

● 函數行數盡量不要超過50行,超過50行的目前階段並非嚴格禁止,但需要說明理由 

● 源檔案長度盡量不要超過500行, 不同子功能、子模組的代碼不要放在一個源檔案中;理論上源檔案分的越細越好。保證同一源檔案中的代碼“強內聚”。 

● 無特殊情況,不允許使用全域變數,一律局部靜態化,對外封裝提供者,對有並發可能的業務, 
均需在提供者中作並發控制。 

●函數拆分封裝基本原則:明顯的重複代碼一律封裝函數;何時該拆分較長函數、封裝新函數?只要能提高代碼的可讀性、可複用性就該拆分:哪怕只有一行。 

1.2標頭檔 
標頭檔總體原則:標頭檔應該越簡單越好,包含的其他標頭檔越少越好 

●標頭檔包含其他標頭檔的原則: 僅包含且必須包含那些標頭檔中所出現的類型需要的標頭檔(如,標頭檔A中出現的類型定義在標頭檔B中,則標頭檔A必須包含標頭檔B,除此以外的其他標頭檔不允許包含)。標頭檔應該包含哪些標頭檔應該僅決定於它自己,而不是那些包含此標頭檔的源檔案(如,將源檔案編譯時間需要用到的標頭檔B,這時源檔案已經包含標頭檔A了,索性將標頭檔B包含在此標頭檔A中,這是錯誤的做法)。 

●標頭檔劃分原則:類型定義、宏定義應與函式宣告相分離,分別位於不同的標頭檔中。內建函式聲明標頭檔與外部函式宣告標頭檔相分離,內部類型定義標頭檔,與外部類型定義標頭檔相分離。需要特別注意的是,外部函式宣告標頭檔與外部宏、結構定義標頭檔一定要獨立出來,這樣可以保證外部調用者引用的標頭檔肯定是“最簡化的”,不會包含外部調用者不需要的內建函式聲明或者介面定義。 

●標頭檔的語義層次化原則:標頭檔需要有語義層次,不同語義層次的類型定義不要放在一個標頭檔中,不同層次的函式宣告不要放在一個標頭檔中。 

●僅在一個源檔案中用到的類型和宏定義,就放在此源檔案中定義,不要放入任何標頭檔中。 

●標頭檔的語義相關性原則:同一標頭檔中出現的類型定義、函式宣告應該是語義相關的、有內部邏輯關係的,避免將風馬牛不相及的定義和聲明放在一個標頭檔中。 

●對開放給其他模組使用的外部標頭檔尤其要注意其所包含的標頭檔的合理性,如包含太多不必要的其他標頭檔,將影響所有引用此標頭檔的模組,因此影響更嚴重。 
1.3 編程規範 
編程規範中的規則必須遵守,如有特殊情況不能遵守的,必須說明理由,編程規範見附件 

1.4 過長函數拆分 
說明見附錄部分《函數設計》 
函數行數盡量不要超過50行,超過50行的需要說明理由 

1.5 代碼複用 
原則: Write once and only once 
唯寫一次,需要作些靈活化處理,需要動腦子,而不是簡單的拷貝粘貼 
唯寫一次,則只需要修改一次,代碼更容易維護與擴充 
原則上超過3行的重複代碼應該複用。 

代碼複用分成下面若干情況,複用的方法一個比一個更抽象,複用的難度也依次增加,下面依次描述。 

●庫。 現成的通用資料結構及基於其上的通用演算法、標準函數庫、通用模板庫 均可直接拿來使用,如C++的STL、BOOST庫。業務程式員應該集中精力於上層業務模型構建,而不是資料存放區、資料訪問這些“做輪子”的重複性的、機械的、還很容易出錯的基礎勞作。C語言這方面的標準庫非常欠缺,但非標準庫並不缺少,這些庫多半是由少數進階程式員開發,且在實踐中久經考驗,安全性、效能、可靠性方面可以說遠超過大部分水平一般的代碼,比如原來的TMS架構中就有相當多的基礎庫,ROS中也有一些。能用的就要直接使用。庫複用的難度在於必須按照庫規定的使用方式來正確使用,越是複雜的庫使用方式與注意事項也就越複雜。 

●完全一樣的代碼, 直接封裝函數予以複用,這是最簡單的情況,沒有任何難度,必須做到。 

●基本上完全一樣的代碼,只是其中用到的變數不一樣,封裝成函數,將不一樣的變數作為函數的參數傳入。 

●大部分一樣的代碼,只有其中少數幾行代碼不一樣,封裝一樣的部分。(1)將不一樣的部分封裝成介面類型一致的函數,將函數作為函數指標參數傳入可複用的函數,一般可以封裝成一種介面的代碼,我們稱作可複用的是一種“代碼結構”。(2) 如果不一樣的地方差異比較大,無法封裝成介面一致的函數,就需要考慮“要複用的東西倒底是什麼”,也許你需要複用的不是“代碼結構”,而是其他的東西,比如某種擷取操作資料的方式。 

●代碼模式類似的代碼,抽象出相同的“模式”,對模式進行編碼,將代碼的抽象層次升級。 

●設計複用。 這是最高層次的複用,有現成的可行的優秀的解決方案,就採用之。 如物件導向領域已經蔚成風氣的“設計模式”。設計模式即:在某種上下文環境適於採用的設計方案,用專用術語予以表達,減低溝通成本,形成一族“設計語彙”。 

代碼複用具體代碼執行個體見附錄部分example.c 

2.細則 
細則中針對目前我在代碼中發現的其他一些常見問題進行詳細說明 

2.1 全域變數 
全域變數的主要問題: 

● 非常難對訪問進行合法性檢查。 全域變數的可見度是“整個工程”,工程中的所有檔案可以直接對之進行讀寫,沒有任何保護措施可以加以防範(如範圍、下標合法性檢查),靠客戶代碼去作檢查是不可靠、代價也是不可容忍的(因為所有調用處都需要檢查)的。 

●可擴充性問題。 也許當前的需求只是簡單的對值進行讀取、設定即可。如果以後需要增加複雜一點的訪問邏輯如何增加? 當前用靜態數組一次性開足空間直接用下標定位非常方便,但後來記憶體不夠了需要替換成動態分配的鏈表,如何擴充? 

●可複用性問題。對複雜全域資料對象(鏈表等)如果沒有封裝提供者以複用其存取碼,對其的訪問將非常痛苦,可以想見會有通篇的拷貝粘貼代碼。 

●並發性問題。全域變數(尤其是訪問方式比較複雜的全域資料,如鏈表等)的直接存取是很難進行並發保護的。 

●語義單薄。通過變數名稱所能傳遞的語義是非常單薄的,通過提供者函數命令可以提供層次更豐富的業務語義資訊。 


全域變數的主要“好處”: 

全域變數普遍被認可的好處當然是: 
●使用方便, 既然非常方便,沒有任何限制,就難免會有使用失誤-沒有控制的東西最危險。如何確保每次對此變數進行訪問時都對值進行了合法性檢查? 如何確保對一個複雜全域資料對象(如鏈表)進行操作時沒有其他的並行作業進行中? 因此,所謂的“好處”也許正是它的害處。 


全域變數的替換性方法: 
●定義靜態變數,以減少其可見度。注意:可見度和生存期是兩碼事情。全域變數之所以有害主要在於其“可見度”太廣,而非其生存期。局部靜態變數生存期和全域變數是一樣的。 
●對靜態變數封裝提供者。 
●複雜資料對象提供者可以根據索引、關鍵字(簡單相等邏輯判斷,通過 “==” 直接判斷)、eqaul函數(複雜邏輯相等判斷函數,可按函數指標參數方式傳給提供者)對之進行定位。 
提供者可以作到什嗎? 
●前面提到的全域變數問題將全部得到解決。 
原則: 數組、鏈表等複雜資料類型全域資料必須封裝提供者,作合法性檢查。 

2.2 平台業務代碼中項目宏、業務宏的處理方式 
業務宏開關相關的函數和代碼應能盡量獨立在某檔案中最好,這樣尋找閱讀更方便,項目相關的特色與通用架構部分應儘可能區分開來,各自獨立變化,不要糾纏錯節在一起。 

這樣做的附加好處是: 
架構代碼的可複用性、可擴充性、可讀性得到提高。 
要做到這一點,尤其是優美的用代碼作到這一點是對架構代碼的靈活性提出了更高的要求,對架構設計提出了更高的要求,設計品質也可因此得到升級。 
架構代碼和特性代碼都更顯眼了,彼此可以各自獨立擴充,提高了架構代碼的可複用性、可擴充性,因為特性代碼已經被抽象語義化了,被賦予了顯示架構語義。架構代碼的抽象性更好,而不再被各類特性代碼填塞,方便從更進階別、更抽象的的語義層次閱讀架構代碼,業務語義邏輯的可讀性得到了提高。 
在物件導向編程領域有成熟的語言特性可以支援這種設計,那就是“派生與多態”,在範型編程(面向類型編程)領域可以使用語言的模板特化萃取機制,對於C語言,使用類似範型的萃取機制更合適(用C語言寫物件導向的程式是沒問題的,只是要自己實現派生機制,這個不符合目前的代碼現狀,因此採用後者更合適)。 

在C語言裡對特性的一種“萃取”實現方式是可以採用利用函數指標向架構註冊各個項目的特化介面,通過尋找註冊介面,很容易尋找不同的項目在有區別的業務關注點上不同的處理策略,代碼可讀性更好,對重複的特性處理代碼,也很方便予以複用。 
如: 

#if INSTALL_MSAN_SLAVESHELF_SUPPORT 
for(shelfNo = 0; shelfNo < IGMP_MAX_SHELF_NUM; shelfNo ++) 
#endif 


可以演變為: 
for(shelfNo = 0; shelfNo < IgmpFuncTbl.GetMaxShelfNo(); shelfNo ++) 

IgmpFuncTbl為架構註冊函數指標表,其中註冊了各類項目、業務的特性處理介面函數,在註冊時根據項目宏進行一次性註冊,此處,由隱式項目特性處理代碼轉化為顯示架構語義介面的是“GetMaxShelfNo”。 

架構代碼中可以不再出現項目宏嵌入的項目相關代碼,成為純粹的架構代碼。反之亦然。 

2.3 標識符命名 
全域對象(全域變數、全域函數, 宏)應包含儘可能豐富的語義資訊,以防對全域命名空間的汙染(命名衝突)效應,局部對象(待用資料,靜態函數)可以弱化語義資訊(沒有命名衝突問題),因為可以通過檔案名稱資訊提供這層附加語義。 
標識符命名現在代碼中的現狀可謂一片混亂。無規則。隨意而為。 
具體命名原則可參考附錄部分:標識符命名.ppt,及《代碼大全》相應章節。 

2.4 迴圈的使用 
●何時應該使用while,何時應該使用for 
while主要用於迴圈次數不一定的迴圈 
for 使用者迴圈次數固定的迴圈 
對while迴圈, 應防止“死迴圈”(可以通過迴圈最大次數限制,如鏈表遍曆,可通過鏈表最大理論長度限制遍曆次數)。 

●對for 迴圈, 不應該在迴圈體內修改迴圈變數,否則,你需要的可能是一個while迴圈 

●減少迴圈的嵌套層次的方法 
運用防衛子句及早break或continue,封裝迴圈體為函數 

2.5 使用宏,不要使用魔數 
這是非常關鍵,非常實用,也非常容易做到的一條規則,但往往容易被忽視沒有遵守好。 
原則: 
●一切有語義、會重複出現的數字常量都應該用宏替代 
●宏不應該重名, 不應該被重定義 
●必要時,使用enum組織一組語義相關的宏 

 

注:原文連結 http://www.douban.com/group/topic/11152037/

代碼重構原則

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.