荔园在线

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

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


发信人: jek (Super Like Cat), 信区: Program
标  题: 完美程式設計指南--調整心態
发信站: 荔园晨风BBS站 (Thu Apr  4 07:02:34 2002), 转信

本書從頭到尾,我都在談你能用來偵測跟避免錯誤的技巧。使用這些技巧不保證你
就能夠寫出零錯誤程式,就好像一隊有熟練的球員也不能保證你贏得球賽。其他必
要的關鍵包括良好習慣與態度的組合。

如果球員們都在抱怨自己為何要練習,你認為這些球員會贏得季賽冠軍嗎?如果他
們經常為了年薪只有一百二十萬美元或總是在擔心被出賣或炒魷魚,他們的表現會
如何?這些都跟球技無關,可是跟球員的表現息息相關。

你可以用本書中全部的建議來幫自己消除錯誤,可是如果你的態度或程式寫作習慣
上就有問題,你就很難寫得出零錯誤程式來。

在本章中,我將談到一些寫出零錯誤程式的最常見障礙。這些障礙都很好修正;往
往,你需要的只是注意到這些障礙的存在而已。

我的下個把戲可以把錯誤變不見
你多常在問別人修好錯誤時聽到他們回答,"哦,那錯誤不見了吧"?我許多年前對
我的第一個經理說過同樣的話。他問我,找到一個我們正在開發的蘋果二號電腦資
料庫產品中的一個錯誤找到沒,我說,"哦,那錯誤不見了。"那經理楞了一下,然
後把我叫進他的辦公室內,我們都坐了下來。

"Steve,你說‘錯誤不見了’,那是什麼意思?"

"喔,你知道嘛,我照著錯誤報告中的步驟檢查過,可是那錯誤沒出現啊。"

我的經理靠回他的椅背。"那你認為這問題怎麼了?"

"我不知道",我說。"我猜問題已經被修好了吧。"

"可是你不知道問題有沒有被修好啊,不是嗎?"

"我是不知道,我想我不知道",我必須承認這點。

"那你不覺得你最好把真正發生了的什麼問題找出來嗎?畢竟,是你在用電腦工作
,而不是電腦在幫你工作;電腦不會自己把問題修好的。"

那名經理繼續解釋了三種錯誤消失的原因:錯誤報告不正確,別的程式員修好了問
題,或者問題還在,只是問題不明顯。他的最後一席話提醒了我,作為一名專業程
式寫作者,我應該決定出我碰到的那些不見了的問題是哪一種情形,並得據此處理
狀況。我不應該因為一個問題消失了,就忽略掉它的存在。

在我第一次聽到那番建議的CP/M跟蘋果二號時代裡,那是很寶貴的意見。其實那番
建議在之前幾十年裡就很珍貴了,即使到了現在,那一席話對我來說,還是無價之
寶。一直到我成為一名專案主持人,我才了解那番話有多麼珍貴。我發現對程式員
們來說,他們常會假設測試人員搞錯了,或者已經有人把問題修好了。

程式中的問題常常只因為你跟測試人員使用不同版本的程式而消失。如果一個錯誤
在你使用的程式中沒出現,你該換用測試人員用的版本看看。如果問題還是沒重現
,通知你的測試人員重新檢查一下。如果錯誤又出來了,往更早期的版本中找找看
,決定該怎麼修理這個問題,然後看看為什麼問題在現在的版本中會消失。更通常
的,一個錯誤還在,只是被周圍的改變給遮蓋住了。你得了解為什麼問題消失了,
你才能採取適當步驟來修正它。


------------------------------------------------------------------------
--------

臭蟲不會自己"消失"。


------------------------------------------------------------------------
--------

------------------------------------------------------------------------
--------
太多努力?
當我要求程式員們把舊版的原始碼拿出來檢查一個已知的問題時,程式員們時而會
抱怨;他們抱怨說這樣子似乎是在浪費時間。如果你也這麼認為,考慮一下,我並
不是因為一時興起才叫你把老程式碼找出來的。叫你把老程式翻出來看是因為問題
很可能也出現在舊版的原始碼中,而且在舊版本的程式中找起錯誤來比現在的版本
更有效率。

假使你在較早版本的原始碼中找到了問題所在,並發現那問題已經確實在目前的程
式中修正了。你浪費時間了嗎?沒有吧。畢竟,將一個消失的問題標示為"修好了
"或是"無法重現"後再送回去叫測試人員把它找出來,與前面的做法比較起來,哪
一種比較好?測試人員當然不能假設問題修好了-他們只有兩個選擇,花更多時間
試著把問題重現,或是跟你作的一樣,當作問題是不能重現的,希望問題真的已經
修好了。兩種選擇都比你自己在較早期的原始碼中找出問題來,把問題真正標示為
"修好了"來得糟糕。


------------------------------------------------------------------------
--------
即時修正問題才能避免更多問題
當我第一次進到Microsoft Excel的小組時,人們的做法是把所有錯誤的修正都放
到專案末尾再處理。並沒有一塊寫著"你在所有功能都寫好以前不應該修理任何問
題"的鐵板釘在小組工作室的牆上,可是我們有趕上時間表跟作出程式功能的壓力
。同時,很少壓力要求我們把問題處理掉。有一次我聽到說,"除非臭蟲把系統當
掉了或者困住了測試小組,不然不要管修正錯誤的事情。反正在我們完成所有預定
功能後,我們還有很多時間可以修正問題。"簡單說來,修正錯誤並不是優先該做
的事。

