l

2011年6月30日 星期四

Automated acceptance tests

June 30 21:05~22:06

講到火鍋,就想到牛x牌沙茶醬;講到 automated acceptance tests,就想到 Robot Framework(以下簡稱 Robot)。

話說約一年前 Teddy 從指導教授那裡聽到 Robot 這個工具,當時就有一股想要卯起來導入的衝動。無奈 Teddy 自己不懂實做細節,也沒時間去學(其實是人老了開始變懶...XD),再加上 product backlog  裡面的 stories 已經快滿出來了,沒什麼空間可以『插花』加入寫 Robot test cases 的 stories。雖然剛開始的時候 Teddy 跟 team members 介紹了 Robot 這個工具,也有安排時間讓大家『摸一下』,但是卻沒有立刻『動起來』確實採用 Robot。

其實說沒有『空間 or 時間』只是自欺欺人的講法,主要的癥結點還是決心問題。某日 Teddy 心一狠,決定每個 sprint 都至少安排一個寫 Robot test cases 的 task,至於能寫幾個 Robot test cases 倒是其次,一開始的重點是『這件事情開始在動了』,這樣 team members 也才有機會可以慢慢熟悉這個工具。

到現在,已經累積了不少 Robot test cases,數量多到可以從晚上下班之後跑一整夜,有時候一直到隔天上班都還沒跑完(ㄟ...這種情況其實是發生不明原因才會跑這麼久...XD)。看到這邊鄉民們應該會問,這些 Robot test cases 都在『跑些什麼』

基本上,可以把 Robot test cases 至少分成以下幾類:
  • 標準安裝,反安裝:講得好聽一點,Teddy 的團隊算是有達到某種程度的『continuous delivery』。基本上,每天都會產生 N 次的安裝程式,以方便做 end-to-end 測試(例如 acceptance tests)。這一類的 Robot test cases,可以快速驗證這一次所產生的安裝程式能不能在該軟體所支援的平台上面以『預設模式』被正確的安裝(該啟動的服務或是應用程式有沒有啟動,該安裝的檔案有沒有存在等等)與反安裝(該移除的目錄,檔案,或是捷徑等有沒有被刪除)。對與每一個所支援的平台(作業系統),跑一次這種測試大概只需要幾分鐘的時間。
  • 快速功能驗證:假設鄉民再過 5-10 分鐘之後就要把剛剛才產生出來,還熱騰騰的安裝程式立即交給客戶使用,那麼你會想跑哪些 Robot acceptance tests?從這個角度來思考,被選出來的 Robot acceptance tests 就屬於這一類。如果是一個 web 系統,通常 login 這種功能是最優先被測試的。如果客戶連 login 都不行,那其他的功能就不用說了。接下來還可以檢查每一個網頁的 link 是否都正常之類的。快速功能驗證有點像現在很流行的『快速剪髮』,方便,便宜,但是...別肖想會剪得多漂亮。
  • 重要功能測試:假設這一次鄉民們時間比較充裕一點點,再過 10-30 分鐘(or 10-60 分鐘,依據不同團隊與專案規模來動態調整) 之後才要把軟體交客戶,那麼就可以多選一點測試案例來執行。當然這一點點時間還是不足以將全部的 Robot acceptance tests 跑完,所以挑選的原則就是『從重要,或是經常使用的功能開始挑選』。如果能夠跑到這邊,那麼對於這個版本的信心指數應該可以達到 60 分了。
  • 所有功能測試:所有的功能都可以被測完一遍,不同的專案依據其規模大小與所支援的平台多寡,所需的時間可能差異很大。基本上跑完這一個分類信心指數應該可以達到 85 分以上。
  • 所有非功能測試:所有的軟體系統一定會有『非功能需求(non-functional requirements)』,例如 availability,reliability,security,performance 等等。如果連這一類的 Robot acceptance tests 都可以跑完,那麼 信心指數應該可以達到 99 分以上了。
為什麼只有 99 分以上而不是 100 分?很簡單,敢問鄉民們何時用過 100 分的軟體?而且有些測試,像是 usability ,還是需要『真人參與』,恐怕很難自動化。

***

以上的分類,主要的精神還是 agile 的 time boxing 精神。做任何事情,都要有『時間觀念』,有多少時間,做多少事,Teddy 覺的這一點很重要。要成功導入 Robot,要克服幾件事:
  • 第一階段要克服『沒有時間做 automated acceptance tests』的心裡障礙(任何改變都是困難滴)。
  • 第二階段要克服『沒有能力做 automated acceptance tests』的技術障礙(寫 Robot tests 也是有點小難度)。
  • 第三階段要克服『沒有足夠數量 automated acceptance tests』可以被執行的障礙(數量不夠,coverage 很低,找不到問題,有跑等於沒跑)。
  • 第四階段要克服『沒有足夠時間來執行 automated acceptance tests』的障礙(時間不夠,想跑也不能跑)。
Teddy 靠很強的 team members 也搞了快一年才略有小成,後面的路還長的很。

***

友藏內心獨白:只要傻的願意相信,就有可能化不可能為可能。

2011年6月29日 星期三

吹牛又不用繳稅

June 29 21:38~22:15

前一陣子 Teddy 和幾個前公司的同事一起吃飯,期間有個朋友提到,他們公司最近來了好幾個高官,每一個都號稱擁有在上市,上櫃公司中管理數十到上百人不等的經驗。但是這些高官到了公司之後,表現卻非常的差,不是混吃等死,就是....還是混吃等死。有些待不到一年就自己走人了,也有待不滿一個月就被公司請走的。當然還有更多是打死不退的,到手的肥肉誰想放掉。

