2012年12月22日

公開致歉

人際關係揭露:我和賽柏飛認識了六七年,不算熟絡但自認是蠻不錯的朋友。我不認識他的名嘴先生,只在他們的婚宴和某些公開場合遇過,沒講過話。

名嘴是公眾人物。公眾人物的公開行為,可以公評,這是我寫【伶牙利齒 vs. 深厚知識】的原因之一。我在寫的時候,擔心會不會影響賽柏飛的心情,但這次的事情我實在難以假裝沒看到。

我沒想到賽柏飛會來留言,更沒想到會引來這麼多對他的批評。

名嘴對教授的批評,我認為是考慮過、作出決定去做的;然而,賽柏飛在我這個亳不知名的部落格留言,比較像是看了文章後一時情急,想向我這個朋友解釋而寫下的,沒有想到會被這麼多網友看到、轉貼、備份。

有網友說賽柏飛公關零分,的確,他不是公眾人物,不擅長公關很正常,何況他一開始恐怕沒意識到這是公開的。

有網友根據賽柏飛的留言批評他,這部分對我是嚴重的利益衝突(用詞怪怪的,又沒牽涉到金錢),要迴避。

這件事對賽柏飛一定造成了不小的傷害,我很擔心他,也覺得很對不起他。

我不殺伯仁,伯仁卻因我而死。我要藉我部落格的小小一隅,對賽柏飛因此事受到的傷害,向賽柏飛公開道歉。

﹣鍵於是芥茉日誤點兩小時的飛機上

2012年12月19日

伶牙利齒 vs. 深厚知識


