程序設計實踐----編程風格

     程序設計風格的原則根源於由實際經驗中得到的常識,它不是隨意的規則或者處方。代碼應該是清楚地和簡單的——具有直截了當的邏輯、自然地表達式、通行的語言使用方式、有意義的名字和有幫助作用的註釋等,應該避免耍小聰明的花招,不使用非正規的結構,一致性是非常重要的東西。

1.1名字

       什麼是名字?一個變量或函數的名字標識這個對象,帶着說明其用途的一些信息。一個名字應該是非形式的、簡練的、容易記憶的,如果可能的話,最好是能夠拼讀的。許多信息來自上下文和作用範圍(作用域)。一個變量的作用域越大,它的名字所攜帶的信息就應該越多。全局變量使用具有說明性的名字,局部變量用短名字。根據定義,全局變量可以出現在整個程序中的任何地方,因此它們的名字應該足夠長,具有足夠的說明性,以便使讀者能夠記得它們是幹什麼用的。給每個全局變量聲明附一個簡短註釋也非常有幫助。

        人們常常鼓勵程序員使用長的變量名,而不管用在什麼地方。這種認識完全是錯誤的,清晰性經常是隨着簡潔而來的。

        現實中存在許多命名約定或者本地習慣。常見的比如:指針採用以p結尾的變量名,例如nodep;全局變量用大寫開頭的變量名,例如Global;常量用完全由大寫字母拼寫的變量,如C O N S T A N T S等。有些程序設計工場採用的規則更加徹底,他們要求把變量的類型和用等都編排進變量名字中。例如用p c h說明這是一個字符指針,用s t r T o和s t r F r o m表示它分別是將要被讀或者被寫的字符串等。至於名字本身的拼寫形式,是使用 n p e n d i n g或P e n d i n g還是n u m _ p e n d i n g,這些不過是個人的喜好問題,與始終如一地堅持一種切實際的約定相比,這些特殊規矩並不那麼重要。

        函數採用動作性的名字。函數名應當用動作性的動詞,後面可以跟着名詞:

        now = date.getTime();

        putchar('\n');
對返回布爾類型值(真或者假)的函數命名,應該清楚地反映其返回值情況。下面這樣的語句

        if(checkoctal(c))...

是不好的,因爲它沒有指名是麼時候返回真,什麼時候返回假。而:

       if(isoctal(x))...

就把事情說清楚了:如果參數是八進制數字則返回真,否則爲假。

1.2  表達式和語句

         名字的合理選擇可以幫助讀者理解程序,同樣,我們也應該以儘可能一目瞭然的形式寫好表達式和語句。應該寫最清晰的代碼,通過給運算符兩邊加空格的方式說明分組情況,更一般的是通過格式化的方式來幫助閱讀。這些都是很瑣碎的事情,但卻又是非常有價值的,就像保持書桌整潔能使你容易找到東西一樣。與你的書桌不同的是,你的程序代碼很可能還會被別人使用。

        用加括號的方式排除二義性。括號表示分組,即使有時並不必要,加了括號也可能把意圖表示得更清楚。在上面的例子裏,內層括號就不是必需的,但加上它們沒有壞處。熟練的程序員會忽略它們,因爲關係運算符(< <= == != >= > )比邏輯運算符(& &和| |)的優先級更高。

1.3 一致性和習慣用法

      爲了一致性,使用習慣用法。和自然語言一樣,程序設計語言也有許多慣用法,也就是那些經驗豐富的程序員寫常見代碼片段的習慣方式。在學習一個語言的過程中,一箇中心問題就是逐漸熟悉它的習慣用法。

       常見習慣用法之一是循環的形式。而習慣用法的形式卻是:

       for (i = 0; i < n; i++)

            array[i] = 1.0;

       這並不是一種隨意的選擇:這段代碼要求訪問n元數組裏的每個元素,下標從0到n-1。在這裏所有循環控制都被放在一個f o r裏,以遞增順序運行,並使用+ +的習慣形式做循環變量的更新。這樣做還保證循環結束時下標變量的值是一個已知值,它剛剛超出數組裏最後元素的位置。熟悉C語言的人不用琢磨就能理解它,不加思考就能正確地寫出它來。

        對於無窮循環,我們喜歡用:

       for (; ;)...

      但while(1)...

也很流行。請不要使用其他形式。

         縮排也應該採用習慣形式。  

        一致地使用習慣用法還有另一個優點,那就是使非標準的循環很容易被注意到,這種情況常常預示着有什麼問題:

1.4 函數宏

      老的C語言程序員中有一種傾向,就是把很短的執行頻繁的計算寫成宏,而不是定義爲函數。完成I / O的g e t c h a r,做字符測試的i s d i g i t都是得到官方認可的例子。人們這樣做最根本的理由就是執行效率:宏可以避免函數調用的開銷。實際上,即使是在C語言剛誕生時(那時的機器非常慢,函數調用的開銷也特別大),這個論據也是很脆弱的,到今天它就更無足輕重了。有了新型的機器和編譯程序,函數宏的缺點就遠遠超過它能帶來的好處。 

        函數宏最常見的一個嚴重問題是:如果一個參數在定義中出現多次,它就可能被多次求值。如果調用時的實際參數帶有副作用,結果就會產生一個難以捉摸的錯誤。

       給宏的體和參數都加上括號。如果你真的要使用函數宏,那麼請特別小心。宏是通過文本替換方式實現的:定義體裏的參數被調用的實際參數替換,得到的結果再作爲文本去替換原來的調用段。

        C++ 提供的在線函數既避免了語法方面的麻煩,而且又可得到宏能夠提供的執行效率,很適合用來定義那些設置或者提取一個值的短小函數。