由於朋友公司的老闆特別喜歡這些所謂『有經驗』的老手,所以只要曾經在『大公司』待過而且 敢唬爛 口才不錯,敢吹敢講的話,到朋友的公司都可以獲得不錯的位置。聽這位朋友說,曾經有一位新任『總監(director)』級的高官,號稱上一份工作是在某大上市公司帶領了好幾個 teams。但根據曾經待過該某大上市公司的其他同事表示,這位新任總監其實以前也只是帶了一個 5-6 人左右的 team 而已。嗯,要升官發財,果然是使出 A Name without Quality 這一招比較快。

此時 Teddy 開口說話了...想當年,Teddy 也是帶過 100 多個人的...

朋友:啊,真的嗎?那你為何現在還是個小小工程師?

Teddy:以前在學校的時候,期中考幫忙監考,一次監考兩個班級,大概 100 多人...XD。

朋友:頑皮,頑皮推一推...

***

 有時候在看別人履歷的時候,常常會看到類似的經歷:
  • 專研 XXX 技術 數十年 十數年以上。
  • 對於 A, B, C, D.... Z... 等等,都有涉獵。
  • 擁有 甲,乙,丙....癸 等等認證。
  • 曾擔任某公司技術長或是技術總監之類的。
  • 曾管理人數:數十 or 數百人。
很漂亮的履歷,令人肅然起敬。不過也不要被嚇到了,套句古人說的話(應該是孔老夫子說滴):視其所以,觀其所由,察其所安。人焉廋哉?人焉廋哉?。履歷有可能連一個人的過去都代表不了(可以造假啊),那有可能代表一個人的未來呢?

***


友藏內心獨白:還是那句老話,社會上騙子太多,嘻...要小心。

2011年6月28日 星期二

Iteration 0 要幹麼?

June 28 21:39~20:45

為了避免讓鄉民們說 Teddy 只會罵人不提供 solutions,今天來談一下 Iteration 0 要幹麼。在說明 Iteration 0 是什麼東東以及這個東東到底是要做什麼的之前,請鄉民們回顧一下 Teddy 之前寫的這一篇『萬事起頭難:如何開始第一個 Sprint? 』。


既然『捧油』在介紹 Scrum 的時候建議要先用一個 Iteration 0 來『打好基礎』,那麼 Teddy 就來幫這位『捧油』說明一下 Iteration 0 的目的與其內容。Teddy 第一次看到 Iteration 0 這個名稱是在 Becoming Agile: in an imperfect world 這本書的第 17 章,Start your engines: iteration 0。理想上,採用 Scrum 應該是不需要在 sprint 1 之前還來插花個 sprint 0 或是 iteration 0。如果 Teddy 沒有記錯的話,只要有 product backlog(就是說已經有可以開工的需求)就可以開始第一個 sprint

Product backlog 是誰寫的?是 Product Owner 寫的。一般的書講到這邊就沒了,但是 Teddy 相信鄉民們一定會有一個疑問:那 Product Owner 何時寫 stories?難道是在 iteration -1 的時候寫的....XD。根據 Teddy 的研究發現,最有可能的答案是:『不關你的事』。沒錯,就是『不關你的事』。因為在專案開始之後 Product Owner 除了一定要參加 sprint planning meeting,sprint demo meeting 以外,剩下的時間他要幹麼咱們管不著。既然在專案開始之後都管不著了,在專案開始之前 Product Owner 何時要寫 stories 那咱們就更管不著了。所以,不管 Product Owner 何時寫 stories,總之就是 sprint 開始之前把開工所需的 stories 生出來就是了。
 
所以,只要 Product Owner 準備『足夠多』(何謂足夠多則又是一門學問了...XD)的 stories,就可以準備開工大吉了。但是在實務上,鄉民們的團隊可能對 Scrum (或是其他的 agile methods) 沒有那麼熟悉,在沒有『打好基礎』的情況下貿然開工心裡會毛毛的(或是極力反對)。所以此時先來個『無責任 iteration 0』給它『預演』或是『彩排』一下也許是個不錯的主意。

依據 Becoming Agile 的說法,iteration 0 要做的事有:
  • Initial vision for the architecture:大家都說 architecture 很重要,而且要修改 architecture 成本很高。所以正式開工之前先偷偷研究一下 architecture 也是很合理滴。
  • Completing contracts with third parties:看看專案會不會用到什麼 third parties 的軟體(例如資料庫或是 workflow engine 等等),如果需要軟體授權的話趕快趁現在簽一簽。
  • Preparing environments and support tools:開工前先把『生產線與機器』給準備好。最常見的是把 svn 與 continuous integration server 架好,IDE 環境設定好,準備測試環境等等。
  • Obtaining funding:如果案子沒錢的話快去找錢否則 iteration 0 就變成第一個,也是最後一個 iteration ...XD。
  • Finalizing and dedicating the project team:沒工人的話快去把工人給找齊...最近缺工問題還滿嚴重滴...XD。
  • Cheating: starting the work early:如果還有時間,就作弊先偷跑,做一點『先期研究』。例如:進一步分析需求,做一些假畫面,測試一些未來可能會是用的 libraries 或是 APIs,或是辦一些教育訓練等等。
***

看到這邊鄉民們應該會冒出另一個問題:iteration 0 需要執行多久?依據 Becoming Agile 的說法(p. 223):Iteration 0 work usually runs for about a week, but it can take less or more time depending on project complexity. 在此 Teddy 再次提醒,千萬不要把 Iteration 0 搞得太長,不然很容易變成 big up-front design。

