#導覽列 #文章列表 #許洛豪
在光之翼的守護下訴說著一段段的英雄傳說。 [ 首頁 | 網誌 | 相簿 | 留言 | 訂閱 ]

FF15 心得--這世界還真小。

此文評價一顆星二顆星三顆星四顆星五顆星
Loading ... Loading ...

今天衝 FF15 去買 SilverMoon 的女僕本,然後在中午吃飯前去痛車區亂晃的時候遇到了要去 CWT24 的孝萱和掌櫃,世界還真是有夠小啊。XD

話說其實我根本不知道今天也是 CWT24,等到會場時才被排隊的人潮給嚇到了。

好想把她抱回家的好蘿。好想把她抱回家的好蘿。
好想把她抱回家的好蘿。(View at PicasaWeb)
一樣想抱回家的小鏡。一樣想抱回家的小鏡。
一樣想抱回家的小鏡。(View at PicasaWeb)
傳說中的草泥馬。傳說中的草泥馬。
傳說中的草泥馬。(View at PicasaWeb)

因為忘記帶相機,只有用手機隨便拍一些,今天看到比較特別的大概就是右邊那幾個唄。

另外有看到兩隻赫蘿,都很可愛,可惜另一隻是在會場裡面看到的,所以沒有照到……>_<

今日戰利品:Silver Moon 女僕本一本(這個時候就要來趁亂告白:麻呂好萌!),赫蘿本一本、一片 RPG Maker XP 做的巫女主題遊戲。

以上。明天的話,看天氣和心情唄……天氣好又沒有很累的話,大概還會去晃一晃,拍一拍照吧。

將本文加入 Hemidemi 書籤
Brian Hsu (墳墓)
2010-02-20 (週六) 20:37:00

所以我應該打敗佛地魔(大誤)!

此文評價一顆星二顆星三顆星四顆星五顆星
Loading ... Loading ...

在喵家的版上面看到孝萱貼的這一則 The Only Thing I Know 的影片,話說這是一部製作得很棒的影片,而且也發人省思。

但……真的是這樣嗎?先聲明,我自己就是個 ACGN 迷,所以我的看法或許是偏頗的。我可以很明白的和你說:我絕對支持不應該沉迷於電玩,但我非常討厭『將因自己無法刻制而導至失敗的人生歸咎於電玩』的這種行為。

我真的覺得這個影片做為所謂的『一堂課』也好、『教誨』也罷,其立論基礎是非常非常之薄弱的,薄弱到我覺得很好笑。

我們一步一步的來檢視影片作者說他打電玩的經過和影響唄。

首先,他說他花了上千美元和上萬小時來打電動,但他卻體認到了一個事實就是這些東西『並沒有讓他更聰明』、『拼命按按鈕也沒有增進他的反射神經』、『我現也也沒有成為功夫大師,甚至連劍都拿不穩』、『如果殭屍來了,我可能連五分鐘也撐不了』。

作者說他從 Atari 就開始玩了,但我沒接觸過 Atari,不能確他他影片出現的是哪一代,但我可以確定他影片中出現了超任--1990 年發售。

所以保守估計,他至少玩了二十年的電動,然後花了上千美元和上萬小時來打電動,但卻沒有什麼實際的成就,而在履歷表上也沒有任何一格是你在電玩世界中的成就。

看起來挺合理的,玩電動挺浪費金錢和時間,而且一無所獲是唄?很合常理咩,本來就這樣啊!所以把電動丟了唄。

等等,我們來看另一個情況……不要打電動,我們來看電視唄!

我記得我家在 1990 年代就有第四台,所以看到現在也看了二十年了,第四台一個月的費用大約在 600 大洋左右,二十年總共有兩百四十個月,再乘上一個月 600 大洋,總共是十四萬四千元,折合美金約 4000 多元。

唔,原來我家的電視花了上千美元吶。

好唄,不打電動的人,一天看一個小時的新聞和一個小時的連戲劇好像也很合理的樣子(這應該算很少看電視的人了唄),所以一天看兩小時的電視,二十年下來是……一萬四千六百個小時。

看了一萬四千六百個小時電視的傢伙,會變得比較聰明嗎?好像不會。

喜歡在廣告時轉台的觀眾,拼命按遙控器的按鈕的動作會讓反射神經會變得比較好嗎?好像也不會。

喜歡看動作片的家伙,會變成格鬥大師嗎?會變拳王嗎?好像也不會。

喜歡看警匪片的人,真的遇上了強盜會變態殺人魔,他們能夠逃掉嗎?似乎也很難說。

而且……好像也沒有啥工作的履歷表上有任何一格,是你至今看過了多少的電視影集嘛?

所以各位啊,電視是個一無用處的東西,除了讓你感到快樂外,只會浪費你的時間和金錢,沒有任何剩餘價值,所以把他給丟了唄!

(以上的論述你會不會覺得很蠢?如果你覺得這個關於電視的論述很蠢,那你就必需承認影片作者對於電動浪費他時間和金錢的那段論述也很蠢。)

接下來他中間關於電玩應該只是娛樂的論述我的觀點是持平,我可以接受。

但仔細想想,他為了玩電玩而放棄睡覺,健康,和野心(我覺得這理的意思應該比較接近『理想』和『夢想』),到底是誰的問題?

一個喜歡喝酒喝到發酒瘋的人,說都是酒的錯;抽煙抽到得肺癌的人說都是香煙的錯;因為毒癮犯了而傾家蕩產或去作奸犯科的人說我都沒有錯,都是毒品害的。

倒底是誰的錯?

他說他發現了他那些失去的時間可以應用在其他地方,而我們應該『把時間花在有價值的事物上』--Do things that create value, not consume time.

我覺得這句話很有道理,我可以接受,也很讚成這句話。事實上我會喜歡寫程式和小說也是基於同樣的理由--我總希望創造些什麼,或在這個世界上留下些什麼,證明我曾經活過,而且不是白活。

我絕絕對對支持這句話,甚至應該說,這句話是這個影片裡我唯一強烈認同的一句話。

但請先等一下……雖然我認同這句話,但作者建議把魔獸世界角色練到八十級的人們可以做以下的事情:

  • 從紐約開車到洛杉磯,橫跨美國三次
  • 讀完戰爭與和平
  • 讀完白鯨記
  • 還有頑童歷險記
  • 當然不能忘了七集合利波特也要看完
  • 每晚多睡三個小時
  • 跑十二次馬拉松
  • 烤 48 隻火雞
  • 寫 500 封信給奶奶
  • 學西班牙語和法語
  • 修完一門大學的課