某教授針對流傳的「12月21日世界末日」七大可能發生原因,從科學角度說明為何不可能。(影片

或許是開場就把謠言四竄歸咎於〈名嘴愛嚇人〉和〈媒體不長進〉,粉絲眾多的某名嘴兼媒體節目主持人就在 Facebook 上批評該教授。為免有人說我竄改原文或是斷章取義,以下貼出全文截圖(因為我電腦的時區設定,Facebook 顯示的時間和台灣時間相差 16 小時)。


2012年12月7日

中文可以接多長?

英文有名詞字句,可以接得落落長;想來試試中文可以接多長。

  1. 教育部長說他關心學生
  2. 學生批評教育部長說他關心學生是偽善
  3. 媒體批評學生批評教育部長說他關心學生是偽善是沒禮貌
  4. 網民批評媒體批評學生批評教育部長說他關心學生是偽善是沒禮貌是媒體霸凌
再來呢?

2012年9月4日

To charge or not to charge, that is the question.

台灣開源界一年一度的大拜拜 COSCUP 從創立以來一向不收報名費,但也不斷有人認為應該要收。我在商業公司工作,我並不反商,使用者付費沒什麼不對。不過對於 COSCUP,我有不同的想法。

COSCUP 和一般學術或技術研討會一樣有專業的演講,但最大的不同是在現場才能感受到的無私分享、熱情和歡樂!

因為 COSCUP 很特別,「人家研討會都收費,為什麼 COSCUP 不能?」這句話未必能直接套用。

COSCUP 從 2006 年第一屆以來,一向是免費報名、報到名才能入場聽講,而活動所需費用都從贊助金支應,相對會給贊助單位一些權利,如大會演講、技術演講、攤位、手冊中的廣告、大會網站上的品牌露出等等。

從財務的角度來說,雖然 COSCUP 每年開辦時都不確定能找到多少錢,但感謝各界熱情贊助,每年量入為出之後都還能留一點未來的雨天基金。COSCUP 的財務報表並不需要有「報名費」這項才能收支平衡。

從經營的角度來說,Yahoo!、Pixnet、Google、Facebook 等等公司提供免費網站,Angry Bird、RoboDefense 等等手機 App 有免費版本,Freemium 也是廣為人知的名詞。營利的公司都能用免費產品或服務生存了,不以營利為目的的 COSCUP 如果要向與會者收費,需要充分的理由。

而且,收費研討會和免費研討會的差別不是帳簿上多一項少一項而已,而是本質上的不同。比方說,幾年前在邀請某國外講者時,曾被問到 COSCUP 是否收報名費,如果收的話,該講者的公司規定不能由公司支付旅費,就要由大會出。同時,即使會眾獲得的比付出的報名費多很多,也不免有會眾持出錢的是大爺的想法,破壞了大拜拜的歡樂氣氛。

我不想寫太長,只總結幾個點供大家參考:

  • 收報名費不只是量變,而是質變。
  • 收報名費後,某些會眾的心態可能不再是「參加大拜拜的朋友」,而是「消費者」,破壞了大拜拜的歡樂氣氛。
  • 收報名費後,視訂價策略,可能會改變會眾組成。
  • 收報名費後,視訂價策略,總人數可能會降低,隨之可能會影響某些公司的贊助意願或金額。
  • 收報名費後,某些公司不再能幫講者出旅費。
  • 收報名費後,報名網站應該不會擠爆了。但照說這是技術問題,有技術的解法。
補充一下會眾組成部分:收報名費可能會減少學生的參與。未來的新星是今天的學生,我認為讓一定比例的學生參加是必要的。另一方面,確保工作忙碌的長輩大神們有時間報名也是必要的。在一位難求的這幾年,大會用 VIP 票制讓大大們不用搶就有票,確保大拜拜的本質,也兼顧了學生參與的機會。

收來的報名費要如何使用也是個要考慮的問題:
  • 如果用在人事費用,無酬志工和有酬雇員之間的關係和籌備團隊內的氣氛可能會有不小的化學變化。
  • 如果用來分攤活動費用,如前所說,COSCUP 不需要報名費就能打平收支。
  • 如果用在贊助台灣社群活動,如乃特所說,不是件容易做好的事。而且本來就可以在 COSCUP 大會期間募捐,報名費不是唯一募得社群活動經費的方法。

我不能預測未來,或許有一天經濟大環境或廠商生態改變了,造成 COSCUP 的贊助收入不足。但是只要大家對 COSCUP 有心,一定能找到出路,募捐、義賣 T-shirt、午餐自理、… 等等,有太多方法可以打平收支。(OS:我好想買 COSCUP T-shirt 啊!)

而我最不希望看到的,是因為收費或其他原因,讓 COSCUP 失去獨有的無私分享、熱情和歡樂。

2012年3月3日

林員外,你累了嗎?

員外擁有一棟之前經營 KTV 的大樓,KTV 搬走了、伴唱機也都拆走了,他就把全部的空包廂提供出來,任何人可以免費借用一間,只要在門上貼上自己的姓名、自己帶床頭音響、CD、iPod 等等,就可以在包廂裡聽。

員外還允許任何人自由免費進出這棟大樓的任何包廂,進入包廂時,如果廂主正在播東西,就會聽到,還可以和廂主聊天交朋友。

為了支付大樓的電費,員外在大樓外牆和內部走道的牆上貼了一些廣告海報,向廣告主收取廣告費。

張三李四有緣進入了王二麻子的包廂,聽到王二麻子正在播他剛買的五月天 CD 裡的《我不願讓你一個人》這首歌。這時有沒有人侵犯音樂創作者、出版商或誰的著作權呢?

如果把場景和人際關係改一下,地點換成王二麻子的家,張三李四王二麻子的朋友,播買來的 CD 是合理使用吧?

場景和人際關係再改一下,地點換成王二麻子的服飾店,張三李四是逛店的客人,照臺灣目前的做法,王二麻子要先向著作人或是著作權仲介團體取得公開播送權,才能播買來的 CD。如果王二麻子沒獲得授權就播出,被掃街人發現了,可以罰王二麻子

我不是律師,我對這件事粗淺不專業的認識是:

  1. 因為員外的大樓允許任何人自由進出,當王二麻子在包廂裡播歌時,聽歌的對象是「不特定人或特定之多數人」,也就是著作權法第 3 條定義的「公眾」。
  2. 王二麻子對公眾播歌的行為,符合著作權法第 3 條定義的「公開播送」:「指基於公眾直接收聽或收視為目的,以有線電、無線電或其他器材之廣播系統傳送訊息之方法,藉聲音或影像,向公眾傳達著作內容。」
  3. 著作權法第 24 條規定:「著作人除本法另有規定外,專有公開播送其著作之權利。」
  4. 王二麻子未獲授權就對公眾播歌,似乎觸犯了著作權法第 92 條:「擅自以公開口述、公開播送、公開上映、公開演出、公開傳輸、公開展示、改作、編輯、出租之方法侵害他人之著作財產權者,處三年以下有期徒刑、拘役、或科或併科新臺幣七十五萬元以下罰金。」
  5. 可是王二麻子沒向任何人收錢,著作權法第 55 條寫:「非以營利為目的,未對觀眾或聽眾直接或間接收取任何費用,且未對表演人支付報酬者,於活動中公開口述、公開播送、公開上映或公開演出他人已公開發表之著作。」

這樣看來,著作權法 55 條似乎是王二麻子的護身符?

如果播歌的王二麻子沒侵權,提供包廂的員外又侵犯了誰的著作權呢?

不然為何調查局要去員外家搬走大樓藍圖、還說員外「侵犯著作權」、「疑獲取不法商業利益」呢?

誰來教教我好嗎?

【3/4 凌晨編輯】在噗浪上有很長的討論和好幾個爭議點,直接影響以上論述的有兩點:(1) Now.in 讓 DJ 用網路把音檔內容傳遞給聽眾,算是公開播送還是公開傳輸? (2) 著作權法第 55 條似乎只規範現場表演,若果真如此,則不適用於 Now.in 的情形。

2012年2月25日

那一年,我們一起辦的 COSCUP 2011

艦長日誌:COSCUP 星曆 6086.20,天氣晴。

不知道為什麼 2011 年的工作人員名冊中我變成艦長了... 一定是從某位自稱船醫的喬巴Chao Bob)開始的。