***

友藏內心獨白:為什麼這些絕世武功練起來都是如此凶險?

2011年6月27日 星期一

捧油,Scrum 在那裡?

June 27 22:09~23:10

話說 N 年前 Teddy 還是年輕小伙子的時候,很熱衷於物件導向技術。當時國內有一本名為 XXXX 的雜誌,專門討論這個主題,Teddy 每次到天瓏書局的時候都會翻看一下。剛開始的時候 Teddy 也是個忠實讀者,連續買了好幾期的雜誌,只可惜當年的 Teddy 武功低微,著實無法理解這本 XXXX 『寶典』裡面高深的武學秘笈,再加上囊中羞澀,時間久了也沒辦法每一期都購買。在 Teddy 幼小的心靈中,留下一絲絲遺憾。Teddy 曾經默默許下願望,希望有朝一日能夠學會寶典裡面的武功,一統江湖...XD。當然,這個願望一直都沒有實現。

一直到若干年後,Teddy 回學校念碩士班的時候,有一次系上請了該 XXXX 雜誌的...應該算是創辦人吧,在此稱之為 K 大師,來系上演講。聽完 K 大師的演講之後,Teddy 只能用幾個字來形容:『我聽你勒放風吹(請用台語發音)』。當下茅塞頓開,原來這本 XXXX 雜誌只是本『九陰假經』,還好 Teddy 資質不夠,不然萬一真的照上面所說的練下去,損失了銀子事小,傷了身體事大(變成歐陽 峰...XD)。當天回家之後 Teddy 就放心的把以前買的 XXXX 雜誌直接拿去資源回收了。
 ***

以上故事告訴鄉民們,雖然現在 Teddy 的功力就算比不上東邪西毒,南帝北丐,但好歹也算是....全真七子的等級...(好像有點遜...XD)。重點是,當年 Teddy 還是被一些『不良刊物』給誤導,所以,當各位鄉民們功力尚淺的時候,一定要『慎選讀物』,不然很容易被抓去賣你還在幫對方算錢。

接下來 Teddy 要開始得罪人了,請多多包涵。Teddy 的公司有訂閱 xxxxme (前面四個字母打馬賽克應該看不出來吧...XD),最近在上面看到介紹 Scrum 的文章。看了第一篇,沒看到什麼和 Scrum 很相關的內容。過了一個禮拜,看了第二篇,還是沒看到。這兩篇文章,有一個很大的特色,那就是這是 Teddy 有印象以來看過所謂介紹 Scrum 的文章裡面,出現最多 UML diagrams 的文章。這算是『掛著 Scrum 頭賣 UML 肉』嗎?

用這樣的方式來介紹 iteration 0 (啊,露餡了...XD) 會不會讓讀者們把 Scrum 誤認為是一種需要 big up-front design 的方法?Teddy 不敢說在 Scrum 中不需要畫 Use Case Diagrams,但是 Teddy 自己是自從採用 Scrum 之後就沒再畫過 Use Case Diagrams 也沒寫過 Use Cases 了。西方人說:『手上有一把鐵鎚,任何東西看起來都像是釘子』。Teddy 說:『熟讀 UML,任何軟體開發方法都需要 UML diagrams...XD』。

 ***

當然,有人敢寫,就有人敢登,讀者們更不用肖想會有什麼『技術編輯』會去幫忙審稿。人家愛怎麼寫關你 Teddy 屁事。的確是不關 Teddy 的事,但是想到可能會有一些『涉世未深』的鄉民們,萬一又跟 Teddy 當年一樣,把『九陰假經』當成『九陰真經』來練,著實於心不忍。善哉,善哉。

***

友藏內心獨白:內容已經多所保留了,不然這篇就要改名成『麥甲我蓋布袋,Part 4』...XD。

2011年6月21日 星期二

我期待...

June 21 21:55~23:01

開發軟體,常常需要安裝一些測試環境,例如在一台新的電腦上面安裝 JDK,MySQL Server,SVN client,ANT,等等等等等.....。這些剛剛裝好的測試環境(尚未安裝待測程式),在此稱之為『乾淨的環境』。如果久久才需要製作一次乾淨的環境也就算了,但是如果需要經常安裝,這些安裝軟體的工作就變得很無聊而且浪費時間。如果測試環境可以用 VM 來取代,那也就還好,只要安裝好之後把 VM 備份,然後把待測程式安裝到這個 VM 裡面,用完之後再用剛剛備份的那個 VM 覆蓋掉這個『髒掉』的 VM,這樣每次測試的時候就都有一個全新乾淨的環境。當然鄉民們也可以用 VM 的快照功能來將 VM 回復到初始(尚未安裝待測程式)的狀態。

有時候因為某種原因,每次都一定要在『實體機器』上面重裝一份新的 OS,然後在這個新的 OS 上面安裝一些測試需要用到的軟體,最後在安裝待測程式。在這種狀況下鄉民們可以用 ghost 或是 Clonezilla 將整個實體機器硬碟中的資料全部備份成一個影像檔,安裝過測試程式之後再用這個影像檔來復原到最初的狀態便可。

假設因為某種神秘不可告人的原因,鄉民們需要不斷的在剛裝好的 OS  裡面安裝一些軟體,如果需要手動去安裝的話,那就很麻煩了。今天 Teddy 介紹一個在 Linux 下面的小工具,叫做 Expect,可以在『文字模式』下用來幫助鄉民們安裝軟體。