我先列出我沒意見的,覺得這是很合理的事項:

  • 横跨美國三次(畢竟這就像我們的環島之旅一樣,一生總要一次)
  • 每晚多睡三個小時(因為我很愛睡,所以無法反駁這項)
  • 跑十二次馬拉松(這沒啥好說的)
  • 寫 500 封信給奶奶(雖然我奶奶不認識字)
  • 學西班牙語和法語(我喜歡這個)
  • 修完一門大學的課(雖然我覺得這本來就是大學生該做的)

但,有些東西我覺得很詭異,例如:

  • 烤 48 隻火雞(這個我真的不能理解除了讓你變胖外有任何意義)

我相信眼尖的人應該看出來了,我把所有的書單都留下來了,沒放在上面的兩個清單裡面,因為這是值得仔細思考的事情。

請你捫心自問,你看完戰爭與和平之後,是不是能夠變得比較聰明,或是讓你在真的身處於戰爭的世界之中時,經歷過無數的苦難後,終於找出人生的真諦呢?

請你捫心自問,當你看完白鯨記後,是不是就會變得能夠出海補鯨,在與鯨魚的搏鬥中獲勝呢?

請你捫心自問,當你因為實在太精彩,熬夜看完了七本哈利波特後,是不是就能夠學會那些魔法?是不是假設當佛地魔真的出現準備終結我們這些麻瓜時,你也可以存活下去?

好像……都不行唄?那麼,看這些書的我們,除了浪費了時間和買書的金錢之外(請別忘了在米國書是很貴的啊),好像也沒得到啥嘛?好像除了當下所得到的那些感動和喜悅,也沒啥剩餘價值可言嘛?

所以,這些書就和電玩一樣也是一無是處的存在,把他們給丟了唄,從此不要再看小說好了,全部改去看教科書唄。

但……這真的合理嗎?小說、電視、電玩,在本質上真有什麼不同嗎?

那麼,為什麼你會認為玩電玩是浪費時間,而這些時間應該要把他拿去看小說呢?

結語:

影片的最後我沒意見,因為沒人說喜歡電玩就一定要沉迷電玩,或是與現實世界脫軌。我認為那是玩家的問題,而不是電玩的問題,自己的選擇的人生道路應該自己負責,而不是推給電玩說都是電玩的錯。

將本文加入 Hemidemi 書籤
Brian Hsu (墳墓)
2010-02-18 (週四) 19:27:53

Maidroid Reminder 0.1.0 Released!

此文評價一顆星二顆星三顆星四顆星五顆星
Loading ... Loading ...

【簡介】

各位熱愛 ACG 的紳士淑女們大家好,在去年的一整年,是不是為了老是忘記各項活動或是動畫的播放時間而煩惱呢?

那麼,何不請一位專屬的妹斗桑來做為私人的貼身小秘書,讓她在特定的時間提醒您呢?

Maidroid Reminder 這次請來了米婭和小萌兩位可愛的女僕服務大家,只要把事情交待給這兩位可愛的女僕,時間到了她們兩位就會提醒各位紳士和淑女們喲!

【畫面】

【特色】

  • 單純的鬧鐘
  • 除了萌還是萌
  • 似乎需要一些恥力才能在公開場合使用(第一次開啟請小心)

【平台】

  • HTC Magic / Hero / Tatto
  • 其他使用 Android 1.5 以上之手機

【下載】

【感謝】

感謝 PTT 上的 whatzzz 以及 dicyia 版友的熱情幫助,可愛又容易害羞的米婭是由 whatzzz 作畫,獸耳萌到爆的小萌是 dicyia 版友的作品。

【附註】

程式本身有實作全語音機能,但因為沒有配音員,所以只能暫時借用 koebu 上面的一些配音……orz…

總之,在此強力徵求兩位可愛角色的配音員,如果您有興趣,歡迎連絡。同樣的,如果有版友有興趣製作其他的角色,也歡迎與我連絡。

【授權】

程式碼的部份採用 GPL v3 授權,有興趣的朋友可以直接到 GitHub 挖出來看或隨便亂改。

將本文加入 Hemidemi 書籤
Brian Hsu (墳墓)
2010-02-18 (週四) 17:06:14

Google 一下有這麼難嗎?

此文評價一顆星二顆星三顆星四顆星五顆星
Loading ... Loading ...

本來寫在噗浪上的,一不小心寫太長了,乾脆貼過來算了。

我真的不知道是不是我的要求太高了?還是現在的科技已經便利到大家認為不會基礎都沒關係,『反正只要上網問,就會有答案,自己不用努力?』

但你不覺得一個人連 CPU 到底要負責哪些範圍的東西都不知道,就說『我要做一顆 CPU 出來』,然後到處問人『做一顆 CPU 要看哪些書、要從哪裡開始』是一件很可笑的事情嗎?特別是在這一個有 Google 和維基百科的年代。

我真的不知道是不是我的要求太高了?還是現在的科技已經便利到大家認為不會基礎都沒關係,『反正只要上網問一個模糊的大概,就會有答案,自己不用努力,連最基本的功課都可以不用做了?』

好像只要會在空中蓋閣樓就好了……而且這個閣樓還是個沒有個任何外觀設計草圖的閣樓。

至少,在蓋空中閣樓前,先說說你要蓋的閣樓長個什麼樣子吧。以 CPU 來說,是 CISC 嗎?是 RISC 嗎?想要用在哪方面呢?

以軟體來說,總要先搞清楚自己要改的東西是哪一層吧?是 UI 嗎?是 OS 嗎?如果是 OS 的話,又是哪方面呢?Process Management?Memory Management?Driver?File system?

如果你自己連自己要做什麼都搞不清楚,就想和人『討論』說『我想做 OS』,不覺得很可笑嗎?

虧你還說你在 113 修過 OS 咧……難道修過 OS 了還不知道這世界上一堆 Open Source OS,從簡單到複雜都有,任君挑選去研究怎麼做一個 OS 嗎?難道修過了 OS,還不知道有一套著名的『教學用』的 Minix 就是專門用來教你怎麼寫 OS 的嗎?甚至還有一份 Developer Guide 教你怎麼 hack 咧。

好好好,就算你說你要做的是 Android,找到這張就在官方 Developer Guide 裡面的第一頁的架構圖,看出 Android 用的 OS 是 Linux 需要多少時間?

你說你想要改 Schuduler,Google這頁這篇是要多少時間?(答:Google 回報需時 0.28 秒。)

做這麼一些動作真的是那麼困難,那麼不近人情的要求嗎?說實話,我覺得如果你在課堂上學到的東西都不能活用,連 Google 都不會用,那麼你說你想做 OS 這種基礎的東西,會有人覺得你不是來亂的?

