荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: jek (Super Like Cat), 信区: Program
标  题: 完美程式設計指南--序
发信站: 荔园晨风BBS站 (Thu Apr  4 06:55:17 2002), 转信

幾年前,我拿到一份TEX:Donald Knuth寫了這個程式,而這程式的前言讓我大吃
一驚:

我相信TEX的最後一隻蟲已經在1985年十一月二十七日被發現而且修正了。不過,
如果程式中還有些錯誤,我將很樂意付出一筆20.48美金的發現獎金給第一個發現
它的人。(這兩倍於之前的金額,而且我計劃每年增加一倍金額;如你所見,我真
的很有自信。)

我不知道Knuth有沒有付給人20.48美金或40.96美金;那並不重要。重要的是
Knuth對自己程式品質的自信。你認識多少個程式寫作者敢正經聲明說他們的程式
是完全沒錯誤的?多少人敢發表這樣一份聲明,然後提供一份臭蟲發現獎金的?

真正相信測試小組能找到所有問題的程式員們敢作出這樣的聲明,不過這就是問題
所在了。你已經聽過多少次程式員們在程式推出、包裝好、發行給經銷商前說,"
我希望測試人員已經找到所有的問題了"?他們只敢交叉手指,禱告說一切都會維
持在最好狀態。

今天的程式員們不確定他們的程式是否毫無錯誤,因為他們已經放棄徹底測試自己
程式的責任了。管理階層並沒有說過像"不要擔心測試你們程式的事-測試員會幫
你測試"的話,而且更不可思議的,他們雖然期望程式員測試自己的程式,但是卻
預期測試員能測試得更通盤些;因為這整個就是測試員被找來作的事。

本書的目的是說明程式員如何重新負起寫出零錯誤程式的責任。那不表示程式必須
在第一次寫出來就達到完美狀態-而是讓一樣產品在第一次進行測試前就已經達到
零錯誤的程度。有些程式員也許會懷疑的嘲笑這個想法,不過本書示範的技巧與提
供的準則還是可供作程式員們邁向那個目標前進之用。

兩個關鍵問題
寫出零錯誤程式的最關鍵需要是能夠理解什麼東西造成了臭蟲的出現。所有本書中
提到的技巧與準則都是程式寫作者們針對每個在自己程式中發現的問題,年復一年
、一遍又一遍詢問底下兩個問題後的產物:

我怎樣自動找到這種問題?

我怎樣預防這種問題的出現?

"多測試些"是對這些問題的簡單答案,但是那並不是個"自動"的答案,也不真的是
個預防性的解答。如"多測試些"的答案普遍得一點也不帶勁-這類型的答案一點用
處也沒有。解決這些問題的良好答案來自於用來消除我們碰到的這些問題的特殊技
巧。

本書專門提供被找來減少或完全消滅整類程式臭蟲的技巧與準則。有些做法完全違
背常見的程式寫作方式,但在你拿"每個人都違背這些準則"或"沒人遵守這種東西
"當作駁斥它們的理由前,先自己停下來好好想想。如果是"沒人遵守這種東西",
那人們為何會不遵守它呢?自己好好確認一下人們為何不照著作的理由,這理由是
不是合宜的。在FORTRAN語言當道的那個年代有意義的做法在現在未必有意義的。


這並不是說你應該盲目遵循本書的指示,這些指示並不是常規。太多程式設計者將
"不要使用goto敘述"這樣的指示當成由上帝所說那不可違背的戒律。當問起我們為
何如此強烈排斥goto時,人們說那樣子會讓程式碼盤根錯節得難以維護。可是經驗
老到的程式寫作者經常加上觸怒編譯器程式最佳化功能的goto敘述。其實,兩種做
法都是合宜的。有些時候,謹慎的使用goto還能大大增進程式的清晰性與效率,照
著"不要使用goto敘述"的指示去作反而會讓程式碼變得更差而非更好。

本書中提供的準則也沒什麼不同:它們大部分時候應被遵從,但也有為了得到更佳
結果而違背的時候。

除了這些準則與技巧,本書大部分的篇章都在結尾有段"該想想的東西"。這些段落
裡的問題探索了那些章節前頭未曾提到的東西。這些問題不是習題-它們並不是用
來測驗你對該章節的了解程度的。我試著在每個問題裡頭介紹一個新觀念,而且我
提供了整組的答案來盡可能的傳遞更多訊息。如果你經常跳過習題不作,那考慮看
看 附錄C 裡頭的習題解答吧,這樣你才不會遺漏任何我在這些段落裡所要介紹的
技巧或準則。


