《I. M. Wright's "Hard Code"》第一章閱讀札記

12/31/2008
I. M. Wright's "Hard Code"》一書的內容係來自作者 Eric Brechner 原先發表於雜誌上的專欄文章,其筆調輕鬆詼諧,有時亦頗辛辣,挺適合躺在床上看。本書還有個特色,就是每篇文章都有附一張作者「玉照」,且每一張都有不同的搞怪表情或動作。不妨到作者的部落格看看,上面的文章也延續了這種搞笑風格。

這本書有簡體中文版,書名是《代碼之道》。台北天龍簡體書局可以買到,價格 180 NTD,還蠻划算,便買了一本。雖然很少讀簡體書,但讀起來並沒感覺特別困難。看了第 1 章,聯想到自己陷入參與的專案,便順手寫下一些感想。

第 1 章的標題是「專案的不當管理」(Project Mismanagement),其中有一篇名為「2001-06-01:開發時程、飛天豬、和其他幻想」的文章,裡頭提到:

我心裡從來沒有特定功能的交付日期,而只有一系列的「專案日期」--里程碑、測試版、正式版發行等等。一個好的開發時程應該只簡單列出每個里程碑所要完成的功能。第一個里程碑要完成的通常都是那些「必須有」(must have)的功能,至於要放哪些、放多少,則須以開發人員的數量和功能的「困難/適中/容易」三種難易度來考量。那些「最好有」(like to have)的功能可放在第二個里程碑,「希望有」(wish)的功能則放在第三個里程碑,其餘功能皆不予考慮。(註1)

[原文]:In my mind, there are no dates for features on a dev schedule beyond the project dates—milestones, betas, and release. A good dev schedule works differently. A good dev schedule simply lists the features to be implemented in each milestone. The "must-have" features go in the first milestone and usually fill it. Fill is based on the number of devs and the "hard/medium/easy" scale. The "like-to-have" features go in the second milestone. The "wish" features go in the third milestone. Everything else gets cut.

第一個念頭是聯想到目前正在纏鬥的專案。這個專案從開標完成、動工開始算,預計整個完工時間大約三年(這是非常樂觀的時程吧 @_@),開發人員粗略估計約 20~30 人--其中包括 PM、SA、PG、測試員、以及其他打雜、跑龍套的人(我算其一)。不過,開發期間人員來來去去,而我並非 PM,所以這些數字只是根據自己觀察所推估的,不見得準。

勉強來說,這個專案也有訂里程碑,稱為一期、二期、三期。以三年分三期來看,這樣的里程碑週期肯定是太長了,作者的建議是每個里程碑應為期 6 至 12 週。但比較奇妙的地方在於,這個專案並沒有明顯區分甚麼 must-have、like-to-have、wish 功能,大概講起來就只有一種,就是:no-matter-what-I-just-want-to-have 功能。回想起來確實挺「隨性」的,專案需要哪些功能,以及各項功能的重要性、急迫性似乎沒有經過審慎評估,而是只要 user 想得到的,通通都放進去,然後再依子系統粗略切割時程和實作的順序。如此隨性的後果,便是有些規劃在初期要先完成的功能,卻因為其他基本功能尚未實作而卡在那裡(沒有先找出功能相依的順序);要不然就是東西寫好了卻沒什麼人用,平白浪費開發人員的時間。

談到做專案總是容易流於發牢騷,寫到這裡就好。

註1:前面摘錄的段落是自己根據原文翻譯,並非取自簡體中文版。我也發現自己翻譯的結果跟簡體版的譯文有些差異(非簡繁術語差異,而是對原文意思的解讀不同)。不過,此差異應屬末節,無關宏旨;抑或是我誤解了原文的意思,也有可能。大體而言,簡體中文讀起來挺順的,比讀原文要快多了(原文挺多俚語之類的,看起來似乎不好翻譯),可以省下不少時間,這要感謝譯者陸其明先生的用心。

2008-12-31 更新:

由於有提到不同譯法的意見,我在發文後便一併到譯者陸先生的部落格留下本文的網址,而陸先生也迅速做出了回應,將翻譯的修正更新到《代碼之道》的勘誤表。對其認真態度表示敬佩與感謝!

撰寫使用案例規格(情節描述)的一些問題與建議

12/27/2008
在 review 一些高抽象層次的系統分析文件時,我發覺經常出現的問題其實跟懂不懂 OO 沒有太大關係,反而跟一些基本的分析技巧以及文件撰寫技巧有關。

先解釋一下,這裡的高抽象層次的系統分析文件,指的是在跟使用者訪談會議結束後,根據使用者描述的需求,以使用者目標為出發點所撰寫的初步分析文件。其中應包含使用者的角色定義、主要的事流程、情節描述等等。為了表達這些內容,我們傾向使用 UML 的使用案例圖(use case diagram)和使用案例規格(use case specification)來描述,以求表達方式的一致性。喔,對了,還有活動圖,而且是比較高階的活動圖,主要用來捕捉事務流程。

在撰寫高階的系統分析文件(或者說初步的系統分析產出)時,我所碰過的一些常見問題包括:
  • 虛應故事的心態。沒有弄清楚使用案例圖和使用案例規格(use case specification)的真正用途,而只是交差了事(有做文件就好);或者實際上進行系統設計時根本不以目前撰寫的系統分析文件為基礎,而是在系統分析時一邊用自己慣用的規格書格式來撰寫,因為將來實際使用的是這份文件(真是浪費時間啊!)。
  • 沒有想清楚抽象層次,分析文件的內容不是太粗就是太細(太快描述太多實作細節)。
  • 文字表達能力不足,無法清楚描述使用者的意圖和操作流程。
前兩個問題彼此互有關聯:如果撰寫使用案例規格(或者說情節描述)只是為了應付驗收,文件內容的抽象層次不太可能拿捏得當(適當的抽象層次不會憑空出現,你得時刻想著這個問題,直到它成為習慣)。如果不了解寫這份文件的目的為何,恐怕也很難分得清楚內容要寫多細。

何謂適當的抽象層次?

文件內容的抽象層次該多細並沒有甚麼明確的標準,要看 SA 對系統的了解程度而定。剛開始,SA 從使用者那邊得到一些線索,此時記錄下來的東西可能是非常粗略的。隨著 SA 對欲設計的系統有更多瞭解,文件內容也會越來越細。而當細到某種程度,SA 認為可以開始進行更細部的設計時(例如:需要開始思考資料庫欄位的設計、類別的設計與關聯),此時大概可界定為進入系統設計的階段(儘管這個兩個階段中間的界線仍不十分明顯)。

舉例來說,在初步的分析文件中把使用案例和將來要實作的系統功能選單項目做一對一的對應,這種描述方式就明顯不當。對一個複雜系統來說,剛開始應該要描述使用者的意圖,以及如何達成這些目標,而不是直接就設想好將來操作介面的選單會有哪些功能項目。畢竟,使用者都還沒確認你寫的東西對不對,還不知道你是否真正了解她要什麼,怎麼能一下子就完成「功能分解」呢?太快做功能分解也會導致使用案例的粒度切得不恰當,當然寫出來的使用案例規格也就很難抓到重點。

雛型畫面到底要不要先做?

曾聽過一些建議,說在進行 OOA 時不要先想到資料庫欄位和操作畫面(UI ),個人認同前者。UI 的部分我覺得可以先做一些簡略的畫面雛型,以便和 end users 確認。

完成初步的系統分析文件之後,我們通常會拿去跟 end users 討論。也許有些人會質疑:「user 怎麼可能看得懂你那些橢圓形圖案和活動圖?他們也不可能有時間去看那一堆情節描述吧?」

不見得。但確實有許多使用者沒辦法逐一細看你寫的分析文件,而且使用者大都比較喜歡看圖說故事,因為總是要看到實際的畫面才比較有感覺。如果光看文字描述,一方面比較花時間、一方面也比較累。