Expect 的間單說明鄉民們可以在 Wikipedia 看到,它是一個 Unix/Linux 下面 automation and testing 的工具,其工作原理非常簡單:
  • Expect 幫我們執行某個程式。
  • Expect 一直等待(一直期待),直到該程式在文字畫面上顯示某些字串。
  • 當 Expect 讀到它所期待的文字之後,代替『人』送出某些訊息給該程式。例如,送出 enter + 換行字元,或是送出  Y + 換行字元。
  • 該程式繼續動作。
  •  Expect 繼續等待(期待),直到該程式在文字畫面上顯示某些字串。
  • ... (以此類推)
舉個實際的例子,假設鄉民們想要寫一隻程式自動連到 ftp.ntut.edu.tw ,然後把登入目錄中的所有檔案全部都抓回來,如果是用『人』來執行這些工作的話,那麼大概會長這樣子:

root@Ubuntu:~# ftp ftp.ntut.edu.tw
Connected to ftp.cc.ntut.edu.tw.
220 (vsFTPd 2.0.5)
Name (ftp.ntut.edu.tw:root): guest
331 Please specify the password.
Password:
(以下省略)

如果用 Expect 的話,可以寫一個 script 來做這件事:

  spawn ftp ftp.ntut.edu.tw
  expect "Name"
  send "teddy\r"
  expect "Password:"
  send "xxx123\r"
  expect "ftp>"
  send "bin\r"
  expect "ftp>"
  send "prompt\r"
  expect "ftp>"
  send "mget *\r"
  expect "ftp>"
  send "bye\r"
  expect eof

第一行的 spawn 這個指令是用來啟動一隻程式,而第二行的 expect "Name" 這個指令是告訴 Expect 去等待剛剛啟動的那隻程式透過 stand output 所輸出的字串中,是否有出現 Name 這個字串,如果沒有,就一直等待,一直到 Name 這個字串出現為止。在此先插花補充說明一下,由於使用 spawn 指令所啟動的那隻程式,算是 Expect 的『子行程(sub-process)』,所以 Expect 是可以讀到該 sub-process 輸出到 stand output 上面的字串(原本這些字串會直接出現在 console 上)。

當 Expect 等到畫面上出現 Name 這個字的時候,就是 ftp 軟體要鄉民們輸入登入的使用者名稱的時候。此時執行到第三行的 send "teddy\r",Expect 會幫鄉民們透過 stand input 輸出登入使用者名稱給 ftp 程式。用下面的圖來解釋應該比較容易理解:

                    stand out
Expect  <-------------------  被啟動的程式
                    [expect xxx]



                  stand in
Expect  --------------------> 被啟動的程式
                  [send yyy]



所以這隻被 Expect 所啟動的程式,其實是不知道倒底是有一個『真正的人類』在和它互動,還是由另外一隻程式在和它互動。寫過文字模式 C 的程式的人都知道,console mode 程式就是透過 stand in 和 stand out 來輸入/輸出資料。

***
以上,和『自動安裝軟體』有何關係?當然有,因為 Expect 可以『騙』console mode 程式,假裝是『人』跟這些  console mode 程式在互動。所以如果所有需要安裝的軟體是可以透過 console mode 來安裝的話,那麼就可以寫 Expect scripts 來將這些安裝的工作自動化了。這個特性對於 automationtesting 都是非常有用滴。

不要小看 Expect,可以用它來佈署應用程式到大量(例如 1000 台)的電腦之中,或是幫 1000 人建 Linux 帳號並且改密碼,也可以幫只有 console mode 的程式建立一個 GUI 畫面,然後透過這個 GUI 畫面來控制原本 console mode 程式。還有很多,很多的應用,有興趣的鄉民們,可以參考 O'Reilly 出版的 Exploring Expect 這本書(500 多頁,還真厚)。

***
友藏內心獨白:學弟,要學起來喔。

2011年6月17日 星期五

老問題:Story 做不完怎麼辦?

June 17 21:34~22:42

你的開發團隊採用 Scrum 而你是 Scrum Master。在 sprint demo (sprint review meeting) 的前一天 18:20, 正當你在培養下班情緒準備打包閃人的時刻,你的 team member 甲突然給你一個驚喜...

Team member 甲:這個 story 可能做不完了。

Scrum Master:那... 明天可以 demo 嗎?

Team member 甲:資料庫還沒接上去,但是可以在我的 Eclipse 開發環境裡面執行,用測試的假資料來 demo。

Scrum Master:可是這個 story 要真正連到資料庫,從資料庫抓真的資料來顯示這樣才算有串起來耶。

Team member 甲:...。


Scrum Master:那我看還是把這個 story 移到下個 sprint。


***
隔天 daily scrum 的時候...

Team member 甲:我昨天已經把資料庫接上去了。

Daily scrum 結束之後...


Scrum Master:那是不是用最新的安裝程式就可以看到這個新功能。

Team member 甲:可是這個功能的 link 還沒放到系統中....

Scrum Master:所以還是沒有把個 story 『完整做完』,那還需要 demo 嗎?

此時 Team member 乙 插了一句話...

Team member 乙:還是可以先看一下畫面。

Team member 甲:對啊,先看一下,大家討論一下,如果有需要修改下個 sprint 可以比較清楚要如何修改。

***

各位鄉民們,請問該如何處置?

這個問題以前有談過,簡單的二分法:
  • 沒做完就是沒做完,把 story 移到下個 sprint 繼續做,或是。
  • 把 story 範圍切小一點,在這個 story 中 demo 已經完成的部份。