我確定這對現在的微軟程式員們聽來有些退步,因為沒有任何專案再以那樣的方式
進行了;那樣的做法有太多問題,而且最糟糕的是難以預料產品哪時會完成。你怎
麼估計修理1742種錯誤需要的時間?當然,不會只有1742種問題等著你-程式員們
在修理老問題時還會產生新問題出來。而且(相關的)錯誤修正可能引出其他測試
小組在一開始因為先前的錯誤而沒找到的潛在問題。

這些還不只是唯一出現的問題而已。

先完成程式功能再修正錯誤,程式員們得讓產品開發時間看來比真正需要的還短。
公司的重要人物會用內部版本,來產品是不是除了偶爾出現的問題以外都能夠正常
動作,然後懷疑為何開發人員得花六個月來完成一個接近完成的產品。他們不會發
現記憶體不足的問題,也不會發現他們沒用到的功能中出現的錯誤。他們只知道程
式的功能完成了,而且基本上會動了。

在專案開發週期末尾花好幾個月來修正問題在團隊士氣上也是一大打擊。程式員們
喜歡寫程式,而不是修正問題,可是在開發每個產品的末尾,他們得花好幾個月,
就只是在修理問題,承受沉重的壓力,只因為開發部門以外的人都認為產品接近完
成了。為何產品趕不上COMDEX,MacWorld Expo這些世界級的展覽,或一個地區電
腦俱樂部的聚會?

真夠糟糕的。

一系列錯誤百出的產品,從麥金塔版的Excel 1.03開始,到一個取消推出-因為到
處橫行的臭蟲清單太長了而取消-的未公開Windows版產品,強迫微軟公司對開發
了的產品採取嚴密的檢查。然後得到底下不太令人驚訝的發現:

在產品週期中延後修正錯誤並不會省下時間。事實上,因為修理一個出現一年多的
錯誤比修正一個出現沒幾天的問題經常更難,你會失去更多時間。

立即修理問題控制了錯誤造成的傷害,因為你預早了解怎麼犯下錯誤的,你愈不可
能重蹈覆轍。

臭蟲是一種抑制速成的懶散程式員出現的一種負回饋現象。如果你不讓程式員在修
理好所有自己產生的問題前繼續寫新功能,你就能避免懶散的程式員在程式中到處
留下寫了一半的功能-他們只會忙著修正錯誤。如果你讓程式員忽略他們的錯誤,
你就喪失了團隊紀律。

讓錯誤數維持在零附近,你就更容易預測何時能完成產品。不用再猜要花多久才能
完成32項功能跟1742種錯誤修正,你只需要推算32種功能要花多久完成。更棒的,
你隨時都可以視需要放棄未完成的功能而推出產品。

這些發現不只適用於微軟的程式開發;他們對任何軟體開發團隊也同樣適用。如果
你在發現問題時沒有立刻修好它們,把微軟公司的負面經驗當成你的教訓吧。你當
然可以自己從錯誤中學習,不過從別人寶貴的失敗經驗中獲取教訓不是更好嗎?


------------------------------------------------------------------------
--------

不要以後再修理錯誤;現在就把問題修好吧。


------------------------------------------------------------------------
--------
臭蟲救星來了!
在Awaken the Giant Within一書中,Anthony Robbins說了一個醫生的故事。一名
女醫生在湍急的河流邊聽到有人掉進河裡的慘叫,她看看四周都沒人能幫忙,就自
己跳進河裡去救人了。當她游過去把人就到岸邊,並作了口對口人工呼吸後,那人
還是沒有呼吸,而且她又聽到河裡傳來兩聲驚叫。她又跳下去救起那兩個人,而當
她在搶救那兩個人時,她又聽到四個人叫著救命,然後她聽到八個人叫著救命...
.不幸的,這醫生忙著救人,根本沒有時間管到底誰把這些人全丟進河裡。

就像這名醫生,程式員們有時忙著"治療"錯誤而從來沒停下來找出到底什麼原因造
成了錯誤。我們在第七章中碰到的strFromUns函式就是這種問題的一個例子。
strFromUns產生問題是因為它強迫程式員們在靜態記憶體中傳遞資料。不過當錯誤
發生時,它們都出現在strFromUns呼叫鏈的下游,而非strFromUns本身。你覺得哪
個有問題的函式會被修好?是strFromUns-問題的真正根源-或是呼叫
strFromUns把前一個呼叫的結果清掉的那個函式?

同個問題的另一個例子發生在我將一個Windows版Excel的功能移植到麥金塔版上時
。(當時這兩個版本還是使用分開的原始碼。)在我把功能移植過去後,我開始測
試程式,並發現一個函式碰到一個意料之外的NULL指標。我檢查了程式,卻不清楚
問題是在呼叫者(傳入了個NULL)或在函式中(沒處理NULL)。我去找本來的程式
員,並解釋出問題的狀況給他聽,他把那段程式載入編輯器後說,"哦,這函式不
能接受NULL指標。"然後,我站在那邊看著他加上一個在NULL指標出現時立刻離開
那函式的修正處理:

if (pb == NULL)
    return (FALSE);
我說不應該傳入一個NULL指標給這函式,問題應該是在呼叫者,而不是在這函式中
,可是他說,"我了解這程式;這樣就能修好了。"問題是修好了沒錯。可是對我來
說,這樣的解決方式就好像我們只治好一種疾病的症狀,並沒有除去病因。我回到
辦公室後,花了十分鐘把原始碼裡所有的NULL指標都找了出來。真正的問題不光是
NULL指標,還有兩個其他已知的錯誤。

我追蹤到了其他兩個錯誤的根源,然後想著,"等等,這樣寫不對;如果是這樣,
這邊的函式也會壞掉,所以這地方不應該是這樣寫的。"我確定你能猜出為什麼別
的函式會有效運作,因為有人對一個廣泛的問題作了區域性的修理。