(咦?船醫被某人黑去做了?喂!船醫變成輪機長,這樣真的可以嗎? XD )

在新艦長啟航前,趁還有點記憶,側寫一下 2011 年的 COSCUP。

在 COSCUP 星曆 5103.11 我們組好了艦橋團隊,正式啟航。

  • 投稿人的命運規劃局局長 Freedom(議程)
  • 募款實力超強的特種精英 Ernest(銷售)
  • 魔球經理還熟悉數字的 Lloyd(會計)
  • 團隊裡許多人的好姊妹小菲(行政)
  • 讓大家知道 COSCUP 是個更好的世界Bob(行銷)
  • COSCUP 的命運化妝師 Laetitia(公關)
  • 做網站火力拳開MozillaPaul Rouget 讚不絕口的 Timdream(網站)
  • 今年終於可以嘴砲的立夫(線路)。
  • 讓大家在報名時大玩鐘點戰、晚來就極速秒殺、還要幫中國大陸朋友辦入境手續特別服務Choupi(會眾)
  • 玩咖兼宅爸Jouston(輔導)
  • 謀殺許多底片和記憶卡的殺手小朱盆栽 Yurenju(記錄)
  • 要問清楚「先生你哪位?」的報到處、讓大家不必外出用餐的餐點組、讓門當證不對的人不得進入的門神組,他們共同的領隊當然就是在會場上上下下的翻滾吧!三秒(場務)

2012年2月1日

開票所應該 open data

好朋友 H.C. 發了噗

根據最近的一些報導跟 po 文,我真切的覺得,中選會應該要把選舉統計程式 (從接收資料到計算資料的部份) open source 出來
這應該是針對這次總統大選後在網路上出現的一些傳言(如這裡)。

比起哪個黨當選,我更關心選舉的公平性與透明度。政治從業人員的財產有陽光法案要求攤在陽光下,開票過程理所當然更應該在陽光下受放大鏡、顯微鏡、電子顯微鏡的檢驗。如果有人作票而當選,他應該負法律和政治責任。如果質疑作票,我希望質疑者和被質疑者都能在第一時間拿出證據,不要放任謠言漫天飛舞。

問題是,鐵證在哪裡?

這次大選如果證據不足,接下來可預見的是陰謀論和口水戰,最後只會信者恒信,不信者恒不信,事實無法見信於社會。

逝者已矣,來者可追。我比較希望有方法、系統性地避免這種模糊空間再度發生。

與其要求中選會把那個接收各開票所票數然後做個小學生都會的加法的程式公佈出來,不如要求各開票所把票數公開出來,讓任何人都得以知道每個開票所是幾點幾分、開出多少票,要加總的人自己去加。不是由中選會接收後公佈,是由開票所開完票,票數確認無誤(各黨要派代表監票沒人會阻擋吧?),就直接公佈在網站上。

更進一步想,與其等待政黨派代表監票,任何關心公平性和透明度的人都可以去監看開票過程,甚至全程錄影,把票數和錄影貼到網路上。只要每個開票所都有人這樣做,還有人能操縱票數嗎?

對落選的政治從業人員,可以心服口服。對勝選的政治從業人員,可以預防黑函謠言。對選民,可以扼殺陰謀論的成長空間,把時間花在更有意義的地方。凡是心中坦盪的政治從業人員和選民,都會贊成的吧?