Teddy 上面提到的這個故事,Team member 甲與另一位 Team member 丁已經花了好幾天的時間在該 story 上面,由於該 story 在開發一個全新的功能,需要實做全新的 UI 元件,因此比預定完成的時間大概多了 2 倍(路人甲內心獨白:那根本是原來的 story 切太大了啊。Teddy 內心獨白:千金難買早知道啊...XD)。 由於該 story 『幾乎』快要完成了,所以 Team member 甲很希望能夠在這個 sprint demo,而且從 Product Owner 的角度來看,此功能主要是要看 UI 呈現的方式,這一點已經完成了,所以在這個 sprint 先看,先討論,這樣會比拖到下個 sprint 再看要來的好。所以 Teddy 會建議可以先 demo,至於 story 要如何修改,以下為一個例子:

原本的 story:身為使用者,我可以在 XXX 系統的網頁上看到股票趨勢圖。

修改後的 story:身為使用者,我可以看到 XXX 系統股票趨勢圖...的 prototype。

ㄟ,其實 Teddy 也不是很確定這樣改到底好不好,不過至少可以從 story 得知,該功能還沒有被整合到系統裡面,只是完成了 prototype。講了老半天看起來好像只是在玩『文字遊戲』,但是這其中的差別其實是很重大滴。Story 做完了,就表示客戶可以立即拿到一個包含該 story 的 running software。如果有人宣稱 story 做完了但是 sprint 結束之後卻『無法交貨』,那麼這種說法就是很有問題的。


現在不是很多賣電子產品的公司們都在搞什麼『預購』,宣稱 X月X日可以交貨,騙了 吸引一堆鄉民們先掏錢去買,但是期限到了卻一再延期,無法交貨。人家這些公司是『叔叔有練過』,咱們小朋友要 run Scrum 先萬不要學喔。

***

Teddy 參加過好幾個不同軟體的 sprint demo,經常看到某個 story 是在開發環境中(例如 Eclipse)被 demo 的,根本不是用一個完整的系統來 demo。很可能是該 story 還沒被整合到系統中。這樣也就算了,但是從 story 的敘述來看,該 story 明明是已經做完了啊。這樣的 demo 方式(或是說,這樣的 Scrum run 法),是讓人(product owner 與 customers)很混淆的,而且離『每個 sprint 結束都完成一個潛在可發布的軟體』這個目標還好遠呢。

Story 沒做完,又想 demo,但是又讓別人看起來覺的這個 story  好像做完了,事實上是產生『Partially Done Work 』,這是一種浪費,要被消除(請參考『消除浪費 (1):Partially Done Work 』)。

結論:『同誰,九陰真經不是這樣子練滴』。

***

友藏內心獨白:有些事,該堅持還是要堅持。

2011年6月15日 星期三

Checked or unchecked exceptions (3)

June 09 22:41~23:59

Checked or unchecked exceptions 第 1 集 結尾 Teddy 留下了四個問題,上次談了第一個問題:

雖說 checked exceptions for recoverable conditions,但是為什麼偏偏當程式中遇到一個 checked exception 的時候鄉民們卻打死也不知道要如何去 recover 任何東東?舉個最常見的 checked exception, IOException,為例,處理檔案,stream,socket 都會『附贈』IOException,收到這份大禮之後該怎麼辦?沒人知道。


簡單的說,當收到一個 exception 的時候,除了 exception type 之外,還要考慮下列幾個因素才能決定要如何處理例外:
  • Recoverability
  • Application Context
  • Robustness Level 
  • Exception Handling Policy (Strategy)
今天先跳過第二個問題,來談一下第三的問題:

Checked exceptions 要宣告在 method 的 signature 上,所以如果一個  method 會丟出去的 checked exceptions 數目或是 type 改變了,那個這個 method 的 signature 也變了,就等於 interface 也變了。改變介面這件事可不是好玩滴...

上述現象有一個術語來稱呼它,就叫做『由 checked exception 所引起的 interface evolution(以下簡稱 interface evolution)』。相信很多(絕大多數?)有在寫 Java 程式的鄉民們都會認為 interface evolution 是一件很煩的事情,因為如果某個 method 一旦發生 interface evolution,那麼所有呼叫到該 method 的 clients 都會被影響。看一下這個例子:


public method x() throws IOException {}

public method callee () {

    try {
         x();
   }
   catch (IOException e) {
        // do something...
  }
}


如果 x 變成了:

public method x() throws IOException,  SQLException {}

那個 callee 要被迫改成下面這個樣子(還記得 Java 的 handle-or-declare rule 嗎...):


public method callee () {


    try {
         x();
   }
   catch (IOException e) {
        // do something...

  }
   catch (SQLException e) {
        // do something...

  }

}
或是:

public method callee () throws SQLException {


    try {
         x();
   }
   catch (IOException e) {
        // do something...

  }

}

只因為程式中用到的某個 method 多丟出了一個 checked exception,原本可以 work 的程式連 compile 都不過,這樣不是很討厭嗎?如果改用 unchecked exceptions 那該多好。請看下面這個改用  unchecked exception 的例子:


public method x() throws IOException {
    // do something...

    throw new MyRuntiimeException();
}


好棒喔,因為用了 runtime exceptions,所以原本的 callee 就不需要修改也不會有 compile errors 了。


public method callee () {


    try {
         x();
   }
   catch (IOException e) {
        // do something...

  }

}
***