------------------------------------------------------------------------
--------
建立在現有基礎上
用C語言寫了一陣子程式的程式員們都知道他們應該在巨集定義的參數前後加上括
號;他們知道字串後頭有看不見的nul字元;他們知道C語言中的陣列元素註標是從
0開始而不是從1開始;他們也知道必須用break敘述來避免switch的一個狀況執行
完後跑去執行別的狀況的處理敘述。這些認知與其他對C語言的錯誤認知經常是程
式臭蟲的根源,不過在本書的討論中,你不會找到這些問題的討論,除非它們包含
在我所要提到的一些東西的一部份討論裡頭。我試著將內容集中在鮮為人知或少見
於市面上,你很少能在程式寫作的教科書中或是程式設計的訓練課程裡學到的零錯
誤程式寫作技巧上。

我不曾試著重新整理Brian Kernighan跟P.J. Plauger合箸的程式設計經典
Elements of Programming Style書中已提到而廣為人知的那些準則。雖然
Kernighan跟Plauger在他們的範例中使用FORTRAN與PL/I,他們的準則-只有一小
部份例外-還是可以用到任何程式設計語言上,包括C語言在內。本書建立在
Elements of Programming Style確立的基礎之上,而且依循著類似的風格。

最後,雖然本書是為有著真實時間底線的真正程式寫作計劃的專業程式員們所寫的
,對於高等C語言程式設計課程的學生們一樣適用。有些修過編譯器課程的學生會
寫出自己的編譯器,不過大部分人會專注於寫出零錯誤程式上。我希望本書能夠給
予學生在畢業後寫出有著成品水準的穩固程式所需的技巧。


------------------------------------------------------------------------
--------
什麼是麥金塔?
有時一本書如果不提到PDP-11,IBM 360或其他的老古董,幾乎就引不起別人的重
視。所以我才在這裡提到這些老古董,而本書之後就完全不會再提到它們囉。本書
中你最常看到的電腦系統是MS-DOS,Microsoft Windows,以及蘋果公司的麥金塔
電腦,尤其是麥金塔-因為我最近才在這些系統上頭寫過程是。

你也會在本書中聽到許多Microsoft Excel與Microsoft Word的發展歷史。Excel是
微軟公司的圖形化試算表程式,原先是為麥金塔電腦而寫的,後來又明顯改寫、整
理並加強過,移植到Windows平台上。

本書從頭到尾,我都會談到我身為一名麥金塔版Excel程式寫作者的經驗,不過我
必須承認我將大部分的時間花在把Windows版的程式碼移植成麥金塔上執行的版本
跟實作相似於Windows版Excel已經有了的功能上,這套產品的傑出成就跟我在這套
產品中出的力不太相關。

我對麥金塔版Excel的唯一策略貢獻是說服了微軟當局停止發展麥金塔專門版的
Excel,而從改良許多了的Windows版Excel的原始碼直接建立起麥金塔版的東西。
麥金塔版的Excel 2.2是第一個從Windows版的Excel移植而來的版本,有80%的原始
碼是相同的。這對麥金塔版的Excel使用者來說是一大改善,因為從2.2版中,它們
可以看到功能與品質上的重大突破。

Word是微軟的文書處理應用程式。實際上,有三個版本的Word:文字模式的
MS-DOS版Word,麥金塔版的Word與Windows版的Word。當我寫這本書時,這三套產
品依然是從不同的原始碼產生出來的,不過每個版本對於大部分使用者來說都還是
挺像的,它們可以以同樣的操作方式來使用這三個不同的版本而不會碰到什麼障礙
。不過,所有版本的Word最後都會從共通的原始碼產生出來,這項工程正在進行中
。(譯按:現在市面上的麥金塔版與Windows版的Word應該都是從同樣的原始碼產
生出來的了。)

程式碼的寫法
即使不是MS-DOS,Microsoft Windows或蘋果麥金塔的專家也可以看懂本書的程式
碼-這些程式都是直接用C寫成的,應該可以在任何ANSI C的開發系統上編譯與執
行。

不過,如果你是個主機程式員或者在微電腦的程式設計上沒有什麼經驗,要留心微
電腦的作業系統還很少有支援記憶體保護措施的。你可以透過NULL指標進行記憶體
讀寫,亂填堆疊框,或是在記憶體中到處亂丟垃圾-即使是屬於其他應用程式的記
憶體-而硬體在你作這些事情時並不會提出任何警告。如果你原先認為"硬體要會
抓出這種問題",那是你原來的程式發展環境剛好幸運的是個有著強力保護措施的
系統而已。並不是所有的程式員都如此幸運的。

在好幾個地方我用到了一些不太合乎標準要求的ANSI C程式酷函式。例如ANSI C版
本的memchr函式將字元c宣告成int變數:

void *memchr(const void *s,int c,size_t n);
內部運作上,memchr將這個字元當成無正負號的unsigned char字元,但是字元為
了後向相容於ANSI標準頒布以前那沒有雛形宣告時代的原始碼,被宣告成了有正負
號的int有號整數。由於我在本書從頭到尾都使用ANSI C語法,我將這些後向相容
的細節完全拋棄,為了讓語法清晰與符合較強勢的型態檢查,而使用了更精確的變
數型態。在第一章中,我會更詳細解釋為何這麼做是必要的,不過現在只要記住並
不是所有的"標準"函式都是精確照著標準寫出來的。你將經常看到memchr的函式中
引用的字元變數被宣告成了char而非int:

void *memchr(const void *pv,unsigned char ch,size_t size);
譯註:在現在的Mac OS,Windows 95/98/NT上都提供了記憶體保護的支援,不過有
時候作業系統實作會為了效率與方便上的需求,在記憶體保護的支援上打出許多大
洞,這也是為何寫錯了的程式還是可以很輕鬆的把這些支援了記憶體保護的微電腦
作業系統當掉的原因。

那些冗長難懂的名稱是作什麼用的?
現在你大概已經概略翻閱過這本書,並注意到程式碼列表裡那許多長得很奇怪的變
數與函式名稱,如常見的pch,ppb與pvResizeBlock。

雖然像pch這樣的名稱看來有趣而且難唸,它們還是表達了些訊息-只要你了解這
種由Charles Simonyi在1970年代早期發展出來的"匈牙利"命名規則(
Huangarian Naming Convention),這種命名規則的前提是讓一個變數名稱除了代
表一個變數本身以外,更重要的是讓檢閱程式碼的人能夠了解一個變數存在的意義


我在本書中使用的簡化版匈牙利命名規則的細節相當簡單。對你程式中每種資料型
態的變數,你拿這資料型態的縮寫作為變數名稱的一部份命名。這不算什麼重大突
破-寫程式的人們老早就習慣了把他們的字元變數叫做c或ch,位元組變數叫做b,
整數變數叫做i等等的做法,匈牙利命名規則只是強制標明了程式中每一種資料型
態的變數。例如:

char    ch;       /* 一個簡單的字元 */
byte    b;        /* 一個位元組,也可以代表無正負號的字元 */
flag    f;        /* 永遠為TRUE或FALSE的旗標 */
symbol  sym;      /* 某種符號結構 */
這種做法並不指定資料型態的縮寫該長什麼樣子-只要在程式中能夠前後一致的表
示同個資料型態就好了。

指標變數帶來了一個有趣的問題,它們總是指向別的資料。對於這個問題,匈牙利
命名規則要求所有指標變數的名稱都以p開始,接著它們指向的資料型態名稱的縮
寫。如果你要宣告指向上頭那些資料的指標,你就會寫成如底下這樣:

char    *pch;       /* 一個字元指標 */
byte    *pb;        /* 位元組指標 */
flag    *pf;        /* 指向自己型態的指標等等 */
symbol  *psym;
指向其他指標的指標也跟指向一般資料型態的指標沒什麼太大的不同-只是在變數
名稱之前再多個p而已。對於一個指向字元指標的指標,它的名稱就是在pch之前再
多個p:

char    **ppch;      /* 指向一個字元指標的指標 */
這種照著匈牙利命名規則接起來的資料型態用法不好唸,不過這命名規則允許程式
員在變數型態的縮寫之後接上一兩個有描述作用的單字-每個都以大寫字母起頭。
這不只增進了可讀性,也易於分辨兩個變數型態相似的變數。以strcpy函式為例,
使用兩個字元指標作為參數,函式的雛形宣告應該如下:

char *strcpy(char *pchTo,char *pchFrom); /* 函式雛形宣告 */
有另一點要提的,匈牙利命名規則的用途在於增進變數使用的可讀性,注重資料型
態所代表的意義更甚於它們實際宣告的形式。雖然strcpy的兩個參數都宣告成指標
變數的型態,但更重要的,它們指向以零字元結尾的字串。將strcpy的兩個參數以
pch開頭命名是對的,但是以str命名的話,會更有意義:

char *strcpy(char *strTo,char *strFrom); /* 函式雛形宣告 */
兩個以str起頭的變數仍然是字元指標,但是當你看到這些名稱時,你知道它們是
比較特別的字元指標-它們指向字串。函式與陣列名稱也依循同樣的命名規則-它
們以傳回的型態接著一個描述性的標籤命名。在正式的匈牙利命名規則中,函式名
稱永遠以大寫起頭,但在本書中,我為了一致性,將命名方式加以統一;命名時的
大寫用法在函式名稱、陣列名稱及變數名稱之間並沒有什麼不同。如果把標準的
malloc跟realloc函式寫成匈牙利命名規則的形式,它們的函式雛形宣告將如下:


void *pvNewBlock(size_t size);            /* 函式雛形宣告 */
void *pvResizeBlock(void *pv,size_t sizeNew);  /* 函式雛形宣告 */
使用匈牙利命名規則的一個好處是易於解讀指標運算式。例如,你在本書中看到許
多指向指標的指標,特別是ppb的:

*ppb = pbNew;
雖然這段程式碼可能在第一次看到時讓人看不懂,一旦你了解你可以拿掉這種運算
式中的*跟p後,你就能簡單了解這種運算式是用來作什麼的了。如果把上頭這運算
式中的*跟一個p拿掉,你得到

pb = pbNew;
由於資料型態吻合-都是pb-你知道這個運算式是正確的。&跟->也可以跟p一起拿
掉。考慮下面的敘述:

pb = & b;
b = psym->bLength;
把第一個敘述中的p跟&一起拿掉,你會得到一個把位元組變數的值指派給一個位元
組變數的敘述,而你知道這樣的運算式是合法的。接下來第二個敘述,如果你把p
跟->拿掉,你也會得到把一個位元組變數的值指派給一個位元組變數的敘述:b
= sym.bLength. 這種稱作"型態運算"的做法簡化了拆開複雜的指標運算式的難度


雖然匈牙利命名規則還有很多部分沒提到,這裡提到的大略足夠讓你看懂本書中的
程式碼了。


------------------------------------------------------------------------
--------
想知道更多?
本書中採用的簡化版匈牙利命名規則與Charles Simonyi發展的完整版本完全不能
相較。pch[]是個pch的陣列呢,或者它只是個指向一個字元陣列的指標呢?在我用
的簡化版匈牙利命名規則中,你得看過這變數的宣告後才能確定是哪一種,但在完
整版的匈牙利命名規則中完全沒有這種狀況與其他模稜兩可的情形。我使用簡化過
的版本,只因為這麼做比較簡單,而且本書中不會碰到那些模稜兩可的情形。對於
只使用Charles Simonyi那定義完整的命名規則的一小部份,我在此道歉。

匈牙利命名規則並不適用於所有人。有些人會認為這麼做在結構化程式設計中是最
好的;其他人則只是因為因為個人感覺而討厭它,兩派人馬各有主張。如果你覺得
匈牙利命名規則有意思,你想深入了解一下,你可以在Charles Simonyi的博士論
文:"Meta-Programming: A Software Production Method"(Standford
University,1997; Xerox Palo Alto Research Center,1997)中找到找到這種
命名規則的完整討論。


------------------------------------------------------------------------
--------
什麼東西才算程式臭蟲?
在我們開始第一章那種假想中會幫你抓蟲的編譯器的討論前,我應該稍微解釋一下
本書所要消滅的程式錯誤的種類。我了解你曉得程式錯誤是什麼-我不需要為讀這
本書的人定義什麼叫做臭蟲吧?不過在本書中,我對兩種程式錯誤作了分類:你在
寫出一個功能時產生的臭蟲,跟你認為你的程式已經完成了以後仍然存在的臭蟲。


許多軟體工作室使用原始碼控制系統來簡化程式發展。一名程式員大致以查閱圖書
館中藏書的方式來找尋需要修改的檔案,唯一的不同點在於,程式員找到的是檔案
的一份複製品,而不是檔案本身。這讓程式員可以寫作新功能而不用碰到原始碼正
本。一旦程式員完成了這個新功能,而且確定程式中沒蟲了,這檔案就被放回去更
新,原始碼控制系統據此更新原始碼正本。

在這種做法之下,一名程式員不必在乎自己寫作新功能時究竟產生了多少錯誤,只
要所有的錯誤在新程式碼歸檔進原始碼正本前都被抓出來了就好。

本書中我所要講的"臭蟲",是指那些存在於原始碼正本裡頭的,那些對推出的產品
造成傷害,而且會影響到消費者的那種。我不期望程式員們每次坐在鍵盤前面時都
能寫出毫無缺點的程式,但是我相信避免讓臭蟲出現在原始碼正本中是辦得到的一
件事。

接下來的篇章裡所出現的準則與技巧說明了如何寫出這樣的零錯誤程式。


--
 === I love Puss forever ===

※ 来源:·荔园晨风BBS站 bbs.szu.edu.cn·[FROM: 192.168.1.241]


[回到开始] [上一篇][下一篇]

荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店