p.s. 就算開票全程監看,真有心作票的人是否能在投票期間或投票前做手腳?這我還真不確定。

p.p.s. 有空該來研究一下做這件事的技術問題和所需資源... 大概需要雲端運算加持一下。 :)

2012年1月28日

悼 Elaster Labs

上個禮拜得知 TCloudElaster Labs關閉服務了,雖說我個人一開始就不太看好在臺灣發展 Platform as a Service 雲端服務的前景,但是看到這麼多熱血工程師苦心建構的服務要收掉,還是不勝唏噓,特別為文悼念。


Elaster CAP 這個平台我並沒有用過,從安裝手冊來看,

Elaster CAP 是個可以免費下載裝在自己的叢集上的 PaaS 軟體系統。

它把 OpenLDAPHadoopZooKeeperHBaseSolrNginx 等等開放源碼軟體包括在內,並且可以和 MySQL 連接,再加上自己開發的軟體,組合成一個平台。一般 PaaS 只有 hosted、沒有可下載的平台軟體,Elaster CAP 的模式很不同。而 Elaster Labs 就是讓大家上傳、測試自己的應用的 hosted Elaster CAP 環境。

搭建一個平台牽涉到的問題繁多,如儲存系統的選用、API 的設計、效能與易用之間的取捨、平台的穩定度、多個租戶之間的隔離、資源的調配、負載的平衡... 等等工程問題,還有文件的齊備度、技術支援的速度與深度、移植應用程式的難易度、甚至平台提供者的知名度,在在影響開發者是否採用該平台的決定,因此支撐起一個平台絕對不是簡單的事。GoogleApp Engine 也是花了巨大的力氣,才逐漸成熟脫離 beta。然而一脫離 beta 就因新定價策略遭受批評,也顯示出 PaaS 營運之不易。

因為以下幾點,我對趨勢科技TCloud 的創辦人張明正董事長是很欽佩的:

  • 不論算公司規模、算營收、算知名度,趨勢科技應該都是從臺灣起家最大的軟體公司,這是從無到有、自小變大的能力。
  • 他在雲端運算剛開始受到矚目沒多久,就投資創立 TCloud,大力延攬工程師進入開發,這是大刀濶斧的氣魄。
  • 為了培育人才,他在臺大開闢【雲端計算趨勢學程】,據聯合報 2010/04/05 報導
    台大雲端計算趨勢學程主任、台大資工系主任呂育道說,據他了解,這應該是大學首開的相關學程,該校這個學程是趨勢公司捐贈的學分學程,師資除了數十位台大資工系 (所)及資訊網路與多媒體研究所教授,還有中研院院士孔祥重等人。
    這個學程的師資陣容可是不同凡響。或許有人會說這是企業打廣告、搶人才的手段,但無論如何他真的有實際投入資源來栽培學生,學生畢業後也沒有義務要進入 TCloud。下面這句聽起來像八股,可是臺灣真的太缺乏這種有氣度的企業主。
  • TCloud 工程師辛苦開發出來的 Infrastructure as a Service 系統 "Elaster",據 TCloud 網站宣布,將以 GPL v2 的授權條款將源碼釋出,如此開放的胸襟更是與眾不同。

姑且不論 Elaster 何時會準備妥當釋出源碼,TCloud 的工程師已經對 Elaster 中用到的開放源碼軟體做出了實質貢獻。比方說,Ceph 這個分散式檔案系統裡至少就有 20 個來自 TCloud 工程師的 patch。

你也許會想:「20 個 patch 有什麼了不起?」

比起某些「拿 GPL 軟體來用、不順手的地方就改、改完若無其事」、「被 gpl-violations.org 來信警告下架再手忙腳亂應變」的公司來說,TCloud 這樣從一開始就遵守開放源碼遊戲規則的企業是一定要讚一下的!

更何況,在伺服器端使用 GPL v2 的軟體時,授權條款並不強制要把修改後的源碼公開出來,因此 TCloud 的行為更屬難得,相信是完全了解開放源碼運作模式和成本才會這樣做的。

張明正董事長或許是企圖心旺盛,一開就是 IaaS 和 PaaS 兩條戰線。如今關閉實驗性的 Elaster Labs 服務,不知 Elaster CAP 未來命運如何?TCloud 是否打算把資源聚焦在 IaaS 上?這一兩年來開發 Elaster CAP 和維運 Elaster Labs 的經驗,相信對 IaaS 產品也有助益。雖然創業維艱、全球競爭激烈,仍然期盼 TCloud 能闖出一番名堂來!