因此我贊成在完成初步的分析文件時,也先製作粗略的 UI 雛型,方便讓 end users 了解我們所知道的東西,以及我們打算要怎麼來設計系統以完成他們的需求(功能、作業流程等)。可是,這樣不就會開始出現前面提到的「功能選單」和「資料庫欄位」了嗎?這不要緊,當它們需要出現在畫面中時就讓它們出現吧。我的意思是,我們是為了要讓細節逐漸浮現而表達這些東西--就像讓冰山在水面下隱藏的部分逐漸顯現出來--而不是連主要的 use cases 都還沒抓出來就直接去想功能選單長甚麼樣子。此外,UI 雛型上面的輸入欄位也不見得就是對應到資料庫欄位;它們還是有差別,而且你也不用在設計 UI 雛型時把全部可能需要的欄位全畫上去,只要關鍵的欄位就夠了。

就像《軟體工程與 Microsoft Visual Studio Team System》書中所建議的:

與人物代表(persona)一樣,情節也應該儘早明確。你會在其中加入操作畫面的簡圖,並顯示從一個畫面切到下一個畫面時的資料流。如果是以使用案例來分析,這些明確的資訊通常會拖到細部設計的步驟時才會出現。不要拖──你應該立刻針對這些明確的需求進行初步的測試。(p.65)

如何確定使用案例規格已經夠細了?

在撰寫使用案例規格時,可以問自己一些問題,來檢驗這份文件的內容是否已經完備:
  • End users 看了這些內容之後,能否確認當初他們所題的需求已獲得充分了解?以及他們是否能提出更進一步的具體需求或建議?
  • 此文件是否包含足夠的資訊,可讓系統分析師或設計師進行下一個層次的分析設計工作(找出互動、介面、關聯性等等)?
  • 現在寫這些細節會不會太快了?萬一下次訪談時使用者更改了部分需求或增加一些功能,會不會導致這個部分要大幅度改寫甚至整個重寫?
  • 使用案例規格是否是以 end users 的語言(problem domain languages)撰寫?是否包含太多與實作相關的專業術語(solution domain languages)?
  • 從這份文件中,能不能抓出將來要測試的項目(測試案例)?
最重要的,我想還是 SA 要有那個心去寫一份讓 user 看得懂的文件,而不是只用自己懂的專業術語來描述,或者敷衍了事。如果出發點正確,剩下的文件撰寫技巧大都可以透過相關書籍的建議逐漸改善。

加快 Visual Studio 2008 IDE 的反應速度

12/26/2008
之前曾聽說過這類問題,但這次總算給自己碰上了:在使用 VS2008 設計 Windows Forms 時,IDE 反應速度慢到令人無法忍受。

Speed up Visual Studio 2005 這篇小技巧也適用於 VS2008,依其建議調整了幾個選項之後,感覺 IDE 的反應似乎是有快一些,但滑鼠點選不同項目時還是會頓一下。

如果是開發 ASP.NET 網頁時碰到 IDE 龜速或其他問題,可以試試微軟提供的 VS2008 Web 開發環境的 hotfix:VS90-KB946581.exe

你也可以在 MSDN Code Gallery 網站上可以找到更多 Hotfixes

翻譯時要第一次就譯對

12/25/2008
有時候覺得,要是自己認識更多艱澀、少見的英文單字,也許翻譯起來就會快多了。但轉念一想,似乎又不是這樣。每次碰到覺得不好翻譯的、猶豫不決的,往往是簡單的詞彙或片語(例如之前提到的 cannot begin to + V),只是沒把握自己譯對了,非得查個字典確認才能放心。萬一正好碰到進度落後或時程很趕的翻譯案子,搞不好就先擱著,待日後校稿時再來推敲。

譯者的好朋友:字典與 Google

12/22/2008
翻譯時若碰到不太有把握的地方,我常常會先用前後文的語境(context)來「猜」作者想要表達的意思。除了英文之外,這還需要一點點想像力。有時福至心靈,隨手捻來都是順當的中文,但也有些時候,在房間踱來踱去也想不出個所以然來,簡直鬼打牆。

魯棒性

12/20/2008
OOADA3 好不容易進入校稿階段,晚上便偷閒去書店逛逛。看到架上一本有關軟體架構設計的書,書籍封面的作者姓名是中文,可是卻又有標示譯者的姓名,不免感到好奇:中文書為什麼需要翻譯?

《搞定一切,還有時間玩》筆記

12/01/2008

書名:搞定一切,還有時間玩
作者:馬克.佛斯特
譯者:鄭文琦

這本書講的是如何管理自己的時間和工作。作者從心理層面的角度來剖析為什麼人們常常都是臨時抱佛腳,拖到最後才焦急地熬夜趕工。是啊!如果不從心理層面著手,光只知道怎麼用 Outlook 或行事曆,最後還是一樣堆了一堆做不完的工作。

以下是一點筆記:

專注,是工作效率提升的原動力。

拖延,因抗拒做某件重要的事,而忙著處理其他瑣碎工作。藉著忙碌的假象來減輕拖延的罪惡感,為拖延找理由。但事情拖到後面,往往形成巨大的壓力而不得不去面對,最終演變成臨時抱佛腳,拼了命趕進度。如此反覆循環,永遠無法從容把事情做完。

越是拖延,阻力會越大,威脅也越大。我們應該要在壓力小的時候就採取行動--也就是壓力剛形成的時候。

