2008年4月15日 星期二

黑傑克捉鬼記


        趁今天結束之前,趕緊補一篇湊數的文章 ^_^。

        上回談到程式鬼打牆,測不準原理,這一篇就讓我來用淺顯易懂的白話文來抓鬼揭秘吧。


        其實程式會出現這樣結果不一致的落差,來自於 Performance 考量的思維 --


        只要當你真正要看運算結果的時後才進行運算,否則你只是要我運算,我還是可以先偷懶擺爛。


        試想,老師交代今天回家要寫數學作業,但是你家的小孩知道下次數學課是下個禮拜老師才會檢查,所以決定今天就不寫作業了,只要記得有這件事就夠了,等到要交作業的前一天再寫就好。

        回到家,你問小孩子作業寫完沒。為了晚上可以看電視打電動、又要維持乖小孩的形象,所以你的孩子就大聲回答:寫完了!


        反正身為父母的你只是問問,不會真的檢查作業,他太了解你了。於是在你心目中,誤以為你家的孩子是自動自發效率很高的乖寶貝。


        或許你會想,早寫晚寫還不是都要寫?幹嘛搞拖延戰術這一套?


        那可未必!你的孩子早就做好以不變應萬變、以逸待勞、收株待兔的萬全戰術:誰知道明後天數學老師會不會出車禍身亡?誰知道明後天中共會不會打飛彈過來,學校停課?誰知道明天會不會再來一次九二一或是教改出新招,宣布廢掉數學課程?


        這世界有太多的可能,程式的邏輯世界未嘗又不是如此?所以越早做不是越有機會虧嗎?


        以上這種思維常常發生在內部運作較為複雜的 Class 身上。


        其實我們很容易看到以下這類的程式碼:



醫生 怪醫黑傑克 = new 醫生( );

怪醫黑傑克 . 研究癌症解藥( ref 實驗樣本 );  // 這是非常複雜的運算工作,可能非常耗記憶力 (
Memory ) 與體力 ( CPU )。

if (政府取消研究計畫 == true) {
        return "FAIL";   // 結束研究計畫,完全沒利用到怪醫黑傑克的努力成果
}

try {
        // 準備研究報告的紙上作業, 做些雜七雜八的
        DoSomePaperWork( );
} catch ( Exception FireException ) {
        實驗樣本 = null;// 實驗室遭失火,實驗樣本全毀,研究計畫終結
        return "FAIL"// 完全沒利用到怪醫黑傑克的努力成果
}
return 怪醫黑傑克 . 取得癌症解藥配方( ); // 怪醫的努力總算有了代價.




        如上所示,怪醫黑傑克有很大的機會做白工。於是乎,嚐過幾次做白工還被罵沒效率的經驗後,黑傑克也學乖了,把真正要做研究的動作,放到 [取得癌症解藥配方( ) ] 這個 method 才進行。


        Ok,讓我們回頭看看之前那支處理 XML 的程式吧,XML 的解析並不是一件簡單的工作,所以很顯然,XmlDocument 的 SelectNodes ( string Xpath ) API,你呼叫的時候它並沒有真正去抓出那些 ChildNodes,而只是單純記住當初傳入的那個 Xpath 參數在它的體內:


Public XmlNodeList SelectNodes (string Xpath ) {
        return new XmlNodeList ( Xpath, this );
}


        讓我們看看 XmlNodeList 簡化的示意作法:

Class XmlNodeList : IEnumerator {
        private readonly XmlDocument _parentDoc =null;
        private readonly string _Xpath ="";
        private ArrayList _NodeList =null;

        public XmlNodeList ( string Xpath, XmlDocument parentDoc ) {
        
        _parentDoc = parentDoc;
                _Xpath = Xpath;
        }

        public int Count {
        
        get {
                        if (null == _NodeList) _NodeList = GetRealNodeList ( _Xpath, _parentDoc );
                        return _NodeList.Count;
                }
        }

        好吧,看得懂的人就懂,看不懂的人...。想想黑傑克的例子吧,你以為他已經找出癌症解藥,就算這時候實驗室失火把實驗樣本全燒毀了,但是藥方應該還記在他腦子裡,所以這時候呼叫 [ 取得癌症解藥配方( ) ] 跟他要答案應該還是拿得到吧?


        這可就猜錯了,黑傑克只會兩手空空地說:甚麼都沒了


        這種取巧的方式其實有其實務面上的價值,因為畢竟寫 code 的品質高低落差太大、工程師素養良莠不齊,很容易看到黑傑克那種程式碼的案例。所以底層 Framework 的 Class 這樣實作,是可以隱性地、有效地增加程式的執行效能。


        當時和 V 討論到這個議題,V 不認為這是 .NET 的 bug,我現在可以認同,這屬於一個 Trap 或 Weakness,但不見得是個 bug,因為實務面上仍有其需求在,所以大家只好罩子放亮一點啦!

沒有留言:

張貼留言