2012年1月20日

一人一天一票,送進太空站!

台灣有三組同學入圍 YouTube 的太空實驗室活動,1/18 - 1/25 期間,全世界每人每天對 60 組中的每一組都可以投一票。這段時間台灣正好在放春節,太不利了!為了幫我們台灣的參賽同學推進下一輪,請大家把話傳出去,

一人一天一票,送進太空站!

  • 投票給玩太空清潔劑的【The diffusing phenomenon in microgravity conditions】台南女中-陳昕妤楊宜華
  • 投票給吹太空泡泡的【Can bubbles keep intact in a microgravity condition?】師大附中-蕭維廷成功高中-林秉均
  • 投票給種太空食物的【馬鈴薯種植實驗】政治大學-陳韋佑新竹高工-陳怡欣
今天投好了,
明天再來投!


投這個票有什麼意義?本來還想寫點文介紹這三個實驗的,最後想想,何必呢?在這個沒有對錯只比人氣的事情上,只需要老梗一句:我只知道義氣!台灣人挺台灣人。



真的想知道 YouTube 太空實驗室是什麼活動?就是 YouTube 和美國國家航空暨太空總署NASA)、歐洲太空總署ESA)、日本宇宙航空研究開發機構JAXA)、太空探險旅遊公司 Space Adventures聯想Lenovo)共同舉辦的活動,

  • 第一輪從全球 2,000 組參賽作品中選出 60 組入圍;
  • 第二輪公開由全球網友投票選出 6 組進入決賽; <= 現在在這裡(1/18 - 1/25)
  • 決賽再選出 2 個年齡組別各一組冠軍實驗;
  • 今年夏天 YouTube「太空實驗室」頻道將全球直播總冠軍的太空實驗在國際太空站上的進行實況。

剩下的細節可以參考聯合報蘋果日報的報導,我就不多說了。

投完台灣大選,來投世界大選!再說一次:

一人一天一票,送進太空站!

相關參考連結:

2012年1月16日

網路會是一根稻草嗎?

我這輩子沒寫過政治文,沒記錯的話,這是第一篇。

在人與人因網路而快速連結的時代,在 2012 大選後一天,忽然想寫點東西。

我永遠記得,1995 年在芝加哥費米實驗室,一位同組攻讀博士學位的美國學生滿懷信心地跟我說:"Internet will change China. Internet will force China to be more democratic." 他的論點很簡單。他認為中國之所以沒有實施真正的民主,是因為人民從小就以為共產黨灌輸的思想是正確的真理,資訊又被黨控媒體壟斷。那時 Web 剛剛起飛,有了無國界的 Web,資訊壟斷不再,人民遲早會了解「真正的民主」,而進一步要求民主的。

我搖頭,笑他太過天真,共產黨沒那麼笨。後來 Great Firewall 的出現,證明了我的看法,Web 倒底還是可以有國界的。

然而 17 年後的今天,不知道是 Great Firewall 忘記了還是什麼原因,台灣的民主選舉,透過網路讓許多中國大陸人民看到了原汁原味的影片和文字,從各個陣營的競選短片、政見辯論、到選後的勝選/敗選演講,這些沒被加料、減料、竄改的影像,赤裸裸地出現在許多人的電腦、手機、iPad 螢幕上,人們再利用社交網路把文字和影像傳播出去。

像我下面貼圖的第一則微博,是中國網友 topku 推薦蔡英文在競選總部前對支持者發表的落選演說。

  • 在中國推薦民進黨的演說,這是多麼不可能發生的事!
  • 一天之內有兩萬多人轉貼、五千多人回應,而且絕大多數是持肯定、欽佩的態度,這又是多麼難以想像的事!
  • 這裡面有沒有文化衝擊Culture shock)?這麼多人轉貼和回應,我想多少有所謂蜜月期的幻想的。
  • 會發生什麼效果?這只能留待時間去回答。

我這兩天在新浪微博上看到了許多文字,怕以後會不見,隨手節錄幾則。