退一千步來講,如果你說你要的是置換所有 Android 預設 AP,那麼 Google這一頁這一頁要多久時間?(答:Google 回報需時 0.28 秒)

退一萬步來講,如果你要的只是更改 UI Style,那麼請問 Google這一頁需要花你多少時間呢?(答:Google 回報 0.12 秒)

面對一個連自己要做哪個層次的東西都不知道,連 Google 都不願意用,卻又說『自己想做 OS』、『想改 Scheduler』、『Sense UI 算不算 OS 我也不知道』的人,又還能說些什麼呢?又還有什麼好客氣的呢?BTW,修過 OS 還能把 Scheduler 和 UI 混在一起做成『整體的 OS 風格』的撒尿牛丸,我覺得也是一件了不起的事了(笑)。

如果是完全外行當然是笑笑就算了,但既然連 Scheduler 這種專有名詞都出來了,我想不會有人會認為他是 CS 外行,那麼,對一個 CS 內行人而言,連 Google 都不會,究竟有什麼好值得客氣的啊?

願意真的從基礎做起--或許這才是我真正佩服宅色夫的原因唄--因為我自己也還做不到那個程度。

我目前頂多只到知道自己還少哪些基礎而已(而老實說,這並不困難),卻一直沒有那些動力去把這些基礎給補足……

算了,變箭靶就箭靶唄,至少我是一個問心無愧的箭靶,至少我是一個誠實面對自己說過的話,做過的蠢事的箭靶,而不是一個只會假裝什麼都沒發生過的箭靶。XD

將本文加入 Hemidemi 書籤
Brian Hsu (墳墓)
2010-02-15 (週一) 11:17:09

不會走就想飛。

此文評價一顆星二顆星三顆星四顆星五顆星
Loading ... Loading ...

我想,或許我真的是老了吧……或許,真的有點自以為是也說不定。

我也不知道我自己孔龍本熟不熟,但至少我現在是在靠在 Linux Kernel 下打轉來混飯吃的……

我只知道,『不懂』並非可以拿來當不會走就想飛的擋劍牌,我只知道,『不懂』並不是可以拿來當做講話不精確的擋劍牌,特別當你在討論一個需要精準度的話題,以及身在一個講究精準的領域裡時。

我只是討厭……不做功課和不會走就想飛這回事。

舉例來說,為什麼會連官方文件都不想看,就跑去用 UI Desinger,然後再來抱怨做不到要的效果?看個文件、範例程式碼有這麼困難嗎?在你要做 UI 前先去了解 Framework 在 UI  設計上的架構,不是一件必需而且基本至極的事嗎?

又譬如說,什麼叫做『我想要讓整體 OS 都能有自己風格』、『想要去改核心的部分例如 Scheduler 之類』、『其實 Sense UI 算不算 OS 我也不知道』。

如果你連你要做的是什麼都不能精確的描述出來,那還需要做嗎?什麼叫做整個 OS 的風格?是 OS 底層的行為嗎?是 UI 嗎?

我想任何一個合格的 CS 背景的人,都知道這兩者有著多大的差距--如果你不能精確的描述出來,你敢說你是個合格的 CS 人?你敢說兩個人討論的東西會是一樣的?你敢說你知道你自己要做的到底是什麼東西?

是啊,CS  的領域是很廣沒錯啊,但那就代表你一句『不懂』就可以呼嚨過去了嗎?就可以拿來當做不想做最基本的功課和不精確的藉口了嗎?

是啊,我也想過要去改 Android 整體呈現出來的風格啊,我也想把 Android 上的程式置換掉啊,但為什麼我可以在還沒寫過任何 Android 程式之前,就知道這件事根本不用動到 Kernel(我想這點只要任何一個有唸過 OS 和看過這張圖片的人都很清楚),甚至連 Framework  都可以不用去動(這個只需要看這個影片就可以知道)?

看一看官方的文件真的很難嗎?好唄,就算不想看文件好了,那幾個講架構和Android 特性的影片在 Android  剛出來的時候就有了,甚至就大喇喇的寫著 Any app on the mobile device can be replaced or extended — even core componets such as the dialer or home 呢。

翻到這個影片要用掉你多久的時間?

原來一句『不懂』、『不熟』就可以當做不去做功課的擋箭牌啊,如果這個擋箭牌也能讓我用在工作上就好了。

『老闆,Kernel/Driver 這個當西我不熟,請你教教我,然後再付我一個月萬把元的薪水。』

我本來是去應徵 AP 的工作,結果被分到 BSP 部門,要是我也能這樣和我老闆或上司說的話該有多好(誤)。

是啊,我也不完全了解孔龍本吶,是啊,我的 OS 分數也不高啊(只不過現在靠胡搞 OS 過活就是了)!是啊,我也做過自己寫 OS 甚至是自己設計程式語言的白日夢(在還沒唸到 OS 之前咧)啊!

但我可不記得我會連我自己想做的是哪一層的東西都分不清楚,我可不記得我會什麼功課都不做--至少,我會去了解自己到底需欠缺以及需要具備哪一方面的技術。

是的,我現在還是做不出 OS 和程式語言,但我卻很清楚我到底還缺哪方面的知識所以做不出來--但話說回來,這不是想要去做一個東西之前最需要去了解的東西嗎?

至少,我不會很天真的,不會走就想去飛。我不會很傻的去問人做 OS 或程式 語言需要啥技術--x、我問 Google 大神都還快一點咧

做人,還是腳踏實地的一點唄。

正因為 CS 的領域很廣,所以更不適合不會走就想飛,也不適合那些連最基本的功課都不想去做的人。

將本文加入 Hemidemi 書籤
Brian Hsu (墳墓)
2010-02-14 (週日) 17:26:00

自學組合語言的必備良方!

此文評價一顆星二顆星三顆星四顆星五顆星
Loading ... Loading ...

話說其實從大學起就一直都很想學組合語言,但一直都沒能夠成功的進入組語的學習領域。

後來陸陸續續接觸了一些其他的程式語言,玩到 Functional Programming 之後,也漸漸地把學組合語言這件事給拋到腦後了,畢竟光是玩 Functional Programming 就已經玩不玩了說。

只是後來又不小心進到了需要慣 C 的工作領域,重新在 Linux Kerenel 下面打轉,於是學組語這個念頭又回來了--畢竟,這是可是基礎中的基礎啊,而且追 Kernel 常常追到最後都是組語。

但問題是組語要怎麼學呢?我想一定也有很多想學組語,但和我一樣不得其門而入的朋友有相同的困擾。