1.5 神祕的數

      神祕的數包括各種常數、數組的大小、字符位置、變換因子以及程序中出現的其他以文字形式寫出的數值。

字形式寫出的數值。
       (1) 給神祕的數起個名字。作爲一個指導原則,除了0和1之外,程序裏出現的任何數大概都可以算是神祕的數,它們應該有自己的名字。在程序源代碼裏,一個具有原本形式的數對其本身的重要性或作用沒提供任何指示性信息,它們也導致程序難以理解和修改。下面的片段摘自一個在2 4×8 0的終端屏幕上打印字母頻率的直方圖程序,由於其中存在一些神祕的數,程序的意義變得很不清楚:



        在這段代碼裏包含許多數值,如2 0、2 1、2 2、2 3、2 7等等。它們應該是互相有關係的……但是……它們確實有關係嗎?實際上,在這個程序裏應該只有三個數是重要的:2 4是屏幕的行數;8 0是列數;還有2 6,它是字母表中的字母個數。但這些數在代碼中都沒出現,這就使上面那些數顯得更神祕了。

通過給上面的計算中出現的各個數命名,我們可以把代碼弄得更清楚些。我們發現,例如數字3是由( 8 0-1 ) / 2 6得到的,而l e t應該有2 6個項,而不是2 7個(這個超一( o ff - b y - o n e )錯誤可能是由於寫程序的人把屏幕座標當作從1開始而引起的)。通過一些簡化後,我們得到的結果代碼是:


        現在,主循環到底做什麼已經很清楚了:它是一個熟悉的從0到N L E T的循環,是一個對數據(數組)元素操作的循環。程序裏對d r a w的調用也同樣容易理解,因爲像M A X R O W和M I N C O L這樣的詞提醒我們實際參數的順序。更重要的是,現在我們已經很容易把這個程序修改爲能夠對付其他的屏幕大小或不同的數據了。數被揭掉了神祕的面紗,代碼的意義也隨之一目瞭然了。

        把數定義爲常數,不要定義爲宏。C程序員的傳統方式是用# d e f i n e行來對付神祕的數值。C語言預處理程序是一個強有力的工具,但是它又有些魯莽。使用宏進行編程是一種很危險的方式,因爲宏會在背地裏改變程序的詞法結構。我們應該讓語言去做正確的工作 。

       與此類似的還有另一個問題,那就是程序裏許多上下文中經常出現的0。雖然編譯系統會把它轉換爲適當類型,但是,如果我們把每個0的類型寫得更明確更清楚,對讀程序的人理解其作用是很有幫助的。例如,用(v o i d *) 0或N U L L表示C裏的空指針值,用‘\ 0’而不是0表示字符串結尾的空字節。

         利用語言去計算對象的大小。不要對任何數據類型使用顯式寫出來的大小。例如,我們應該用s i z e o f(i n t)  而不是2或者4。基於同樣原因,寫s i z e o f(a r r a y [ 0 ] ) 可能比s i z e o f(i n t) 更好,因爲即使是數組的類型改變了,也沒有什麼東西需要改變。利用運算符s i z e o f常常可以很方便地避免爲數組大小引進新名字。

1.6 註釋

       最好的註釋是簡潔地點明程序的突出特徵,或是提供一種概觀,幫助別人理解程序。註釋應該提供那些不能一下子從代碼中看到的東西,或者把那些散佈在許多代碼裏的信
息收集到一起。當某些難以捉摸的事情出現時,註釋可以幫助澄清情況。如果操作本身非常明瞭,重複談論它們就是畫蛇添足了。

        否定性的東西很不好理解,應該儘量避免。

        無論產生脫節的原因何在,註釋與代碼矛盾總會使人感到困惑。由於誤把錯誤註釋當真,常常使許多實際查錯工作耽誤了大量時間。所以,當你改變代碼時,一定要注意保證其中的註釋是準確的。

        學生常被告之應該註釋所有的內容。職業程序員也常被要求註釋他們的所有代碼。但是,應該看到,盲目遵守這些規則的結果卻可能是丟掉了註釋的真諦。註釋是一種工具,它的作用就是幫助讀者理解程序中的某些部分,而這些部分的意義不容易通過代碼本身直接看到。我們應該儘可能地把代碼寫得容易理解。在這方面你做得越好,需要寫的註釋就越少。好的代碼需要的註釋遠遠少於差的代碼。

1.7 爲何對此費心

        這裏最關鍵的結論是:好風格應該成爲一種習慣。如果你在開始寫代碼時就關心風格問題,如果你花時間去審視和改進它,你將會逐漸養成一種好的編程習慣。一旦這種習慣變成自動的東西,你的潛意識就會幫你照料許多細節問題,甚至你在工作壓力下寫出的代碼也會更好。





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章