「力薦蔡英文的演講,此客家老鄉感染力和氣場太強了,講話如此堅定自信和不卑不亢,絲毫沒有政客的做作:“台灣不能沒有反對的聲音,台灣不能沒有制衡的力量......你可以哭泣,但不要洩氣。你可以悲傷,但是不要放棄。因為明天起來,我們要像過去四年一樣的勇敢,心裡充滿著希望” http://t.cn/z0gKlhh」

------ 中間截掉 -----
topku 發的微博,帖中所貼的蔡英文演講 Flash 影片在新浪這裡
「有比較才有鑑別。這次看了馬英九、蔡英文、宋楚瑜在競選過程中和大選結束後的每場演講,眼界大開,感受到民主制度的魅力。在大陸,還沒有見過這樣生動、實在的演講。台灣,嫉妒死你了。」
地球周末發的微博
「總的說來我等修羅大陸,對岸桃花源雙黨都是攢足眼球,好感倍增,國民黨自不必說,連個一天之前還有絕大部分修羅人恨的咬牙切齒,十數年的敵視的民主進步黨,這次也在修羅大陸攢得口碑無數,一夜間掃去在修羅大陸的負面形象!桃花源人表示很詫異!修羅人說你是飽漢不知餓漢飢,桃花源人說你們是政治漁民」
木峰雲雲的心魔發的微博
「看到票數差距,第一次領會到大陸居委會阿姨們的力量;感悟演講內容,第一次享受政治家的魅力;思考民主進程,第一次認可“兩個國家”是利於台灣人民的好事。// @糖螂 :喜歡!」
沛松的客观世界發的微博
「中國不能沒有反對的聲音,中國不能沒有制衡的力量。有選舉,真好!弱弱的問一句,中國共產黨,我能反對你麼?// @imkket :轉發微博」
Tedcom 發的微博
「中國需要真正的普選。反對民主的人以廣大人民文化水準太低為藉口,企圖拖延民主的實行。選舉的能否進行和能否進行得好,主要關鍵在於人民有沒有發表意見和反對他人意見的權利,在於人民能不能真正無拘束的擁護某個人和反對某個人,至於選舉的技術問題並不是無法解決的。——《新華日報》1946年1月24日」
覃彪喜 發的微博

最後這一則反過來震撼到我。如果是真的,這是共產黨利用黨報在 1946 年向國民黨要求普選的喊話。對照兩岸的現況,恍如隔世。

2012年1月14日

也來聊聊物件導向程式的測試、封裝和相依性注入

今天是 2012 總統大選的日子,一大早就看到朋友 H.C. 貼出來的 Hinet 網頁截圖,實在是太歡樂了!



Hinet 的報票網站,在總統大選投票日 2012/1/14 早上 9:23 被網友 H.C. 抓到的截圖。投票還沒結束,票都開出來了?!

現在是 2012/1/14 傍晚 5:40,票剛開始開,早上 Hinet 就透過時光機看到結果了?還是有人早早「把票數算好了」?

我猜是有人在用上線系統做新元件的測試。

什麼年代了,這麼不專業的事還有人做?

就來聊聊物件導向程式的封裝和相依性注入好了,這和測試有關,也順帶聊一點點單元測試。

封裝和相依性注入

剛學物件導向程式設計時,一定學過要把類別(class)的介面設計好,

  • 找出對的抽象定義(Abstraction),讓使用類別 A 的人很容易理解 A 是做什麼的;
  • 適當地封裝(Encapsulation),讓別的程式不用了解 A 的實作細節就能正確使用 A。就像開車的駕駛,要加速只要會〈看速度表〉和〈踩油門〉兩件事,不用知道油門踩下去時,有什麼訊號傳到了什麼機構、用什麼方式控制了汽缸和噴油嘴的動作、甚至不用知道車子裡有這些機構存在。

等到開始寫有點規模的程式、有許多物件要互相合作時,一定會做單元測試(Unit tests),沒多久就會開始用仿製物件(Mock Objects)來測試程式的行為,總有一天你會聽到人家在說,相依性注入(Dependency Injection)是寫易測程式的重要技巧。

但你發現,相依性注入和封裝是互相矛盾的,怎麼辦?

如果你不知道為何矛盾,我來舉個例子。如果你懂,可以跳到下一節

封裝和相依性注入

假設你在設計一個網站的帳號系統,前台有個登入畫面,後台有個帳號資料庫。登入系統和資料庫顯然都是物件,所以你這樣寫:(我用 C++ 說明,但觀念應該適用在大多數物件導向語言上)
class AccountDB {
 public:
  AccountDB();
  bool GetPassword(const string& name, string* password) const;  // password 不會是明碼吧? XD
 private:
  // ...
};

