2008年4月7日 星期一

程式鬼打牆,海森堡測不準原理

  最近這兩天都遇到鬼打牆事件,明明預估很簡單輕鬆,可以提早下班的工作,卻連兩天都遇到當場傻眼無法解釋的現象,弄到加班都差點搞不定。

  其實軟體開發就像是堆疊黑盒子的積木,你很難確定一個函式呼叫後,底層到底是怎樣運作才生出你所渴望的回傳值?

  今天我遇到一個鬼打牆的狀態,那現象就像是你要學生背英文單字,要求他邊寫邊大聲地唸出來。你看他唸得字正腔圓,紙上書寫的單字也拼得正確極了,於是乎你放心的說:這些單字你都會了,默寫就好,不用唸出聲。但結果你回頭檢查紙上默寫的單字,卻每個全都拼錯!?

  大驚之下,你又要求學生邊寫邊唸,結果又是全都正確,但是等下默寫又全拼錯了...。

  如果你是老師,八成這時候已經認定這個學生性格乖戾在惡搞你,恨得心癢癢拿起教鞭痛扁這劣徒一頓。但是,我這次所遇到的這個學生,卻是一支 .NET Framework 標準 Class Library 內的標準 API。

  上面寫的,是給不懂程式開發的朋友理解我所遇到的鬼打牆狀況。這種現象,像極了我物理界背景的朋友耳熟能詳的量子物理現象,測不準原理。

  先聲明,這是一個單一處理程序與單一執行緒簡單到不行的程式。

  整件事就是開發時期,為了仔細觀察資料的變化,我會不斷把資料每個步驟後的狀態都輸出到螢幕上 ( Debug.WriteLine( ... ) ),確定他的變化如我預期。等到確定資料的每個狀態都正確後,我唯一做的事情就是把輸出到螢幕上的程式全部移除,交給客戶測試。沒想到資料結果怎麼測怎麼錯,連我自己機器上也跑不出正常預期的結果,我在客戶面前簡直是傻眼變成張口結舌的阿呆,五分鐘前那句我都測過,絕對沒問題。無疑是當場賞兩巴掌讓我信用破產。

  我不敢吭聲地趕緊把剛剛移掉的那些用來把資料輸出到螢幕上的程式復原回來 ( 就只是單純的把註解掉的 Debug.WriteLine( ... ) 回復,絕沒有更多多餘的動作 ),準備開始除錯找出問題,結果一切又都正常了。

  「可能我剛剛版本更新沒有成功吧?我心想,然後又把那些輸出螢幕的程式移掉再更新一次,靠!程式一跑資料又全錯?

  這件事真的是反反覆覆搞了我快一小時那麼久,客戶在旁看我這跳樑的小丑都快打嗑睡了。當然,這件事我最後找出可以理解的原因,也避開了這個問題,不過理解原因後,不免要對微軟的 .NET Framework 的 API 不夠直覺的設計缺失感到不悅。

  如果大家對這個程式的測不準原理的現象感到好奇想體驗,我提供以下的程式碼讓有興趣的人玩玩,至於造成此現象的原因,就留給大家自行參透吧 (或我下次在撰文解答 ):

  程式說明:這是一個處理以下 XML 文檔的簡單程式,

  <Root>
    <A/>
    <B>
      <BB/>
      <BB/>
    </B>
  </Root>

  程式要取出所有 <BB></BB> 內的資料,並且從整份 XML 內移除 <B></B> 節點 ( XmlNode )。

  Code (.Net 1.1 & C#):




string sXml = "<Root><A/><B><BB/><BB/></B></Root>";

XmlDocument oDoc = new XmlDocument();

oDoc.LoadXml(sXml);

// 關鍵來了

XmlNodeList oBBNodes = oDoc.SelectNodes("//Root/B/BB");

// Debug.WriteLine("oBBNodes.Count=" + oBBNodes.Count);

XmlNode oBNode = oDoc.SelectSingleNode("//Root/B");

if (null != oBNode) oDoc.DocumentElement.Remove(oBNode);    // 從原本的 XML 文件中拔除 <B></B> 以下的整個節點

Debug.WriteLine("oBBNodes.Count=" + oBBNodes.Count);




  這樣你就可以看到這懸疑的現象了。oBBNodes 明明就應該有兩個節點 ( Count=2 ),但是執行以上的程式,螢幕上卻會得到 "oBBNodes.Count=0" 的答案。

  但是如果把第六行的 // Debug.Wr..... 的註解恢復成可執行的 Code,結果螢幕上跑出來的輸出卻是:

   oBBNodes.Count=2
   oBBNodes.Count=2

  螢幕重複兩次輸出,但都是正確答案。

  哈,真的是很像測不準吧。如果不想跑出第六行的螢幕輸出,卻又想呈現最後正確的結果,可以把原本第六行的程式改成 "int n = oBBNodes.Count;" 做個愚蠢的虛功即可。

  真的是一整個蠢到不行。

沒有留言:

張貼留言