事情當然沒有鄉民們想得那麼簡單,請思考下面幾點:
  • 如果 method x 在第一版(只會丟出 IOException 的那個版本)完成之後發現會因為其他的原因導致 method x 執行失敗,所以需要通知 callee 讓 callee 有機會可以做相對應的因應措施。此時 method x 如果採用  runtime exception 來代表一個 recoverable exception,那麼關於 method x 的修正版會丟出一個新的 exception 這件事情, callee 是『無從得知』的(無法從 compiler 那裡得到通知)。假設有 N 個 clients 都呼叫了 method x,那麼這 N 個 clients 在 compile time『看起來』是不會因為 method x 的 interface evolution 造成任何影響,但是實際上 runtime 的行為其實已經因為 method x 丟出一個新的 exception 這件事情而被改變了。
  • 也就是說,從『廣義』的角度來看,checked exceptions 會造成 explicit interface evolution ,而 unchecked exceptions(如果是拿來當成 recoverable exceptions 來用的話)會造成 implicit interface evolution
  • 當然,如果 unchecked exceptions 是當成 programming errors(bugs)的用法,那麼就不會造成 interface evolution。

Interface evolution(無論是否因為 exceptions 所造成)雖然討厭(尤其是當改變介面的那個 method 已經很有多人使用者的時候),但卻是必要的。為什麼?舉個例子,在結婚前身份證上面的配偶欄是空白的,那麼結婚之後配偶欄就會登記配偶的姓名(interface evolution)。如果沒有這個 interface evolution,那麼很多人就可能在『無意之下』變成了別人的『小三』(小三內心吶喊:怎麼會這樣,我老早就檢查過了,XXX 身份證上的配偶欄是明啊明是空白的啊!)這下子知道 interface evolution 有多重要了吧...XD

所以,如果程式的行為已經改變,其變化之大已經和原本的行為『不相容』了(如何定義程式行為以及新舊的行為是否相容則又是另外一個複雜的問題了),那麼就應該要改變原先的介面(或是創造一個新版本的介面),讓 client 知道該 method 已非昔日阿蒙了...XD。

***

最後結論,如果真的需要改面介面的時候該怎麼辦?
  • Separating a public interface from a published interface
  • Refactoring unpublished interfaces
  • Declaring published interfaces with circumspection
  • Making published interfaces immutable
上述四點請自行端詳...

***

友藏內心獨白:睡覺先。

2011年6月13日 星期一

都畢業了,開發軟體還要讀 papers...嗎?

June 13 22:13~23:20

本以為考完駕照之後會比較輕鬆,沒想到更忙。想說分期付款買輛國產小車來開始練習在道路上開(廢話,難不成要在河濱公園開...又不是腳踏車...XD),否則過一陣子一定更不敢開車了。但是對於從來沒買過車的 Teddy 而言,沒想到買車居然是一件這麼花時的事情,每天晚上回家幾乎都把時間花在網路爬文。原本列入考慮的有 5-6 款,後來好不容易篩選剩下 Yaris 和 Colt Plus... 又研究了許久,終於決定買 Colt Plus。但是某一天突然看到 Tiida,好像價錢差不多但是『功能』比較強(搞得好像在買電腦...XD),後座的座椅也比較大(雖然這個功能可能極少使用...XD),所以最後下定決心準備買 Tiida 5D L 這一款。

決定之後又繼續不斷的爬文,哇哩勒,看了老半天,覺的怎麼好像很多賣車的業代(Teddy 內心獨白:為什麼不直接說『業務』,而要說『業代』呢?)都是搶錢來的。像 Teddy 這種這麼『古意』的人,一定會被當成肥羊滴...想到這裡就覺的好累...

***

言歸正傳,由於最近 Teddy 需要做一些和電腦電源管理有關的事情,但是 Teddy 對這個領域又不熟,唯一知道的大概就只有作業系統所提供的『休眠』功能,於是就想說要找資料來看。一般人想到要『找資料』,第一個想到的一定是拜一下『Google 大神』,答案就出來了。的確,用 Google 可以找到一些關於電腦電源管理方面的文章,從這些文章中得知電腦電源管理有 ACPI (Advanced Configuration and Power Interface)這個標準。其實 Teddy 相信大部分的鄉民應該都有看過 ACPI 這幾個字,因為在主機板的 BIOS 畫面中,幾乎都有這個設定選項,在今天之前 Teddy 只知道這東東和電源管理有關,但從來也沒想去研究它。

既然有標準,不就容易了嗎?把 spec. 印出來不就好了...哇哩勒,一本 730 幾頁,看完之後都不知何年何月了。當然如果要看簡短的說明,可以看 Wikipedia,不過 Teddy 從以前唸書的時候就養成了一個習慣,那就是:找電子期刊上面的論文

雖然說一般正常的人類沒事絕對不會想去看 papers,但是有時候對於某個 domain 不是很了解的時候,看  papers 與讀教科書都是很有用的方法。教科書太厚了而且還要去書店找,所以還是以找 papers 為第一優先。

***

接下來的問題就是,怎麼找?通常時間不是很多的情況下,Teddy 只找兩個地方:
如果還有時間,或是上面找不到滿意的資料,就繼續到 SpringerLink 與 John Wiley Journal 去找。看到這邊不要太高興,接下來是壞消息:關於這些電子期刊所收錄的論文,幾乎 99.9999999% 以上都是要收費滴。那怎麼辦?這...請自行想辦法....XD。

Teddy 用 ACPI 當作關鍵字去查詢,找到了幾篇 paper,其中一篇 Teddy 覺的最棒的就是:

Modern System Power Management, by Andrew Grover, ACM Queue, October 2003.

