C語言的面向?qū)ο笫街貥?gòu)
重構(gòu)(Refactoring)就是在不改變軟件現(xiàn)有功能的基礎(chǔ)上,通過調(diào)整程序代碼改善軟件的質(zhì)量、性能,使其程序的設(shè)計模式和架構(gòu)更趨合理,提高軟件的擴展性和維護性。
在越來越多的方便實用的語言曾不出窮的今天,c語言作為編程語言的常青樹,在用戶數(shù)方面仍然占據(jù)著老大的地位。當(dāng)然,這與C語言的使用環(huán)境有很大關(guān)系,諸多的嵌入式和底層代碼仍然離不開這個經(jīng)典的語言。
最近工作有了個空,如何使自己寫的C代碼容易閱讀,便于維護,成為近期我思考的主要問題。
于是,我找到了Martin Fowler寫的《重構(gòu)》。這本書從Martin的經(jīng)驗出發(fā),結(jié)合代碼中出現(xiàn)的各種“臭味”,提出了重構(gòu)的思路、指導(dǎo)思想和重構(gòu)步驟,從而使得代碼不斷的得到進化,從而使得代碼越來越優(yōu)美,越來越適應(yīng)于變化,越來越便于維護。這本書雖然是針對Java語言寫的,但是大量的重構(gòu)思路都是語言無關(guān)的,我們在任何語言中都可能遇到Martin大師描述的問題,并能夠利用Martin的解決思路來改正問題?,F(xiàn)在,就把我看這本書的一些想法、思路記錄下來,也算是一個讀書筆記吧。
一、函數(shù)的改進
過程化語言直接面對的是要解決問題的系統(tǒng)責(zé)任,也就是系統(tǒng)的功能域。要實現(xiàn)什么樣的功能,采用什么樣的流程來解決,這些內(nèi)容的實現(xiàn)都是由函數(shù)來完成的。因此,函數(shù)的重新構(gòu)建成為C語言重構(gòu)的最重要的一環(huán)。
1、重新命名函數(shù)
代碼首先是為人寫的,其次才是為機器寫的。函數(shù)的名稱應(yīng)該準確表達出它的用途。當(dāng)我們維護代碼時,通過名稱猜出這個函數(shù)的用途可以節(jié)省大量的時間。想想自己的一些經(jīng)歷,通過費力的閱讀代碼才能明白,哦,它原來是干這個的!如果有個好的名稱該多好,看看名字,就能大概得到這個函數(shù)的用途,有了這個大概情況,再去閱讀代碼,是那么的輕松、愉快。
有些人會想,我寫的是C程序,不是Java語言。C語言要求的是簡潔的名稱,而不是Java那樣,恨不能名字比實現(xiàn)的代碼還長。我覺得,簡潔不代表含糊不清。我們都有默許的規(guī)范,read就是代表讀,取一個名字叫rfile(),我就會猜它是讀文件;要是叫sfile(),誰知道它是什么?
2、提煉函數(shù)
過長的函數(shù)當(dāng)中,多半會有很多繁雜的過程包含在里面。當(dāng)我們需要不斷的上下翻頁來了解一個函數(shù)的功能時,很快就會不可耐煩,并且喪失信心。因此要想讓函數(shù)變得容易讓人看懂,就得盡量讓函數(shù)簡練。這就得把函數(shù)功能細化,不斷的提煉函數(shù)。每個函數(shù)都有其基本的功能,使得函數(shù)復(fù)用的機會很大,復(fù)雜函數(shù)就是這些小函數(shù)的組合,這樣復(fù)雜函數(shù)讀起來就像一系列注釋,通俗易懂。
3、提煉判斷條件
有時候在判斷條件當(dāng)中,會遇到一個很長的表達式表示一個條件分支,這個表達式有時不光包含了“與”、“或”、“非”,還有查詢函數(shù),大于小于等等。要看懂這么一個表達式,首先要做的是查清楚括號的對應(yīng)關(guān)系,然后查清楚各種運算符的運算優(yōu)先級,最后在去考慮這個表達式代表的意思。
為了擺脫這種“與非”困境,我們要做的是把這個判斷條件提煉出來,或者賦值給一個臨時變量,或者徹底提煉成一個函數(shù),變量或者函數(shù)的名字明確代表了判斷條件的意思,例如isEmpty等等。這樣在判斷語句中,就可以一眼看出來這個分支是怎么一回事了。如果真要考慮效率問題,可以把提煉出來的函數(shù)作為inline函數(shù)。總之,要寫出給人類看的代碼,這個工作是很值得使用的。
4、簡化函數(shù)參數(shù)
使用C語言編程,缺少強大的自動補齊工具(尤其是喜歡vi的人,雖然下面有補齊的插件,但是很少使用,也不大適應(yīng)),如果函數(shù)的參數(shù)太多,根本記不住應(yīng)該加的參數(shù),編寫代碼時要不斷的去翻看,降低了效率,尤其在多個人合作編程時,這種情況會尤為明顯。但是這在c語言中有時候是很難避免的事情,總不能為了數(shù)值傳遞設(shè)置大量的全局變量吧。
我的辦法是設(shè)置結(jié)構(gòu)體,把意思相近的參數(shù)合并成一個整體,然后為這個結(jié)構(gòu)體設(shè)置賦值函數(shù)。這樣在一定程度上簡化了主要函數(shù)的參數(shù)個數(shù),便于記憶。
二、改進代碼總體結(jié)構(gòu)
1、解除全局變量的噩夢
在c語言編程中不可避免的要使用全局變量。傳遞數(shù)值、進行條件判斷等等時,全局變量給我們帶來了極大的方便,但同時也帶來了噩夢。有時,全局變量根本不是自己預(yù)想的數(shù)值,而是被哪個莫名的代碼悄悄修改了,要在那么多代碼中查找這個作祟者簡直痛苦不堪。
好的做法是把全局變量設(shè)置成static,只能在本文件中看到,然后編寫訪問/設(shè)置函數(shù)來控制變量的訪問入口。查找函數(shù)總比查找一個小小的賦值語句簡單的多,起碼還有斷點可用。退一步講,如果使用多線程或者多進程沒有斷點可用,那么好的做法是使用宏定義編寫訪問控制點,在宏定義中可以加入打印,把這個語句在的文件和函數(shù)都打印出來,我就不信,找不到它,哈哈哈。
2、避免函數(shù)傳遞中的隱形bug
c語言的參數(shù)傳遞其實就是傳遞的一塊內(nèi)存,在函數(shù)執(zhí)行時,在這塊內(nèi)存中,根據(jù)參數(shù)類型取得一塊塊相應(yīng)大小的數(shù)據(jù)。這樣就產(chǎn)生了隱形的bug。比方說,在沒有記住函數(shù)參數(shù)類型的情況下,傳遞了一個結(jié)構(gòu)體給它,結(jié)果該函數(shù)只是幾個int類型的參數(shù),這時編譯器也不會報錯給你,更恐怖的是代碼照樣可以運行,如果沒有測試充分,沒準代碼還能正確運行。
這樣一個隱形的bug會把你搞的很慘。要把它干掉,最簡單的辦法就是加入函數(shù)聲明。靈活便利的c編譯器,在函數(shù)調(diào)用上,沒有強制要求加入函數(shù)聲明,而函數(shù)聲明尤其在調(diào)用另外文件的函數(shù)時,很重要。如果調(diào)用與聲明的類型不一致,編譯器會報錯。這樣這個隱形bug就不會在騷擾你了。所以,不要圖省事,為了以后的和諧生活,聲明你要調(diào)用的每一個函數(shù)吧。
3、調(diào)整函數(shù)位置
在多文件程序當(dāng)中,函數(shù)的位置很重要,要根據(jù)每個文件完成的功能調(diào)整函數(shù)的位置,不能亂放。亂放的現(xiàn)象在那些臨時加的被調(diào)用函數(shù)身上表現(xiàn)的非常突出(我經(jīng)常就干這事)。亂放導(dǎo)致的后果就是,后面維護代碼時,根據(jù)文件名去找相關(guān)的函數(shù),根本沒有,結(jié)果一搜發(fā)現(xiàn)它在一個內(nèi)容完全不相干的文件中。為了不打亂思路,臨時放就放吧,但是這個功能完了,一定要把這個函數(shù)重構(gòu)到它應(yīng)該在位置上。
對于重構(gòu)來講,想到的暫時就是這些,其實有一些是要開始編寫時就要注意的。這也應(yīng)了這么一句話:重構(gòu)本來就應(yīng)該是融入到日常的代碼編寫中。最高境界就是手中無重構(gòu),心中也無重構(gòu)。
【編輯推薦】