以我自己而言,我有莫名其妙、不知道為何會出現的,從 80386 時代遺留下來的組語書籍(還用 PE2 咧),有自己去書局買的 NASM 的書,也有資工系開的組合語言課程的課本,甚至是網路上開放下載的教學書籍。

可是以上沒有任何一本書真正讓我進入組語的世界,理由很簡單--這些書我怎麼看都覺得不對勁,不知道該如何寫出我的第一隻組語程式。

有的一開始就和你講什麼 MOVL 是幹嘛用的,ADD 又是啥,但卻又沒有一隻完整的程式可以執行試驗--這樣根本就沒感覺啊!根本就不能從錯誤之中學習啊(例如隨便亂加兩個暫存器會怎樣)!

有的嘛,遵照古老的傳統,一開始就寫一個 Hello World 給你,然後再告訴你不要管那些 include 的黑魔法,反正程式可以跑就好--等一下,我學組語就是為了要了解最底層的運作,結果你叫我不要管他?

總而言之,看這些書的挫敗感真的很大,也因為如此,我一直沒有真正下定決心好好把組合語言給學起來。

一直到前一陣子,我在 Hacking Thursday 的討論區上看到這一本超級棒的書籍--Programming from the Ground Up,這真的是自學組合語言的好物啊!

廢話不多說,我們來看書中第一個程式範例:

.section .data
.section .text
.global  _start

_start:
    movl $1, %eax   # This is the linux kernel system call for exit
    movl $0, %ebx   # This is the status number return to OS
    int  $0x80      # This wake up the kernel to run system call

三行程式,而且每一行書裡面都解釋的很清楚(是的,包括 System Call 的部份也有說明,雖然經過簡化與譬喻),沒有任何的黑魔法。

同時,你也可以亂改這個程式,例如試著改變 EBX 暫存器的值,讓他返回不同的值給 Shell,又或者亂改 EAX 裡面的值,然後讓他產生 Segmentation Falut 而當掉。

真是太神奇有趣了!這才叫學組語嘛。

我真的很佩服作者可以想出把程式的結束狀態代碼當成輸出這個點子,完全避開了其他書裡面為了要產生輸出而不得不先使用黑魔法的問題。

再舉另一個例子,他的第二隻程式是介紹控制流程和迴圈,要透過他介紹的各種跳躍指令找到一個數列裡的最大值,這隻程式如下:

.section .data

items:
    .long 3, 6, 7, 10, 22, 34, 12, 0

.section .text
.global  _start

_start:
    movl $0, %edi             # move 0 to index register
    movl items(,%edi,4), %eax # load the first number
    movl %eax, %ebx           # put it to the EBX (cureent biggest)

start_loop:
    cmpl $0, %eax             # check to see if we've hit the end
    je   loop_exit

    incl %edi                 # increment index by 1
    movl items(,%edi,4), %eax # load next number
    cmpl %ebx, %eax           # compare with current biggest
    jle  start_loop           # jump to start_loop if not bigger
    movl %eax, %ebx           # else move this value as the largest
    jmp  start_loop           # next turn

loop_exit:
    movl $1, %eax             # System Call exit (No. 1)
    int  $0x80                # Singal batman

同樣的,這隻程式也是利用離開狀態做輸出--所以你用到的,都是你學過的東西,沒有黑魔法,每一行每一行都可以解釋到底是在做什麼,讓你驗證你是不是真的了解他。

另外,他用的是 GNU as 的語法,這對我而言有以下幾個好處:

  • 這是 Linux Kernel 裡面用的東西,我不用再去熟悉其他語法
  • 我只要有一台 Linux Box 就可以試著跑書裡的程式
  • 這意謂著你可以用 GCC 把 C 語言編譯到組合語言,然後和這本書裡面的範例做比對,例如講到 Function 的時候,你就可以寫幾個 C 語言函數來驗證書裡講的東西。

所以我一定要大推這一本書的啊~~這本書真的是自學組語的必備良方,只要會一點程式設計,一定可以看得懂的好東西!

將本文加入 Hemidemi 書籤
Brian Hsu (墳墓)
2010-02-07 (週日) 11:37:18

遞回只應天上有,凡人應當用迴圈。

此文評價一顆星二顆星三顆星四顆星五顆星
Loading ... Loading ...

距離脫離尼特族接近兩個星期了,工作方面還在努力學習中,大部份的時間都在追別人寫的東西,只有小修小改而已,希望自己也能加快腳步,盡量能夠早點弄熟自己要接手的東西吶。

回到正題,『遞迴只應天上有、凡人應當用迴圈』這句話應該不少人聽過了,我在很久很久以前也寫過一篇『Haskell 真的比較好懂和不容易出錯嗎?』的文章。

不過,事後證明,我錯了。我接觸了 Scala 之後,我認為這句話很有可能是錯的,遞迴加上 Pattern Matching 或許在某些時候真的比較好懂。

直接來看例子吧,這個例子很簡單:給一個 List,請找出最後一個元素。

程式碼如下:

def last[T] (list: List[T]): T = list match {
    case x :: Nil    => x                 // 如果這個值後面沒有東西,他就是目標
    case x :: remain => last(remain)      // 不然的話繼續處理剩下來的東西
    case _ => throw new Exception("opps") // 不可能有這兩種以外的狀況
}

整個程式碼就和用說的一樣簡單容易理解,而另一個遞迴比較好懂的經典例子應該就是費伯納西數列了吧。

def fib (n: Int): Int = {
    n match {
        case 0 => 0                     // 如果是 fib(0) 就是 0
        case 1 => 1                     // 如果是 fib(1) 就是 1
        case x => fib (x-1) + fib (x-2) // 不然的話就是前兩項相加
    }
}

幾乎和用嘴巴上數學定義一模一樣。XD

但話說回來,我還是覺得 Haskell 很難懂,理由很簡單--他只給你一種他認為『對』的方法來做事,但他的『對』不見得在每種情況下都是直覺的。

例如在我之前的那一篇文章裡提到的,『把 List 裡數字加總』這件事,如果是在 Scala 裡,可以分別寫出兩種版本:

def sumByIterate (list: List[Int]): Int = {

    // 一開始計算機上是 0
    var sum: Int = 0

    // 一個個把 List 裡的元素加到計算機上
    for (x <- list) { sum = sum + x }

    // 其實上面那一句你也可以這樣寫
    // list.foreach {x => sum = sum + x}

    // 最後就是結果
    return sum
}
def sumByRecursive (list: List[Int]): Int = {
    list match {
        case Nil     => 0
        case x :: xs => x + sumByRecursive(xs)
    }
}

我還是覺得 sumByIterate 比較好懂和直覺,三行解決,不用遞回,做的動作就和我們要利用計算機把一個數列給加總一模一樣。