雖然這一篇 2003 年的 paper 算是有點舊了,有小部份的內容甚至有點過時,不過作者利用短短的七頁內容就把 ACPI 的來龍去脈介紹得很清楚,所以值得推薦。更棒的是 ACM Queue 這個雜誌是免費的,可以線上閱讀網頁版本,也可以下載 pdf 版,真是佛心來的。ACM Queue 上面經常會有不錯的文章,閒閒沒事做,有上進心,又想省錢的鄉民們值得參考看看。


***

友藏內心獨白:為什麼 PxHome 沒有 24 HR 購車專區?!

2011年6月2日 星期四

白飯一碗

June 02 21:29~22:16

先聲明一下,本日主題與軟工完全無關(路人甲:YA....)。


今天晚上 Teddy 和 Kay 去住家附近的圖書館準備借一本預約的書,走到圖書館樓下才發現今天公休,於是直接到下一站:圖書館對面的『XX 起司』吃晚餐。這家店 Teddy 常來,幾乎是只要到圖書館借書都會來這裡用餐,價錢介於 89 ~ 160 之間,除了主餐以外,還有土司麵包吃到 飽外加紅茶飲料,還有冷氣可以吹,算是滿平價又還乾淨的用餐環境。

就在 Teddy 剛點好晚餐的時候,隔壁桌來了一對父女,爸爸的腿有點一拐一拐的,手也不是很靈活。女兒看起來大概是小三(小學三年級,別想歪了)的樣子,天真可愛,看起來很開心的樣子。他們只點了一份 89 元的咖哩飯,外加小朋友不用餐的最低消費 30 元(麵包與飲料吃到飽的費用)。爸爸點餐的時候,向 電源 店員提出一個要求:

爸爸:我還要加一碗白飯。

店員:一碗白飯要 10 塊錢喔。

爸爸:以前不是都不用錢嗎?

店員:對啊,可是現在加一碗白飯要 10 塊錢。

***

爸爸盧了很久,還是不成功,不禁露出失望的表情,但是他還是不放棄。就在老闆娘經過的時候....

爸爸:以前加白飯不是不用錢嗎?

老闆娘:不是不用錢,上次是因為你說小朋友吃不夠,加一小口,所以不用錢。我們加白飯是要收 10 塊錢的。


爸爸:可是小朋友吃不飽啊.....[喃喃自語]


老闆娘:現在物價一直上漲,米和麵包都漲價。像是米上個月就漲了三次。很對不起啦,沒辦法免費加白飯。(Teddy 內心獨白:兔子,聽到了沒...XD)


老闆娘:那你還要加白飯嗎?

爸爸:...... 好吧....


***

此時女兒很高興的幫忙倒飲料回來,正準備去拿麵包。

女兒:拔拔,我去拿麵包。

爸爸:好,多拿一點......
***

過一會咖哩飯和白飯送上來了,只見這位爸爸用湯匙舀了一些咖哩到那碗白飯上面,沒兩下子就把這碗白飯...應該說,淋了咖哩的白飯,給吃光光了。而一旁的小女孩,似乎什麼也沒察覺,還很高興的吃著她的咖哩飯,只是邊吃邊說『咖哩好辣喔』。

***

說真的,Teddy 很想偷偷跑去跟老闆娘講,以後這對父女不管加幾碗飯都算 Teddy 的...最後還是放棄(路人甲:你這個俗啊...XD)。Teddy 覺得很感慨,有的人努力爭取,就為了想吃一碗免費的白飯,而 Teddy 雖然是一位『涉世未深,口袋也不深的軟體從業人員』,但畢竟生活還算過得去。有時候生活上,工作上,為了一點小事就經常忿忿不平。想想別人,其實自己已經算是很幸福了。

各位鄉民們,當你覺的有苦難申或是受到委屈的時候,就去吃碗白飯吧...記得要淋上咖哩...XD....

***

友藏內心獨白:切,1000 塊的路考保證班費用都可以吃 100 碗白飯了....

2011年6月1日 星期三

Checked or unchecked exceptions (2)

June 01 21:26~23:15

Checked or unchecked exceptions 第 1 集 結尾 Teddy 留下了四個問題,今天來談一下第一個問題:

雖說 checked exceptions for recoverable conditions,但是為什麼偏偏當程式中遇到一個 checked exception 的時候鄉民們卻打死也不知道要如何去 recover 任何東東?舉個最常見的 checked exception, IOException,為例,處理檔案,stream,socket 都會『附贈』IOException,收到這份大禮之後該怎麼辦?沒人知道。

把上面這個問題再簡化一下,就是說:為什麼大家都不知道要如何處裡 exceptions(尤其是 checked exceptions)? 看一個 The Java Programming Language, 4ed, p.33 的例子:

class BadDataSetException extends Exception {}

Class MyUtility {

    public double[] getDataSet(String setName) throws BadDataSetException {
        String file = setName + ".dest";
        FileInputStream in = null;
    try {
       in  = new FileInputStream(file);
       return readDataSet(in);
    } catch (IOException e) {
       throw new BadDataSetException();
    } finally {
       try {
           if (in != null)
                    in.close();
       } catch (IOException e);
          ; // ignore; we either read teh data OK
        // or we're throwing BadDataSetException
       }
    }      
  }
    // ... definition of readDataSet ...
}