------------------------------------------------------------------------
--------

治療病因而非症狀。


------------------------------------------------------------------------
--------
你是個愛管閒事的程式員嗎?
"沒壞的東西也該修理幾下"似乎是某些程式員過度拼命的寫照。不管一份程式動作
得多少,某些程式員都覺得應該在上頭改些什麼東西。如果你曾經跟一名會將整個
程式重新整理成他或她喜歡的格式的程式員共事過,你就知道我在說什麼了。大部
分程式員的保守程度得稱不上那種"習慣改東改西"程度的人,不過所有程式員似乎
都會將程式某種程度上都會把程式改來改去。

清理程式碼的麻煩在於程式員們不種是將他們改進過的程式版本當作新的程式碼處
理。有程式員會在檢視整個檔案時看到底下的程式而想把那個對0的測試改成對
'\0'的比較;其他人會想把那個測試一起拿掉。

char *strcpy(char *pchTo, char *pchFrom)
{
    char *pchStart = pchTo;
    while ((*pchTo++ = *pchFrom++) != 0)
        {}

    return (pchStart);
}
把0改成零字元的問題在於這樣從一打成'0'而不是'\0',可是有多少程式員會在這
樣簡單的修改之後去測試strcpy?有個更好的疑問:當你改了如此簡單的東西後,
你會把程式當作重新寫過般的徹底測試嗎?如果你不會,你就冒著經由這些不必要
的修改產生出錯誤的風險。

你會認為有些修改不可能會出錯,因為程式還是能編譯過關啊。舉例來說,改個區
域變數的名稱怎麼可能產生錯誤?喔,當然可能。我曾經追蹤一個問題到一個函式
中,裡頭有個叫做hPrint的區域變數,跟一個同名的整體變數互相衝突。因為這函
式一直到最近都動作得好好的,我看了一下舊版的原始碼,想找出哪裡改了什麼東
西,好確定我的修正方式不會重新產生舊問題。結果我發現整個程式都被清理過了
。早期版本中有個叫做hPrint1的區域變數,可是沒有hPrint2或hPrint3,讓那個
'1'看來似乎不是必要的。不知道誰把那個'1'拿掉了,這個人假設hPrint1是以前
留下的東西,就把它整理了一下,造成名稱上的衝突跟程式執行錯誤。

避免讓你自己產生同樣的清理程式錯誤,在螢幕上貼上一個小訊息:跟我共事的程
式員們不是笨蛋。在你以為程式明顯寫錯或沒必要時,這樣的訊息應該會提醒你要
特別小心。如果程式明顯可疑,可能有個不明顯的好理由在裡頭。我看過只為了繞
過編譯器產生執行碼錯誤的問題而寫得很滑稽的程式,把那段程式重新清理過就會
產生出老問題來。當然,這樣的程式應該會有個註解說明為什麼這樣做,不過並不
是所有程式員都會想到那麼多。

如果你看到像這樣的程式

char chGetNext(void)
{
    int ch;       /* ch必須是個整數。 */

    ch = getchar();
    return (chRemapChar(ch));
}
不要把那個明顯"不必要"的ch拿掉:

char chGetNext(void)
{
    return (chRemapChar(getchar()));
}
如果你拿掉ch,你會在chRemapChar是個將參數評估超過一次的巨集時製造出問題
來。保留那個"不必要"的區域變數,就能避免掉不必要的錯誤。


------------------------------------------------------------------------
--------

除非對程式的更動對產品的成功很重要,不然不要這麼做。


------------------------------------------------------------------------
--------
將酷斃了的功能冷凍起來吧
避免把程式碼改來改去只是一條更廣泛避免錯誤的原則的特例:不必要時不要寫程
式(或改程式)。這似乎是個奇怪的建議,不過你一定會被自己多常問自己"這功
能對產品的成功有多重要?"而嚇到。

有些功能對產品毫無價值,存在只是為了填補門面;其他功能存在則是因為大公司
的顧客要求加上這些東西;剩下的則是因為競爭產品裡頭也有類似功能,而某雜誌
的編輯決定把這些功能放在功能列表上。如果你有良好的行銷語產品規劃隊伍,你
就不應該再加上這些用不著的功能。不過作為一名程式員,你會碰到這些不必要的
功能,或甚至你自己就是這些不必要功能的來源。

你曾經聽過程式員說過類似"如果WordSmasher可以怎樣怎樣,那真夠酷的了..."的
話嗎?問題是,一個功能很炫是因為它能改進產品的特性,還是因為把它作出來在
技術上是一種挑戰?如果一個功能可以改善產品表現,那等到你程式的下一版再來
實作這東西的話,不是更能對這功能作出適當的評估跟規劃嗎?如果寫出一個功能
只具有技術上的挑戰性,拿掉那功能吧。我並不是在扼殺創意;而是在建議避免不
必要的功能跟相關錯誤的出現。

有時具有技術挑戰性的功能是可以改善產品表現;有時則不然。小心選擇吧。


------------------------------------------------------------------------
--------

不要實作不具策略價值的功能。


------------------------------------------------------------------------
--------
天下沒有白吃的午餐
"附加"功能是不必要問題的另一個根源。表面上,附加功能似乎值得,因為它們以
現成的設計方式幾乎不費代價就可以完成。有什麼比不用代價的東西更好的?不過
附加功能有個大問題存在-對產品的成功與否幾乎不重要。當然,不重要的功能是
錯誤的潛在根源。程式員們在程式中加上附加功能,因為他們可以加上那些東西,
而不是因為他們應該加上那些東西。畢竟,不加白不加,又不費事,對吧?