我相信當你要拿計算機加總一個數列的時候,是不會去想『第一個數字再加上其剩下的數字的總合就是答案』這種事,或者去做『從數列最後一個開始往回加』的動作的。

畢竟,加總不就是一個一個把他給打到計算機裡嗎--正是我們第一個版本做的事情。

所以我還是喜歡 Scala 這類 Multi-Paradigm 的程式語言--給你一個完整的工具箱,裡面有各種扳手和鉗子,讓你決定哪一種比較合適,而不是只給一個平口老虎鉗,叫你什麼事都要用這隻老虎鉗來做……

將本文加入 Hemidemi 書籤
Brian Hsu (墳墓)
2010-02-06 (週六) 22:25:43

脫離尼特生活的前一夜。

此文評價一顆星二顆星三顆星四顆星五顆星
Loading ... Loading ...

話說從十一月二十四號生效,到今天剛好也滿兩個月了,雖然等了一小段時間,不過總而言之還是走上預定的道路了,從明天開始,我就要開始人生的另一個階段--賣肝換錢的階段。XD

也就是說,今天是我當尼特族的最後一個夜晚啦!畢竟,我還是沒辦法接授在神的記事本裡關於我的那一頁上寫著:『工作就輸啦!』這樣子的字樣。

畢竟雖然我不相信清教徒工作倫理的那一套說法,但能夠做自己喜歡的事情(應該啦)又可以賺錢討生活,幹嘛不去呢?

再說,我終於要實現小學的時候的『我的志願』的作文裡寫的,要成為一個程式設計師啦,哇哈哈~~有多少人能夠實現小學時候的夢想啊!

(仔細想想,換句話說,原來我從小就想當阿宅工程師嗎?)

不論如何,人生的另一個階段要開始了,尼特族的生活也結束了。除非有一天像立花悟一樣中了三億兩千萬,不然應該也不會再有機會當個尼特族了吧?

統計了一下,入伍前和退伍後的尼特族生涯共計八個月左又,但卻沒有做出什麼驚天動地的事情出來,就是單純的浪費生命,慚愧啊。

對了,小小的推銷一下,『程式設計師的自我修養--連結、載入、程式庫』這本書是個不可多得的好物,寫程式的人一定要看!很難得看到寫得這麼流暢,又有深度的中文程式相關的書籍。

最後,脫離尼特族的前一個晚上在幹嘛呢?答案就是--玩夜明前的琉璃色,唔,菲娜和麻衣都好萌喲!

最後的最後,我一定要說,地下街裡 cos 成初音的柊鏡 PVC 實在是萌到爆表啊!好想抱回家……

最後的最後的最後,水沙連的日記版怎麼愈看愈像我的個人版。XD

將本文加入 Hemidemi 書籤
Brian Hsu (墳墓)
2010-01-24 (週日) 20:16:40

幾個簡單的機率問題。

此文評價一顆星二顆星三顆星四顆星五顆星
Loading ... Loading ...

這是這一陣子在 PTT TechJob 和 Math 版上吵得很兇的關於機率的話題。

【前言】

原始命題:

  • 小明過年期間去拜訪許久沒見面的同學,聽說同學已經生了兩個小孩,進門後小明看到朋友其中一個小孩是男生,請問,另一個是男生的機率為多少?

老實說,我已經懶得再裝瘋賣傻說服認為是 1/3 的人了。

雖然 terrorlone 大已經在 Math 版 17833 篇用程式說明這個命題以及為何會有人得出三分一之的結論,但我想程式不是每個人都看得懂,所以我做了這個實驗。

希望這篇能夠幫助像我一樣,數學差到不行,對於機率的認識就僅止於【機率=發生次數/樣本空間】和【所有情況的機率總合為 1】這個兩個原則,然後認為這個問題好像 1/2 也對,1/3 也說得通的朋友。

在這篇文章裡沒有任何除了上述兩個原則之外的算式,所以請數學和我一樣差的朋友也可以放心觀看。

裡面有的只有實驗設定與實驗步驟,以及每一次實驗的樣本與結果,所以唯一需要有的就是耐心,以及觀察力。

【三個命題】

首先,因為生小孩並沒有辦法實驗,所以我們轉換命題。

  • 桌上有兩個用杯子蓋住的銅板,移開第一個杯子,發現是正面,則第二個杯子裡的銅板還是正面的機率是?

呃……因為我們數學不好,所以不會算,好像是二分之一,又好像是三分之一咧?是獨立事件嗎?還是條件機率?我只記得兩個的算法好像不一樣,可是也傻傻分不清楚要用什麼公式啊!orz…

不過沒關係,機率問題和其他數學問題不同而有趣的地方就在於我們大可以『做實驗驗證我們的猜想』。

但在實驗開始之前,你可以先想一下,以下這三個命題是不是等價。

  • 桌上有兩個用杯子蓋住的銅板,移開第一個杯子,發現是正面,則第二個杯子裡的銅板還是正面的機率是?【命題A】

  • 桌上有兩個用杯子蓋住的銅板,隨機開某個杯子,發現是正面,則另一個杯子裡的銅板還是正面的機率是?【命題B】

  • 桌上有兩個用杯子蓋住的銅板,在其中至少有一個是正面的情況下,另一個杯子裡的銅板還是正面的機率是?【命題C】

覺得很像三個問題是等價的對不對?那我們開始來做實驗驗證我們的猜想吧!

【命題A的實驗】

實驗開始,實驗內容與結果可以在下面兩個網址下載,打開後請隨便在一個儲存格上打些字,就會更新實驗數據。

在這個檔案中,第一個活頁標籤是原始的樣本空間,也就是在實驗開始前,每一個杯子裡的狀態。而在這個實驗中,我們有以下的命題與假設以及符號標計設定。

  • 命題

    • 桌上有兩個用杯子蓋住的銅板,移開第一個杯子,發現是正面,則第二個杯子裡的銅板還是正面的機率是?【命題A】
  • 假設

    • 硬幣是公正的(Uniform 分佈),在只有『正』『反』的二元狀況下,有 50% 是正面,50% 是反面。
  • 符號標記

    • 0 代表硬幣反面
    • 1 代表硬幣正面

實驗內容在第二張活頁標籤『實驗內容』裡面,實驗步驟如下。

  1. 開啟第一個杯子,如果是反面,此次實驗失敗(因為不符合題目『打開第一個是正面』的情況),在 G 欄的地方打 X,代表實驗失敗,直接進行下一輪。

  2. 若步驟 1 成功,在 G 欄的地方標 1,並且計數後開啟第二個杯子。在這邊成功進行實驗的次數總計在 G4。

  3. 若第二個杯子是正面,符合命題,把 H 欄標成 1,計數,總次數在 H4。

  4. 若第二個杯子是反面,符合反命題『第二個杯子是反面』,把 I 欄標 0,計算總共有幾次反面,總次數在 I4。

