版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認(rèn)領(lǐng)
文檔簡介
1、第1章風(fēng)格人們看到最好的作家有時并不理會修辭學(xué)的規(guī)則。還好,當(dāng)他們這樣做雖然付出了常規(guī)的代價,讀者還經(jīng)常能從句子中發(fā)現(xiàn)某些具有補償性的價值。除非作者自己也明確其做法的意思,否則最好還是按規(guī)矩做。William Strunk和E. B. White,風(fēng)格的要素下面這段代碼取自一個許多年前寫的大程序:這段代碼寫得很仔細(xì),具有很好的格式。它所在的程序也工作得很好。寫這個系統(tǒng)的程序員會對他們的工作感到驕傲。但是這段摘錄卻會把細(xì)心的讀者搞糊涂:新加坡、文萊、波蘭和意大利之間關(guān)系?為什么在注釋里沒有提到意大利?由于注釋與代碼不同,其中必然有一個有錯,也可能兩個都不對。這段代碼經(jīng)過了執(zhí)行和測試,所以它可能沒
2、有問題。注釋中對提到的三個國家間的關(guān)系沒有講清楚,如果你要這些代碼,就必須知道的東西。上面這幾行實際代碼是非常典型的:大致上寫得不錯,但也還存在許多應(yīng)該改進的地方。本書關(guān)心的是程序設(shè)計實踐,關(guān)心怎樣寫出實際的程序。的目的是幫助讀者寫出這樣的,它至少像上面的代碼所在的程序那樣工作得非常好,而同時又能避免那些污點和弱點。進它。如何從一開始就寫出更好的代碼,以及如何在代碼的發(fā)展過程中進一步改從一個很平凡的地方入手,首先程序設(shè)計的風(fēng)格問題。風(fēng)格的作用主要就是使代碼容易讀,無論是對程序員本人,還是對其他人。好的風(fēng)格對于好的程序設(shè)計具有關(guān)鍵性作用。題。希望最先談?wù)擄L(fēng)格,也是為了使讀者在閱讀本書其余部分時能
3、特別注意這個問寫好一個程序,當(dāng)然需要使它符合語則、修正其中的錯誤和使它運行得足夠快,但是實際應(yīng)該做的遠(yuǎn)比這多得多。程序不僅需要給計算機讀,也要給程序員讀。一個寫得好的程序比那些寫得差的程序更容易讀、更容易修改。經(jīng)過了如何寫好程序的訓(xùn)練,生產(chǎn)的代碼更可能是正確的。幸運的是,這種訓(xùn)練并不太。程序設(shè)計風(fēng)格的原則根源于由實際經(jīng)驗中得到的,它不是隨意的規(guī)則或者處方。代碼應(yīng)該是清楚的和簡單的 具有直截了當(dāng)?shù)倪壿?、自然的表達式、通行的語言使用方式、2計計程序設(shè)計實踐有意義的名字和有幫助作用的注釋等,應(yīng)該避免耍小聰明的花招,不使用非正規(guī)的結(jié)構(gòu)。一致性是非常重要的東西,如果大家都堅持同樣的風(fēng)格,其他人就會發(fā)現(xiàn)你
4、的代碼很容易讀,你也容易讀懂其他人的。風(fēng)格的細(xì)節(jié)可以通過一些局部規(guī)定,或管理性的公告,或者通過程序來處理。如果沒有這類東西,那么最好就是遵循大眾廣泛采納的規(guī)矩。在這里將遵循C程序設(shè)計語言(The C Programming Language)一書中所使用的風(fēng)格,在處理Java和C+ 程序時做一些小的調(diào)整。一般將用一些好的和不好的小程序設(shè)計例子來說明與風(fēng)格有關(guān)的規(guī)則,因為對處理同樣事物的兩種方式做比較常常很有啟發(fā)性。這些例子不是人為臆造的,不好的一個都來自實際代碼,由那些在太多工作負(fù)擔(dān)和太少時間的壓力下工作的普通程序員 (偶然就是自己)寫出來。為了簡單,這里對有些代碼做了些精練,但并沒有對它們做
5、任何錯誤的解釋。在看到這些代碼之后,重寫它們,說明如何對它們做些改進。由于這里使用的都是真實代碼,所以代碼中可能存在多方面問題。要代碼里的所有缺點,有時可能會使遠(yuǎn)離討論的。因此,在有的好代碼例子里也會遺留下一些未加指明的缺陷。為了指明一段代碼是不好的,在本書中,就像下面這段:在有問題的代碼段的前面標(biāo)出一些問號,為什么這些#define有問題?請想,如果某個具有 TWENTY個元素的數(shù)組需要修改得更大一點,情況將會怎么樣。至少這里的每個名字都應(yīng)該換一下,改成能說明這些特殊值在程序中所起作用的東西。1.1名字名字?一個變量或函數(shù)的名字標(biāo)識這個對象,帶著說明其用途的一些信息。一個名字應(yīng)該是非形式的、
6、簡練的、容易的,如果可能的話,最好是能夠拼讀的。許多信息來自上下文和作用范圍(作用域)。一個變量的作用域越大,它的名字所攜帶的信息就應(yīng)該越多。全局變量使用具有說明性的名字,局部變量用短名字。根據(jù)定義,全局變量可以出現(xiàn)在整個程序中的任何地方,因此它們的名字應(yīng)該足夠長,具有足夠的說明性,以便使讀者能夠記得它們是干什么用的。給每個全局變量附一個簡短注釋也非常有幫助:全局函數(shù)、類和結(jié)構(gòu)也都應(yīng)該有說明性的名字,以表明它們在程序里扮演的角色。相局部變量使用短名字就夠了。在函數(shù)里, n可能就足夠了, npos也還可以,用numberOfPos就太過分了。按常規(guī)方式使用的局部變量可以采用極短的名字。例如用 i
7、、j作為循環(huán)變量, p、q作為指針, s、t表示字符串等。這些東西使用得如此普遍,采用更長的名字不會獲,可能反而有害。比較:益處或收第 1章 風(fēng)格計計3和人們常常鼓勵程序員使用長的變量名,而不管用在什么地方。這種認(rèn)識完全是錯誤的,清晰性經(jīng)常是隨著簡潔而來的?,F(xiàn)實中存在許多命名約定或者本地。常見的比如:指針采用以 p結(jié)尾的變量名,例如nodep;全局變量用大寫開頭的變量名,例如 Global;常量用完全由大寫字母拼寫的變量名,如CONSTANTS等。有些程序設(shè)計采用的規(guī)則更加徹底,他們要求把變量的類型和用途等都編排進變量名字中。例如用 pch說明這是一個字符指針,用 strTo和strFrom表
8、示它們分別是將要被讀或者被寫的字符串等。至于名字本身的拼寫形式,是使用 npending或 numPending還是num_pending,這些不過是個人的喜好問題,與始終如一地堅持一種切合實際的約定相比,這些特殊規(guī)矩并不那么重要。命名約定能使自己的代碼更容易理解,對別人寫的代碼也是一樣。這些約定也使人在寫代碼時更容易決定事物字就更加重要。名。對于長的程序,選擇那些好的、具有說明性的、系統(tǒng)化的名C+ 的名字空間和Java的包為管理各種名字的作用域提供了方法,能幫助的意義清晰,又能避免過長的名字。保持一致性。相關(guān)的東西應(yīng)給以相關(guān)的名字,以說明它們的關(guān)系和差異。除了太長之外,下面這個Java類中各
9、成員的名字一致性也很差:保持名字這里同一個詞“隊列 ( queue)”在名字里被分別寫為ueue或queue。由于只能在類型UserQueue里,類成員的名字中完全不必提到隊列,因為存在上下文。所以:完全是多余的。下面的寫法更好:因為這時可以如此寫:這樣做在清晰性方面沒有任何損失。在這里還有可做的事情。例如 items和users實際是同一種東西,同樣?xùn)|西應(yīng)該使用一個概念。函數(shù)采用動作性的名字。函數(shù)名應(yīng)當(dāng)用動作性的動詞,后面可以跟著名詞:對返回類型值(真或者函數(shù)命名,應(yīng)該清楚地反映其返回值情況。下面這樣的語句4計計程序設(shè)計實踐是不好的,因為它沒有指明什么時候返回真,什么時候返回假。而:就把事情
10、說清楚了:如果參數(shù)是八進制數(shù)字則返回真,否則為假。要準(zhǔn)確。名字不僅是個標(biāo)記,它還攜帶著給讀程序人的信息。誤用的名字可能引起奇怪的程序錯誤。本書作者之一寫過一個名為isoctal的宏,并且發(fā)布使用多年,而實際上它的實現(xiàn)是錯誤的:正確的應(yīng)該是:這是另外一種情況:名字具有正確的含義,而對應(yīng)的實現(xiàn)卻是錯的,一個合情合理的名字掩蓋了一個害人的實現(xiàn)。下面是另一個例子,其中的名字和實現(xiàn)完全是的:函數(shù)g e t I n d e x 如果找到了有關(guān)對象,就返回 0 到n T a b l e -1 之間的一個值;否則返回nTable值。而這里able返回的值卻正好與它名字所說的相反。在寫這段代碼時,這種寫法未必會
11、引起什么問題。但如果后來修改這個程序,很可能是由別的程序員來做,這個名字肯定會把人弄糊涂。練-1評論下面代碼中名字和值的選擇:練-2改進下面的函數(shù):練-3大聲讀出下面的代碼:1.2表達式和語句名字的合理選擇可以幫助讀者理解程序,同樣,也應(yīng)該以盡可能一目了然的形式寫好表達式和語句。應(yīng)該寫最清晰的代碼,通過給運算符兩邊加空格的方式說明分組情況,更一般的是通過格式化的方式來幫助閱讀。這些都是很瑣碎的事情,但卻又是非常有價值的,就像保持書桌整潔能使你容易找到東西一樣。與你的書桌不同的是,你的程序代碼很可能還會被別人使用。第 1章 風(fēng)格計計5用縮行顯示程序的結(jié)構(gòu)。采用一種一致的縮行風(fēng)格,是使程序呈現(xiàn)出結(jié)
12、構(gòu)清晰的最省力的方法。下面這個例子的格式太糟糕了:重新調(diào)整格式,可以改得好一點:更好的是把賦值作為循環(huán)體,增量運算單獨寫。這樣循環(huán)的格式更普通也更容易理解:使用表達式的自然形式。表達式應(yīng)該寫得你能大聲念出來。含有否定運算的條件表達式比較難理解:在兩個測試中都用到否定,而它們都不是必要的。應(yīng)該改變關(guān)系運算符的方向,使測試變成肯定的:現(xiàn)在代碼讀起來就自然多了。用加括號的方式排除二義性。括號表示分組,即使有時并不必要,加了括號也可能把意圖表示得更清楚。在上面的例子里,內(nèi)層括號就不是必需的,但加上它們沒有壞處。熟練的程序員會忽略它們,因為關(guān)系運算符( = )比 邏輯運算符(&和|)的優(yōu)先級更高。在混合
13、使用互相無關(guān)的運算符時,多寫幾個括號是個好主意。 C語言以及與之相關(guān)的語言存在很險惡的優(yōu)先級問題,在這里很容易犯錯誤。例如,由于邏輯運算符的約束力比賦值運算,在大部分混合使用它們的表達式中,括號都是必需的。字位運算符(&和|)的優(yōu)先級低于關(guān)系運算符(比如=),不管出現(xiàn)在哪里:實際上都意味著這個表達式所表達的肯定不會是程序員的本意。在這里混合使用了字位運算和關(guān)系運算符號,表達式里必須加上括號:如果一個表達式的分組情況不是一目了然的話,加上括號也可能有些幫助,雖然這種括6計計程序設(shè)計實踐號可能不是必需的。下面的代碼本來不必加括號:但加上括號,代碼將變得更容易理解了:這里還去掉了幾個空格:使優(yōu)先級高
14、的運算符與運算對象連在一起,幫助讀者更快地看清表達式的結(jié)構(gòu)。分解復(fù)雜的表達式。 C、C+和Java語言都有很豐富的表達式語法結(jié)構(gòu)和很豐富的運算符。因此,在這些語言里就很容易把一大堆東西塞進一個結(jié)構(gòu)中。下面這樣的表達式雖然很緊湊,但是它塞進一個語句里的東西確實太多了:把它分解成幾個部分,意思更容易把握:要清晰。程序員有時把自己無窮盡的創(chuàng)造力用到了寫最簡短的代碼上,或者用在尋求得到結(jié)果的最巧妙方法上。有時這種技能是用錯了地方,因為而不是最巧妙的代碼。下面這個難懂的計算到底想做什么?的目標(biāo)應(yīng)該是寫出最清晰的代碼,最內(nèi)層表達式把bitoff右移3位,結(jié)果又被重新移回來。這樣做實際上是把變量的最低 3位
15、設(shè)置為0。從bitoff的原值里面減掉這個結(jié)果,得到的將是 bitoff的最低3位。最后用這3位的值確定subkey的右移位數(shù)。上面的表達式與下面這個等價:要弄清前一個版本的意思簡直像猜謎語,而后面這個則又短又清楚。經(jīng)驗豐富的程序員會把它寫得更短,換一個賦值運算符:有些結(jié)構(gòu)似乎總是要引們?nèi)ニ鼈?。運算符 ?:大概屬于這一類:如果不仔細(xì)地追蹤這個表達式的每條路徑,就幾乎沒辦法弄清它到底是在做什么。下面的形式雖然長了一點,但卻更容易理解,其中的每條路徑都非常明顯:運算符?:適用于短的表達式,這時它可以把 4行的if-else程序變成1行。例如這樣:或者下面這樣:第 1章 風(fēng)格計計7但是它不應(yīng)該作為條
16、件語句的一般性替換。清晰性并不等同于簡短。短的代碼常常更清楚,例如上面移字位的例子。不過有時代碼長一點可能更好,如上面把條件表達式改成條件語句的例子。在這里最重要的評價標(biāo)準(zhǔn)是易于理解。當(dāng)心副作用。像 + 這一類運算符具有副作用,它們除了返回一個值外,還將隱含地改變變量的值。副作用有時用起來很方便,但有時也會成為問題,因為變量的取值操作和更新操作可能不是同時發(fā)生。 C和C+ 對與副作用有關(guān)的執(zhí)行順序并沒有明確定義,因此下面的多次賦值語句很可能將產(chǎn)生錯誤結(jié)果:這樣寫的意圖是給str中隨后的兩個位置賦空格值,但實際效果卻要依賴于i的更新時刻,很可能把str里的一個位置跳過去,也可能導(dǎo)致只對i實際更新
17、一次。這里應(yīng)該把它分成兩個語句:下面的賦值語句雖然只包含一個增量操作,但也可能給出不同的結(jié)果:如果初始時i的值是3,那么數(shù)組元素有可能被設(shè)置成 3或者4。不僅增量和減量操作有副作用, I/O也是一種附帶活動的操作。下面的例子希望從標(biāo)準(zhǔn)輸入讀入兩個互相有關(guān)的數(shù):這樣做很有問題,因為在這個表達式里的一個地方修改了 yr,而在另一個地方又使用它。這樣,除非yr的新取值與原來的值相同,否則 profityr就不可能是正確的。你可能認(rèn)為事情依賴于參數(shù)的求值順序,實際情況并不是這樣。這里的問題是: scanf的所有參數(shù)都在函數(shù)被真正調(diào)用前已經(jīng)求好值了,所以 &profityr實際使用的總是yr原來的值。這
18、種問題可能在任何語言里發(fā)生。糾正的方法就是把語句分解為兩個:下面的練習(xí)里列舉了各種具有副作用的表達式。-4 改進下面各個程序片段:練練-5 下面的例子里錯?8計計程序設(shè)計實踐練-6 列出下面代碼片段在各種求值順序下可能產(chǎn)生的所有不同的輸出:在盡可能多的編譯系統(tǒng)中試驗,看看實際中會發(fā)生什么情況。1.3一致性和用法一致性帶來的將是更好的程序。如果程序中的格式很隨意,例如對數(shù)組做循環(huán),一會兒采用下標(biāo)變量從下到上的方式,一會兒又用從上到下的方式;對字符串一會兒用 strcpy做復(fù)制,一會兒又用for循環(huán)做;等等。這些變化就會使人很難看清實際上到底是怎么回事。而如果相同計算的每次出現(xiàn)總是采用同樣方式,任
19、何變化就預(yù)示著是經(jīng)過了深思熟慮,要求讀程序的人注意。使用一致的縮排和加括號風(fēng)格??s排可以顯示出程序的結(jié)構(gòu),那么什么樣的縮排風(fēng)格最好呢?是把花括號放在if的同一行,還是放在下面一行?程序員們常常就程序的這些編排形式爭論不休。實際上,特定風(fēng)格遠(yuǎn)沒有一致地使用它們重要。不要在這里浪費時間。應(yīng)該取一種風(fēng)格,當(dāng)然作者希望是他們所采用的風(fēng)格,然后一致地使用。應(yīng)該在那些并不必須用花括號的地方都加上它們嗎?與一般的圓括號一樣,花括號也可以用來消除歧義,但是在使代碼更清晰方面的作用卻不那么大。為了保持某種一致性,許多程序員總在循環(huán)或if的體外面加花括號。當(dāng)這里只有一個語句時,加花括號就不是必要的,所以作者傾向于
20、去掉它們。如果你贊成的方法,那么就要注意在必需的時候不要忽略了它們,例如,在程序里需要解決“懸空的else(dangling else)”問題時。下面是這方面的一個例子:這里的縮排方式把人搞糊涂了,實際上else隸屬于行:代碼本身也是錯的。如果一個if緊接在另一個之后,那么請一定加上花括號:第 1章 風(fēng)格計計9語法驅(qū)動的編輯工具可以幫助避免這類錯誤。雖然上面程序里的錯誤已經(jīng)修正,但這個結(jié)果代碼還是很難懂。如果存二月的天數(shù),計算過程就很容易看明白了:用一個變量保這段代碼實際上還是錯的2000年是閏年,而1900和2100都不是。要把現(xiàn)在這個結(jié)構(gòu)改正確是非常容易的。此外,如果你工作在一個不是自己寫
21、的程序上,請注意保留程序原有的風(fēng)格。當(dāng)你需要做修改時,不要使用你自己的風(fēng)格,即使你特別喜歡它。程序的一致性比你本人的要,因為這將使隨你之后的其他人生活得更容易些。更重為了一致性,使用用法。和自然語言一樣,程序設(shè)計語言也有許多慣用法,也就是那些經(jīng)驗豐富的程序員寫常見代碼片段的方式。在學(xué)個語言的過程中,一個中心問題就是逐漸熟悉它的用法。用法之一是循環(huán)的形式??紤]在 C、C+和Java中逐個處理n元數(shù)組中各個元素常見的代碼,例如要對這些元素做初始化。有人可能寫出下面的循環(huán):或者是這樣的:也可能是:所有這些都正確,而用法的形式卻是:這并不是一種隨意的選擇:這段代碼要求n元數(shù)組里的每個元素,下標(biāo)從 0到
22、n-1。在這里所有循環(huán)控制都被放在一個 for里,以遞增順序運行,并使用 +的形式做循環(huán)變量的更新。這樣做還保證循環(huán)結(jié)束時下標(biāo)變量的值是一個已知值,它剛剛超出數(shù)組里最后元素的位置。熟悉C語言的人不用琢磨就能理解它,不加思考就能正確地寫出它來。C+或Java里常見的另一種形式是把循環(huán)變量的也包括在內(nèi):下面是在C語言里掃描一個鏈表的標(biāo)準(zhǔn)循環(huán):10計計程序設(shè)計實踐同樣,所有的控制都放在一個for里面。對于無窮循環(huán),喜歡用:但也很流行。請不要使用其他形式??s排也應(yīng)該采用一個循環(huán):形式。下面這種垂直排列會妨礙人的閱讀,它更像三個語句而不像寫成標(biāo)準(zhǔn)的循環(huán)形式,讀起來就容易多了:這種故意拉長的格式還會使代碼
23、攤到的頁或顯示屏去,進一步妨礙人的閱讀。常見的另一個慣用法是把一個賦值放進循環(huán)條件里:do-while循環(huán)遠(yuǎn)比for和while循環(huán)用得少,因為它將至少執(zhí)行循環(huán)體一次,在代碼的最后而不是開始執(zhí)行條件測試。這種執(zhí)行方式在許多情況下是不正確的,例如下面這段重寫的使用getchar的循環(huán):在這里測試被放在對phar的調(diào)用之后,將使這個代碼段無端地多寫出一個字符。只有在某個循環(huán)體總是必須至少執(zhí)行一次的情況下,使用 do-while循環(huán)才是正確的。后面會看到這種例子。一致地使用況常常預(yù)示著用法還有另一個優(yōu)點,那就是使非標(biāo)準(zhǔn)的循環(huán)很容易被注意到,這種情問題:在這里分配了nmemb個項的空間,從iArray
24、0到iArraynmemb-1。但由于采用的是 =第 1章 風(fēng)格計計11做循環(huán)測試,程序執(zhí)行將超出數(shù)組尾部,覆蓋掉區(qū)中位于數(shù)組后面的內(nèi)容。不幸的是,有許多像這樣的錯誤沒查出來,直到造成了很大的損害。C和C+ 中也有為字符串分配空間及操作它們的隱藏著程序錯誤:寫法。不采用這種做法的代碼常常就絕不要使用函數(shù)gets,因為你沒辦法限制它由輸入那兒讀入內(nèi)容的數(shù)量。這常常會導(dǎo)致一個安全性問題。第6章還會再來這個問題,那里明選擇 fgets總是更好的。上面代碼段里還有另一個問題: strlen求出的值沒有計入串結(jié)尾的 0字符,而strcpy卻將所以這里分配的空間實際上是不夠的,這將使 strcpy的寫入超
25、過所分配空間的界限。法是:它。寫或在C+里:如果你在這里沒有看見 +1,就要當(dāng)心。在Java里不會遇到這個特殊問題,那里的字符串不是用零結(jié)尾的數(shù)組表示,數(shù)組的下標(biāo)也將受到檢查。這就使Java不會出現(xiàn)超出數(shù)組界限的問題。許多C和C+ 環(huán)境中提供了另一個庫函數(shù) strdup,它通過調(diào)用malloc和strcpy建立字符串的拷貝。有了這個函數(shù),要避免上述錯誤就變得更簡單了。可惜, strdup不是ANSI C標(biāo)準(zhǔn)中的內(nèi)容。還有一點,無論是上面的原始代碼,還是其修正版本里都沒有檢查 malloc的返回值。我們忽略這個改進,是為了集中精力處理這里的主要問題。在實際程序中,對于m a l l o c、re
26、alloc、strdup及任何牽涉到分配的函數(shù),它們的返回值都必須做檢查。用else-if表達多路選擇。多路選擇的else,形式如下:表示法是采用一系列的if . else if . 這里的條件(condition)從上向下讀,遇到第一個能夠滿足的條件,就執(zhí)行對應(yīng)的語句,而隨后的結(jié)構(gòu)都跳過去。在這里,各個語句部分可以只是單個語句,也可以是由花括號括起的一組語句。最后一個else處理默認(rèn)情況,或說是處理沒有選中其他部分時的情況。如果不存在默認(rèn)動作,尾隨的else部分就可以沒有。另一個更好的辦法是利用它給出一個錯誤信息,以幫助捕捉“不可能發(fā)生”的情況。在這里應(yīng)該把所有的 e l s e垂直對齊,而
27、不是分別讓每個 e l s e與對應(yīng)的 i f對齊。采用垂直對齊能夠強調(diào)所有測試都是順序進行的,而且能防止語句不斷退向頁的右邊緣。一系列嵌套的 i f語句通常是說明了一段粗劣笨拙的代碼,或許就是真正的錯誤:這些i f要求讀它的人在頭腦里維持一個下推堆棧,不斷記住前面做了什么測試,讀到某個地方能把記住的內(nèi)出來,直到確定了對應(yīng)動作 (如果還記得的話)。由于這里最多就是做一個動作,改變測試順序完全可以得到一個更清晰的版本。這里失問題 。還糾正了原版本中的資源流從開始向下讀,直到遇到第一個值為真的測試,轉(zhuǎn)而去做對應(yīng)的動作,然后從最后的 e l s e后面繼續(xù)下去。這里要遵守的規(guī)則是:一個判斷應(yīng)該盡可能
28、接近它所對應(yīng)的動作。也就是說,一旦做過了一個測試,馬上就應(yīng)該去做某些事情。人們往往企圖重復(fù)使用某段代碼,結(jié)果常寫出一段緊緊糾纏在一起的程序:在這個開關(guān)語句里,寫了一個狡猾的從上面掉下 的語句序列,目的不過是避免重寫那僅有一行的代碼。這樣做也不符合寫法: c a s e語句最后都應(yīng)該寫一個b r e a ,k 如果偶爾有例外,第 1章 風(fēng)格計計13在那里一定要加上注釋。按傳統(tǒng)編排方式和結(jié)構(gòu)寫出來的內(nèi)容很容易讀,雖然稍微長了一點:增加長度也不符合提可能更清楚些:晰性的要求。還好,對于這種不常見的結(jié)構(gòu),用一系列 else-if語句寫圍在每個單行語句塊外面的花括號強調(diào)了它們是平行結(jié)構(gòu)。 “從上面掉下”
29、的方式在一種情況下是可以接受的,那就是幾個 case使用共同的代碼段。常規(guī)的編排形式是:這里不需要任何注釋。練-7 把下面的C/C+程序例子改得更清晰些:14計計程序設(shè)計實踐練-8 確定下面的Java程序段中的錯誤,并把它改寫為一個符合的循環(huán)。1.4函數(shù)宏老的C語言程序員中有一種傾向,就是把很短的執(zhí)行頻繁的計算寫成宏,而不是定義為函數(shù)。完成I/O的getchar,做字符測試的isdigit都是得到認(rèn)可的例子。人們這樣做最根本的理由就是執(zhí)行效率:宏可以避免函數(shù)調(diào)用的開銷。實際上,即使是在 C語言剛誕生時(那時的機器非常慢,函數(shù)調(diào)用的開銷也特別大 ),這個論據(jù)也是很脆弱的,到它就更無足輕重了。有了
30、新型的機器和編譯程序,函數(shù)宏的缺點就遠(yuǎn)遠(yuǎn)超過它能帶來的好處。避免函數(shù)宏。在C+ 里,函數(shù)更削減了函數(shù)宏的用武之地,在 Java里根本就沒有宏這種東西。即使是在C語言里,它們帶來的麻煩也比解決的問題。函數(shù)宏最常見的一個嚴(yán)重問題是:如果一個參數(shù)在定義中出現(xiàn)多次,它就可能被多次求值。如果調(diào)用時的實際參數(shù)帶有副作用,結(jié)果就會產(chǎn)生一個難以捉摸的錯誤。下面的代碼段來自某個,其意圖是實現(xiàn)一種字符測試:請注意,參數(shù)c在宏的體里出現(xiàn)了兩次。如果isupper在下面的上下文中調(diào)用:那么,每當(dāng)遇到一個大于等于 A的字符,程序就會將它丟掉,而下一個字符將被讀入并去與 Z做比較。C語言標(biāo)準(zhǔn)是仔細(xì)寫出的,它允許將 isu
31、pper及類似函數(shù)定義為宏,但要求保證它們的參數(shù)只求值一次。因此,上面的實現(xiàn)是錯誤的。直接使用ctype提供的函數(shù)總比自己實現(xiàn)它們更好。如果希望更安全些,那么就一定不要嵌套地使用像getchar這種帶有副作用的函數(shù)。兩個,這里還為捕捉文件結(jié)束留下機會:重寫上面的測試,把一個表達式改成有時多次求值帶來的是執(zhí)行效率問題,而不是真正的錯誤??紤]下面這個例子:這種寫法使平方根函數(shù)的計算次數(shù)比實際需要多了一倍。甚至對于很簡單的實際參數(shù),像ROUND_TO_體這樣的復(fù)雜表達式也會轉(zhuǎn)換成許多指令。這里確實應(yīng)該把它改成一個函數(shù),在需要時調(diào)用。宏將在它每次被調(diào)用的地方進行實例化,結(jié)果會導(dǎo)致被編譯的程序變大 (C
32、+的函數(shù)也存在這個缺點)。給宏的體和參數(shù)都加上括號。如果你真的要使用函數(shù)宏,那么請?zhí)貏e。宏是通過文本替換方式實現(xiàn)的:定義體里的參數(shù)被調(diào)用的實際參數(shù)替換,得到的結(jié)果再作為文本去替換原來第 1章 風(fēng)格計計15的調(diào)用段。這種做法與函數(shù)不同,常給人帶來一些麻煩。假如 square是個函數(shù),表達式:的工作將很正常。而如果它的定義如下:上面表達式將被展開成一個錯誤的內(nèi)容:這個宏應(yīng)該定義為:這里所有的括號都是必需的。即使是在宏定義里完全加上括號,也不可能解決前面所說的多次求值問題。所以,如果一個操作比較復(fù)雜,或者它很具一般性,值得包裝起來,那么還是應(yīng)該使用函數(shù)。C+ 提供的函數(shù)既避免了語法方面的麻煩,而且又
33、到宏能夠提供的執(zhí)行效率,很適合用來定義那些設(shè)置或者提取一個值的短小函數(shù)。練-9 確定下面的宏定義中的問題:神秘的數(shù)神秘的數(shù)包括各種常數(shù)、數(shù)組的大小、字符位置、變換因子以及程序中出現(xiàn)的其他以文字形式寫出的數(shù)值。給神秘的數(shù)起個名字。作為一個指導(dǎo)原則,除了 0和1之外,程序里出現(xiàn)的任何數(shù)大概都可以算是神秘的數(shù),它們應(yīng)該有自己的名字。在程序源代碼里,一個具有原本形式的數(shù)對其本身的重要性或作用沒提供任何指示性信息,它們也導(dǎo)致程序難以理解和修改。下面的片段摘自一個在2480的終端屏幕上打印字母頻率的直方圖程序,由于其中存在一些神秘的數(shù),程序的意義變得很不清楚:在這段代碼里包含許多數(shù)值,如 20、21、22
34、、23、27等等。它們應(yīng)該是互相有關(guān)系的但是它們確實有關(guān)系嗎?實際上,在這個程序里應(yīng)該只有三個數(shù)是重要的:24是屏幕的行數(shù); 80是列數(shù);還有26,它是字母表中的字母個數(shù)。但這些數(shù)在代碼中都沒出現(xiàn),這就使上面那些數(shù)顯得更神秘了。通過給上面的計算中出現(xiàn)的各個數(shù)命名,可以把代碼弄得更清楚些。發(fā)現(xiàn),例如數(shù)字3是由(80 - 1)/26得到的,而l e t應(yīng)該有26個項,而不是27個(這個超一(off-by-one)錯誤可能是由于寫程序的人把屏幕坐標(biāo)當(dāng)作從1開始而引起的)。通過一些簡化后,得到的結(jié)果代碼是:現(xiàn)在,主循環(huán)到底做什么已經(jīng)很清楚了:它是一個熟悉的從 0到N L E T的循環(huán),是一個對數(shù)據(jù)(數(shù)
35、組)元素操作的循環(huán)。程序里對 d r a w的調(diào)用也同樣容易理解,因為像MA X R O和WM I N C O這L樣的詞提醒實際參數(shù)的順序。更重要的是,現(xiàn)在已經(jīng)很容易把這個程序修改為能夠?qū)Ω镀渌钠聊淮笮』虿煌臄?shù)據(jù)了。數(shù)被揭掉了神秘的面紗,代碼的意義也隨之一目了然了。把數(shù)定義為常數(shù),不要定義為宏。 C程序員的傳統(tǒng)方式是用# d e f i n行e來對付神秘的數(shù)值。C語言預(yù)處理程序是一個強有力的工具,但是它又有些魯莽。使用宏進行編程是一種很的方式,因為宏會在背地里改變程序的詞法結(jié)構(gòu)。應(yīng)該讓語言去做正確的工作。在C和C+ 里,整數(shù)常數(shù)可以用枚舉語句,就像上面的例子里所做的那樣。在 C+里任何類型
36、都可使用c o n s t的常數(shù):在Java中可以用f in a l:C語言里也有c o n s 值t ,但它們不能用作數(shù)組的界。這樣, e n u m就是C中惟一可用的選擇了。使用字符形式的常量,不要用整數(shù)。人們常用在的函數(shù),或者用與它們等價的內(nèi)容檢測字符的性質(zhì)。有一個測試寫成這樣:第 1章 風(fēng)格計計17這種寫法將完全依賴于特殊的字符表示方式。寫成下面這樣更好一些:但是,如果在某個編碼字符集里的字母編碼不是連續(xù)的,或者其中還夾有其他字母,那么這種描述的效果就是錯的。最好是直接使用庫函數(shù),在 C和C+ 里寫:或在Java里面:與此類似的還有另一個問題,那就是程序里許多上下文中經(jīng)常出現(xiàn)的 0。雖
37、然編譯系統(tǒng)會把它轉(zhuǎn)換為適當(dāng)類型,但是,如果把每個 0的類型寫得更明確更清楚,對讀程序的人理解其作用是很有幫助的。例如,用 (void*)0或NULL表示C里的空指針值,用0而不是0表示字符串結(jié)尾的空字節(jié)。也就是說,不要寫:應(yīng)該寫成:贊成使用不同形式的顯式常數(shù),而把 0僅留做整數(shù)常量。采用這些形式實際上指明了有關(guān)值的用途,能起一點文檔作用??上У氖牵?C+ 里人們都已接受了用 0(而不是NULL)表示空指針。Java為解決這個問題采用了一種更好的方法,它定義了一個關(guān)鍵字 null,用來表示一個對象實際上并沒有任何東西。利用語言去計算對象的大小。不要對任何數(shù)據(jù)類型使用顯式寫出來的大小。例如,應(yīng)該
38、用s i z e o f (sizeof() 而不是 2 或者 4 ?;谕瑯釉颍瑢?s i z e o f ( a r r a y 0 ) 可能比) 更好,因為即使是數(shù)組的類型改變了,也沒東西需要改變。利用運算符sizeof常??梢院芊奖愕乇苊鉃閿?shù)組大小引進新名字。例如,寫:這里的緩沖區(qū)大小仍然是個神秘數(shù)。但是它只在這個中出現(xiàn)了一次。為局部數(shù)組的大小引進一個新名字價值并不大,而寫出的代碼能在數(shù)據(jù)大小或類型改變的情況下不需要任何改動,這一點肯定是有價值的。Java語言中的數(shù)組有一個length域,它給出數(shù)組的元素個數(shù):在C和C+里沒有與 .length對應(yīng)的內(nèi)容。但是,對于那些可以看清楚的數(shù)
39、組(不是指針),下面的宏定義能計算出數(shù)組的元素個數(shù):18計計程序設(shè)計實踐在這里,數(shù)組大小只在一個地方設(shè)置。如果數(shù)組的大小改變,其余代碼都不必改動。對函數(shù)參數(shù)的多次求值在這里也不會出問題,因為它不會出現(xiàn)任何副作用,事實上,這個計算在程序編譯時就已經(jīng)做完了。這是宏的一個恰當(dāng)使用,因為它做了某種函數(shù)無法完成的工作,從數(shù)組計算出它的大小。練-10 如何重寫下面定義,使出錯的可能性降到最小?1.6注釋注釋是幫助程序讀者的一種。但是,如果在注釋中只說碼本身已經(jīng)講明的事情,或者與代碼,或是以精心編排的形式干擾讀者,那么它們就是幫了倒忙。最好的注釋是簡潔地點明程序的突出特征,或是提供一種概觀,幫助別人理解程序
40、。不要大談明顯的東西。注釋不要去說明明白白的事,比如 i+能夠?qū)值加1等等。下面是我們認(rèn)為最沒有價值的一些注釋:所有這些都該刪掉,它們不過是一些無謂的喧囂。注釋應(yīng)該提供那些不能一下子從代碼中看到的東西,或者把那些散布在許多代碼里的信息收集到一起。當(dāng)某些難以捉摸的事情出現(xiàn)時,注釋可以幫助澄清情況。如果操作本身非常明了,重復(fù)談?wù)撍鼈兙褪钱嬌咛碜懔耍旱?1章 風(fēng)格計計19這些注釋也都應(yīng)該刪除,因為仔細(xì)選擇的名字已經(jīng)攜帶著有關(guān)信息。給函數(shù)和全局?jǐn)?shù)據(jù)加注釋。注釋當(dāng)然可以有價值。對于函數(shù)、全局變量、常數(shù)定義、結(jié)構(gòu)和類的域等,以及任何其他加上簡短說明就能夠幫助理解的內(nèi)容,都應(yīng)該為之提供注釋。全局變量常被分
41、散使用在整個程序中的各個地方,寫一個注釋可以幫人記住它的意義,也可以作為參考。下面是從本書第 3來的一個例子:放在每個函數(shù)前面的注釋可以成為幫人讀懂程序的臺階。如果函數(shù)代碼不太長,在這里寫一行注釋就足夠了。有些代碼原本非常復(fù)雜,可能是因為算法本身很復(fù)雜,或者是因為數(shù)據(jù)結(jié)構(gòu)非常復(fù)雜。在這些情況下,用一段注釋指明有關(guān)文獻對讀者也很有幫助。此外,說明做出某種決定的理由也很有價值。下面程序的注釋介紹了逆離散余弦變換 (inverse discrete cosine transform,DCT)的一個特別高效的實現(xiàn),它用在一個JPEG圖像器里:這個很有幫助的注釋點明了參考文獻,簡短地描述了所使用的數(shù)據(jù),
42、說明了算法的執(zhí)行情況,還說明為什么原來的代碼應(yīng)該修改,以及做了怎樣的修改等等。不要注釋差的代碼,重寫它。應(yīng)該注釋所有不尋常的或者可能迷惑人的內(nèi)容。但是如果注釋的長度超過了代碼本身,可能就說明這個代碼應(yīng)該修改了。下面的例子是一個長而釋和一個條件編譯的查錯打印語句,它們都是為了解釋一個語句:的注20計計程序設(shè)計實踐否定性的東西很不好理解,應(yīng)該盡量避免。在這里,部分問題來自一個毫無信息的變量名字result。改用另一個更具說明性的名字 matchfound之后,注釋就再沒有存在的必要,打印語句也變得清楚了:不要與代碼。許多注釋在寫的時候與代碼是一致的。但是后來由于修正錯誤,程序改變了,注釋常常還保持
43、著原來的樣子,從而導(dǎo)致注釋與代碼的脫節(jié)。這很可能是本章開始的那個例子的合理解釋。無論產(chǎn)生脫節(jié)的原因何在,注釋與代碼總會使人感到困惑。由于誤把錯誤注釋當(dāng)真,常常使許多實際查錯工作耽誤了大量時間。所以,當(dāng)你改變代碼時,一定要注意保證其中的注釋是準(zhǔn)確的。注釋不僅需要與代碼保持一致,更應(yīng)該能夠支持它。下面的例子里的注釋是正確的,它正確地解釋了后面兩行的用途。但細(xì)看又會發(fā)現(xiàn)它與代碼中說的則是空格:,注釋中談的是換行,而代碼一個可能的改進是采用慣用法重寫代碼:現(xiàn)在注釋和代碼一致了。但是這兩者都還可以進一步改進,應(yīng)該寫得更直截了當(dāng)些。這里要解決的問題就是刪除函數(shù) ctime返回時放在字符串最后的換行字符。注釋里應(yīng)該說明這個情況,代碼也應(yīng)該是做這件事:最后這個表達式是在C語言里截去字符串最后字符的它,解釋了為什么這
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 2025年度鄉(xiāng)村休閑旅游房屋租賃服務(wù)合同規(guī)范4篇
- 2025年度車輛轉(zhuǎn)讓附帶二手車置換及信貸服務(wù)協(xié)議4篇
- 無線網(wǎng)絡(luò)安全防護-第2篇-深度研究
- 二零二五版智能門禁系統(tǒng)設(shè)計與施工合同4篇
- 2025年度綠色農(nóng)業(yè)技術(shù)推廣與應(yīng)用合作協(xié)議4篇
- 二零二五年度明星代言產(chǎn)品開發(fā)合作協(xié)議4篇
- 2025年度農(nóng)藥殘留風(fēng)險評估與控制合同范本3篇
- 2025年新能源電動汽車個人租賃運營合同3篇
- 2025年度門禁監(jiān)控設(shè)備銷售與售后服務(wù)合同4篇
- 2025年度農(nóng)產(chǎn)品網(wǎng)絡(luò)營銷全程代理服務(wù)合同4篇
- 人教版物理八年級下冊 專項訓(xùn)練卷 (一)力、運動和力(含答案)
- 山東省房屋市政工程安全監(jiān)督機構(gòu)人員業(yè)務(wù)能力考試題庫-中(多選題)
- 《七律二首 送瘟神》教案- 2023-2024學(xué)年高教版(2023)中職語文職業(yè)模塊
- 2024年中考語文滿分作文6篇(含題目)
- 北師大版 2024-2025學(xué)年四年級數(shù)學(xué)上冊典型例題系列第三單元:行程問題“拓展型”專項練習(xí)(原卷版+解析)
- 2023年譯林版英語五年級下冊Units-1-2單元測試卷-含答案
- Unit-3-Reading-and-thinking課文詳解課件-高中英語人教版必修第二冊
- 施工管理中的文檔管理方法與要求
- DL∕T 547-2020 電力系統(tǒng)光纖通信運行管理規(guī)程
- 種子輪投資協(xié)議
- 執(zhí)行依據(jù)主文范文(通用4篇)
評論
0/150
提交評論