class LoginSystem {
 public:
  LoginSystem();
  bool Verify(const string& name, const string& password) const;
 private:
  AccountDB* account_db_;
  // …
};

登入系統必需向資料庫要(編碼過的)密碼,才能驗證密碼是否正確。一種典型做法是在 LoginSystem 的 constructor 裡建立和資料庫的連結,把連結記住,之後就可以重複使用這個連結來讀密碼了。

LoginSystem::LoginSystem() {
  account_db_ = new AccountDB();  // account_db_ 是私有資料
  // ...
}

bool LoginSystem::Verify(const string& name, const string& password) const {
  string stored_password;
  if (!account_db_->GetPassword(name, &stored_password)) {
    return false;  // 也可以傳錯誤訊息
  }
  return Encode(password) == stored_password;  // Encode() 是某個編碼函式
}

這種寫法的抽象概念很清楚、封裝很乾淨,問題是 LoginSystem 和 AccountDB 緊緊卡在一起,如果你要測試 LoginSystem::Verify(),你必須在真的帳號資料庫裡塞測試帳號,然後餵測試帳號的資料進 LoginSystem::Verify()。這種做法很容易產生開頭講的問題,勸你千萬不要這樣做。

把測試系統和上線系統分離的一種做法是讓 AccountDB 的 constructor 接受 hostname 引數,就可以找台測試機器,在上面建個測試專用的資料庫,測試時不用碰到使用者的帳號資料庫。為了讓測試程式和上線程式用不同的 hostname 引數,就要讓 LoginSystem::LoginSystem() 也吃這個引數,好從 main() 和測試程式餵進去。

class AccountDB {
 public:
  AccountDB(const sting& db_hostname);
  bool GetPassword(const string& name, string* password) const { /* ... */ }
  // ...
};
class LoginSystem {
 public:
  LoginSystem(const string& db_hostname);
  // …
};

LoginSystem::LoginSystem(const string& db_hostname) {
  account_db_ = new AccountDB(db_hostname);  // account_db_ 是私有資料
  // ...
}

這種寫法程式不難測試,可是犧牲了一點封裝:為什麼登入系統需要吃個資料庫主機名稱?資料庫在哪裡不該是登入系統的實作細節嗎?

這樣的設計,LoginSystem 和 AccountDB 仍然是緊密結合的(Tightly-coupled),如果將來有一天因為現有資料庫不夠快、長不大、或是其他原因要換掉,新資料庫可能會有不太一樣的介面,那你該為它寫個 AccountDB2 類別吧?寫完還要改 LoginSystem 裡和資料庫聊天的程式。

這時你可能會想弄個 AccountDBInterface 介面,讓舊的 AccountDB 和新的 AccountDB2 都實作這個介面,LoginSystem 只需要和 AccountDBInterface 打交道就好了,至於打交道的對象倒底是哪個類別的物件?不重要。打交道的對象是哪個物件?LoginSystem 不必自行決定,由造 LoginSystem 物件的程式決定,再把資料庫物件和登入系統物件「送作堆」就好了。這個作法就是物件導向中很重要的 "Program to an interface, not an implementation" 概念。

所以程式變成這樣:

class AccountDBInterface {
 public:
  virtual bool GetPassword(const string& name, string* password) const = 0;
  // ...
};

class AccountDB2 : public AccountDBInterface {
  AccountDB2(/* 可能有不一樣的引數 */);
  virtual bool GetPassword(const string& name, string* password) const { /* ... */ }
};
// 以上是新的 classes

class AccountDB : public AccountDBInterface {
 public:
  AccountDB(const string& db_hostname);
  virtual bool GetPassword(const string& name, string* password) const { /* ... */ }
  // ...
};


class LoginSystem {
 public:
  LoginSystem(AccountDBInterface* db);
  bool Verify(const string& name, const string& password) const;
 private:
  AccountDBInterface* account_db_;
  // …
};

LoginSystem::LoginSystem(AccountDBInterface* db) {
  account_db_ = db;
  // 或許要檢查 account_db_ 不是 NULL
  // ...
}

寫到這裡,要造 LoginSystem 物件的程式要先造好資料庫物件,再把資料庫物件餵給 LoginSystem 的 constructor,不知不覺寫成相依性注入了。