這真是荒謬的想法。附加功能也許不費太多事,可是比起在程式中加上了一個功能
,得有人替那功能寫出使用文件,得有人測試那功能,而且當然也得有人修理所有
跟那功能相關的問題。

當我聽到一名程式員說一個功能是附加的時,那告訴我說他或她一定沒花多少時間
想過加上那功能的真正代價有多高。


------------------------------------------------------------------------
--------

世界上沒有不用代價就能加到程式中的功能。


------------------------------------------------------------------------
--------
彈性帶來問題
另一個能讓你避免錯誤的策略是從設計中去掉不必要的彈性。從第一章起,你就看
我用著這條準則。在第一章中,我用了選擇性的編譯器警告功能來禁止多餘而危險
的C語言語法的使用。在第二章中,我把ASSERT定義成一個用來防止這巨集被誤用
在表示式中的敘述.第三章中,即使我可以用NULL參數呼叫free函式,我還是用了
個除錯檢查來捕捉傳給FreeMemory的NULL指標。在每一章中,我都列出了一些我去
除彈性來防範錯誤的例子。

彈性設計的麻煩在於,愈有彈性,愈難找出裡頭的問題。記得我在第五章中討論過
關於realloc的部分嗎?你幾乎可以把任意組合的輸入參數丟給realloc,而它一定
會完成某些動作,即使這些動作不是你預期要作的。更糟糕的,realloc的問題很
難找出來,因為這函式彈性到了你沒辦法加上有意義的除錯檢查來核對輸入參數。
不過如果你將realloc切成處理擴大、縮小、配置跟釋放記憶體塊的四個分開的函
式,你核對起函式的參數就簡單多了。

除了注意不當的彈性函式,你也應該留心那些彈性過度的功能。彈性功能麻煩多多
的原因在於它們能帶來意料之外的合法狀態,讓你沒想到要測試這些你甚至不知道
會出現的狀態。

當我在替蘋果電腦公司的第二代麥金塔電腦加上Microsoft Excel的彩色顯示支援
時,我將Windows版Excel裡頭允許使用者指定試算表儲存格中文字顏色的程式移植
過去。要在一個儲存格中加上顏色,使用者得有個現成的儲存格顯示格式如下

$#,##0.00          /* 將1234.5678顯示成$1,234.57。 */
然後在這格式字串前面加個顏色指定代碼。要把一個數字顯示成藍色,使用者會把
上頭的格式改成

[blue]$#,##0.00
如果使用者用了[red],顯示出來的數字就是紅色的。

Excel的產品規格相當清楚-顏色指定代碼應該放在數字格式字串之前-可是在我
把這功能移植過去,並開始測試程式後,我發現底下的格式也有效:

$#,##0.00[blue]
$#,##[blue]0.00
$[blue]#,##0.00
使用者可能會將[blue]放在任何地方。我問本來的程式員,這算是個錯誤還是個程
式特性,他說顏色指定字能擺在任意處"只是剛好在語法分析迴圈中排定的結果。
"他看不出來允許這樣一點的額外彈性會有什麼問題-我當時也沒看出來-所以程
式就保留那樣沒改。回想起來,我覺得我們不應該讓那點額外的彈性留著。

不久後,測試小組找到半打隱藏的錯誤,全都跟格式語法分析部分有關,語法分析
程式並沒有預期會在一個格式字串的中間碰到一個顏色指定字。

不幸的,我們沒有拿掉這點額外彈性來修正問題-一個只要修改一個簡單的if敘述
就好了的改法-另一個程式員跟我一直在修理那個指定字的錯誤-修理症狀-來維
持沒人需要的彈性。到今天,Microsoft Excel還是讓你能將顏色指定字放在任何
地方。

當你在自己的專案中實作功能時,要讓它們易於使用;不要給它們不必要的彈性。
易用跟彈性有很大的差別的。


------------------------------------------------------------------------
--------

不要允許不必要的彈性。


------------------------------------------------------------------------
--------

------------------------------------------------------------------------
--------
別處移植過來的程式都算新的
我從移植這麼多Windows版Excel的程式碼到麥金塔版Excel的過程中學到的一個教
訓就是,人們會想跳過測試這樣移植過的程式碼。你的理由一定是"我已經在本來
的產品中測試過那程式碼了"。我應該已經在測試小組看到程式以前就把Excel數字
格式程式中的所有問題都找出來了,可是我沒有。我只有把程式放進麥金塔版
Excel中,修改了把程式整合進專案中所必要的部分,然後簡單的測試一下正確整
合好了沒有。我沒有徹底測試這功能,因為我想我已經測試過了。這是不對的,特
別在Widnows版Excel本身還在開發中,而微軟內部各開發小組還把修正錯誤的時間
排在產品開發週期最後頭的時代裡。

不管你怎麼實作功能的-不管是從頭寫起或是從現有的程式改起-你都有責任讓錯
誤在你把一段程式加到專案內時全抓出來。事實是Windows版Excel上的同樣問題不
見得比麥金塔版Excel的輕微。我的懶散都在程式中表現出來了。


------------------------------------------------------------------------
--------
不要亂試
你有多少次聽到像這樣的對話,"我想不出來該怎麼...",然後另一名程式員回答
,"你試過..."?這樣的對話總是以不同的形式出現在幾乎每個程式設計的網際網
路新聞群組中。一名程式員會貼出一段訊息,問"我怎樣把游標隱藏起來,好讓它
不會遮住畫面?"而有人會答,"試著把游標移到畫面外的座標。"另一個人則會建
議,"試著將游標遮罩設成0,表示沒有半個游標上的圖素會被顯示出來。"還有第
三個人可能會說,"游標只是點陣圖形而已,把它的寬度跟高度設成0就好了。"