以上,第一個命題實驗結束,就結果來看,你會發現 H4 與 I4 的值接近。

不過既然是算機率嘛,總還是要有個答案出來,而且也要驗證我們的實驗有沒有錯漏,不然機率加起來不等於 1 就好笑了。

所以,以下是計算的部份,套用我們已知的兩個機率原則。

  • 機率 = 發生次數 / 樣本數
  • 所有可能發生的情況機率相加為 1

以下計算開始。

  1. 樣本數 = 第一次開啟時是正面的次數 = G4
  2. 正命題發生次數 = 第二個杯子也是正面的次數 = H4
  3. 正命題機率 = 發生次數 H4 / 樣本數 G4 = M20(請看實驗結果)
  4. 反命題發生次數 = 第二個杯子是反面的次數 = I4
  5. 反命題機率 = 發生次數 I4 / 樣本數 G4 = M22(請看實驗結果)
  6. 正命題機率 M20 + 反命題機率 M22 = M23

請對照實驗檔案,你會發現不論樣本空間的硬幣如何分佈,M20 和 M22 都會接近 0.5,而 M23 會逼進於 1。

第一個【命題A】的實驗,答案是 0.5。

【命題B的實驗】

對對對,我知道,你會說我會得到 0.5,是因為我先開第一個杯子的問題,如果我是隨便開第一個或第二個杯子,那麼答案一定會不一樣!

來吧!因為我們的命題改變了,所以重新做實驗唄!實驗的檔案如下。

這次的假設比較複雜一點,這是因為我們需要記錄實驗內容,所以必需用不同的方式達成同樣的命題。

簡單的來講,我們達成『隨機開第一個或第二個杯子』的方式,是先以夠亂並且公正的亂數,隨機調換兩個杯子內的硬幣。

換而言之,當調換完成後,我們打開第一個杯子時,有可能開到的是原始樣本空間內的第一個杯子的硬幣,但也很有可能是原始樣本空間內第二個杯子的硬幣。

以下是這次實驗的命題與假設。

  • 命題

    • 桌上有兩個用杯子蓋住的銅板,隨機開某個杯子,發現是正面,則另一個杯子裡的銅板還是正面的機率是?【命題B】
  • 假設

    • 我不知道第一次開的是第一個杯子或是第二個杯子。

    • 當杯子內容隨機互換後,符合上述假設,亦即我是隨機開啟第一個杯子或第二個杯子。所以此時我已經不知道我是看到哪一個硬幣。

    • 互換是公正的(Uniform 分佈),在只有『換』與『不換』的二元情況下,有 50% 會換,50% 不會換。

    • 硬幣是公正的(Uniform 分佈),在只有『正』『反』的二元狀況下,有 50% 是正面,50% 是反面。

  • 符號標記

    • 0 代表硬幣反面
    • 1 代表硬幣正面
    • 0 代表不換
    • 1 代表換

首先,在正式打開硬幣之前,我們先用以下的方式進行隨機掉換洗牌的動作,經過洗牌過後的杯子的情況,在第二個活頁標籤『隨機掉換順序』裡面。

  1. 先以亂數決定是否調換 = B欄

  2. 如果 B 欄為 1(換),原始樣本空間 C、D 欄互換

接著進行正式的實驗,過程與記錄在第三個活頁標籤『實驗內容』中,整個實驗的步驟如下。

  1. 開啟第一個杯子,如果是反面,此次實驗失敗(因為不符合題目『打開第一個是正面』的情況),在 G 欄的地方打 X,代表實驗失敗,直接進行下一輪。

  2. 若步驟 1 成功,在 G 欄的地方標 1,並且計數後開啟第二個杯子。在這邊成功進行實驗的次數總計在 G4。

  3. 若第二個杯子是正面,符合命題,把 H 欄標成 1,計數,總次數在 H4。

  4. 若第二個杯子是反面,符合反命題『第二個杯子是反面』,把 I 欄標 0,計算總共有幾次反面,總次數在 I4。

接著觀察 H4 與 I4,好像也很接近。為了驗證我們的想法和實驗,用機率的概念來試著計算看看唄!

套用我們已知的兩個機率原則。

  • 機率 = 發生次數 / 樣本數
  • 所有可能發生的情況機率相加為 1

以下計算開始。

  1. 樣本數 = 第一次開啟時是正面的次數 = G4
  2. 正命題發生次數 = 第二個杯子也是正面的次數 = H4
  3. 正命題機率 = 發生次數 H4 / 樣本數 G4 = M20(請看實驗結果)
  4. 反命題發生次數 = 第二個杯子是反面的次數 = I4
  5. 反命題機率 = 發生次數 I4 / 樣本數 G4 = M22(請看實驗結果)
  6. 正命題機率 M20 + 反命題機率 M22 = M23

請對照實驗檔案,你會發現不論樣本空間的硬幣如何分佈,M20 和 M22 都會接近 0.5,而 M23 會逼進於 1。

啥?怎麼算起來好像和【命題A】的答案一樣啊??是的,其實不論你先開哪個杯子,機率還是 0.5 的,除非我隨機掉換的假設是錯的(說實話,這邊我真的沒把握我隨機掉換的做法是對並且符合學理的)。

【命題C的實驗】

哇咧咧!我不相信,我一定要實驗出 1/3 這個數字才甘心,畢竟這麼多人說是 1/3,一定有他的道理在!佛說我不入地獄誰入地獄,既然堅持是 1/3 的朋有沒有人願意做實驗,那就我來做吧,哇哈哈~~~

同樣的,來看這次的命題與假設。首先,為了避免又有人在盧是啥開啟順序的關係,所以我們用【命題B】的方式,在正式實驗前,先將杯子亂數洗牌,讓我們是隨機開啟杯子一或是杯子二。

  • 命題

    • 桌上有兩個用杯子蓋住的銅板,在其中至少有一個是正面的情況下,另一個杯子裡的銅板還是正面的機率是?【命題C】
  • 假設

    • 我不知道第一次開的是第一個杯子或是第二個杯子。

    • 當杯子內容隨機互換後,符合上述假設,亦即我是隨機開啟第一個杯子或第二個杯子。所以此時我已經不知道我是看到哪一個硬幣。

    • 互換是公正的(Uniform 分佈),在只有『換』與『不換』的二元情況下,有 50% 會換,50% 不會換。

    • 硬幣是公正的(Uniform 分佈),在只有『正』『反』的二元狀況下,有 50% 是正面,50% 是反面。

  • 符號標記

    • 0 代表硬幣反面
    • 1 代表硬幣正面
    • 0 代表不換
    • 1 代表換
    • 0 代表兩者都為反面
    • 1 代表兩者至少有一個是正面