我們常在下意識裡先去做那些阻力較小的事情,因為它們會讓我們有藉口逃避處理那些具挑戰性與更大阻力的事情。殊不知後者才會帶給我們人生與工作真正有突破性的進展(就好像「黑天鵝效應」,真正對我們的生活有重大影響的,是那些極端事件,而非經常出現的瑣事。

問自己:「你的時間值多少錢?」如果花一個小時去做某件事,你覺得值得花費你的專注力去做那件事嗎?

抗拒的症狀
  • 拖延
  • 時間常花在處理瑣事與簡單工作上
  • 常做讓自己分心的事
  • 注意力經常分散
  • 有焦慮感
  • 經常出現危機與緊急狀況
  • 不想別人插手幫忙
如何克服抗拒
  • 在抗拒形成阻力前採取行動。
  • 將大型任務拆解成幾個小步驟。
  • 增加不去做的痛苦(刻意增加不便性)。
  • 建立良好的習慣,盡量主動完成任務。
勇敢說不
  • 「效率第一」策略的風險在於,我們極可能將省下來的時間拿去做更多瑣事。
  • 學習時間管理的第一步,就是想辦法減輕手邊已有的負擔。
  • 說「不」時,口氣要婉轉,不要聽起來像被激怒或騷擾似的。
  • 永遠不要找藉口。如果找一些藉口,你會發現你再替自己辯護。
  • 可以這麼說:「我很感激你的請託,但這件事目前無法排入我的優先順序。」或者「我目前正在專心做我的新計畫。」你的理由越平常,對方就越不會質疑。
  • 如果是老闆要求,則可以說:「我手邊還有正在處理的工作,你希望我先暫緩處理哪些,然後優先處理這件工作?」
方法
  • 使用 checklist。把所有大小事情(包括接電話、回覆 email 等)都列在清單上,做完就馬上槓掉。詳細列出所有事項可以協助思考有哪些工作要做,詳細記錄已經做的事項,可供事後檢視自己一天當中的時間都花在哪些事情上。
  • 區分重要性,已決定優先順序。
  • 將資料分門別類,方便查找。

《黑天鵝效應》:Random walker 的預測無用論

11/30/2008
書名:黑天鵝效應(Tha Black Swan)
作者:納西姆.尼可拉斯.塔雷伯
翻譯:林茂昌
出版社:大塊文化
出版日期:2008-4-28

一言以蔽之:Random walker 的預測無用論。

這本書我是跳著看,感覺作者用很多文字談了很多東西,可是又好像只談了一點點。之所以跳著看,除了許多內容無法吸引我,多少也因為有點受不了作者的敘述方式--太冗長、談太多拉里拉雜的東西。有點像走一趟遠路,路上的風景似乎不怎麼美麗,但偶爾在路旁的草叢裡可以撿到一點有用的東西。

這裡不重複太多網路上已經有的書摘內容,僅寫點自己特別關注的部分就好。

氾濫、重複的資訊

「我注意到,幾乎所有人對當前事物都知之甚詳。各種報紙的內容嚴重重複,因此,你讀得越多,可以得到的資訊就越少。然而,每個人依然迫不及待地想要了解每一份剛出爐文件中的每一件事......大家成了誰見了誰、哪個政治人物對哪個政治人物說了哪些話的百科全書。然而,這一切都是徒勞無功。」 (p.42)

話雖如此,每天早上搭火車和捷運的上班途中,我還是會在車上瞄一下別人手上的報紙或雜誌什麼的。我自己是從沒拿過這些報紙,因為我不希望手上沾了油墨,而且,通常我手上都已經有一本書了。那些免費發送的報紙,我發現許多內容都挺煽情、八卦的(難怪叫做「爽報」?),有時我也禁不起好奇瞄個幾眼。

每天餵養社會大眾靈魂的,有多少是知識?資訊?抑或大多是垃圾?

學術研究的歪風-馬太效應

作者在前言中寫道:

「請注意,我這本書並不靠收集選擇性「佐證」這種惡劣的手法。根據我在第五章所說明的理由,我稱這種過度舉例行為為天真的經驗主義(naive empiricism)——挑選一連串合適的文章以拼湊成一個故事,並不能構成證據。任何尋求確認的人,將可以找到足夠的故事來欺騙自己——毫無疑問,還有欺騙他的同儕。」

第 14 章又提到 Robert Merton 提出的「馬太效應」(在社會學裡稱為「累積優勢」;cumulative advantage):

「如果有一個人寫了一篇學術論文,文章中引用了五十位從事相關主題研究者的文章,......另一個研究相同主題的研究者,會從這五十個人中,隨機抽取三人出來,以做為自己論文的參考文獻。Merton 證明,許多學者並未真正讀過他所列的參考文獻的原作;而是讀一篇論文,就從該文的資料出處抽一些出來,以作為自己的參考文獻。因此,研讀第二篇論文的第三個研究者,會從前一篇文章中抽出三名作者以作為自己的參考文獻。這三名作者會累積越來越多的聲譽,因為他們的名字已經和現有的這個主題緊密地結合在一起。勝出的這三人,和原始研究大軍的其他成員之間,其差異主要在於運氣:他們一開始就被選上並不是因為比較行,而只是因為他們的名字出現在先前文獻上的方式使然。......請注意,學者主要是由其作品被其他人引用了多少次來評量,於形成了相互引用的派系(就是『我引用你,你引用我』的這種把戲)。」 (p.317-318)

一針見血!點出了學術論文的「灌水文獻」以及「互推」現象。這跟推推王的推文功能還真是有異曲同工之妙啊。

謬論、誤導、過度簡化

作者在好幾個地方,用了好些篇幅、故事、專有名詞來討論一些邏輯思考上的錯誤,例如:
  • 「幾乎所有的恐怖份子都是回教徒」這個命題,是否就表示「幾乎所有的回教徒都是恐怖份子」?很明顯不是。但若有人提出第一種說法,聽者很可能會立即對回教徒產生刻板印象:他們很可能都是恐怖份子。同理:所有的白馬都是馬,你看到的馬一定就是白馬嗎?
  • 幼稚泛化(naive generalization)。躺在鐵軌上一夜沒死,可以宣稱臥軌是安全的嗎?
  • 過度因果化。這從報章雜誌,尤其是金融市場的評論最為常見,例如 2008-11-21 日有一篇美股盤後的評論,標題為「美股盤後─憂衰退期延長 道瓊大跌445點 S&P 500創11年半低點 」。隔天,同一家機構發出的美股盤後評論標題為「美股盤後─歐巴馬提名Geithner任財長 道瓊大幅收高494點」。反正不管股市漲還是跌,就是要分析個理由出來不可。就好像某一天台股大漲 500 點時,我們也不禁會問:「發生什麼事了?」是一樣的道理。這些媒體和評論員,正好給了社會大眾一個簡單的理由。所以......我們的心就終於可以安定下來了,因為我們不喜歡不確定、複雜;我們要簡單、確定的理由。
  • 沉默證據。故事:有人拿一塊畫板給無神論者看,上面畫了幾個信徒在禱告,並告訴他,這些人在一次船難中都倖存下來。隱含的意思是禱告可以保護你不被淹死。無神論者問到:「那些禱告而後來淹死的人,他們的畫像在哪裡?」哈,問得好!隱藏事實所產生的誤導,適足以令大眾迷信。
這些邏輯思考上的錯誤其實都有點類似,作者花了好多篇幅談這些東西(而且散在各處),看得有點累。要是可以再整理得精簡一些,讀起來應該會輕鬆許多。

預測與運氣

在「如何尋找鳥屎」這一章,作者舉了一個例子來說明很多發現、創新、與成功都是偶然的。他說,1965 年貝爾實驗室的兩名天文學家架了一具大型天線,這具天線接收時有些雜訊,而他們認為可能是天線碟盤上的鳥糞造成的,可是就算清了鳥糞結果還是一樣。過了一些時間,他們才發現那是微波幅射。

後來,貝爾實驗室總裁曾經盛讚發現此微波的天文學家為「文藝復興級的人物」。作者對此的評論是:

「文藝復興個頭。這兩個傢伙當時是在找鳥屎!......而且和大多數這類案例一樣,他們並沒有立即瞭解到這項發現的重要性。」 (p.251)

真敢說啊!難怪作者在前言中說「我冒著被攻擊的危險來表達我的主張」。他也認為微軟之所以在作業系統市場上打贏蘋果電腦,是因為運氣使然。這點個人並不贊同;我相信凡事多少要有點好運的幫忙,可是成敗全歸咎於運氣,這會不會也犯了過度簡化的毛病呢?

很明顯的,作者是一名 random walker,認為世界上一些重大事件其實都是隨機、無法預測的。他之前還曾寫過一本談隨機理論的投資書籍:《隨機的致富陷阱》(Fooled by Randomness)。

槓鈴策略(Barbell Strategy)

如果預測是沒用的,我們該怎麼辦?作者在第 13 章提出他的構想:

「如果你知道你會被預測錯誤所傷,而且,你接受大多數的『風險指標』--基於黑天鵝事件--都有瑕疵,那麼,你的策略就是盡可能的超保守和超積極,而不是溫和的積極或保守。」

這也是他在當交易員時所採用的策略。這個策略對股市投資人來說應該能有一些啟發吧,也許是:不要預測平時的小幅波動,耐心等待重大事件的發生,當機會來臨時,狠狠地大撈一票。

小結

不用放太多心力在常態分布(鐘形曲線)中最集中、比例占最大的瑣碎部分,而要注意那些極少發生的極端特例(事件)。但不用費心預測何時會發生那些重大事件(因為它們是隨機的),只要隨時準備好,抓住機會,因應事件的發生。剩下的,就交給運氣吧。

索引的翻譯

11/26/2008
在翻譯索引時,有一些自己慣用的譯法。本文只考慮頁頁對譯的情況。

英文索引是依字母的順序編排,而且通常是多層巢狀的結構。例如下圖是字母 I 開頭的索引範例:



這個範例最深有三層縮排,但只有第一層的第一個字母用大寫,其餘詞彙都是小寫。

準備工作

建立一份 Word 文件,先把英文索引全複製到這份文件裡。如此一來,往後我們只要直接翻譯這份文件就行了。

英文索引通常採 two-column 的編排方式,所以中文索引的文件可以先在第一頁插入一個兩行的表格,然後把第一頁英文索引的文字複製過來。接著調整字型大小和頁面邊界,讓中文索引的一頁正好可以放得下一頁英文索引。調整完畢之後,以後每一頁就可以直接複製第一頁的格式。

第一層的詞彙以英文為主,接著用括弧顯示中文

範例:

I
Inheritance(繼承)

第二層以上的詞彙則以中文為主,必要時加註英文

範例:

I
Inheritance(繼承)
  元素語意與~, 299
  的度量, 319
    多重
      類別之間的, 106–9

善用文字搜尋取代

索引通常有很多重複出現的詞彙,因此,為了加快翻譯速度,在每碰到一個詞彙時,若往後有可能重複出現,就直接用編輯器的文字取代功能一次處理完相同的詞彙。

注意:第一層的詞彙不要用這種方式置換,因為同一詞彙不可能重複出現在第一層,而第二層以後的中文表現方式又跟第一層不同。

以上所描述的,只是我自己在翻譯索引時的習慣和一點心得,其實都是些枝微末節的東西,記下來方便日後參考而已,倒也沒甚麼特別的地方。

物件導向技術的光環效應

11/24/2008
要是你以為穿上一雙 Nike 球鞋,就可以像喬登一當抓球飛身灌籃,這叫做「錯覺」,是自欺欺人,不可能發生的事。
--Phil Rosenzweig, 《光環效應

近日聽到一些有關 OOAD 的正反意見,這篇文章只是在整理自己對這些意見的一點感想。

光環效應:過度渲染 OO

在碰到推廣 OO 技術的場合,常會聽到傳統的程序導向分析和物件導向分析的比較,並宣稱如果團隊採用 OOAD 方法,軟體專案將能夠獲得許多好處。以下是我最近聽到的一些說法:
  • 更模組化。理由:藉由類別封裝,應用程式都會被切成適當大小的單元。(真的嗎?
  • 可提高重複使用性。理由:物件導向有繼承機制。(反面來看:牽一髮動全身的連鎖效應?
  • 應用程式更容易擴充。理由:未來如果需要增加一項新功能,我們只要增加一個類別,再動態「插入」既有的框架就行了,用戶端程式都不用修改。(如果設計得宜的話
  • 應用程式更好維護。理由:將來如果有一個地方要修改,我們就只要改 XX 類別就行了,其他地方都不用動。(如果設計得宜的話
  • 更自動化、更一致的開發模型。理由:我們使用自動化工具來設計和產生類別骨幹,程式設計師只要填肉進去就行了。(高度懷疑此說法,包括其實用性以及產出的品質;此外,我們是否正在將程式設計師塑造成不動腦筋的 coding machine?
  • 文件更清楚、更好管理。理由:我們全部使用 Use Case 規格來描述功能需求,所有的分析設計文件和模型都是用工具管理,所以文件更集中、好找,也更能清楚表達系統規格。(用工具集中管理的確有好處,但是跟文件內容是否清楚達意無關
每當聽到有人宣稱只要採用 OO 就能獲得上述優點,我不禁懷疑,他曾經帶領或開發過幾個成功的 OO 專案?或者最起碼,寫過幾個 OO 程式?如果沒有用一個實際的小案例將整個設計 walk through 一遍,光憑這些接近廣告的說詞,實在很難確定他的團隊是否有把 OO 技術用在對的地方。

OO 技術可以協助設計師思考如何切割系統,將一個複雜的系統分出條理,切成幾塊,再針對各部分逐步細化、各個擊破。Grady Booch 等人在 《物件導向分析設計與應用》裡面指出:「對軟體的複雜性不夠了解,是導致專案延遲、預算超支、以及無法滿足需求的主要原因。」因此真正的關鍵在於如何對付軟體本身固有的複雜性,至於上面所說的那些 OO 優點,很多都是跟如何用 OO 去設計,以及團隊的開發流程與專案管理實務有關。

淑女和賣花女的差別不在於她們的行為舉止,而是人們對待她們的方式。
--蕭伯納《賣花女》

光環效應:聽說 OO 太理論

另一種極端,是對 OO 技術棄如敝屣,認為那根本是空談、是理想、空有理論的東西。就我個人的經驗,實際上抱持這些想法的人,或多或少都曾在 OO 的路上跌跤,或者受到過多反 OO 言論的感染,而沒有機會實作 OO、深入了解 OO 實際的優點(和缺點)。我想道理很簡單:如果未曾試著了解某件事物或某個人,又怎麼能對它妄加論斷呢?

我不是說每個不贊成 OO 技術的人都是如此,這只是陳述我自己碰過的情形。其實,就算在軟體專案中嘗試導入 OO 技術而不幸失敗,也不見得是 OO 本身的問題。專案失敗的因素通常很多,若沒有深入分析,就把失敗原因歸咎於採用 OO 技術,恐怕是太武斷了。

一位著名的統計學家指出,十九世紀的美國因為酒醉被捕的人數,和牧師的人數呈現明顯相關性。我們認為兩者人數的同時增加的確有明顯相關性,但卻沒有因果關係;此現象反而是另一個因素造成的:美國人口大幅增加。
-- Stephen Jay Gould,《生命的壯闊》

以下是我所聽到的一些關於 OO 的批評與反面意見:
  • OO 其實是很古老的技術了。
  • OOP 還有些幫助,但 OOAD 的 reuse 根本是謊言,business objects 根本不能重複使用,每一個專案都要重寫。(儘管 business objects 很難跨專案 reuse,但分析設計的 patterns 還是可以重複使用。不要只看 code reuse,對設計師來說,design reuse 也很有幫助。記得要 reuse in the large
  • 其實台灣根本沒有多少人真正實行 OO 技術,甚至大學課程都沒什麼在教了,因為太理論了,上不了戰場。(呃...我沒有數據,但這說法似乎有打迷糊仗的嫌疑,以及因為自己不用或用得不好,所以也同理可證地認為別人沒在用或用得不好
  • OO 方法設計出來的程式效能很差。(一般來說,如果類別階層較深,執行速度是會稍微慢一些,但如果「效能很差」指的是數倍以上的速度延遲,那可能得看程式或架構的設計,才能判斷是因為 OO,還是人的因素。
  • 現在的 OOAD 都被程式語言限制住了,大家都只會 Java、C++ 這些,所以思考方式都被程式語言的語法框住了,沒辦法想出更好的設計方法。(是程式語言本身的表達能力過於侷限而阻礙了人的思考,還是人的知識、技能、與想像力不夠?
  • 對一個問題的設計,John 可能會用 5 個類別來解決,Vivid 可能會有 3 個類別,而 Michael 可能就只用一個類別來解決。那麼,誰來決定哪一種設計是正確的?OO 的問題就在這裡,不像關聯式資料庫,還有正規化理論可以驗證;OO 設計根本沒有標準可言,那我們怎麼確定你的設計就是對的?(That's why we need architects and reviewers.
人的思維的確會受限於既有工具的框架,但是專案的失敗或程式品質不良,真的絕大部分都是因為開發人員受到程式語言或工具本身的侷限而無法達到他所想要的設計嗎?這點我是挺懷疑的。

此外,最後一項批評認為:OO 設計沒有任何標準可茲驗證其正確性。這個說法我認為似是而非。我們不妨把「OO 設計」替換成其他字眼,應該就能看出它的問題在哪裡,例如:「結構化分析設計」、「敏捷開發方法」、「C++/Java/VB」。

好比蓋房子,工程師根據設計圖蓋了一棟大樓,是否有標準方法可以驗證這棟大樓的設計一定「正確」,因此住戶住起來都一定覺得舒服?如果有這種標準,我們買房子時就不用先看屋了,只要用工具檢測一下就知道這棟大樓設計的「正確性」。這跟防震、輻射檢測安全標準、或建築法規完全是兩回事;它牽涉的是設計的部分,比較偏向藝術,而非精確的科學。

軟體開發技術不斷改進,是為了提供更好的方法,以協助開發人員設計出品質更好的產品(當然不見得每個新方法都是好的)。我們可以試著遵循一些方法和最佳實務來改善設計的品質,但軟體該如何設計存乎一心,就像每個廚師都能用同一套工具和食材變出不同口味的菜色--哪有要求驗證料理的口味是否符合「統一標準」的道理呢?

小結

這裡我就自己所見的情況整理了一些有關 OO 的正反兩面說法,並加入了個人的意見。我想,輕易全盤相信一套說詞總是不妥,我們能夠做的,還是多看、多學、多實作,並相信自己的體會吧。

最後謹以 Frederick Brooks 在《人月神話》中引用 David Parnas 的話作結尾:

「我們是否能寫出好程式或爛程式,跟使用什麼樣的工具並沒有關係。除非我們教大家如何設計,否則這些語言發揮的效用就會很有限,結果就是大家用了這些語言做出了爛設計。」

《UP 學》:如何避免成為渾球

11/23/2008
《UP 學:所有經理人相見恨晚的一本書》閱讀筆記

利用程式發送 MSN 訊息

11/22/2008
會寫這個程式,主要是因為一個重要的 Web 應用程式三不五時會掛掉,而系統管理員希望能在第一時間發現這個情況(以免使用者來電開罵)。

要偵測伺服器是否掛點,可以用 .NET 的 TcpClient 嘗試連接伺服器的特定 port,這種簡單的方法不僅可以用在 Web 伺服器,也可以用於資料庫伺服器(例如:嘗試連接 Oracle 伺服器的 1521 port)。

一旦發現伺服器無法正常運作,程式可以發送 mail 通知系統管理員,可是系統管理員不見得會經常收信,這樣的時效性就不夠快。現在 MSN messenger 已經很普遍,如果發現系統出問題時,能夠發即時訊息給相關人員,似乎是個不錯的辦法。

這裡我用來發送 MSN 即時訊息的元件是 MSNPSharp。這個元件有附一個 Windows Forms 範例程式可以參考,此範例就像一個陽春型的 MSN Messenger,可以讓你登入 MSN 伺服器、發送訊息給聯絡人等等。

仔細研究這個範例程式,大概就可以瞭解 MSNPSharp 的基本用法,同時也可以發現,此元件大量運用了非同步處理的程式撰寫模型。這種非同步的撰寫方式不僅增加了學習門檻,除錯也比較麻煩,更重要的是,這並不是我想要的。

我的需求很簡單:當程式需要發送 MSN 訊息時,就先登入 MSN 伺服器,然後依事先設定的聯絡人逐一發送文字訊息。訊息發送的對象有可能是離線狀態,這沒關係,繼續發送給下一位聯絡人即可。

針對上述需求,我寫了一個簡單的 wrapper 類別:SimpleMsnMessenger。這裡我不打算詳細說明原理,謹列出幾個重點步驟:
  1. 建立 Messenger 物件 => messenger。
  2. 利用 messenger.Connect() 登入伺服器。
    關鍵事件:messenger.Nameserver.SignedIn
  3. 利用 messenger.CreateConversation() 建立 Conversation 物件 => conversation。
    關鍵事件:messenger.ConversationCreated
  4. 利用 conversation.Invite(("someone@hotmail", ClientType.PassportMember); 邀請對方加入對話。
    關鍵事件:conversation.Switchboard.ContactJoined (已成功邀請對方加入對話)
  5. 利用 conversation.SwitchBoard.SendTextMessage() 發送文字訊息。
有興趣研究的朋友可以參考這篇文章:MSNPSharp傳送信息程序詳解,大致瞭解其設計原理後,再對照範例程式來研究,應該就比較容易瞭解 MSNPSharp 的基本用法。

SimpleMsnMessenger 的完整程式碼可按此連結下載。Note: 此程式碼還有許多改進空間,尤其是錯誤處理的部分,但基本上還堪用。

以下是使用此 wrapper class 的範例:

class ServerAliveChecker

{

private SimpleMsnMessenger msn = new SimpleMsnMessenger("robot@hotmail.com", "password");

private void SendMsn(StringBuilder text)

{

string receivers = ConfigurationManager.AppSettings["ReceiverMsn"];

if (String.IsNullOrEmpty(receivers))

{

return;

}

lblStat.Text = "正在傳送 MSN 訊息...";

Application.DoEvents();

string[] msnMail = receivers.Split(';');

try

{

msn.Connect();

// 等待登入動作完成。

while (msn.IsConnecting && msn.ErrorCount <= 0)

{

Application.DoEvents();

}

if (msn.Connected && msn.SignedIn)

{

// 登入成功

foreach (string mail in msnMail)

{

msn.SendTextMessage(mail, text.ToString());

}

// 等待訊息全部發完,但只等待一段時間。

DateTime now = DateTime.Now;

while (!msn.MessageQueueEmpty)

{

Application.DoEvents();

System.Threading.Thread.Sleep(500);

TimeSpan ts = DateTime.Now.Subtract(now);

if (ts.Minutes >= 3)

{

break;

}

}

msn.Disconnect();

}

else

{

string err = "MSN Messenger 登入失敗";

if (msn.ErrorCount > 0)

{

LogError(err + ": " + msn.LastError.Message);

}

else

{

LogError(err + "。" + msn.LastError.Message);

}

return;

}

}

catch (Exception ex)

{

LogError(ex.ToString());

}

}

}



按此連結下載完整的類別與範例原始碼

OOADA3:程式語言的編年史與排行榜

11/15/2008
《Object-Oriented Analysis and Design with Applications 3rd edition》的附錄 A 裡面有一張圖描繪了自 1960 年以來的部分程式語言編年史(圖 A-1)。這張圖有個蠻明顯的錯誤,就是所有的年代都標示為 1960。

中文版會修正這個錯誤,如下圖所示:



若讀者有興趣看看完整版的程式語言編年史,可以到 Éric Lévénez 的網站:http://www.levenez.com/lang/

另外,原文書的表 A-1 是一份程式語言受歡迎程度的排行榜,其中第一名是 C 語言,Java 位居第二。此排行榜係取自 TIOBETCP-index,但從官方網站的長期趨勢圖可以看出,Java 從 2005 年擠下 C 語言奪得冠軍寶座之後,就一直獨占鰲頭。可見原文書的表 A-1 應該是 2005 年以前的排名結果。中文版會改用 2008 年 11 月份的 TCP-index 排名。

Position
Nov 2008
Position
Nov 2007
Delta in PositionProgramming LanguageRatings
Nov 2008
Delta
Nov 2007
Status
1 1 Java 20.299% -0.24% A
2 2 C 15.276% +1.31% A
3 4 C++ 10.357% +1.61% A
4 3 (Visual) Basic 9.270% -0.96% A
5 5 PHP 8.940% +0.25% A
6 7 Python 5.140% +0.91% A
7 8 C# 4.026% +0.11% A
8 11 Delphi 4.006% +1.55% A
9 6 Perl 3.876% -0.86% A
10 10 JavaScript 2.925% 0.00% A
11 9 Ruby 2.870% -0.21% A
12 12 D 1.442% -0.26% A
13 13 PL/SQL 0.939% -0.24% A
14 14 SAS 0.729% -0.40% A--
15 18 ABAP 0.570% -0.08% B
16 19 Pascal 0.511% -0.13% B
17 17 COBOL 0.510% -0.20% B
18 25 ActionScript 0.506% +0.04% B
19 23 Logo 0.489% -0.04% B
20 16 Lua 0.473% -0.27% B

Configuration Management:形態管理?

11/01/2008
Configuration Management 這個術語比較常見的譯法有「組態管理」和「建構管理」,另外還有「配置管理」、「形態管理」等等。

我在翻譯時採用「組態管理」,主要原因是:
  • Configuration management 跟我們一般講的軟體「建構」不完全一樣。的確,它是有包含軟體建構過程中的版本控制與發行作業,但這些並不等同於 configuration management。
  • 「建構管理」很容易聯想成 "construction management"。
  • Configuration 個人多譯為「組態」。
這裡無意比較各種譯法,只是......把 configuration management 譯為「形態管理」,並將 configuration 解釋為「形」(外觀)和「態」(功能),這會不會有點牽強呢?摘錄其解釋如下:

形態管理 (Configuration Management, 以下縮寫 CM ) = 產品 的 "形" 與 "態" 的 管理 !!
"形" = 物理特性 (Physical Characteristics) = 產品 的 外觀 (How the Product looks like)

"態" = 功能特性 (Functional Characteristics) = 產品 的 功能 (What the Product is suppose to do)

套用在軟體開發領域總覺得怪...

前 100 名最佳軟體工程書籍

9/16/2008

舊文打包:Java 網站應用程式入門實作

9/10/2008
有位網友來信詢問五年前寫的幾篇 Java Web 應用程式設計入門的文章:

1.0 前言
2.0 Servlet 程式設計初步(含 JDK 與 Tomcat 的安裝與環境設定)
3.0 JSP 程式設計初步
4.0 JSP、Servlet 與 JavaBean 的組合應用
5.0 JDBC 入門
6.0 Model-View-Controller 範例

我把以上文章全都包成一個壓縮檔,有需要的朋友可按此下載。解開後開啟 index..htm 即可。

不過,這些文章有點舊了,裡面有些東西或許有一些過時了,建議可以找一些新發表的文章或書籍。

讀蔣勳《孤獨六講》的一點聯想

8/02/2008
我的記性不好,平日工作的一些瑣事常過耳即忘,以致於常出槌、鬧笑話,不僅如此,就連小時候的童年記憶都非常少。每聽到有人回憶童年往事,不禁納悶,為什麼小時候的事情我都想不太起來?能記得的事情寥寥可數。照某些心理學的說法,可能我的童年大都是些不堪回首的痛苦回憶,所以大腦選擇忘記它們吧!

雖然能記得的童年往事不多,但有些記得的部分卻格外印象深刻,只要受到一些外在事物的刺激,那些記憶就會突然湧現。閱讀蔣勳的《孤獨六講》,便令我想起小時候祖父過世的情景。當時家裡長輩請了和尚來誦經,我們家三個小孩年紀都很小,對喪禮的規矩並不清楚,只知道依大人指示行禮、跪拜。我記得的其中一幕,是一些親戚長輩們對我們猛喊:「快哭!要哭!哭大聲點!」我跟祖父相處時間不多,不能說感情深厚,只覺得他是個慈祥又有點滑稽的老人,可是聽到那些大人的命令,我反而更哭不出來,只覺想笑。不知為何,別的記不牢,這件事情的印象卻特別深刻。

蔣先生在書裡面說:「如果你仔細觀察,便會發現在群體文化中,婚禮喪禮都是表演,與真實的情感無關。」

群體文化從小時候便不知不覺地影響著我們,逼迫每個人就範,必須待在大人畫的框框裡。長大以後,情況有改觀嗎?好像沒差多少。在生活周遭、職場上,壓抑個人言行的事情還是屢見不鮮。好比說,曾聽一位朋友提起,有一次他穿件 polo 衫去上班,衣服上面印有某家公司的標誌,主管見狀,便質問他為什麼要穿這種衣服來上班,好像在幫別家公司宣傳。

對比竹林七賢的嵇康,夏天穿著厚棉衣在柳樹下燒火打鐵,被指為違背社會禮俗,最後壓至刑場砍頭。這樣看來,古今皆然--特立獨行就是不對,跟別人不一樣就是不合群、該打。即使是現代,選擇展現自己的獨立意志、表達自己的立場,還是得面對不少衝突。更何況,穿著印有特定字樣的衣服,根本還稱不上特立獨行或標新立異哩。

生命的意義

偶爾還會想起一件事,就是小時候(讀小學的年紀)站在自家後門口,獨自望著後院的天空,憂慮:「如果我死了怎麼辦?我會去哪裡?我對這個世界發生的事都一無所知了,好恐怖。」我也不知道同年齡的小孩會不會也這樣想,但我到現在都還會有這些想法。阿佳小姐說:「你死了,世界還是繼續運轉啊,根本沒影響,你以為世界繞著你轉嗎?」我知道我不是世界的中心,只是那股莫名的恐懼感偶爾還是會浮上心頭。也許是自己對死亡、生命的意義不了解所產生的恐懼吧。

蔣先生認為,生命的意義或許就是在尋找意義的過程,「當你開始尋找時,那個狀態才是意義。」又說:「不管生命的意義為何,如果強迫把自己的意義加在別人身上,那是非常恐怖的事。我相信,意義一定要自己去尋找。」誠哉斯言!

想到另一位蔣先生--故總統蔣中正先生--說的話:「生命的意義在創造宇宙繼起之生命。」小時候老師並沒有解釋這句話是什麼意思,我們只是把它背熟用來考試而已,原來翻譯成白話,就是繁殖下一代。嗯,這也是一種看法,簡單明瞭,只是用詞太拐彎抹角,偉大不掉。

業餘與專業之間

7/30/2008
看書時的一點聯想,順手抄上來。

在《Object-Oriented Analysis and Design with Applications 3rd Edition》的第六章〈Process〉一開始提到:

業餘的軟體工程師總是在尋找神奇、萬能的方法或工具,冀望它們能把軟體開發工作變得極為簡單。專業的軟體工程師就不同了,他們知道那種東西並不存在。業餘人士經常只想要像煮菜那樣照著食譜一步一步做,專家則知道這種開發方法會設計出呆板的產品,並導致開發人員用一連串的謊言來掩蓋一開始就做了錯誤決策的事實。業餘的軟體工程師往往毫不重視文件,要不然就是太過以文件為中心,只擔心客戶不滿意這些文件,對於文件真正應該呈現的重點反而不那麼在意。專家了解文件的重要性,但是他們絕不會為了寫文件而犧牲掉改進軟體架構的時間。

這是業餘和專業開發人員的差別。

我又想到薩依德在《知識分子論》當中的論述:

....所謂的業餘性就是,不為利益或獎賞所動,只是為了喜愛和不可抹煞的興趣....今天在教育體系中爬得愈高,愈受限於相當狹隘的知識領域。當然,沒有人會反對專業能力,但如果它使人昧於個人直接領域--比方說,早期維多利亞時代的情詩--之外的事情,並為了一套權威和經典的觀念而犧牲一己廣泛的文化時,那種能力就得不償失。....專業化也戕害了興奮感和發現感....陷入專業化就是怠惰,到頭來照別人的吩咐行事。因為聽命於人終究成為你的專長。

以上摘錄的兩段內容都提到了專業和業餘的不同,看起來兩邊的觀點是彼此衝突,不過,由於取自完全不同領域的書籍,直接拿來比較會有點奇怪。

第一段的業餘和專業,講的是對自己吃飯謀生的東西熟不熟,厲不厲害的程度差別。第二段的層次則高一些,談的是忠於自己還是服從他人、是否願意犧牲自由的心靈和廣泛的興趣,以換取特定領域的專業形象(訓練有素的狗?)。

我想,專業的軟體工程師也可以有業餘人士的生活態度和價值觀。而不那麼專業(業餘)的軟體工程師當然也可以透過取得高學歷、考證照等方式,為自己塑造出專業的形象(但能維持多久還是得看後面的努力了)。

我好像喜歡業餘多一些 :)

[.NET Code] 如何在指定的逾時時間內偵測伺服器能否連接

7/06/2008
問題

應用程式使用 .NET WebClient 類別執行偵測新版本以及線上自動更新的功能,可是如果伺服器因為某些原因無法連線(如網路斷線、伺服器關機等),應用程式在檢查是否有更新版時,就得等 20 秒左右才會發生連線失敗。
想要使用 Socket 或 TcpClient 類別事先偵測伺服器的 80 port 能否連線,可是結果還是一樣,因為它們並沒有提供連線逾時的屬性。

解決方法

可使用非同步的方式解決。以下是利用 TcpClient 類別偵測伺服器 80 port 能否連接的工具函式:

/// <summary>

/// 偵測指定的伺服器的特定 port 是否可以連接。

/// </summary>

/// <param name="host">伺服器名稱或 IP 位址。</param>

/// <param name="port">Port 號。</param>

/// <param name="timeOut">連線逾時時間,單位:秒。</param>

/// <returns></returns>

private bool IsServerConnectable(string host, int port, double timeOut)

{

  TcpClient tcp = new TcpClient();

  DateTime t = DateTime.Now;

 

  try

  {

    IAsyncResult ar = tcp.BeginConnect(host, port, null, null);

    while (!ar.IsCompleted)

    {

      if (DateTime.Now > t.AddSeconds(timeOut))

      {

        throw new Exception("Connection timeout!");

      }

      System.Threading.Thread.Sleep(100);

    }

 

    tcp.EndConnect(ar);

    tcp.Close();

    return true;

  }

  catch

  {

    return false;

  }

}


呼叫 IsServerConnectable 時,可將參數 timeOutSeconds 指定為 1,如此一來,函式最多只等待 1 秒便會立即傳回結果。

2008-11-22 增加:偵測某個網頁是否能正常回應

    private void PingWebPage(string url, int timeout)

    {

      HttpWebRequest req = (HttpWebRequest) WebRequest.Create(url);

      req.Method = "GET";

      req.Credentials = CredentialCache.DefaultCredentials;

      req.ContentType = "text/xml";

      req.Timeout = timeout;

      WebResponse rsp = req.GetResponse();

      StreamReader sr = new StreamReader(rsp.GetResponseStream(), Encoding.UTF8);

      try

      {

        sr.ReadLine();

      }

      finally

      {

        sr.Close();

        rsp.Close();       

      }

    }


p.s. 以上程式碼是由 CopySourceAsHtml 產生。選項:Wrap words + Embed styles + Overwrite tab size + Overwrite font list (Fixedsys) + Strip line breaks。

UML 套件圖:細說 «import» 與 «access»

6/23/2008
摘要

  UML 套件圖的 «import» 與 «access» 概念雷同,但語意略有不同,這篇文章主要即在說明這兩個 stereo types 的差異。本文的圖都是引用自《Object-Oriented Analysis and Design with Applications》的第 5 章,部分有稍微修改(以免圖太大)。

UML 套件圖基本觀念

在進入細節之前,先來看一下 UML 套件圖的基本概念。

UML 套件的一個主要用途,是展現系統各元素(例如:子系統、模組、元件等)的組織與分割方式。套件裡面可以包含其他子套件而形成巢狀套件,也可以包含其他 UML 圖形元素,例如:類別、Use Case 等。當我們使用套件圖來表達系統的邏輯或實體結構時,可能會需要在圖中呈現套件之間的相依關係,例如下面這張套件圖:


此套件圖呈現的是一個園藝系統的套件結構,裡面包含兩個子套件:Planning 和 CropTypes,而這兩個套件裡面又包含了一些類別。相依關係的符號是虛線加上一個開放箭頭,圖中箭頭的方向表示 Planning 套件內的元素會用到 CropTypes 套件內的元素。這個「用到」的意思,以比較偏實作的術語來說,通常就是「呼叫方法」、「存取屬性」的意思。在表達分析概念時,我們比較常用抽象的、更一般化的術語,例如:參考、引用等。

「存取」也好,「引用」也罷,究竟它有什麼值得討論的地方?就上圖而言,這表示 Planning 套件中的程式碼在使用 CropTypes 套件中的元素時,必須指名欲存取的元素名稱,例如: CropTypes::CropDatabase 或 CropTypes.CropDatabase。這種從外層套件開始指定每一層元素的名稱,即稱為限定名稱(qualified name);而每一層套件,也就由其名稱構成一個存取範圍,這個範圍就是所謂的命名空間(namespace)。

什麼是 «import»

在撰寫程式的時候,如果都得使用 qualified name 來指名某個類別,這未免太累了。因此,許多物件導向程式語言都有提供引用其他套件(模組、命名空間....whatever)的語法來減輕程式設計的負擔(程式碼也更簡潔),例如 Java 的 import、C# 的 using、VB.NET 的 Imports 等等。對應到分析設計領域,使用的術語就是「匯入」(import)。也就是說,一旦你在某個類別中指定匯入其他套件(命名空間),當你要存取匯入套件的類別時,就不用指定 full qualified name,而只要寫類別名稱就可以了。

說到這裡,還是得提一下 visibility。我們可以指定元素的 visibility 來控制它可以被誰看見,像圖中的 CropDatabase 類別名稱前面有個加號('+'),就代表它是公開的(public),即誰都可以存取;PlanAnalyst 前面有個減號('-'),代表它是私有的(private),亦即只有它所屬的命名空間(套件)中的元素可以存取它。

呼~基本概念就講到這裡,快點進入主題吧....

«import» 與 «access»

一般來說,在表現套件之間的相依關係時,用剛才那張圖的表示法就夠用了,因為相依關係的箭頭在沒有特別標註其他符號的情況下,預設就是 «use» 關係。不過,UML 還提供了 «import» 和 «access» 來進一步描述相依關係,而這兩種關係就有點微妙了。

一言以蔽之:«import» 就是公開的 «use»;«access» 則是私有的 «use»。

聽起來有點像在繞口令,我用另一張圖來解釋。



這張圖是上一張圖的進一步描述,裡頭的相依關係都標上了 «import» 或 «access»。根據剛才「一言以蔽之」的說法,圖中 Planning 套件是以公開的方式使用 CropTypes 套件中的類別,而以私有的方式使用子套件 Plans 裡的類別。說得更明白些,Plans 套件裡的類別就只有 Planning 套件看得見;Greenhouse 套件雖然有 import Planning,但它看不見 Plans 裡面的類別。

遞移性

在《UML 2 and The Unified Process 2nd Edition》中的說法是:«import» 代表被匯入的套件的元素名稱會加入到要求匯入的套件中,而成為該套件的公開元素;«access» 則代表被匯入的套件的元素名稱會加入到要求匯入的套件中,而成為該套件的私有(隱藏)元素。

也就是說,«import» 與 «access» 的主要差異在於遞移性(transitivity)。 «import» 是可遞移的關係,舉例來說,若套件 A import B,B 又 import C,則表示 A 也隱含地 import 了 C,換言之,套件 A 中的類別可直接存取套件 C 中的類別。相對的,«access» 則沒有遞移性,所以上圖中的 Greenhouse 套件無法使用 Plans 套件中的類別。

對應到實作

那麼,如果在分析設計時使用了 «import» 或 «access» 來表達套件之間的關係,對應到實作時會是什麼語法呢?

以 .NET 的 C# 語言來說,如果所有引用的類別都宣告為 public,那就是 «import» 關係;如果某個類別宣告為 internal,亦即只有該類別所在的組件(assembly)才能看見它,那麼它就是僅供同一組件中的類別使用,這便符合了 «access» 的語意。

同樣以前面的圖例來說明,如果將 Planning 套件實作成一個 .NET 組件,那麼它裡面的兩個子套件就代表該組件中的兩個命名空間,而且 Plans 命名空間裡面的兩個類別 GardeningPlan 和 PlanMetrics 都是宣告為 internal(儘管圖中是以 '+' 表示公開)。如此一來,就只有 Planning 裡面的類別可以存取 Plans 的 GardeningPlan 和 PlanMetrics,其他外部類別則無法存取。

Java 語言支援的四種 visibility 層級中,package-private 的作用即類似 C# 的 internal

最後一個問題:更簡化,還是更複雜了?

或許有人會覺得,軟體專案本身已經夠複雜了,為何要弄個 «import» 或 «access» 語法來折磨自己(和別人),全都用預設的 «use» 不就結了?

這就跟你不會把類別的所有成員都宣告為 public 的理由一樣:封裝與資訊隱藏。在稍微複雜一點的系統中,切分套件時一定會碰到有些小型套件僅供特定對象使用,這時候當然就不需要將它公開。資訊太多,對人和電腦其實都是一種負擔。

設計師的工作就是要以簡馭繁,我對這句話的解讀是:把繁複的思考工作攬下來,設計出簡單好用的類別,讓使用類別的人覺得簡單。這時候,「簡單就是美」就不能拿來當作偷懶的理由了(雖然我們做軟體開發的其實都是在想辦法「偷懶」),否則每次碰到大量資料時開個超大陣列就好了,又何必學資料結構和演算法呢?

[.NET Code] 在 .NET 程式中透過 Gmail 伺服器發信

6/21/2008
當程式需要寄送 e-mail 時,我原本是透過 Hinet 郵件伺服器發送,程式碼大概長這樣:

Encoding enc = Encoding.GetEncoding("BIG5");

MailMessage msg = new MailMessage();

msg.From = new MailAddress("寄信人@culala.com", "寄信人姓名", enc);

msg.To.Add(new MailAddress("收信人@wulala.com", "收信人姓名", enc));

msg.Subject = "使用者意見回報";

msg.SubjectEncoding = enc; // 設定標題編碼

msg.Body = "Bug 好多 Orz....";

msg.BodyEncoding = enc; //設定內文編碼


SmtpClient smtpMail = new SmtpClient();

smtpMail.Host = "msa.hinet.net";

smtpMail.Port = 25;

smtpMail.UseDefaultCredentials = true;
smtpMail.DeliveryMethod = SmtpDeliveryMethod.Network;

smtpMail.Send(msg);


但由於 Hinet 只允許自家客戶使用它們的伺服器發信,所以當我的程式部署到別的網路環境時,就無法寄送信件(錯誤訊息是 ....relaying denied)。當然,我可以將寄送郵件的參數儲存在應用程式組態檔中,並且在部署到目標環境時,根據當時的網路環境調整相關參數。但我想,何不試試 Gmail?這樣不管到哪裡都可以用同一組設定了。

從 Gmail 官方網站的說明文件可以得知相關參數,包括:SMTP host 、必須啟用 SSL 連線、使用的 port、必須驗證使用者的帳號密碼等等。其中比較值得注意的,是依郵件用戶端軟體的不同,有的 SMTP port 必須用 465(如 Outlook 2003)、有的則是 587(如 Outlook 2007)。

我試的結果,如果 port 用 465,.NET 的 SmtpClient 類別在寄送郵件時就會發生 timeout 錯誤。用 587 則可以成功寄送。程式碼如下:

SmtpClient smtpMail = new SmtpClient();
smtpMail.Host = "smtp.gmail.com";

smtpMail.Port = 587;
smtpMail.EnableSSL = true;

smtpMail.UseDefaultCredentials = false;
smtpMail.DeliveryMethod = SmtpDeliveryMethod.Network;
NetworkCredential cred = new NetworkCredential("使用者名稱", "使用者密碼");
smtpMail.Credentials = cred;

smtpMail.Send(msg);


我還試了 port 25,發現也可以成功寄送。這倒有點意外,Gmail 官方網站上似乎沒有提到可以用這個 port。

網路上有的文件說,透過 Gmail 發信時,傳入 NetworkCredential 建構元的"使用者名稱"必須使用完整的 e-mail 帳號(也就是後面要加上 @gmail.com),我試的結果是無論有沒有加 domain 名稱都可以。

另外,你也可以用 CredentialCache 類別來指定驗證的帳號密碼,像這樣:

......
NetworkCredential
cred = new NetworkCredential("使用者名稱", "使用者密碼");
CredentialCache credCache = new CredentialCache();
credCache.Add(smtpMail.Host, smtpMail.Port, "login", cred);
smtpMail.Credentials = credCache;

smtpMail.Send(msg);


程式碼稍微多一點,其實如果沒有碰到特別的問題,用前面的方法就好了。

棘手問題:無法處置 BufferedGraphicsContext,因為緩衝作業正在進行中。

6/19/2008
使用者回報一個錯誤訊息:「無法處置 BufferedGraphicsContext,因為緩衝作業正在進行中。」而且是當應用程式處理的資料量大到某個程度時才會出現。用 Google 搜尋可以找到一些討論串,但沒有標準答案。我試了幾種方法,最後雖然解決了,但卻只是避開它而已,嚴格來講,我並沒有找到問題的癥結點。底下是我處理這個問題的過程,如果您也碰到同樣的問題,我的作法也許可以提供一點靈感。

初步測試

首先,我請 user 提供他的資料檔,然後在自己的兩台 Windows Server 2003 機器上測試。結果一台會出錯,一台可順利執行。碰到這種情形,自然會懷疑是因為環境組態的差異造成。由於這是 .NET Windows Forms 應用程式,因此我檢查了兩台機器的 .NET Framework 版本,結果一樣都是 .NET Framework 2.0 with SP1,然後再比對其他用到的軟體元件,版本也都一樣。唯一的差異,似乎就只有兩台機器的記憶體容量,在 3GB RAM 的機器上測試不會出現錯誤,在 2.5GB 機器上測試則有錯誤。難道真的是因為記憶體耗盡?可是工作管理員顯示實體記憶體還有大於 1GB 的可用空間。若把處理的資料量減少,兩台機器又都可以通過測試。

簡單描述一下我的程式處理架構:程式有個 MainForm,當使用者在 MainForm 中執行某個功能時,會進行大量的資料運算及處理(這裡有頻繁、大量的配置記憶體動作);資料處理完後,會建立另外一個 ResultForm,以呈現處理的結果,而錯誤就是出現在呼叫 ResultForm 的 ShowDialog 方法的時候。

就像一個經常熬夜的人,平常都覺得沒事,等到 50 歲以後,有一天突然倒下,才發現肝臟已經嚴重纖維化了。這個問題就有點這個味道,它不像騎車跌倒那樣能夠明確知道出事的地點和原因,而是累積一段時間後,直到執行一行平淡無奇的程式碼才發生錯誤(例如 form.ShowDialog),此時就算在 exception 發生的時候查看 call stack 也沒有什麼有用的線索,這是它棘手的地方。

網路爬文

錯誤訊息的原文是「BufferedGraphicsContext cannot be disposed of because a buffer operation is currently in progress.」用 Google 可以找到一些討論串,例如微軟技術論壇這帖:http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=200483&SiteID=1,但是都沒有直接的答案。也就是說,雖然知道大概跟圖形處理有關,但錯誤發生的原因可能有好多情況。其中一個來自微軟員工的回答更令人沮喪:

This indeed seems like a bug in the framework and we at MS haven't been able to identify it since we have not been able to reproduce this problem internally - again, if someone has a consistent repro please report it to us - I don't expect this to be fixed in 3.0 .

Ok! 看起來像是 .NET 的 bug,微軟也還不知道原因(當時是 2007 年 8 月),而且就算改用 .NET 3.0 恐怕也一樣(合理,.NET 3.0 本來就只是 .NET 2.0 外加其他新元件而成的 superset,骨子裡還是 .NET 2.0)。

歸納一下從網路上爬文所得到的資訊,可以大概猜測問題很可能跟以下因素有關:
  1. 應用程式大量配置記憶體或 GDI 資源;
  2. 某個控制項使用了 double buffer 技術來加速顯示。
使用獨立的 App Domain 來隔離嫌移程式碼

既然只有大概的方向,那只好用消去法了。我將 ResultForm 上面的控制項逐一拿掉,Form_Load 事件裡面的 code 也全移除,到最後只剩下一個 3rd-party 元件:SourceGrid.Grid。如果將這個 grid 也去掉,程式就完全不會出錯。於是我看了一下 SourceGrid.Grid 的原始碼,果然,在它的基礎類別的建構元裡面有用到 double buffer:

SetStyle(ControlStyles.Selectable, true);
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
SetStyle(ControlStyles.DoubleBuffer, true);
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
SetStyle(ControlStyles.ContainerControl, true);

為了確定問題是否是 Grid 元件引起,我把 Grid 換掉,找了一個可能有用到 doubble buffer 的標準 .NET 元件取代:PictureBox。結果證明,用 .NET 標準元件也會出現同樣的錯誤。這下子更傷腦筋了。我的程式雖然處理資料時非常複雜,也需要大量的記憶體(其實也才 50MB 左右而已,視輸入的資料量而定),但並沒有用到 multi-thread,所以程式碼其實也沒有甚麼特殊的寫法。裡面用到最頻繁的,是一堆可序列化的類別,以及許多動態配置的泛型串列物件。

既然標準元件也會出問題,我只能懷疑是當程式配置了大量記憶體或 GDI 資源時,就會引發 .NET 圖形處理元件的罕見 bug 了。所以我先採取的作法,是將那些處理大量資料的 DLL 組件(姑且統稱為 HeavyTask.DLL 吧)載入到獨立的 App Domain 中執行,也就是說,利用 App Domain 的隔離特性把可疑的程式碼隔絕於主程式(預設 App Domain)之外,而且每次資料處理完就將那個新建立的 App Domain 卸載。程式碼大概像這樣:

AppDomain appdm = AppDomain.CreateDomain("HeavyTaskFacade");
IHeavyTaskFacade hvf = (IHeavyTaskFacade) appdm.CreateInstanceAndUnwrap( "HeavyTaskFacade", "HeavyTaskFacade.Manager");
hvf.DoHeavyTask(bigInputData);
AppDomain.Unload(appdm);


程式改寫之後,執行看看,結果可以順利執行 HeavyTask,程式也沒出錯了。但是我只高興了一下下而已,因為,再執行一次 HeavyTask 就又出錯了。儘管程式有卸載 App Domain,後來甚至用上 GC.Collect 和 GC.WaitForPendingFinalizers 方法,用工作管理員觀察第一次執行 HeavyTask 前後的記憶體用量,發現應用程式佔用的記憶體根本沒釋放多少。不死心,我再將程式改寫,在獨立 App Domain 中以另一條執行緒來執行 HeavyTask,看看是否有影響(前面那個寫法還是只有一條預設的執行緒,在不同 App Domain 間穿梭)。程式運作架構如下圖。



結果......還是一樣 >_<|||

採取更大的隔離範圍:process

利用 App Domain 隔離沒有用,那就試試看把 HeavyTask 改放到單獨的 process 吧。還好原本寫程式時已經元件化、模組化,程式碼要怎麼乾坤大挪移都不算太麻煩。這次是把 HeavyTask 製作成一個 console 應用程式。用這種方法,經過幾天反覆測試,都沒有再出現錯誤訊息了。

雖然還不明白癥結點究竟在哪一行程式碼或哪一個類別裡,
但問題總算是解決了......或者應該說:被隔離了。
技術提供:Blogger.
回頂端⬆️