嘗試,嘗試,嘗試。

我承認這是個蠢例子,不過我確定你一定看過這類的對話發生過,不管是在網際網
路新聞群組或是在辦公室的咖啡機前面,而這些別人要你嘗試的做法裡頭只有少數
找對了方向。當有人要你試著怎樣解決一個問題時,他們是在給你一個經驗上的猜
測,而非正式的明文解答。

試著用不同方法解決一個問題有什麼不對?沒有不對,只要你嘗試的每件事都在你
使用的系統上清楚的定義著。不過程式員們在嘗試這些東西時,往往表示他們已經
來到超出了已知系統領域,而進到了找尋任何會動的解決方案的領域,即使這些做
法可能依賴於一個無意間造成而未來可能改變的副作用。你認為那些故意從已釋放
記憶體中讀取東西的程式員們是怎樣養成壞習慣的?free當然沒有定義它會對已釋
放的記憶體內容進行什麼處理,不過有時那些程式員會覺得他們需要參考到已釋放
的記憶體。他們這樣試了,程式動了,所以他們現在認為這樣的行為是可以依賴的


注意聽那些"你試過..."後頭的建議,我想你大概會發現大部分的建議都用到了未
定義或定義不良的程式副作用。如果作出這些建議的程式員們知道正確的解決方法
,他們就不會告訴你"試著"怎樣作了,他們會告訴你,"只要用系統呼叫‘
SetCursorState(INVISIBLE)’就好了。"


------------------------------------------------------------------------
--------

不要一直"嘗試"不同的解決方案來找出有效的做法。

花時間找尋正確的解決方法才是正途。


------------------------------------------------------------------------
--------

------------------------------------------------------------------------
--------
不要"亂試",看看文件吧
有好幾年,微軟的麥金塔程式員們會收到Usenet網際網路論壇上的麥金塔新聞群組
內經過編輯的唯讀文章。這些文章有趣,可是不能回應其他程式員貼出來的問題相
當令人洩氣。程式員們總是問著在蘋果公司出版的Inside Macintosh說明手冊中已
經清楚回答過了的問題,而網路上的程式員們總是用各種猜測性的答案代替文件中
清楚的解答。幸運的,總有少部分熟悉Inside Macintosh的人會在沒人正確解答時
貼出正確的答案:"看看Inside Macintosh弟四冊,弟32頁,那邊說你該...."。

如果你發現自己在測試一個問題的可能解法,那就停下來把你的說明手冊打開來看
吧。對,這並比不上到處修改程式有趣,也不比問別人怎麼解決容易,可是你會對
自己的作業系統更了解,更曉得該怎樣在上頭寫程式。


------------------------------------------------------------------------
--------
神聖的時間表
有些程式員在被要求寫出一個相當大的功能時,會花上兩個月抱著鍵盤寫程式,而
從來沒測試過自己寫的東西跑得如何,其他程式員則會在停下來檢查程式跑得好不
好以前寫出一堆小功能。只要這些程式員之後有徹底測試過他們的程式,這些做法
都沒做錯。可是他們有徹底測試過自己寫的東西嗎?

想想一名程式員得在五天內實作出五種功能的例子。假設這名程式員有兩種選擇:
同時實作跟測試各單一功能,或是寫好五種功能再一次測試這五樣功能。實際上,
你覺得哪一種做法會產生出比較穩固的程式碼?多年來我看過兩種程式寫作風格,
除了少數例外,那些編寫程式邊測試程式的人製造的問題比較少,我也可以告訴你
原因何在。

假設一名程式員花了全部五天的時間來寫出五種功能,卻發現他沒有足夠的時間在
時間表的限期內徹底測試過他的程式。你認為這名程式員會多花一兩天來徹底測試
過程式,或他會簡單跑一下程式確定程式會動就了事了?答案如何取決於程式員與
工作環境。不過後果就是讓產品發行日延期,或是縮短測試時間。前者會讓大部分
公司都傷腦筋,而後者經常不會造成什麼負面影響;而這名程式員大概會因為讓產
品如期上市而受到讚揚。不過只要有一個困難的功能,就可以把保留給所有功能的
測試時間都用光了。

訂下時間表的一個缺點是程式員們會將時間表當成比程式的測試工作更重要的事,
就是說基本上,達成時間表上的目標比寫出正確的程式更重要。我的經驗告訴我,
如果程式員能在期限內的時間裡頭把程式寫出來,他就會準時把程式完成,而不管
有沒有把程式充分測試好。"此外",他將認為,"程式中如果還有任何未發現的問
題,測試小組會讓他知道"。

要消除這樣的傾向,程式員們應該在繼續進行下一個功能的實作以前,先把已經完
成的部分逐一測試好。如果頭三項功能要花五天來寫,程式員們就得多花時間來寫
剩下的兩樣功能。他們也許會跳過剩下兩個功能的測試以縮小時間表的延誤,不過
至少頭三種功能已經測試過了,總比五種功能都沒徹底測試過要好。


------------------------------------------------------------------------
--------

一小段一小段的寫跟測試程式。

一定把程式測試過,即使那樣作會讓時間表出現延遲。


------------------------------------------------------------------------
--------
名稱的意義
在 第五章 中,我解釋過getchar的函式名稱多常讓程式員們認為這函式傳回的是
個字元值,實際上卻是傳回整數值。同樣的,程式員們常相信測試小組該負起測試
程式的責任,不然測試小組還要作什麼?但是儘管許多程式員們那樣認為,測試小
組的工作並不是測試程式員們寫出來的程式碼;測試小組的工作是保護公司,最後
維護顧客使用的產品品質。