首先,我們用以下的動作進行亂數洗牌,洗牌過後的結果,位於第二張活頁標籤『隨機掉換順序』中。

  1. 先以亂數決定是否調換 = B欄

  2. 如果 B 欄為 1(換),原始樣本空間 C、D 欄互換

接著,正式開始做實驗,實驗過程與記錄在第三張活頁標籤『實驗內容』裡面記錄得很清楚,實驗步驟如下。

  1. 開啟第一個杯子,如果是正面,打開第二個杯子。

  2. 如果是反面,因為我們不確定『至少有一個是正面』的正面是不是出現在第二個杯子,所以也要打開第二個杯子,看裡面是不是正面。

  3. 如果第一個杯子或第二個杯子其中有一個是正面,符合題目描述的『至少有一個是正面』的前提,把 J 欄標 1,並將其加總至 J4。

  4. 若第一個杯子是正面,第二個杯子也是正面,此情形符合命題,將 H 欄標為 1,並於 H4 加總此狀況的次數。

  5. 若第一個杯子是正面,但第二個杯子卻是反面,符合反命題,我們將 I 欄標成 1,並且於 I4 加總發生這個狀況的次數。

  6. 若第一個杯子是反面,但第二個杯子卻是正面,同樣符合『至少有一個是正面』的先決件,是此題的反命題,所以我們將 K 欄標 1,並於 K4 計算發生這種狀況的次數。

接著,觀察實驗結果,你會發現 H4、I4 以及 K4 非常接近。

接下來,復習機率的基礎原則。

  • 機率 = 發生次數 / 樣本數
  • 所有可能發生的情況機率相加為 1

復習完了,就來計算機率唄!

  1. 樣本數 = 至少有一個是正面的狀況 = J4
  2. 正命題發生次數 = 第一個杯子是正面的情況下,第二個杯子也是正面 = H4
  3. 正命題機率 = 發生次數 H4 / 樣本數 J4 = N21
  4. 反命題(一)發生次數 = 第一個杯子是正面,但第二個杯子是反面 = I4
  5. 反命題(一)機率 = 發生次數 I4 / 樣本數 J4 = N23
  6. 反命題(二)發生次數 = 第一個杯子是反面,但第二個杯子是正面 = K4
  7. 反命題(二)機率 = 發生次數 K4/樣本數 J4 = N25
  8. 正命題機率 N21 + 反命題(一)機率 N23 + 反命題(二)機率 N25 = N26

仔細觀察 N21、N23 和 N25,你會發現大致上都接近 0.3333,而 N21 + N23+ N25 會逼進於 1。

是的,我們終於得出 1/3 的答案了!

【結論】

我數學很差,不過我知道如何做實驗,經過了以上的實驗,我們可以發現以下非常簡單明瞭的事實。

  • 從結論來看【命題A、B】兩者與【命題C】是不等價的!

  • 從實驗的方式和過程來看,『翻開後發現其中一個硬幣是正面』和『在已知其中至少有一個是正面的狀況下』也是不等價的敘述的!

發現問題所在了嗎?【命題A、B】在我們描述『第一次翻開來為正面』的時候,就已經把樣本空間縮小為『正反』與『正正』兩種了。(因為可以從題目敘述中推論出在一正一反的狀況下,保證正面一定會在第一次被翻出來)

但在【命題C】的時候,在『已知至少有一個是正面』的描述時,樣本空間卻包含了『正反』、『正正』、『反正』三種。是的,多了一個『反正』的組合出來!(因為我們不能從題目敘述中推論出一正一反的情況下,正面一定在第一次被翻出來。)

所以拜托,兩者是完完全全不同的命題,就算數學再差,差到和我一樣機統二修才低空飛過的差,也不要把這兩個命題混在一起做撒尿牛丸好不好啊。XD

將本文加入 Hemidemi 書籤
Brian Hsu (墳墓)
2010-01-18 (週一) 18:16:18

[Scala] 簡簡單單做 Mock Object 及 BDD 單元測試。

此文評價一顆星二顆星三顆星四顆星五顆星
Loading ... Loading ...

話說在做單元測試的時候,Mock Object 是很常使用的技巧,可以協助開發者隔離實際的環境,或以人工方式產生錯誤,以加強測試環境的控制。

簡而言之,Mock Object 的精神就是將非你可以控制的部份獨立出來,設計成可以隨時以其他的實作取代。

在 Scala 裡面,要在做單元測試時使用 Mock Object 是相當簡單,只要使用內建的 Trait 就可以很容易的達成 Mock Object 的技巧,而且對於客戶端的使用者而言,並不會感覺到任何使用上的差異。

在這邊,我們使用上次實作的 GeoService 函式庫來做示範,首先先複習一下上次的函式庫(我有做一些小修正,不過介面是一樣的)。

package org.maidroid.utils

import scala.io._
import scala.xml.XML
import scala.xml.Node
import scala.xml.Elem
import java.net.URLEncoder

case class GeoPlacemark (val query: String, val address: String, 
                         val accuracy: Int, val longitude: Double,
                         val latitude: Double)
{
    val longitudeE6 = (longitude * 1E6) toInt
    val latitudeE6  = (latitude  * 1E6) toInt
}

class GeoService (val query: String, val locale: String)
{
    private var statusCode   = 0
    private var errorMessage = ""

    val placemarkList: List[GeoPlacemark] = doQuery ()

    def this (query: String) = this (query, "")
    def error = (statusCode, errorMessage)

    private def createGeoPlacemark (node: Node) = 
    {
        val address    = (node \ "address").text
        val accuracy   = (node \ "AddressDetails" \ "@Accuracy").text
        val coordinate = (node \ "Point").text.split (",").toList
        val List (longitude, latitude, _) = coordinate

        GeoPlacemark (query, address, accuracy.toInt, 
                      longitude.toDouble, latitude.toDouble)
    }

    private def loadXML = {
        val url = "http://maps.google.com/maps/geo?" +
                  "q=%s&output=xml&gl=%s".
                  format(URLEncoder.encode(query), locale)

        val source = Source.fromURL (url, "utf-8")
        val iter   = for (line <- source.getLines) yield line

        XML.loadString (iter.mkString)
    }