這是一個很典型的例子,寫過 Java 程式的鄉民應該都不陌生。getDataSet() method 遇到兩次 IOException:

  • 第一次 IOException:執行 new FileInputStream(file) 會丟出 IOException,請看 FileInputStream 的宣告 public FileInputStream(String name) throws FileNotFoundException。上面的這個例子捕捉到這個 IOException 之後,處裡的方法就是『轉成 BadDataSetException 然後繼續往外丟』。也就是說,把這個問題推給別人去煩惱。
  • 第二次 IOException:執行 in.close() 會丟出 IOException,這次更妙,直接忽略這個 IOException。奇怪了,Java 的 exception handling 規則中,不是有一條提到『不可以忽略例外嗎』,為什麼這個範例就可以正大光明的忽略例外?難到這就是傳說中的『叔叔有練過,小朋友不要學喔』。

***

當收到一個 exception 時(不管是 checked or unchecked),要判斷如何處裡,光靠 exception type 是不夠滴,需要『同時』考慮好幾個因素: 

Recoverability

收到這個 exception 時,有沒有可能修復例外所造成的錯誤。如果套用 Java 原本的設計精神,遇到 unchecked exceptions 表示程式中有 bug,所以鄉民們也不用想著在程式中如何處理 unchecked exceptions,只要乖乖的回報給 users or developers 便可,不要連在程式中連 unchecked exceptions 都給吞掉了,以下為錯誤示範:

try {
         // do something

} catch (RuntimeException e) {
        // 沒事不補抓 unchecked exceptions,除非是在 main program 中要準備回報該例外
}

那 checked exceptions 就一定是 recoverable 的嘛?也不一定,因為一個 exception 只提供了某種 local context,光依靠這個 local context 在絕大多數狀況下是無法直接判斷要採取何種措施,通常要考慮到下面這個因素(application context)才能夠判斷是否應該在『此時,此地』來 recover 某個由例外所造成的錯誤狀態。

Application Context

Application context 提供了一個 global context,協助 developers 來設計例外處理的方法。最簡單的一個例子,就是依據 exception 發生在 software architecture 的那一層(如果是 layered architecture)或是那一個 tier 來協助 developers 判斷要如何處理例外。舉個具體的例子,如果鄉民們的軟體架構長成這樣子:

------------------------
        UI
------------------------
   Controller
------------------------             
      Utility
 ------------------------           

如果有一個 IOException 發生在 Utility 這一層,鄉民們該如何處置?由於 Utility 這一層的物件很有可能被其他的 applications 所共用,所以在這裡面如果存在太多  application specific exception handling code 那就會降低 Utility 的可重複使用性。所以,通常在越底層的物件,由於其所具備的 application context 知識較少,所以例外處理方法越簡單越好,可能只要保證當例外發生的時候,自己的狀態是正確的,並且將例外回報給上一層的人便可。

但是,如果 Controller 這一層的人收到例外,就有比較多的 information 可以判斷要如何處理。例如,鄉民們撰寫一個檔案複製的工具,Utility 這一層收到了一個 IOException,不知道如何處理,繼續往上丟。在 Controller 這一層的 CopyController 物件收到了底層所丟上來的 IOException,此時 CopyController 就可以選擇 retry 這種策略。

Robustness Level

有時候明明知道某個 exception 在某些狀況下一定會發生,而且有很具體的方法可以來處理,但是實際上 developers 卻沒有去處理,這是為什麼?答案很簡單,通常是...『沒時間』(因為老闆說要 time to market 啊)。長期下去,程式變得越來越亂,也越來越不可能有時間去處理這些以前留下來的例外處理問題。所以,收到一個 exception 的時候,到底要不要去花時間 recover ,就取決於所謂的 Robustness Level(請參考『敏捷式例外處理設計的第一步:決定例外處理等級』)。鄉民明把 Robustness Level 簡單想成『例外處理的 requirement』便可,基本上只有三個簡單的 level:
  • Level 1: Error-reporting (錯誤回報)
  • Level 2: State-recovery (狀態回復)
  • Level 3: Behavior-recovery (行為回復)
在此就不再重複說明了,只是簡單補充一下,如果鄉民們在某一個 sprint 決定這個 sprint 的所有  stories 的例外處理等級都只要達到  Level 1 就好了,那麼就算是遇到某個很明顯可以 recover 的 checked exception,此時也不用處理,直接轉成 unchecked exception 往上丟即可。

Exception Handling Policy (Strategy)

最後一點就是鄉民們可能不知道要如何撰寫例外處理程式來 recover 錯誤狀況。假設從 recoverability,application context,  robustness level 這三個面向來判斷,某個 exception 是應該要被處理的,但是,問題來了,要採用哪種方法,策略來處理這個例外狀況?舉個例子,假設鄉民們在 Windows 上要讀取硬碟的 serial number 資料,這個資料可以從 WMI 裡面得到,但是,某一天突然有客戶回報說,他的某顆硬碟在 Windows 2003 上面讀不到 serial number,請問 單兵 鄉民該如何處置?

用 exception handling 的術語來講,primary method (採用 WMI 方式讀取 serial number)失敗,那就可以有下列三種選擇:
  • Retry with the original method and arguments.
  • Retry with the original method and new arguments.
  • Retry with alternative methods.
以 WMI 的例子來看,這看起來好像是 Windows 2003 WMI 的 bug,所以前兩種策略是行不通的(有問題的方法,呼叫再多次還是有問題)。看起來要找其他讀 serial number 的方法(alternative)才有可能可以解決。
***

結論,為什麼遇到一個 exception (無論是 checked or unchecked)不知道要如何處理?看完這一篇,就算是沒有辦法涵蓋 100% 的狀況,至少也包含了 99.99% 80% 的情形。什麼,還是不懂...嗯嗯...如有此種現象的鄉民,請報名『exception handling 保證班』...XD。

***

友藏內心獨白:Exception handling 的觀念其實很像保險 。