看看另一種產業:建築業中的測試過程,應該能夠更易於了解測試小組所扮演的角
色。在蓋房子時,承包商負責建設工程,驗收員責檢查工程有沒做好,不過驗收員
並不負責測試整個工程。水電工人在鋪好房子中的電線後,在離開前會先打開電源
看看保險絲,並用電表檢查每個插座能不能用。水電工人永遠不會認為"我不用測
試這些東西。如果有問題,驗收員會讓我知道哪邊出了問題",因為有這樣念頭的
水電工人很快就會丟掉飯碗。

測試人員如驗收員般不負責測試工作的好理由是他們很少碰得到需要的動到的東西
,也很少有測試所需的工具或技能。儘管與普遍的觀念互異,測試人員並不能比你
對程式作出更好的測試。測試人員能在程式中加上除錯檢查來找出錯誤的資料流嗎
?他們能夠如第三章中替記憶體子系統所作的,加上那些子系統測試嗎?他們能用
除錯程式逐步檢查你程式的執行路徑與功能都如預期般動作嗎?令人難過的事實是
,能夠確實比測試人員更有效的作到測試工作的程式員們往往沒做該做的測試。

測試小組扮演了開發過程中重要的角色;不過並不是許多程式員們認為的那種角色
。當測試人員檢查一個產品時,他們會尋找功能上的缺陷與漏洞,核對產品是否與
先前版本後向相容,然後告訴開發團隊哪些地方修改潤飾過後可以改善產品,而且
他們在"真實"環境中使用這些產品,確保產品功能真正有用,並回報任何他們注意
到的錯誤情形。

即使你的測試人員除了找尋錯誤以外沒作任何事,你還是不能假設他們會好好幫你
把程式測試好。記住我在第一章中提到過的:測試人員只能夠在程式的資料輸入端
猛灌一堆東西來看看問題會不會自己跑出來。當然,沒人真的會想那樣作,因為現
在的測試工具似乎能夠更科學的作到這點。不過實際上,現在的測試工具祇是更有
效的執行那種測試方式而已。一名測試人員最多也不過能說"這程式似乎能動"。對
我來說,那樣子完全比不上程式員已經把程式整個追蹤執行並觀察過程式中的每條
執行路線,來查對合法的輸入產生出正確的輸出。

此外,從實務角度來看,如果測試小組的某些成員經驗不足,又會如何呢?或者如
果你的測試小組因為更緊急的工作而抽不出時間來測試你的程式,那會怎樣?這些
情形在微軟總是發生著;我相信別處也是如此。


------------------------------------------------------------------------
--------
重複的工作
如果程式員負起徹底測試程式的責任,有個問題自然會出現,"這樣子程式員跟測
試人員不是在重複彼此的作過的事情嗎?"或許他們做的是同個性質的事情,可是
當程式員們在測試程式時,他們是從裡到外進行測試,而測試人員則是從程式外頭
往裡頭測試。

程式員們從每個函式開始測試,逐步追蹤檢查每條執行路線,查對程式與資料流的
正確性。由這些步驟,他們逐步向外檢查每個函式與子系統中其他函式是否正確運
作。最後,程式員們使用單元檢查來確定子系統間合作愉快。單元檢查,作為額外
的查核過程,規律性的檢查測試過程中內部資料結構的狀態。

測試員們將程式視為黑盒子,設計對程式的輸入端扔出所有可能狀態的資料來找尋
程式缺陷的整體測試方式。測試員們也會使用回歸測試來查對所有已知錯誤是否已
經修好了而沒再出現。從外部,測試員們逐漸往內部測試,使用程式碼涵蓋工具來
了解整體測試檢查過多少內部程式碼了。測試人員用這些資訊來建立還沒測試過的
程式碼所需要的測試方式。

這是用兩種不同"演算法"來測試程式的重要範例。這些做法用在一起會管用,是因
為程式員們集中在程式的測試上,而測試人員在乎的是功能表現的正確性。藉由兩
個相反方向的合作,找出未知錯誤的機率就增加了。


------------------------------------------------------------------------
--------

你不能僅僅依賴測試小組來幫你測試程式-至少如果你想實在的寫出零錯誤程式的
話,你就不應該這樣做。


------------------------------------------------------------------------
--------

不要依賴測試小組來找出你的錯誤。


------------------------------------------------------------------------
--------
戴白帽的測試員
你注意到過當測試小組找出一個問題時,一些程式員們是如何鬆了一口氣的嗎?"
哦!"他們說,"我當然高興測試小組能在我們推出產品以前找出那個問題來。"其
他程式員們則在測試員報告程式有問題時怨聲載道,特別當測試人員回報了同一份
程式的多個問題時。我看過程式員氣得毛髮直立的說:"測試員為什麼不饒了我?
"我還聽過有專案主持人(他們應該更清楚怎樣才是對的吧?)說,"公開測試日期
的延誤都是測試小組的錯。"有一次,我還為了這種問題,不得不將一名專案主持
人跟測試小組主持人架開,免得他們打起來。

這聽來很蠢吧?當我們沒推出產品跟受攻訐的壓力時,我們是可以很簡單的坐下來
,看出這樣的行為有多荒謬。可是當你已經延誤產品推出日期好幾個月,而程式中
仍然佈滿錯誤時,你就很容易將測試員們當成大壞蛋了。

當我看到程式員們對測試員們生氣時,我會將他們拉開,然後問他們為何認為測試
員們該為程式員們自己產生的問題負責。他們不應該對測試員們生氣;測試員們祇
是幫他們把自己產生的問題找出來而已。

當一名測試員回報你程式中的錯誤時,你的第一反應應該是難以置信的嚇到了-你
不應該預期測試員們會在你的程式中找出問題來。你的第二反應應該是感激他們,
因為測試員們讓你免於把一個問題呈現在使用者面前。