一旦寫成相依性注入,連帶有個好處:測試時可以用仿製的資料庫物件(Mock database object),也就是個實作 AccountDBInterface 但不真的連到任何資料庫的物件,只要能提供測試用的資料,連測試專用資料庫都不用架設了。

到此功德圓滿,資料庫可以隨意抽換,甚至可以抽換成仿製的資料庫以便測試 LoginSystem 的邏輯。

可是封裝怎麼辦?LoginSystem 依賴 AccountDBInterface 這件事因為放在 constructor 的引數裡而曝露出來了,要使用 LoginSystem 的程式要自己造好帳號資料庫物件再餵給 LoginSystem。如果要用到帳號資料庫的只有 LoginSystem 這 101 個類別(這個例子沒舉好,事實上改密碼系統也要用到帳號資料庫,但不想改例子了,請讀者包涵),以封裝的角度來看,帳號資料庫可以變成 LoginSystem 的私有類別,不必讓任何其他類別看到。要造出和使用 LoginSystem 的程式,不用知道帳號資料庫的存在,LoginSystem 知道就夠了。

這個例子只有一個相依類別,讀者可以想像在較大的軟體系統裡,有的類別會有不少相依類別,造這種物件時,要把相依物件一一造好餵進去。要造這種物件的程式需要知道太多事,封裝性破壞殆盡。

一種解法:工廠法

既然〈相依性注入〉影響到的是造物件時的封裝性,不是使用物件時的封裝性,很自然會想到在造物件的部分用點技巧。

講到造物件,OOP 四人幫的書裡講到五個樣式:工廠法(Factory Method)、造物師(Builder)、抽象工廠(Abstract Factory)、芻型(Prototype)、Singleton(我想譯成「獨支」,不知道誰有更好的譯法?),在這裡可以用工廠法。

工廠法的做法是:提供統一的造物介面,但不同的實作可以造出不同類別的物件。這正好可以用來造用到不同類別資料庫的 LoginSystem:

class LoginSystemFactory {
 public:
  LoginSystemFactory();
  virtual LoginSystem* Build() = 0;
  // ...
};

class LoginSystemWithType1DBFactory : public LoginSystemFactory {
  virtual LoginSystem* Build() {
    return new LoginSystem(new AccountDB("localhost"));  // 參數部分為了方便說明先寫死。
  }
};

class LoginSystemWithMockDBFactory : public LoginSystemFactory {
  virtual LoginSystem* Build() {
    return new LoginSystem(new MockAccountDB());
  }
};
也就是說,把「清楚記錄相依性、一一造好相依物件、再造好登入系統」這件事設計成工廠的責任,不同的工廠可以生產不同的相依物件、和最後的產品物件。這樣子在上線程式中任何需要造登入系統物件的程式不用知道細節,達到封裝的目的,在測試登入系統時也能注入仿製物件。

後話

一個留給讀者的問題:如果你很重視單元測試的覆蓋度(coverage),時時追求 100%,你要怎麼測試這裡的工廠法呢?

【2012/01/15 編輯】謝謝 fr3@k 的提醒,寫太快還真的忘了 virtual,哈哈!

2012年1月5日

The Knack

分享一個最近看到的呆伯特短片。因為沒有字幕,就順手譯了。

如果我的孩子成為工程師,倒底該哭還是該笑呢?



呆媽帶小呆伯特去看醫生。

呆媽:「我擔心小呆伯特,他不像其他小孩。」
醫生:「你的意思是?」
呆媽:「昨天我才放他不管一分鐘,他就拆了電視、時鐘、和音響。」
醫生:「那完全正常,小孩就是會拆東西。」

醫生用小鎚敲了小呆伯特的膝蓋。

小呆伯特:「噢!」

呆媽:「我擔心的是,他拿拆下來的零件組了個火腿無線電。」
醫生:「天哪!」
呆媽:「很糟糕嗎?」
醫生:「我一般會檢查他的腦電波,但是儀器壞了。」

小呆伯特瞬間修好了儀器。

醫生:「這比我害怕的還慘。」
呆媽:「怎麼了?」
醫生:「你兒子恐怕得了 The Knack。」
呆媽:「The Knack?」

醫生翻開厚厚的醫學書,指著文字解釋。

醫生:「The Knack 是種罕見疾病,特徵是對機械和電路有極強的直覺,在社交上是徹底的無能。」
呆媽:「他能過正常的生活嗎?」

「不能,他會成為工程師。」


呆媽崩潰