    private def doQuery () =
    {
        try {
            val xml = loadXML
            this.statusCode = (xml \ "Response" \ "Status" \ 
                               "code").text.toInt

            // code 200 means OK
            if (statusCode != 200) {
                throw new Exception ("Query Faild")
            }

            val placemark   = xml \ "Response" \ "Placemark"
            val addressList = for (node <- placemark) yield 
                                  createGeoPlacemark (node)

            addressList.toList
        } catch {
            case e => errorMessage = e.getMessage
                      Nil
        }
    }
}

上述的程式碼中,我們可以發現所謂『非我們所能控制』的部份,就是連到 Google Maps 服務取得 XML 文件的 loadXML 這個函式。

這就是我們要把它隔離的程式碼,所以我們先設計一個 Trait,裡面有一個 loadXML 函式的介面。

trait LoadXML
{
    protected def loadXML: Elem
}

在這邊要注意的是,由於這個函式是內部使用,不應該透露給外界,所以我們將其宣告為 protected,這和 Java 的介面裡面只能有 public 不一樣。

接著,我們更改我們的 GeoService 的實作,要改的部份只有兩個:

  • 讓 GeoService mix-in LoadXML
  • 將 GeoService#loadXML 的部份改成 override protected

完整的程式碼如下:

package org.maidroid.utils

import scala.io._
import scala.xml.XML
import scala.xml.Node
import scala.xml.Elem
import java.net.URLEncoder

trait LoadXML
{
    protected def loadXML: Elem
}

case class GeoPlacemark (val query: String, val address: String, 
                         val accuracy: Int, val longitude: Double,
                         val latitude: Double)
{
    val longitudeE6 = (longitude * 1E6) toInt
    val latitudeE6  = (latitude  * 1E6) toInt
}

class GeoService (val query: String, val locale: String) extends
      LoadXML
{
    private var statusCode   = 0
    private var errorMessage = ""

    val placemarkList: List[GeoPlacemark] = doQuery ()

    def this (query: String) = this (query, "")
    def error = (statusCode, errorMessage)

    private def createGeoPlacemark (node: Node) = 
    {
        val address    = (node \ "address").text
        val accuracy   = (node \ "AddressDetails" \ "@Accuracy").text
        val coordinate = (node \ "Point").text.split (",").toList
        val List (longitude, latitude, _) = coordinate

        GeoPlacemark (query, address, accuracy.toInt, 
                      longitude.toDouble, latitude.toDouble)
    }

    protected def loadXML = {
        val url = "http://maps.google.com/maps/geo?" +
                  "q=%s&output=xml&gl=%s".
                  format(URLEncoder.encode(query), locale)

        val source = Source.fromURL (url, "utf-8")
        val iter   = for (line <- source.getLines) yield line

        XML.loadString (iter.mkString)
    }

    private def doQuery () =
    {
        try {
            val xml = loadXML
            this.statusCode = (xml \ "Response" \ "Status" \ 
                               "code").text.toInt

            // code 200 means OK
            if (statusCode != 200) {
                throw new Exception ("Query Faild")
            }

            val placemark   = xml \ "Response" \ "Placemark"
            val addressList = for (node <- placemark) yield 
                                  createGeoPlacemark (node)

            addressList.toList
        } catch {
            case e => errorMessage = e.getMessage
                      Nil
        }
    }
}

什麼?結束了?!別懷疑,真的就只有這樣子而已,這正是 Scala 吸引我的地方,夠方便吧!

在測試開始之前,我們先來做幾個 Mock 來用吧,要做 Mock 很簡單,只要再宣告繼承 LoadXML 的 Trait 即可。在這次的測試中,我們建造了以下三個 Mock。

  • 正確的 XML 回傳值
  • 錯誤的 XML 回傳值
  • 執行期間發生連線錯誤的 Exception
// 正確的 XML 回傳值
trait MockXML extends LoadXML
{
    override def loadXML = 
    
      
      
      
    
}

// 錯誤的 XML 格式
trait WrongXML extends LoadXML
{
    override def loadXML = 
}

// 連線錯誤 Exception
trait ExceptionXML extends LoadXML
{
    override def loadXML = throw new java.net.ConnectException
}

有了這些 Mock 物件之後,就可以著手來寫單元測試了,在這裡所使用的是 ScalaTest 這個支援多種單元測試風格的 Framework,我們用的是 FlatSpec 這個 Behavior Driven Development 測試。

由於使用 FlatSpec 所寫出的測試都相當直覺,看起來就像英文句子,所以程式碼的部份就不做詳細的解釋了。

在這邊要注意的地方,就是當我們要使用 Mock 的時候,直需要在建立物件時將要使用的 Mock 給 mix-in 進來就好,範例如下。

// 不使用任何 Mock
val noMock = new GeoService ("中央研究院")

// 分別使用上述三種 Mock
val mock1 = new GeoService ("中央研究院") with MockXML
val mock2 = new GeoService ("中央研究院") with WrongXML
val mock3 = new GeoService ("中央研究院") with ExceptionXML

知道了上述的規則後,我們就可以使用這三個 Mock 撰寫我們的測試案例了,完整的測試案例如下。

import org.scalatest.FlatSpec
import org.scalatest.matchers.ShouldMatchers
import org.maidroid.utils._

class GeoServiceSpec extends FlatSpec with ShouldMatchers 
{
    // 不使用任何 Mock 物件
    "A GeoService" should "retun a list of GeoPlacemark when successed" in {
        val service = new GeoService ("中央研究院")
        val correct = GeoPlacemark ("中央研究院", 
                                    "115 Taiwan Taipei City Nangang  "+
                                    "District中央研究院", 9, 
                                    121.6122646, 25.0405918)

        service.placemarkList should be === List (correct)

    }

    // 使用 MockXML 以保確取得正確的 XML 回傳值
    it should "has statusCode 200 and no error message when successed" in {
        val service = new GeoService ("中央研究院") with MockXML
        service.error should be === (200, "")
    }

    // 例用 WrongXML Mock 測試當回傳值為不合格式的 XML 時的狀況
    it should "has statusCode 0 and empty list when XML format is wrong" in {
        val service = new GeoService ("中央研究院") with WrongXML

        service.placemarkList should be === Nil
        service.error._1 should be === 0
    }

    // 例用 ExceptionXML 測試發生 Exception 時的行為
    it should "has statusCode 0 and empty list when Exception occurred" in {
        val service = new GeoService ("中央研究院") with ExceptionXML

        service.placemarkList should be === Nil
        service.error._1 should be === 0
    }
}

如何?要在 Scala 使用 Mock Object 進行單元測試很方便吧?!請多多利用這個技巧,讓單元測試變得更簡單愉快喲!

將本文加入 Hemidemi 書籤
Brian Hsu (墳墓)
2010-01-16 (週六) 12:00:10