------------------------------------------------------------------------
--------

不要責怪發現你製造的錯誤的測試人員。


------------------------------------------------------------------------
--------

------------------------------------------------------------------------
--------
世界上沒有笨問題
有時你會聽到程式員抱怨說出現了一個奇怪的荒謬錯誤,或說測試員們經常回報笨
問題。如果你聽到程式員跟你抱怨笨問題的出現,停下來提醒他或她,錯誤的嚴重
性跟值不值得修正並不是由測試員們決定的。測試人員必須回報所有問題,不管問
題蠢不蠢,因為就他們所知,那些蠢問題一定是某些嚴重問題的副作用。

真正的問題不在一個問題笨不笨,而在為何測試程式的程式員沒有抓出那個問題來
。即使錯誤的情形很微小而不值得修正,找出問題的癥結依然是重要的,這樣才能
讓你避免相似的錯誤一再出現。

問題也許不大,但是只要有問題出現,就是嚴重的事情。


------------------------------------------------------------------------
--------
建立優先順序
當你翻完本書,看到那些快速複習要點,你也許會驚訝其他有些是互相矛盾的。不
過多思考一下,你也許就不會驚訝了。畢竟,程式員們時常在處理把程式寫得又快
又小的矛盾目標。

當你面對兩個可能實作的選擇時,你會選哪個?我想你在執行效率跟程式大小間作
出選擇並不會有問題-你總是在作著這類的決定-可是要怎麼在執行效率跟維護性
各見高下,或是小而錯誤百出跟大而異於測試的兩個程式間作出選擇?有些程式員
們會不加思索的回答這些問題,不過其他人則會猶豫,而且如果你把同樣問題隔了
幾週後再問,你還可能得到不同的答案。

那些程式員在面對不同性質代價時不能確定選擇何者,是因為他們不了解在如程式
大小與執行效率這樣常見的條件何者為重所造成的。沒有明確優先順序目標設定的
程式設計方式就像不知道終點何在的行進路線,在每個街角,你都會停下來問,"
我該何去何從?"而你一定會走錯路。

有的程式員相當清楚自己排定的各項條件的優先度,可是由於他們排定的順序錯誤
或互相衝突,使他們前進的方向還是朝著錯誤的路線。例如,許多老程式員對各種
條件排定的優先順序還是停留在1970年代晚期的想法,當時電腦記憶體相當捉襟見
拙,而微電腦還跑得很慢。當時要寫出一個可用的程式,你得以程式的維護性為代
價來交換程式大小與執行速度。不過今天,記憶體空間大得很,而電腦已經快到即
使執行很差的演算法來作大部分的工作,都不會慢到令人不能忍受了。因此,這些
條件的優先順序倒過來了,再拿程式維護性來交換程式大小與執行速度已經事件不
合理的事情了,因為這樣子作的結果大概只會出現感覺不出執行速度加快了多少而
卻缺乏可維護性的程式。不過還是有些程式員將程式大小與執行速度的最佳化當成
神聖不可侵犯的準則,使他們做出錯誤的實作選擇,只因為他們對各項程式條件的
順序排定過時了。

不管你有沒想過自己排定的條件順序如何,或者你最近從來沒想過這些條件之間的
順序排定,你得坐下來清楚的建立一份順序列表(或如果你身為專案主持人時,幫
你的團隊安排一份列表),好讓自己能夠好好依據專案的這些目標作出最好的選擇
。注意,我說的是:"專案的目標"。你的優先度列表應該反映的,不是你想做的,
而是你應該做的事情。如果一名程式員將"個人表現"列為優先,那對這名程式員的
產品會有什麼好處?這名程式員會接受一個識別字命名標準,或接受別種程式區塊
排列的風格嗎?

沒有一個所謂"正確"的排列順序方式,因為你選擇的排列順序就代表著你程式的風
格與品質。看一下兩名程式員Jack跟Jill的條件排列順序:

Jack的順序排列 Jill的順序排列
正確性 正確性
整體效率 可測試性
程式大小 整體效率
區域效率 可維護性/程式清晰性
個人方便 一致性
可維護性/程式清晰性 程式大小
個人表現 區域效率
可測試性 個人表現
一致性 個人方便

這些條件的排列順序對Jack跟Jill的程式有什麼影響?兩名程式員都將寫出正確的
程式當成第一優先,可是剩下的考量就不同了。如你所見,Jack強調程式大小與速
度,而不太在乎程式的清晰性,也沒考慮過程式是否易於測試。

Jill也將寫出正確程式列為首要重點,不過不光是現在寫出正確程式而已,她還考
慮了未來程式的維護能否也正確進行。她只有在程式大小與執行速度對產品成敗很
重要時,才會擔心這兩種條件。"可測試性"在她的順序排列清單中排得很前面,因
為她相信除非程式易於測試,不然就不好查對程式的正確性(而程式的正確性當然
是她的首要課題)。

對這兩名程式員,誰比較可能

打開所有編譯器警告選項來自動找出錯誤,即使這樣會比較安靜而不報告問題的解
決方式耗去額外的程式大小與執行速度?

使用除錯檢查敘述與子系統除錯檢查?

追蹤每條程式執行路徑,仔細核對剛寫好的新程式碼?

使用安全的函式介面而不用危險的介面方式,即使這樣會在每次呼叫函式時多產生
一兩個額外的指令?

使用具可攜性的資料型態,並在位元位移應該可用時改以除法或乘法處理(例如寫
成/4而不是>>2)?

避免使用 第七章 中提到過的那些抄捷徑的技巧?

這是一連串不合理的疑問嗎?我不認為如此。換個比較簡單的問法,"你認為這兩
名程式員中的哪一位,會在看完本書後,照著書中的建議去做?""哪一位程式員會
看過The Elements of Programming Style,或任何一本建議人如何寫程式比較好
的書,並照著書中的建議去做?"

Jack照著他自己的順序排列方式,會將重點放在寫出實際上會傷害產品正確性的程
式上。她會浪費時間去嘗試將每一行程式碼寫得盡可能又小又快,而不會對產品的
長期正確性放在腦海中想想。Jill剛好相反,照著她的條件設定,她會將重點放在
產品的正確性上,而不是程式本身,除非有必要,不然她也不會在乎程式的大小與
執行速度。

自我反省一下,程式的大小與執行速度表現跟產品的正確性,哪一個對你的公司比
較重要。現在你覺得該將各種條件的排列順序如何安排?


------------------------------------------------------------------------
--------

建立各種條件的優先順序排列,並照著作。


------------------------------------------------------------------------
--------

------------------------------------------------------------------------
--------
呃,我不曉得
你曾經在看過別人寫的程式後,想著他們為何那樣子寫程式嗎?當你問他們為何那
樣寫實,他們會說出像"呃,我不曉得我為什麼那樣寫。我猜那時我覺得那樣子寫
起來應該是正確的吧"?

我總是檢查著程式,尋找各種幫助程式員提昇程式寫作技巧的方法,而且我發現這
種"呃,我不曉得"的反應相當普遍。我也發現有這種反應的程式員們沒有一個清楚
的優先順序設定;他們的決策方式,有時似乎祇是依據他們飯後的感覺好壞而已。
有清楚的優先順序設定的程式員們清楚知道他們該怎樣選擇實作方式,也能很快告
訴你他們為何那樣寫。

如果你發現你常常不曉得自己為何把程式寫成你寫出來的樣子,那就表示你真的得
停下來建立一份屬於自己的程式設計條件優先順序排列清單了。


------------------------------------------------------------------------
--------
不要拿你不要的東西
我在本章中還沒提到的一個重點是,你得發展出質疑自己寫作程式方式的習慣。這
整本書就是長久下來幾個簡單問題不斷被思索後的結果:

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

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

而在這結尾的一章中:

這種觀念會幫助,或是妨礙我寫出零錯誤程式的能力?

質疑自己的觀念是重要的,因為這些觀念就反映在你自己的程式設計條件優先設定
上。如果你相信測試小組會好好測試你的程式,你想寫出零錯誤程式就會碰到麻煩
,因為你會認為,某種程度上,你可以省掉某些測試工作。想想:如果你不認為寫
出零錯誤程式是可能的,那你有多少機會寫得出零錯誤程式?如果你不認為那是可
能的,你還會去試著寫出零錯誤程式嗎?

如果你想寫出沒有任何錯誤的程式,你就得捨棄那些阻止你達成目標的觀念。要捨
棄那些觀念的方法,就是問問你自己,那些觀念對你寫出零錯誤程式的能力有沒有
幫助,或是有無妨礙。


------------------------------------------------------------------------
--------
快速回顧
問題不會自己跑出來;問題也不會自己不見。如果你得到一份錯誤報告,可是你沒
辦法重現那個問題,不要假設測試人員看錯了。即使得把舊版本的程式碼翻出來,
也不要放棄找出錯誤的努力嘗試。

不要等"以後"再修正錯誤。重要產品因為錯誤百出而取消推出已經成了驚人的常見
現象了。如果你在找到錯誤時立刻修正問題,你的專案就不會遭到同樣的命運。如
果程式總是接近零錯誤,你當然不會看到一份填滿各種錯誤的臭蟲清單。

當你追蹤一個問題時,永遠自我反省一下,這個問題是否只是一隻更大隻的臭蟲的
症狀而已。沒錯,修正你找到的症狀當然比較容易,不過你應該盡可能找出問題的
真正癥結所在。

不要寫出不必要的程式或進行不必要的修正處理。讓你的競爭者去寫出那些花俏而
不必要的功能,到處修改他們的程式,讓他們去為了這些要付出額外代價的"附加
"功能延誤他們的產品推出日期,讓你的競爭者們浪費時間在修正這些無用的程式
碼中不必要的錯誤吧。
?
記住,彈性並不等於易用。當你設計函式與功能時,注意它們的易用性;如果它們
只是有彈性而已-如realloc函式跟Microsoft Excel中的顏色格式功能的話-這些
功能並不會變得更有用;只是變得更難找出錯誤。

抗拒"試著"達到要求效果的誘惑,把你拿去嘗試研究不確定事物的時間拿來找出正
確的答案。如果必要,就打電話給你的作業系統廠商,找他們的開發支援小組問你
想問的事情,這比起寫出一個將來可能不能用的古怪實作要好多了。

把程式寫成一小段一小段易於徹底測試的樣子,不要省略測試過程。記住,如果你
不好好測試程式,就沒人有辦法好好測試它了。不管你怎麼做,不要期望測試小組
能幫你把程式測試好。

決定出你的程式開發小組依循的程式設計條件優先順序。如果你像Jack那樣在乎程
式最佳化,可是你的專案需要像Jill那樣小心的人,你就得改變自己的習慣,至少
在工作時得改變你自己。

學習計劃:說服你的程式設計團隊建立並依循一份他們同意遵循的條件優先順序設
定表。如果你的公司接受不同技能程度的雇員(像是入門新手,普通資歷的程式員
,資深程式員,或是功力高深得很可怕的人),你也許會考慮給不同人不同的優先
順序設定。你覺得為什麼要這樣做?





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

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


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

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