<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-451051488891335923</id><updated>2012-02-19T17:53:33.342+08:00</updated><category term='Python'/><category term='Formal Language'/><category term='Research'/><category term='Software Engineering'/><category term='javascript'/><category term='Dairy'/><category term='Game'/><category term='English'/><category term='Pro'/><category term='Information Retrieval'/><category term='Machine Learning'/><category term='Math'/><category term='Management'/><category term='Comic'/><category term='Movie'/><category term='Programming'/><category term='Web'/><category term='Testing'/><category term='Present'/><category term='Diary'/><category term='Dream'/><category term='AI'/><category term='Travel'/><category term='格言錦句'/><category term='閒書'/><category term='ACG'/><category term='InfoFlow'/><category term='Communication'/><category term='中文'/><category term='Book'/><category term='Fiction'/><category term='Animation'/><category term='MiscInfo'/><category term='Mobile'/><category term='All'/><category term='System'/><category term='Computer Science'/><category term='Music'/><category term='Rails'/><category term='Data Mining'/><category term='Tips'/><category term='Search'/><category term='Fun'/><category term='Java'/><category term='MicroSoft'/><category term='Refactoring'/><category term='Google'/><category term='Pro Book'/><category term='Tool'/><category term='Business'/><category term='詩詞'/><category term='PHP'/><category term='Algorithm'/><category term='Life'/><category term='Economy'/><category term='Firefox'/><category term='Thinking'/><category term='Ruby'/><category term='Django'/><category term='Database'/><category term='Hardware'/><category term='VIM'/><category term='Wiki'/><category term='BlahBlah'/><category term='Functional Language'/><category term='Piano'/><category term='日劇'/><category term='Blog'/><title type='text'>fcamel's blog</title><subtitle type='html'>自強不息的駱駝有草吃</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><link rel='next' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default?start-index=101&amp;max-results=100'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>472</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-7708031988053755110</id><published>2012-02-19T03:11:00.001+08:00</published><updated>2012-02-19T17:53:33.356+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Software Engineering'/><category scheme='http://www.blogger.com/atom/ns#' term='Thinking'/><title type='text'>專注於滿足需求而非工具或方法</title><content type='html'>&lt;p&gt;看到 &lt;a href="https://plus.google.com/102249536116095884743/posts"&gt;command&lt;/a&gt; 提到&lt;a href="http://prog21.dadgum.com/128.html"&gt;《Don't Fall in Love With Your Technology》&lt;/a&gt;，而有一些感觸。&lt;/p&gt;&lt;p&gt;從高中開始，我一直想弄明白 Perl、Python 到底那一個比較好用，這樣我學其中一個就可以了。後來又多了 Ruby 這個選項，讓這問題變得更複雜。大概到大學後期或研究所的時候，我才肯定這是一種&lt;a href="http://fcamel-fc.blogspot.com/2009/07/blog-post_22.html"&gt;信仰上的爭辯&lt;/a&gt;，而將這個問題拋之於腦後。&lt;/p&gt;&lt;p&gt;同一時期，我也花了滿長一段時間才明白許多問題&lt;a href="http://fcamel-fc.blogspot.com/2010/04/3.html"&gt;沒有標準答案&lt;/a&gt;，&lt;a href="http://fcamel-fc.blogspot.com/2011/09/problem-solving-3.html"&gt;得視情況而定&lt;/a&gt;。每當對此有所體會時，就會想起大學電子學老師整年重覆強調的一句話：&lt;b&gt;「沒有前提，就沒有答案」&lt;/b&gt;。雖然兩學期的電子學都是低空飛過，這句話深深印在心裡，只是那時我對這句話的理解仍不深，還需時時重新琢磨它的含意。&lt;/p&gt;&lt;p&gt;我花了更長的時間才將前面兩個心得連結在一起，從而明白&lt;strong&gt;任何工具或方法的爭辯很可能都是偽命題，重點在於需求是什麼？要如何滿足需求？&lt;/strong&gt;如今回想起來，&lt;a href="http://www.ruanyifeng.com/blog/2011/10/dont_call_yourself_a_programmer.html"&gt;《不要自称为程序员》&lt;/a&gt;將這個觀念解析得相當清楚，相當值得一看。&lt;/p&gt;&lt;p&gt;舉例來說，「vim vs. emacs」是個偽命題，這取決於自己當下的環境為何。若團隊內多數人使用 emacs 且自己兩者都不熟，那麼 emacs 是較為合理的選擇。反之，若自己相當熟 vim 而團隊內多數人兩者都不熟，那繼續使用 vim 較為合理。重點在於&lt;strong&gt;「如何有效率地在自己的環境下解決問題&lt;/strong&gt;」，&lt;strong&gt;而非「一般而言，那一個編輯器比較強？」&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;再以軟體開發的方法來看，「&lt;a href="http://en.wikipedia.org/wiki/Agile_software_development"&gt;agile&lt;/a&gt; vs. 某個軟體開發方法」也是偽命題，不論 &lt;a href="http://en.wikipedia.org/wiki/Agile_software_development"&gt;agile&lt;/a&gt; 公認的定義為何，重點在於滿足需求，而滿足需求不見得需要一套完備的軟體開發方法；有完備的軟體開發方法不見得能滿足需求。要滿足需求有太多事要做，研讀相關技術、軟體開發、市場行銷等，軟體開發可能是滿足需求的其中一項基石，但不是全部。若滿足需求的前提需要改善軟體需求，自然需要改善它；反之則否。&lt;a href="http://www.csie.ntu.edu.tw/~p92005/Joel/fog0000000018.html"&gt;《Joel on Software - 別讓架構太空人嚇到你》&lt;/a&gt;對「開發軟體的方法 vs. 滿足需求」提了生動的描述。&lt;/p&gt;&lt;p&gt;舉另一個具體例子，「是否需要重構？」往往帶來許多爭議性的討論，各方人馬（PM、RD、QA、...) 對此有不同看法。若這段程式一直都不需要加新功能，那的確不需要重構。重構只會花費時間讓程式碼變漂亮，對於滿足需求沒有任何影響。反之，之後需要繼續大幅加功能，逐步重構部份功能，則對完成產品（滿足需求）大有幫助。&lt;/p&gt;&lt;p&gt;最近幾年有一個很紅的議題，開發網站是用 &lt;a href="http://rubyonrails.org/"&gt;Ruby on Rails&lt;/a&gt; 好，還是用 ... 好。最近一年可能還會多一些人問是否要改用基於 &lt;a href="http://nodejs.org/"&gt;node.js&lt;/a&gt; 的新 framework。要回答這問題得先看需求為何，若只是做幾頁的小網站，用什麼方法差異都不大；若是做長期維護的大網站，要看目前團隊成員熟悉的工具和程式語言為何，再來評估使用 Rails 的相對成本。若再涉及和後端整合，又和既有的 code base 有大幅關聯。而要回答這一切一切衍生的議題，還是得先看：究竟需求為何，基於什麼原因而採用 X 會更好？以 &lt;a href="http://justin.tv"&gt;Justin.tv&lt;/a&gt; 為例，&lt;a href="http://www.quora.com/Django/Why-is-Justin-tv-porting-their-codebase-to-Django-from-RoR"&gt;《Django: Why is Justin.tv porting their codebase to Django from RoR?》&lt;/a&gt;說明 Justin.tv 轉換的主因是全部程式都是用 Python 寫的，此外，他們也想藉機重新設計一遍架構，去除 legacy codes，以符合現今的使用需求。&lt;/p&gt;&lt;p&gt;舉這些例子的用意不是無限上綱地說工具和方法都不重要，而是強調將焦點放在如何滿足需求，若有需要選用好工具，才有必要討論它。&lt;strong&gt;問錯問題的話，永遠不會得到有用的答案。&lt;/strong&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-7708031988053755110?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/7708031988053755110/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2012/02/blog-post.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/7708031988053755110'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/7708031988053755110'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2012/02/blog-post.html' title='專注於滿足需求而非工具或方法'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-5179517793212774990</id><published>2012-01-19T00:22:00.013+08:00</published><updated>2012-01-29T16:09:27.108+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Software Engineering'/><title type='text'>在 Linux 下開發 C/C++ 的新手指南</title><content type='html'>&lt;p&gt;新加入一個專案，最先面對的課題是如何正確地編譯和執行專案，可從 &lt;a href="http://nebulousarion.wordpress.com/2008/02/23/top-20-replies-by-programmers-when-their-programs-do-not-work/"&gt;"It works on my machine" &lt;/a&gt; 如此地風行，印證這件事的困難性；再來則是閱讀負責工作相關的程式碼。至於發揮程式語言的特性，運用高階設計模式等，都是另開新專案或熟悉狀況後才有機會發揮。 &lt;/p&gt;&lt;p&gt;過去數年沉浸在愉快的 scripting language 和開發新專案中，一直沒踏入這殘酷的世界。這篇記錄在這樣的情境下，可能需要的技能，結算一下這一個多月的心得，全都是血淚談啊 ...。&lt;/p&gt;&lt;h4&gt;系統工具&lt;/h4&gt;&lt;p&gt;熟悉作業系統的安裝套件是首要之務，這樣才知道如何補足需要的 header、library，或是安裝含 debug symbol 版的函式庫以執行 gdb 觀察程式或除錯。參見&lt;a href="http://fcamel-life.blogspot.com/2012/01/debug-symbol-package.html"&gt;《自行編譯含 debug symbol 的套件 (package)》&lt;/a&gt;了解 Ubuntu/Debian 下的套件命名規則。 &lt;/p&gt;&lt;p&gt;在未安裝套件的情況下，可用&lt;/p&gt;&lt;ul&gt;&lt;li&gt; aptitude search SUBSTRING # 找套件&lt;/li&gt;&lt;li&gt; aptitude show PACKAGE # 顯示套件用途&lt;/li&gt;&lt;li&gt; apt-file search X # 找出 X 包在那個套件裡，找 header 時很有用。&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;注意在用 apt-file 前要先跑 sudo apt-file update，不然搜不出東西來。 &lt;/p&gt;&lt;p&gt;對於已安裝套件，可用&lt;/p&gt;&lt;ul&gt;&lt;li&gt; dpkg &lt;span class="escaped"&gt;-&lt;/span&gt;-search SUBSTRING # 找出安裝在那個套件，已知 header 時，適合用來找 library&lt;/li&gt;&lt;li&gt; dpkg -L PACKAGE # 列出套件內容，可用來找 header、library&lt;/li&gt;&lt;li&gt; locate SUBSTRING # 我比較常用它找 header 的位置，再觀看 header 內容&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;執行 locate 前記得先執行 sudo updatedb，原因同 apt-file。 &lt;/p&gt;&lt;p&gt;其它參考資料: &lt;a href="http://www.thegeekstuff.com/2009/10/debian-ubuntu-install-upgrade-remove-packages-using-apt-get-apt-cache-apt-file-dpkg/"&gt;How To Manage Packages Using apt-get, apt-cache, apt-file and dpkg Commands ( With 13 Practical Examples )&lt;/a&gt;&lt;/p&gt;&lt;h4&gt;編譯&lt;/h4&gt;&lt;ul&gt;&lt;li&gt; 參考&lt;a href="http://fcamel-life.blogspot.com/2011/12/undefined-symbol-reference.html"&gt;《解決 undefined symbol / reference》&lt;/a&gt;了解整個編譯的流程，先有觀念才清楚問題的環節，才能選對工具檢查問題。&lt;/li&gt;&lt;li&gt; 另在&lt;a href="http://fcamel-life.blogspot.com/2012/01/c-c.html"&gt;《從 C 呼叫 C++ 函式的過程理解程式編譯、連結的原理》&lt;/a&gt;以一個小個案，從另一個角度描述編譯的流程來除錯。&lt;/li&gt;&lt;li&gt; &lt;a href="http://fcamel-life.blogspot.com/2012/01/debug-info-optimization.html"&gt;《debug info 和 optimization》&lt;/a&gt;提到 -O 和 -g 可同時用，以及注意事項。&lt;/li&gt;&lt;li&gt; &lt;a href="http://fcamel-life.blogspot.com/2012/01/man-page.html"&gt;《讀懂函式庫的 man page》&lt;/a&gt; 說明使用系統函式庫時，如何從 man page 得知該定義的 feature test macro 和連結用的參數。&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;連結&lt;/h4&gt;&lt;p&gt;這一塊讓我卡了一陣子。一些粗淺心得:&lt;/p&gt;&lt;ul&gt;&lt;li&gt; &lt;a href="http://fcamel-life.blogspot.com/2011/12/list-shared-library.html"&gt;《列出用到的 shared library》&lt;/a&gt;。&lt;/li&gt;&lt;li&gt; &lt;a href="http://fcamel-life.blogspot.com/2011/12/linking-time.html"&gt;《加速 linking time》&lt;/a&gt;&lt;/li&gt;&lt;li&gt; &lt;a href="http://fcamel-life.blogspot.com/2012/01/ld-ldso-ldconfig.html"&gt;《ld, ld.so 和 ldconfig 的行為》&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;執行&lt;/h4&gt;&lt;p&gt;光只是讀程式碼就像大海撈針一樣，不太有效率。可從動態執行過程找出主要執行的路徑，再專注相關的程式碼。 &lt;/p&gt;&lt;p&gt;1. strace 和 ltrace &lt;/p&gt;&lt;p&gt;srace 是分析執行行為的強大工具，google 一下會看到很多別人的個案心得，看看再自己試一試，很快能上手，不知能發揮它多少功能。這裡列自己用的兩個小案例:&lt;/p&gt;&lt;ul&gt;&lt;li&gt; &lt;a href="http://fcamel-life.blogspot.com/2011/12/stracedebugger.html"&gt;《善用 strace、debugger 從執行期間找出問題根源》&lt;/a&gt;&lt;/li&gt;&lt;li&gt; &lt;a href="http://fcamel-life.blogspot.com/2012/01/blog-post.html"&gt;《熟悉系統工具好處多多》&lt;/a&gt;&lt;/li&gt;&lt;li&gt; &lt;a href="http://fcamel-life.blogspot.com/2012/01/strace-ubuntu.html"&gt;《用 strace 找出 Ubuntu 如何提示未安裝的指令》&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;反而是 ltrace 一直都想不到使用它的時機，也沒找到好的個案心得文。 &lt;/p&gt;&lt;p&gt;2. gdb &lt;/p&gt;&lt;p&gt;gdb 的重要性不需多說明，之前的幾則心得:&lt;/p&gt;&lt;ul&gt;&lt;li&gt; &lt;a href="http://fcamel-life.blogspot.com/2012/01/gdb.html"&gt;《gdb 初步心得》&lt;/a&gt;&lt;/li&gt;&lt;li&gt; &lt;a href="http://fcamel-life.blogspot.com/2011/12/core-dump-cgdb.html"&gt;《打開 core dump 和使用 cgdb 檢查程式掛點原因》&lt;/a&gt;&lt;/li&gt;&lt;li&gt; &lt;a href="http://fcamel-life.blogspot.com/2012/01/glibc.html"&gt;《追踪 glibc 裡的程式》&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;強烈建議使用 &lt;a href="http://cgdb.sourceforge.net/"&gt;cgdb&lt;/a&gt;，簡易安裝 + 無痛上手，瞬間省下大量操作和讀碼的時間。 &lt;/p&gt;&lt;p&gt;3. 打開除錯功能 &lt;/p&gt;&lt;p&gt;依照開發者的習性，一定會留後門讓自己方便除錯，從這角度下手也可省下不少時間:&lt;/p&gt;&lt;ul&gt;&lt;li&gt; &lt;a href="http://fcamel-life.blogspot.com/2011/12/cc-debug.html"&gt;《C/C++ 檢查和打開 debug 功能的小技巧》&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;4. 載入函式庫&lt;/p&gt;&lt;ul&gt;&lt;li&gt; 若在編譯、連結時無法解決相依問題，可考慮偷吃步&lt;a href="http://fcamel-life.blogspot.com/2011/09/ldpreload.html"&gt;在載入程式時用 LD_PRELOAD 換掉部份函式&lt;/a&gt;。&lt;/li&gt;&lt;li&gt; 另外備忘用 LD_LIBRARY_PATH 補充載入 shared library 的位置，目前仍沒用過它。&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;除以上所言外，我另外有找過畫出程式流程的靜態和動態分析工具，像是畫 call graph 或是 C 的 cflow。不過 C++ 的靜態分析效果很糟，就沒花太多時間研究。目前用 strace 和 gdb 覺得已夠用了，不知用工具產生 call graph、class 相依圖或其它東西，是否會更有幫助。待有需求看整體的程式時再來試試。&lt;br /&gt;&lt;/p&gt;&lt;h4&gt;閱讀程式碼&lt;/h4&gt;&lt;p&gt;聽了大家的建議後，做了一些實際操作，而有些心得:&lt;/p&gt;&lt;ul&gt;&lt;li&gt; &lt;a href="http://fcamel-life.blogspot.com/2011/12/cc.html"&gt;《閱讀 C/C++ 原始碼的好幫手》&lt;/a&gt;&lt;/li&gt;&lt;li&gt; &lt;a href="http://fcamel-life.blogspot.com/2011/12/eclipse-cdt-cc.html"&gt;《用 Eclipse CDT 讀 C/C++ 原始碼》&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Eclipse CDT 雖然方便，後來我還是用 &lt;a href="https://github.com/fcamel/configs/blob/master/bin/gj"&gt;gj&lt;/a&gt; 居多。原因有幾點:&lt;/p&gt;&lt;ul&gt;&lt;li&gt; 我已很習慣用 vim + screen 做事，&lt;a href="https://github.com/fcamel/configs/blob/master/bin/gj"&gt;gj&lt;/a&gt; 最合這個情境&lt;/li&gt;&lt;li&gt; &lt;a href="http://www.gnu.org/s/idutils/"&gt;id-utils&lt;/a&gt; 真的是超級快&lt;/li&gt;&lt;li&gt; 我針對自己的需求更新 &lt;a href="https://github.com/fcamel/configs/blob/master/bin/gj"&gt;gj&lt;/a&gt; 多次，愈用愈順手&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;另外 &lt;a href="http://betterthangrep.com/"&gt;ack&lt;/a&gt; 也滿方便的，懶得建 index 或是想比對子字串時，可直接使用。當然 &lt;a href="http://www.gnu.org/s/idutils/"&gt;id-utils&lt;/a&gt; 也支援子字串比對，只是暫時懶得為此修改 &lt;a href="https://github.com/fcamel/configs/blob/master/bin/gj"&gt;gj&lt;/a&gt; 的程式，目前大部份需求是找完整的 symbol。 &lt;/p&gt;&lt;h4&gt;熟悉 Linux 系統程式&lt;/h4&gt;&lt;p&gt;在基本工具都上手後，打算每天抽一點時間加減讀一點相關知識。一兩年下來應該會有不錯的成果。目前打算讀&lt;a href="http://man7.org/tlpi/"&gt;《The Linux Programming Interface》&lt;/a&gt;，年假時試看看效果如何。 &lt;/p&gt;&lt;p&gt;這一個月的心得以了解 /proc 為主，對觀察 CPU 用量、RAM 用量、載入那些函式庫、multi-thread、程式執行狀態等都很有幫助:&lt;/p&gt;&lt;ul&gt;&lt;li&gt; &lt;a href="http://fcamel-life.blogspot.com/2012/01/procpidtask-multi-thread-status.html"&gt;《透過 /proc/PID/task/ 觀察 multi-thread 狀態》&lt;/a&gt;&lt;/li&gt;&lt;li&gt; &lt;a href="http://fcamel-life.blogspot.com/2011/12/list-shared-library.html"&gt;《列出用到的 shared library》&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;結論&lt;/h4&gt;&lt;p&gt;即使大概知道有那些東西，還是需要實際動手的經驗，才會真的學進去。一個月下來進步了不少，不過對於要面對的戰役，還有一大段路要趕上，還有很多很多要學的。&lt;/p&gt;&lt;h4&gt;2012-01-29 更新&lt;/h4&gt;&lt;p&gt;補上一些後來新寫的連結。此外，&lt;a href="http://man7.org/tlpi/"&gt;《The Linux Programming Interface》&lt;/a&gt; 相當實用，讀 ch1 ~ 3 讓我補足不少基礎知識。ch41、42 講解 shared library 也相當值得一看。&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-5179517793212774990?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/5179517793212774990/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2012/01/linux-cc.html#comment-form' title='2 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/5179517793212774990'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/5179517793212774990'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2012/01/linux-cc.html' title='在 Linux 下開發 C/C++ 的新手指南'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-3265270423495120488</id><published>2011-11-23T14:49:00.001+08:00</published><updated>2011-11-23T14:59:02.108+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Web'/><category scheme='http://www.blogger.com/atom/ns#' term='Django'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Database'/><title type='text'>使用 Django 的雜感</title><content type='html'>&lt;p&gt;換工作後大概很少會用到 &lt;a href="https://www.djangoproject.com/"&gt;Django&lt;/a&gt;, 在這對它做個了結吧。&lt;/p&gt;&lt;h4&gt;先說結論&lt;/h4&gt;&lt;p&gt;對於不熟 web 又想用 python 開發的人來說, &lt;a href="https://www.djangoproject.com/"&gt;Django&lt;/a&gt; 仍是首選, 主要的優勢有:&lt;/p&gt;&lt;ol&gt;&lt;li&gt; 官方文件超級豐富, 網路上文件也豐富, 也有出一些書, 不過書上內容應該沒官網新, 看官網就夠了。不夠的話再看原始碼也比較方便。&lt;/li&gt;&lt;li&gt; 社群龐大, 有許多 middleware 和 plugin 可用, 像多人合寫 web 一定會需要 database integration, &lt;a href="http://south.aeracode.org/"&gt;South&lt;/a&gt; 相當好用。其它像用 Facebook / Google / Yahoo 等帳號登入, 也都有整合好的套件可用。&lt;/li&gt;&lt;li&gt; 框架本身實作了許多 web 相關功能 (像 session、cookie、cache、傳輸 zip 後的內容), 可避免犯錯 (像是有擋 &lt;a href="http://en.wikipedia.org/wiki/Cross-site_request_forgery"&gt;CSRF&lt;/a&gt;), 很多東西是新手從來沒想過的, 但框架都有考慮到並且實作好了。在使用的過程中可學到這些知識。&lt;/li&gt;&lt;li&gt; 極佳的向下相容。官方的 roadmap 會明確提到那些功能已 deprecated, 並會在未來的那一版移掉, 有滿長的過渡期。使用者升級的負擔相當小。&lt;/li&gt;&lt;li&gt; 提供一組規範, 切開 model、view、control, 還有 class / table 命名規則等, 減少團隊合作的問題。&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;當初選 &lt;a href="https://www.djangoproject.com/"&gt;Django&lt;/a&gt; 的主因是第一、二點, 第三點稍微有想到, 而第四、五點是使用後得到的驚喜。&lt;/p&gt;&lt;p&gt;說完結論後, 要開始碎碎念我的不滿, 請各位看倌記得, &lt;strong&gt;即使如此, 我還是推薦不熟 web 又想用 python 開發的人使用 &lt;a href="https://www.djangoproject.com/"&gt;Django&lt;/a&gt;&lt;/strong&gt;, 理由如前所述。&lt;/p&gt;&lt;h4&gt;關於 template&lt;/h4&gt;&lt;p&gt;&lt;a href="https://www.djangoproject.com/"&gt;Django&lt;/a&gt; 內建的 template 不好用, 效率也差, 甚至在 FAQ 裡有一項提到&lt;a href="https://docs.djangoproject.com/en/dev/faq/usage/#i-can-t-stand-your-template-language-do-i-have-to-use-it"&gt;「I can't stand your template language. Do I have to use it?」&lt;/a&gt;。有些人可能覺得 template 的效率不是重點, 瓶頸會在 database。當我費盡心力減少 SQL、改 schema、改index、改寫 SQL 將 database 花的時間壓到極致後, 卻發現 template 怎麼縮都要 0.1s, 讓我很無力。更別提在關掉 i18n / l10n 前, template 要 0.2s。看著簡單的 template 內容, 很難理解為什麼這樣的東西要花到 0.1s。&lt;/p&gt;&lt;p&gt;此外, template 的語法很受限, 不過到 1.3 版後 &lt;a href="https://docs.djangoproject.com/en/dev/ref/templates/builtins/?from=olddocs#include"&gt;include 多了 with&lt;/a&gt; 的語法方便代入子版面後, 變得好用許多, 不然有類似這種簡單需求時要另寫 templatetag, 實在是多餘又不易懂。相關心得見&lt;a href="http://fcamel-life.blogspot.com/2010/05/django-template.html"&gt;之前寫的文章&lt;/a&gt;。實在是換掉也為難, 不換也為難。&lt;/p&gt;&lt;h4&gt;關於 ORM&lt;/h4&gt;&lt;p&gt;先來個免責聲明, &lt;strong&gt;若是需要頻繁地寫多個不同的小型網站, 用 ORM 是利多於弊, 可減少重覆的程式碼。&lt;/strong&gt;以下的論點基於我的個人經驗, 需求是長期維護一個資料量大且有嚴苛速度需求的網站。&lt;/p&gt;&lt;p&gt;之前已寫過幾篇 ORM 心得, 在&lt;a href="http://fcamel-life.blogspot.com/2010/06/django-python-database.html"&gt;《Django 和 Python 操作 database 時的額外負擔》&lt;/a&gt;提到實測大量數據的情況有多慢, 注意我的使用情境有超過百萬筆資料, 若資料量沒那麼大, 這個負擔較無所謂。&lt;/p&gt;&lt;p&gt;在&lt;a href="http://fcamel-fc.blogspot.com/2011/01/blog-post.html"&gt;《撰寫資料庫相關程式的心得》&lt;/a&gt;有提到使用 ORM 的成本。這裡要補充的是, &lt;strong&gt;無論如何, 我們都需要分開「資料庫的操作」和「邏輯操作」。而在程式裡直接使用 ORM 並沒有隔離好兩者。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;舉例來說, 在頁面裡使用 &lt;tt&gt;get_user("fcamel")&lt;/tt&gt; 會比 &lt;tt&gt;User.objects.get(name="fcamel")&lt;/tt&gt; 來得好。想想要如何針對這個頁面寫 unit test, 會發覺後者仍和 database 有很大的相依性, 不容易 mock。或著換個說法, &lt;tt&gt;get_user()&lt;/tt&gt; 的抽象程度比直接使用 ORM 高。&lt;tt&gt;get_user()&lt;/tt&gt; 裡是使用 ORM 還是下 raw SQL, 都和使用者無關。當需要連續幾個 ORM 操作以達成一個目的時, 另外包函式的優勢會更明顯。&lt;/p&gt;&lt;p&gt;若能接受上面的論點的話, 會發覺 ORM 的優勢又少了一點。所以, 我個人的看法是: &lt;strong&gt;重點是必須另外包一層 API 存取資料, 供邏輯操作使用。所以, 對前端開發者來說, 是否使用 ORM, 影響不大。但對後端開發者來說, ORM 的缺點遠大於優點。&lt;/strong&gt;ORM 最吸引人的地方是提供不錯介面隔離 database, 可以有彈性地存取資料並且不用太了解如何寫 SQL。但隨著使用經驗漸增, 會發覺這些優點並非事實。但若一直不去了解 database, 不會發覺付出的隱性成本。&lt;/p&gt;&lt;p&gt;題外話, 聽說 &lt;a href="http://www.sqlalchemy.org/"&gt;SQLAlchemy&lt;/a&gt; 相當強大, 不知實際用起來效果如何。我後來淡化使用 ORM 的場合, 加上需要使用 &lt;a href="http://south.aeracode.org/"&gt;South&lt;/a&gt; (基於 Django ORM 的 plugin), 不方便換掉, 就沒有研究 &lt;a href="http://www.sqlalchemy.org/"&gt;SQLAlchemy&lt;/a&gt; 了。&lt;/p&gt;&lt;h4&gt;關於 test&lt;/h4&gt;&lt;p&gt;內建的 &lt;a href="https://docs.djangoproject.com/en/dev/topics/testing/"&gt;django.test&lt;/a&gt; 相關模組不太好用, 而且執行 test 要先重建全部 table, 然後在每個 test case 前 truncate 全部 table, 效率不好。若能指定只 truncate 需要的 table, 可省下許多時間。通常 test case 是愈寫愈多, 實務上執行 test case 的效率相當重要。&lt;/p&gt;&lt;p&gt;使用 &lt;a href="http://south.aeracode.org/"&gt;South&lt;/a&gt; 後更會有 production schema 和 test schema 不同的問題, 因為 production 用 &lt;a href="http://south.aeracode.org/"&gt;South&lt;/a&gt; 建 schema, 中間可能用到客制化的 SQL 改 schema (如建 multiple column indexes), 但 &lt;a href="https://docs.djangoproject.com/en/dev/topics/testing/"&gt;django.test&lt;/a&gt; 不會呼叫 &lt;a href="http://south.aeracode.org/"&gt;South&lt;/a&gt;, 而是用 &lt;a href="https://www.djangoproject.com/"&gt;Django&lt;/a&gt; 原本讀 models.py 建 schema 的方式。我後來改用自己寫的模組來建 test database, 沒研究後續發展, 不知後來是否有修正。&lt;/p&gt;&lt;h4&gt;關於 coding style&lt;/h4&gt;&lt;p&gt;Django 違反許多 &lt;a href="http://www.python.org/dev/peps/pep-0008/"&gt;PEP 8&lt;/a&gt; 的規則或 Python 精神, 像是:&lt;/p&gt;&lt;ul&gt;&lt;li&gt; 在程式碼裡面 import 別的 module (而非開頭), 並且有 circular import。&lt;/li&gt;&lt;li&gt; 有多種方法做一件事。我很討厭這點, 像自訂 login 的重導頁面卻沒成功, 除錯時很麻煩, 要搞清楚多種規則的執行順序, 才知道問題出在那。&lt;/li&gt;&lt;li&gt; 有許多 lazy initialization。我不確定這是否違反 Python 社群的 "explicit is better than implicit", 我個人不喜歡一堆 lazy initialization, 很難掌握程式的行為。&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;以上這些事對使用者有什麼影響? 當行為不合預期, 文件也看不出所以然時, 讀原始碼並加 log message 是滿有效率的除錯方法。上述都是我在研究功能 (像是如何使用 cache) 或除錯時, 讀原始碼遇到的困擾。&lt;/p&gt;&lt;h4&gt;結語&lt;/h4&gt;&lt;p&gt;除前面一再強調的「結論」外, 再多強調一下, 一個東西愈多人罵, 表示愈多人用, 出事也愈好處理。本篇不是建議別用 &lt;a href="https://www.djangoproject.com/"&gt;Django&lt;/a&gt;, 也不是反串推廣, 只是之前一年多的使用心得。&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-3265270423495120488?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/3265270423495120488/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2011/11/django.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/3265270423495120488'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/3265270423495120488'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2011/11/django.html' title='使用 Django 的雜感'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-4078579160651563775</id><published>2011-09-18T11:39:00.001+08:00</published><updated>2011-09-25T21:06:41.677+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Thinking'/><title type='text'>Problem Solving 的技巧 (3)：因事制宜</title><content type='html'>&lt;p&gt;不知不覺，這類文章可以寫到第三篇，感到頗意外的。前兩篇是：&lt;/p&gt;&lt;ul&gt;&lt;li&gt; &lt;a href="http://fcamel-fc.blogspot.com/2010/11/problem-solving.html"&gt;《Problem Solving 的技巧 (1)：系統設計是一連串的取捨》&lt;/a&gt;&lt;/li&gt;&lt;li&gt; &lt;a href="http://fcamel-fc.blogspot.com/2011/04/problem-solving-2_6909.html"&gt;《Problem Solving 的技巧 (2)：別把解法當作問題定義》&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;昨天看到 TonyQ 在 Soft_Job 寫的文章：&lt;a href="http://www.ptt.cc/bbs/Soft_Job/M.1316191715.A.323.html"&gt;《Re: [閒聊] 你在開發程式時，是重視績效還是品質&lt;/a&gt;。覺得深有同感，摘錄幾段如下：&lt;/p&gt;&lt;blockquote&gt;對我們來講很多其實可以是 nice to have 的東西，都會被我們當成 must have。&lt;br /&gt;&lt;br /&gt;這個判斷是經驗跟 domain 累積下來的，沒有公式，沒有法則，做久了你就是會知道什麼架構之後會一直噴 exception ，&lt;br /&gt;&lt;br /&gt;而且你還會知道等他出事時你一定沒辦法好好處理，所以你要在這個當下把它處理掉。&lt;br /&gt;&lt;br /&gt;...&lt;br /&gt;&lt;br /&gt;觀察他們影響到哪些地方，你有沒有能力測試他們，還有你的環境允不允許這件事情帶來的不穩定性。&lt;br /&gt;&lt;br /&gt;一般來講，如果是我個人自己的專案，我非常不介意大改，只要我後續還有時間可以處理這些出來的問題。&lt;br /&gt;&lt;br /&gt;對公司或者客戶的專案，我會採取相對保守的態度。&lt;br /&gt;&lt;br /&gt;&lt;b&gt;這是對風險管理的策略問題。&lt;/b&gt;&lt;br /&gt;&lt;/blockquote&gt;&lt;p&gt;在 Plurk 上貼了&lt;a href="http://www.plurk.com/p/e0nbzi"&gt;這篇文章&lt;/a&gt;後，&lt;a href="http://www.plurk.com/Thinker"&gt;Thinker&lt;/a&gt; 和 &lt;a href="http://www.plurk.com/qrtt1"&gt;qrtt1&lt;/a&gt; 提出另一面看法，強調&lt;strong&gt;「早期發現，早期治療」&lt;/strong&gt;的好處。於是決定借題寫一下自己的看法。&lt;/p&gt;&lt;p&gt;在討論這個議題前，我想先強調，為了方便聚焦討論，我們往往會先偋除一些條件，或是先依自己的假設開始論述。&lt;strong&gt;而有爭論的地方，往往卻是這些隱藏的前提。&lt;/strong&gt;好比說&lt;a href="http://fcamel-fc.blogspot.com/2009/06/unit-test.html"&gt;「寫測試碼重不重要？」&lt;/a&gt;單單這樣一個命題，只能討論出很模糊的概念，不論支持與否，看起來都有些道理。但加入一些條件後，像是「這是長期開發並有多人參與的專案」或是「後天要交的雛型」，相信大家對此會有不同的答案，也會少一些分歧。相關的想法，可以參考&lt;a href="http://www.books.com.tw/exep/prod/booksfile.php?item=0010399930"&gt;《黑天鵝效應》&lt;/a&gt;。 &lt;a href="http://hoamon.blogspot.com/2009/07/1000-1001-999-1001-2009-1-1-5-2008-12.html"&gt;這篇&lt;/a&gt;有摘要讀書心得，其中敘事謬誤和戲局謬誤啟發我這個觀點。&lt;/p&gt;&lt;p&gt;我的想法是，在達到必要需求的前提下，依個人以及團隊的能力，看看能提昇多少品質。問題在於，何謂 must have、何謂 nice to have，每個人的見解不同。&lt;strong&gt;爭議點在於，每個人的能力不同，導致評估的實作成本不同。&lt;/strong&gt;一樣是 nice to have，有些被認同可以做，有些則否。&lt;/p&gt;&lt;p&gt;( ps. 這篇文章不討論規格不明確的問題，這是另一個大議題。 )&lt;/p&gt;&lt;p&gt;舉例來說，A 認為現在重構只能提升一點品質，卻要花兩天；B 認同 A 評估的品質，但 B 覺得只要花半天。所以 A 覺得不划算，而 B 持相反意見。從這樣的評估結果很難說 A 或 B 誰對誰錯，也許 A 沒重構經驗不信任重構，也可能 A 有豐富的經驗，估得比 B 準。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;在體認到個人能力不同、&lt;a href="http://fcamel-fc.blogspot.com/2010/05/blog-post.html"&gt;習慣&lt;/a&gt;不同的前提下，對自己的要求是盡量提高自己的能力，多付出時間提高品質，&lt;/strong&gt;減少日後維護成本並能練功，形成&lt;a href="http://chingyichan.wordpress.com/2010/04/26/cycle/"&gt;正向循環&lt;/a&gt;；&lt;strong&gt;對其他人來說，看對方是那種人，是「過」或「不及」，再從另一個角度和對方討論。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;舉例來說，若團隊成員不熟或不認同&lt;a href="http://fcamel-fc.blogspot.com/search/label/Testing"&gt; unit test&lt;/a&gt;。當下就要出貨了，這時硬要大家學習並實作測試，並不適合 (不如當「負面教材」，待下次專案的開頭提出討論)。但若成員有過相關經驗，同樣時程下，就能針對核心程式補些測試，花最小成本減少最多風險。當成員嘗到測試甜頭，不小心寫過多測試碼時，和對方討論優先順序，減少寫 C/P 值低的測試碼。&lt;/p&gt;&lt;p&gt;回頭看品質的問題，能力愈強、經驗愈豐富，增加 nice to have 的成本愈低，自然能前期處理，減少&lt;a href="http://en.wikipedia.org/wiki/Technical_debt"&gt;技術債&lt;/a&gt;。反之，在時程的考量下 (ship or die)，能力和經驗不夠時，得選擇先放過一些潛在問題，日後仍有需求時，再花更多成本補救。先活下去，才有更多的本錢來還債。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;因事制宜說來簡單，只有四個字而已。實務上卻需經年累月的經驗，&lt;/strong&gt;不止要考量時程、未來需求變化等項目，也要留意每個人的能力和習性，才能在當下找到較為適當的平衡點。而增加經驗的方法，如同杜書伍在&lt;a href="http://www.books.com.tw/exep/prod/booksfile.php?item=0010449478"&gt;《打造將才基因》&lt;/a&gt;裡所言，除了比別人投入更多時間外，沒有其它的捷徑。&lt;/p&gt;&lt;p&gt;&lt;b&gt;2011-09-25 更新&lt;/b&gt;&lt;/p&gt;&lt;p&gt;看到 Thinker 提了他的相關看法：&lt;a href="http://www.codemud.net/~thinker/GinGin_CGI.py/show_id_doc/457"&gt;《程式碼要清的多乾淨?》&lt;/a&gt;，裡面提了不錯的建議：&lt;blockquote&gt; 除了能力不同之外，個人的膽量、積極態度和價值觀也同樣影嚮著評估的差異。 我強調的是，積極態度和個人的能力呈正相關。&lt;br /&gt;...&lt;br /&gt;&lt;b&gt;要評估自己的能力，並適度的承受犯錯的風險。&lt;/b&gt;&lt;/blockquote&gt;勇於嘗試、有控制性地犯錯，對於學習很有幫助。我覺得在我學習的前一大段生涯裡，過於謹慎而不敢犯錯，以致於學習速度較為緩慢。而我認識一些晚起步、卻成長相當快的朋友，都具有大膽嘗試、不斷犯錯的特質。他們快速地累積經驗，並培養出更多的膽識和更大的企圖心。&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-4078579160651563775?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/4078579160651563775/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2011/09/problem-solving-3.html#comment-form' title='3 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/4078579160651563775'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/4078579160651563775'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2011/09/problem-solving-3.html' title='Problem Solving 的技巧 (3)：因事制宜'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-7468217328447299144</id><published>2011-08-07T12:41:00.005+08:00</published><updated>2012-01-26T00:15:41.964+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Python'/><title type='text'>Python 的特別之處 (1)</title><content type='html'>&lt;p&gt;從新手的眼中來看 Python，比較能看出 Python 和其它語言不同之處。最近有機會幫別人快速上手 Python，就順便整理一下我從中發覺 Python 較為突出的優點。 &lt;/p&gt;&lt;h4&gt;list、dictionary and string&lt;/h4&gt;&lt;p&gt;平時 coding 最常用到的 container 就是 list 和 dictionary，另外也會常用到字串操作，Python 提供方便的方法來操作它們。string 可看成一個有實作 list 介面的類別，一些常用操作像是 slice："abcd"[1:3] 回傳 "bc"；負數的索引： "abcd"[-1] 回傳 "d"；直接和 for-loop 整合在一起：&lt;/p&gt;&lt;pre class="prettyprint"&gt;In [1]: for ch in "abcd":&lt;br /&gt;  ....:         print ch&lt;br /&gt;  ....:&lt;br /&gt;a&lt;br /&gt;b&lt;br /&gt;c&lt;br /&gt;d&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;讓存取這些常用資料型態輕鬆許多。 &lt;/p&gt;&lt;h4&gt;iterator&lt;/h4&gt;&lt;p&gt;使用 iterator 比傳統的 &lt;tt&gt;for (i=0; i&amp;lt;n; i++)&lt;/tt&gt; 來得清楚，Python 針對 iterator 下了不少工夫，提供好用的輔助函式，像是 enumerate 補足需要用到 index 的情況：&lt;/p&gt;&lt;pre class="prettyprint"&gt;In [2]: for i, n in enumerate([1, 3, 5]):&lt;br /&gt;  ....:     print i, n&lt;br /&gt;  ....:&lt;br /&gt;0 1&lt;br /&gt;1 3&lt;br /&gt;2 5&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;使用 zip 整合多個 list：&lt;/p&gt;&lt;pre class="prettyprint"&gt;In [3]: names = ["John", "Marry", "Tom"]&lt;br /&gt;In [4]: sexes = ["Male", "Female", "Male"]&lt;br /&gt;In [5]: for name, sex in zip(names, sexes):&lt;br /&gt;  ....:     print name, sex&lt;br /&gt;  ....:&lt;br /&gt;John Male&lt;br /&gt;Marry Female&lt;br /&gt;Tom Male&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;h4&gt;map, filter and reduce&lt;/h4&gt;&lt;p&gt;任何使用過 map 的人，都會喜歡 map 輕巧的用法，來看幾個例子：&lt;/p&gt;&lt;pre class="prettyprint"&gt;In [1]: map(int, ["12", "37", "999"])&lt;br /&gt;Out[1]: [12, 37, 999]&lt;br /&gt;In [2]: map(str, [12, 37, 999])&lt;br /&gt;Out[2]: ['12', '37', '999']&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;int 是一個函式，將傳入的物件轉成整數；str 則是轉成字串。使用 map 可以將一個 iterator 轉為另一種 list。 &lt;/p&gt;&lt;p&gt;另一個常見的情境是，從一個 list 裡篩選出需要的物件，比方說只留下偶數：&lt;/p&gt;&lt;pre class="prettyprint"&gt;In [1]: numbers = [1, 2, 3, 4, 5]&lt;br /&gt;In [2]: filter(lambda x: x % 2 == 0, numbers)&lt;br /&gt;Out[2]: [2, 4]&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;或像 &lt;tt&gt;filter(lambda s: s.endswith('.py'), file_names)&lt;/tt&gt; 只留下結尾為 ".py" 的字串。 &lt;/p&gt;&lt;p&gt;除 map 和 filter 的重心放在轉換 list 之外，reduce 則是將 list 匯整成一個物件。有了這些函式，就能任意的操作 list，用以匯整或擴散資料容器。 &lt;/p&gt;&lt;p&gt;比方說將一串數字加起來：&lt;/p&gt;&lt;pre class="prettyprint"&gt;In [1]: numbers = [1, 2, 3, 4, 5]&lt;br /&gt;In [2]: reduce(lambda x, y: x + y, numbers, 0)&lt;br /&gt;Out[2]: 15&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;上面這個例子可以用內建的 sum 取代，來看另一個複雜點的例子，將一串 0、1 值合成一個整數：&lt;/p&gt;&lt;pre class="prettyprint"&gt;In [1]: bits = [0, 1, 0, 0, 1]  # bits[i] 的值表示 2^i 的系數&lt;br /&gt;&lt;br /&gt;In [2]: reduce(lambda x, (i, b): x | (b &amp;lt;&amp;lt; i), enumerate(bits), 0)&lt;br /&gt;Out[2]: 18&lt;br /&gt;&lt;/pre&gt;&lt;h4&gt;list comprehension&lt;/h4&gt;&lt;p&gt;map 和 filter 雖然方便，要用到 lambda 或是混合使用時就沒那麼好讀了。Python 提供一個重量級的武器 &lt;a href="http://en.wikipedia.org/wiki/List_comprehension"&gt;list comprehension&lt;/a&gt; 來解決這問題。比方說留下偶數並乘以三再加一：&lt;/p&gt;&lt;pre class="prettyprint"&gt;In [1]: numbers = [1, 2, 3, 4, 5]&lt;br /&gt;&lt;br /&gt;In [2]: [n * 3 + 1 for n in numbers if n % 2 == 0]&lt;br /&gt;Out[2]: [7, 13]&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;綜合以上的語法，可以輕鬆地寫出易懂的 &lt;a href="http://en.wikipedia.org/wiki/Quicksort"&gt;quick sort&lt;/a&gt;：&lt;/p&gt;&lt;pre class="prettyprint"&gt;def qsort(numbers):&lt;br /&gt;    if len(numbers) &amp;lt;= 1:&lt;br /&gt;        return numbers                                                                                     &lt;br /&gt;    pivot = numbers[0]&lt;br /&gt;    rest = numbers[1:]&lt;br /&gt;    smaller = [n for n in rest if n &amp;lt;= pivot]&lt;br /&gt;    larger = [n for n in rest if n &amp;gt; pivot]&lt;br /&gt;    return qsort(smaller) + [pivot] + qsort(larger)&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;對於習慣 C、C++、Java 世界的人來說，應該不曾看過這麼直覺易懂的 &lt;a href="http://en.wikipedia.org/wiki/Quicksort"&gt;quick sort&lt;/a&gt; 吧。 &lt;/p&gt;&lt;h4&gt;tuple&lt;/h4&gt;&lt;p&gt;tuple 是一個很妙的資料結構，它和 list 的主要差別是它是唯讀的，Python 裡鮮少有這種唯讀物件。不過它較易發覺的好處是被用在 Python 的 &lt;a href="http://en.wikipedia.org/wiki/Assignment_(computer_science)#Parallel_assignment"&gt;parallel assignment&lt;/a&gt; 和函式傳回值。 &lt;/p&gt;&lt;p&gt;於是在 Python 裡可以這麼寫：&lt;/p&gt;&lt;pre class="prettyprint"&gt;a, b = b, a # swap&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Python 在看到 &lt;tt&gt;b, a&lt;/tt&gt; 時會產生一個 tuple 表示 (b, a)，再透過 tuple 達到 &lt;a href="http://en.wikipedia.org/wiki/Assignment_(computer_science)#Parallel_assignment"&gt;parallel assignment&lt;/a&gt;。 &lt;/p&gt;&lt;p&gt;函式也可以一次「傳回多個結果」：&lt;/p&gt;&lt;pre class="prettyprint"&gt;In [1]: def divide_and_mode(a, b):&lt;br /&gt;   ...:     if b == 0:&lt;br /&gt;   ...:         return None, None&lt;br /&gt;   ...:     return a / b, a % b&lt;br /&gt;   ...:&lt;br /&gt;&lt;br /&gt;In [2]: divide_and_mode(7, 3)&lt;br /&gt;Out[2]: (2, 1)&lt;br /&gt;&lt;br /&gt;In [3]: a, b = divide_and_mode(7, 3)&lt;br /&gt;&lt;br /&gt;In [4]: a&lt;br /&gt;Out[4]: 2&lt;br /&gt;&lt;br /&gt;In [5]: b&lt;br /&gt;Out[5]: 1&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;原理一樣是先轉成 tuple 再傳回，再視等號左側放什麼，決定要存成 tuple 或做 &lt;a href="http://en.wikipedia.org/wiki/Assignment_(computer_science)#Parallel_assignment"&gt;parallel assignment&lt;/a&gt;。 &lt;/p&gt;&lt;h4&gt;2012-01-25 更新&lt;/h4&gt;&lt;p&gt;應該沒什麼力氣更新續篇，在這裡簡短描述一下，有興趣的人可以找看看相關介紹。&lt;/p&gt;&lt;h4&gt;with&lt;/h4&gt;&lt;p&gt;在 Python 2.6 後，支援用 &lt;a href="http://effbot.org/zone/python-with-statement.htm"&gt;with&lt;/a&gt; 管理資源。像讀檔案可以用 with 的方式寫：&lt;/p&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;# 印出所有使用者的 id&lt;br /&gt;with open('/etc/passwd') as fr:&lt;br /&gt;    for line in fr:&lt;br /&gt;        print line.split(':')[0]  &lt;br /&gt;&lt;/pre&gt;&lt;p&gt;在進入 with 的 block 前，會呼叫 file object 的 &lt;tt&gt;__enter__&lt;/tt&gt; 以獲得 file descriptor；在離開 block 前會呼叫 &lt;tt&gt;__exit__&lt;/tt&gt; 關掉 file descriptor。即使中間呼叫了 &lt;tt&gt;sys.exit()&lt;/tt&gt; 或丟出 exception，仍會執行到 __exit__，不用擔心會漏關。方便用在許多情境 (比方說 lock / unlock、自動 flush output buffer)，易讀易用。&lt;/p&gt;&lt;h4&gt;內建常用函式庫&lt;/h4&gt;&lt;p&gt;除上述的基本資料結構和 string 外，還有 &lt;a href="http://docs.python.org/library/sqlite3.html"&gt;sqlite&lt;/a&gt;、&lt;a href="http://docs.python.org/library/json.html"&gt;json&lt;/a&gt;等。&lt;/p&gt;&lt;h4&gt;簡單不易出錯的語法&lt;/h4&gt;&lt;p&gt;舉幾個寫 C 可能發生的問題，但在 Python 的語法下則不會發生：&lt;/p&gt;&lt;pre class="prettyprint"&gt;if (condition);&lt;br /&gt;{&lt;br /&gt;    // BUG!! 這裡的程式一定會被執行&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;pre class="prettyprint"&gt;if (x &amp;lt; 60)&lt;br /&gt;    number_of_fail++;&lt;br /&gt;    total_fail_score += x; // BUG!! 這行每次都會執行&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;另外，由於 Python 的 condition 只能是 expression，不能是 assignment。不會有 &lt;tt&gt;if (x -= 3 &amp;lt; 0)&lt;/tt&gt; 這種 bug。&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-7468217328447299144?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/7468217328447299144/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2011/08/python-1.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/7468217328447299144'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/7468217328447299144'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2011/08/python-1.html' title='Python 的特別之處 (1)'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-4249578041653282701</id><published>2011-07-09T17:07:00.002+08:00</published><updated>2011-07-09T20:38:52.129+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Testing'/><title type='text'>使用 Mockito 更輕鬆地寫 unit test</title><content type='html'>&lt;p&gt;聽 &lt;a href="http://blog.vgod.tw/"&gt;vgod&lt;/a&gt; 提到可以用 &lt;a href="http://code.google.com/p/powermock/"&gt;PowerMock&lt;/a&gt; 來換掉 static、private、final 的方法，還有 &lt;a href="http://mockito.org/"&gt;Mockito&lt;/a&gt; 用起來很順手。於是大概看了一下相關文件:&lt;/p&gt;&lt;ul&gt;&lt;li&gt; &lt;a href="http://code.google.com/p/powermock/"&gt;PowerMock&lt;/a&gt; 的作法是換掉 classloader 和修改 bytecode，所以它可以跨越 Java 的限制 (像是無法換掉 final）。聽起來很威，感覺很危險，不確定是否夠穩定。應該不會想試它。&lt;/li&gt;&lt;li&gt; &lt;a href="http://mockito.org/"&gt;Mockito&lt;/a&gt; 承接 &lt;a href="http://easymock.org/"&gt;EasyMock&lt;/a&gt; 的思維，但是用起來更容易。雖說&lt;a href="http://code.google.com/p/mockito/wiki/FAQ#What_are_the_limitations_of_Mockito"&gt;它也無法換掉 static、private、final 等方法&lt;/a&gt;。 &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;我花了一些時間看了 &lt;a href="http://docs.mockito.googlecode.com/hg/org/mockito/Mockito.html"&gt;Mockito 範例&lt;/a&gt;和作者設計的思路，覺得很有意思。 &lt;a href="http://hamletdarcy.blogspot.com/2007/10/mocks-and-stubs-arent-spies.html"&gt;《behind the times: Mocks and Stubs aren't Spies》&lt;/a&gt; 提到在寫測試碼時，替換掉實際互動的元件，有四種不同層級的輔助元件: dummy、stub、&lt;strong&gt;spy&lt;/strong&gt;、mock：&lt;/p&gt;&lt;ul&gt;&lt;li&gt; dummy: 什麼事也不做。&lt;/li&gt;&lt;li&gt; stub: 依據輸入傳回物件，藉此控制後續的邏輯。(相對來說) 不在意被呼叫的方式，像是何時被呼叫、呼叫了幾次等。&lt;/li&gt;&lt;li&gt; spy: 用來確認該物件如何被使用。比方說呼叫 cursor 物件的 commit() 前有沒有先呼叫 execute()。&lt;/li&gt;&lt;li&gt; mock: 同stub + spy，既需要傳回物件供待測方法使用，也在意它如何被呼叫。&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;寫測試碼常遇到的困擾是：mock 太囉唆了。&lt;a href="http://hamletdarcy.blogspot.com/2008/03/mockito-new-mock-framework-on-block.html"&gt;這篇&lt;/a&gt;提供一個小例子對照 &lt;a href="http://mockito.org/"&gt;Mockito&lt;/a&gt; 和 &lt;a href="http://easymock.org/"&gt;EasyMock&lt;/a&gt; 的差異。&lt;a href="http://monkeyisland.pl/2008/07/12/should-i-worry-about-the-unexpected/"&gt;《should I worry about the unexpected?》&lt;/a&gt; 解釋 mock 之所以囉唆，是因為它管太多了，導致加新功能時，常常行為沒錯，卻無法通過舊的測試。使用 spy (&lt;a href="http://mockito.org/"&gt;Mockito&lt;/a&gt; 的主要功能) 就不會有這種困擾。大多情況我們並不在意是否每一個物件都要如預期般執行，只要關鍵的幾個步驟沒錯即可。 &lt;/p&gt;&lt;p&gt;之前會排斥使用 mock 的原因為：&lt;/p&gt;&lt;ol&gt;&lt;li&gt; 沒有和真正的物件互動，不夠踏實。&lt;/li&gt;&lt;li&gt; 寫起來很囉唆。&lt;/li&gt;&lt;li&gt; 囉唆就算了，稍微改改程式還很容易出錯。&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;第一點是 trade-off，有時候是不得不做的必要之惡。在看到 spy 的概念後，發覺它少了後兩項缺點。之後需要用到「mock」時，再來用 &lt;a href="http://mockito.org/"&gt;Mockito&lt;/a&gt; 看看。&lt;a href="http://mockito.org/"&gt;Mockito&lt;/a&gt; 另一個好處是，它有&lt;a href="http://code.google.com/p/mockito/wiki/MockitoForOtherLanguages"&gt;提供各種語言的版本&lt;/a&gt;，可以學一套語法走天下。&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-4249578041653282701?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/4249578041653282701/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2011/07/mockito-unit-test.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/4249578041653282701'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/4249578041653282701'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2011/07/mockito-unit-test.html' title='使用 Mockito 更輕鬆地寫 unit test'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-7591097571826902484</id><published>2011-06-02T22:18:00.012+08:00</published><updated>2011-07-09T17:34:21.766+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Wiki'/><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><title type='text'>ego-post!! 即時同步編寫 wiki code 和顯示 html 畫面</title><content type='html'>&lt;img alt="Ego" src="https://lh5.googleusercontent.com/-6BQoVrUnoiA/TeOYmrrv5pI/AAAAAAAABao/idgswdm_JJg/s400/Ego03.png" title="Ego" /&gt;&lt;br /&gt;&lt;br /&gt;對一個時常寫 blog 的人來說, 好的編輯介面相當重要。以前寫過 &lt;a href="http://fcamel-fc.blogspot.com/2009/04/blog-td-post.html"&gt;td-post&lt;/a&gt; 以節省重覆輸入同樣連結的時間。但 td-post 有個問題, 每次改完文章又要重執行一次。我常常修來修去, 用起來有點彆手。後來偷懶, 就改用 blogger 的編輯介面。&lt;br /&gt;&lt;br /&gt;但 blogger 的所見即所得並不太準, 有時會包含 div 有時不會; 有時會多空行; 複製網路上的文字時, 常會不小心連帶複製到文字的格式, 之後不方便修改。於是得切到 blogger 的 raw html 編輯介面修正這類問題, 再切回來繼續寫, 還挺麻煩的。&lt;br /&gt;&lt;br /&gt;去年 &lt;a href="http://traditionalchineseblog.scottt.tw/"&gt;Scott&lt;/a&gt; 和強者學弟 Will 幫高中學弟妹寫了個 &lt;a href="http://code.google.com/p/itrs-test3/"&gt;on-line judge&lt;/a&gt;。server 端用 Google App, client 端網頁裡嵌了 telnet client, 連到綁好的  local server (透過 QEMU 執行), 用來即時開發程式、編輯和提交程式。並有一個所見即所得的 wiki 編輯器, 用來編題庫。整個工程相當驚人 (或著該說是.......吃飽太閒)！&lt;br /&gt;&lt;br /&gt;整個專案有不少地方值得深入玩玩, 而我最有興趣的, 是所見即所得的 wiki 編輯器。畢竟, &lt;a href="http://fcamel-life.blogspot.com/2011/05/virtual-box-host-os-guest-os.html"&gt;用 putty 連到 VirtulBox 裡的 Ubuntu&lt;/a&gt; 開發還是方便許多。但 wiki 編輯器卻沒其它替代品。&lt;br /&gt;&lt;br /&gt;昨天晚上錯過早睡的時機, 自暴自棄地開始拼湊程式, 今天晚上再改一改就有個不錯的雛型。寫好的東西放在&lt;a href="http://code.google.com/p/ego-post/"&gt;這裡&lt;/a&gt;, 有興趣的人可以看看。如同大家所猜, 這篇文章是用 ego-post 打出來的。剛好前陣子&lt;a href="http://fcamel-daily.blogspot.com/2011/05/ratatouille.html"&gt;在看《料理鼠王》練英文&lt;/a&gt;, 就順便用 Ego 命名了。&lt;br /&gt;&lt;br /&gt;待做事項&lt;br /&gt;&lt;ul&gt;&lt;li&gt; auto save: 不小心按到 F5 可是很慘的, 寫這篇文章時我按了好幾次 Ctrl+A Ctrl+C。&lt;/li&gt;&lt;li&gt; 記錄用過的 link, 達到和 td-post 相同效果。&lt;/li&gt;&lt;li&gt; 提供新的語法用來貼程式碼。&lt;/li&gt;&lt;li&gt; 補寫使用說明。&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;2011-06-02 更新: 有圖有真相, 附上 screenshot:&lt;br /&gt;&lt;a href="https://picasaweb.google.com/lh/photo/u6c6BJurbwhUhF5Y0NPGzEZZhvS8A7xyyi0-E9QamLM?feat=embedwebsite"&gt;&lt;img src="https://lh3.googleusercontent.com/-VScv6DRpbBI/TeeeMBoTykI/AAAAAAAABa4/1TysQE6KRRo/s400/ego_post_screenshot.png" height="284" width="400" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;2011-07-09 更新: 補上自動記錄 link 和發文到 blogger 的功能, 並在 &lt;a href="http://code.google.com/p/ego-post/"&gt;project 首頁&lt;/a&gt;補上如何執行。&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-7591097571826902484?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/7591097571826902484/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2011/06/ego-post-wsyiwyg-wiki.html#comment-form' title='2 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/7591097571826902484'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/7591097571826902484'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2011/06/ego-post-wsyiwyg-wiki.html' title='ego-post!! 即時同步編寫 wiki code 和顯示 html 畫面'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='https://lh5.googleusercontent.com/-6BQoVrUnoiA/TeOYmrrv5pI/AAAAAAAABao/idgswdm_JJg/s72-c/Ego03.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-1730819646608310810</id><published>2011-04-02T11:49:00.006+08:00</published><updated>2011-05-15T00:56:15.030+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Thinking'/><title type='text'>Problem Solving 的技巧 (2)：別把解法當作問題定義</title><content type='html'>&lt;p&gt;在&lt;a href="http://www.books.com.tw/exep/prod/booksfile.php?item=0010470961"&gt;《真正的問題是什麼？你想通了嗎？》&lt;/a&gt;裡提到一點重要的觀念，別把別人的解法當作問題。最近累積了不少實例，比較能清楚地表達這個想法。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;h4&gt;「把解法當作問題」的意思以及它帶來的負面影響&lt;/h4&gt;&lt;p&gt;在分配工作時，為了減少溝通成本，常會隱藏一些訊息，只告訴合作同伴他「應該」知道的事，而省略了原始的動機和考量。問題在於，實際執行的人通常比對方清楚細節，很多問題在執行後才會浮現。但分配到工作的人不清楚前因後果，即使查覺不對勁，也無法 (或不願) 做出進一步修正。&lt;/p&gt;&lt;p&gt;好一點的情況，在分工前會先進行討論。然而，討論常會陷入各執一詞的局面，討論雙方作法優缺點後，會發覺找不到共識。實際上這只是眾多解法的兩個提案而已，若深入討論雙方作法背後的動機，各自預先假定的前提，會發覺尋找共識並不困難，畢竟，雙方是在解同一個問題。&lt;/p&gt;&lt;p&gt;在需求一樣，假設一致的情況下，兩個頭腦清楚願意討論的人，沒道理找不到共識，只是我們習慣省略前提，也沒先釐清需求，直接討論解法，才會覺得有個看不見的牆擋在中間，對話難以有交集，我不認同你認同的點，你也不認同我認同的點。&lt;/p&gt;&lt;p&gt;當我開始不預設立場後，看清楚很多事，我第一件事不是評斷大家作法的好壞，而是先問這樣做的考量為何？從考量的點會問出需求，從需求和考量的點會發覺矛盾，從而發現隱藏的假設。接著就能討論假設是否成立，或是不成立又會如何？很可能所有的點會重新洗牌，然後聚焦出更清楚的需求。&lt;/p&gt;&lt;p&gt;按部就班從需求討論各種作法，尋找大家認同的假設，接著在假設下收斂可能的作法，或是列出有影響但還不確定的點，然後評估何時由誰來釐清不確定的點，再來做更細的決定。這樣做下來，通常會有大家都滿意的成果，互動的過程中也會相處愉快。&lt;/p&gt;&lt;h4&gt;以開發網站為例&lt;/h4&gt;&lt;p&gt;舉例來說，A、B 要合寫一個網站，A 想用 PHP 直接寫，因為他覺得大家都會寫 PHP，寫起來也快；B 想用 Python + Django，因為他覺得日後比較好維護。&lt;/p&gt;&lt;p&gt;A 和 B 的想法都沒錯，但是一討論就陷入死結，這是很吊詭的事，兩個人想法都對，也在做同一件事，為什麼不會有共識？&lt;/p&gt;&lt;p&gt;這表示上述的論點一定有其中一點是錯的，仔細思考會發覺，其實 A 和 B 「沒有在討論同一個問題」，也沒有在同樣的立足點 (假設) 上討論。&lt;/p&gt;&lt;ul&gt;&lt;li&gt;A 可能假設時程很趕，沒有時間慢慢從頭學 Django，若他不會 Python，還得多學 Python，而且他可能不想學新東西，覺得 PHP 用起來沒什麼問題，為什麼要自找麻煩用別的作法？&lt;/li&gt;&lt;li&gt;B 可能假設學習 Python + Django 很簡單，即使時程很趕也還好，或是時程可以再談，沒有人說一定要什麼時候出來，在 B 認為可以來得及做完的前提下，B 認為要認真考量維護的事。&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;從 A 和 B 隱藏的假設可看出一些衝突：學習新東西花的時間成本、對於時程的認知、是否需要重視維護。而這些假設大概和 A、B 各自過去的經驗有關，比方說 B 收拾過用 PHP 開發的爛攤子，A 沒有和多人共同開發過稍具規模的專案。若 A、B 能和對方說明自己的前提，就有機會順著前提再討論為何有這樣的前提，彼此較能接受對方的看法。&lt;/p&gt;&lt;p&gt;然後再對照目前的需求，會發覺有些前提不再成立，或是更為重要。像是 A 當初收拾的爛攤子是別的因素造成的，可能是時程太趕或開發者習慣不好，主因不是 PHP。&lt;/p&gt;&lt;p&gt;將這些事都攤出來討論後，會發覺幾個立基不牢靠的推論：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;為了方便維護，要用 Python + Django。&lt;/li&gt;&lt;li&gt;為了快速開發，要用 PHP。&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;這是將需求和解法綁在一起討論，但是實際的情況是，方便維護的作法不只有 Python + Django，快速開發的作法也不只有 PHP。視開發人員的經驗，將兩者反過來陳述也可能成立。&lt;/p&gt;&lt;p&gt;所以，真正考量的點應該是：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;先確定時程和功能需求，才能判斷開發速度要多快。&lt;/li&gt;&lt;li&gt;確定日後是否需要擴充，擴充的程度和時程，從而判斷維護的成本。&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;然後列出相關選項，需注意的是，要依現有人員的能力列，而不是「一般」的認知。若時程不是太緊，可以先排時間評估各種方案，像是 Python 不只有 Django 一家 framework，也許可以考慮 Flasky 或 Pyramid；PHP 也不是沒 framework，若願意犠牲多一點執行速度，可以研究 PHP 的 framework；或是另找別的路，用 Ruby on Rails 或用 Java 體系的解法。&lt;/p&gt;&lt;p&gt;要注意的是，過猶不及，當考量的點過於發散時，不確定的點太多，沒有心力一一確認，討論會流於空泛。這時要列好需求，依現有人員的能力做些假設，先達成局部共識再繼續討論，比較容易聚焦。像是因為內部人員最熟 PHP 和 Python，用這兩者風險較低，所以只考慮這兩個體系的解法。&lt;/p&gt;&lt;h4&gt;結語&lt;/h4&gt;&lt;p&gt;類似的例子很常發生在日常生活的討論裡，不限於技術方面。隱藏的前提不見得都是技術議題，人性是很複雜的。關鍵在於別把別人提的解法當問題，自己也別預設立場著重在說明解法，而沒說明問題需求和自己的假設 (前提)。&lt;/p&gt;&lt;p&gt;在討論過於發散時，要先取得共識排除一些考量。即使共識的原因只是「不為什麼，我們都覺得不重要」，也是不錯的作法。可以先聚焦往後討論，待討論到後面有更明確的想法，或是再和第三個人討論時，可能會查覺原本的前提有問題，這時再回頭修正它們，重新沙盤推演一遍，得到更確實的解法。有了紮實的共識，清楚各項決策的前因後果後，之後遇到各種變動，都能迅速明確地處理。&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/fcamel/~4/QmD_PFdb794" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-1730819646608310810?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/1730819646608310810/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2011/04/problem-solving-2_6909.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/1730819646608310810'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/1730819646608310810'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2011/04/problem-solving-2_6909.html' title='Problem Solving 的技巧 (2)：別把解法當作問題定義'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-7490516803293708004</id><published>2011-02-19T15:08:00.001+08:00</published><updated>2011-05-15T00:56:16.104+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Testing'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Software Engineering'/><title type='text'>DRY 的缺點以及測試碼的衝突</title><content type='html'>&lt;p&gt;這篇是看到 Pylons (Pyramid) 的 &lt;a href="http://docs.pylonsproject.org/community/testing.html"&gt;《Unit Testing Guidelines》&lt;/a&gt; 後寫的心得。&lt;/p&gt;&lt;p&gt;以前我覺得每件事都有標準答案，或是所謂的「Best practice」。後來才發覺這是很嚴重的錯誤認知。因為希望能簡單地處理事情，而一廂情願地認定有「標準答案」，結果忽略了許多反面的訊息。&lt;a href="http://fcamel.twbbs.org/archives/2010/04/24/1053/"&gt;《百人百觀》系列&lt;/a&gt;裡道出我的心態轉變。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;以 DRY (Don’t Repeat Yourself) 來說，這是資訊人奉為終旨的鐵則，可應用到各種情境。這裡我們先縮小範圍，討論 DRY 對於寫程式的影響。它的優點顯而易見，只需要改一處程式，不會因漏改程式而產生 bug。重覆的程式碼容易造成 bug，複製貼上是常見的主因，甚至有 paper (&lt;a href="http://www.computer.org/portal/web/csdl/doi/10.1109/TSE.2006.28"&gt;CP-Miner&lt;/a&gt;) 提出方法自動偵測這種 bug。&lt;/p&gt;&lt;p&gt;但是 DRY 的缺點呢？造成 client codes 之間的相依性，迫使所有 client codes 共用同一介面，這帶來不少問題：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;寫錯共享程式時，影響不止一份程式。&lt;/li&gt;&lt;li&gt;最簡單的情境得配合最複雜的情境使用，增加簡單情境的維護成本。即使介面設計的很完善，不需更改呼叫方式，執行時勢必多了一些檢查手續，或在空間上做了些妥協，提高時間和空間的成本，各種 framework 是最好的例子。&lt;/li&gt;&lt;li&gt;承上，像 Django 的 session 為了能存各種 object，選擇以 dict 表示 session，直接序列化 session 物件存到資料庫或檔案裡。為了簡化實作並提供無限的空間存 session 資料，用 MySQL 時選擇用 LONGTEXT 以儲存無限制大小的資料，造成每次取資料都要從 disk 讀。在大量使用者連入的時候，這會是個問題。&lt;/li&gt;&lt;li&gt;變更一處 client codes 的需求，可能會影響共享程式的介面。選擇相下向容的話，介面會變複雜，可能會多一些選擇性參數。邏輯變複雜，共享程式容易寫錯，client code 使用方式也變複雜。&lt;/li&gt;&lt;li&gt;承上，選擇改變介面的話，需找出影響到的 client codes。對 dynamic typing 的語言來說，這是件苦差事，甚至無法 100% 保證沒有遺漏。&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;如同&lt;a href="http://fcamel.twbbs.org/archives/2010/11/21/1081/"&gt;《Problem Solving 的技巧》&lt;/a&gt;裡說的，每個方法都有帶來的好處，也有帶來的壞處，也有針對壞處所做的後續修補。關鍵在於弄清楚現在的需求，明白各項設計的優缺點，配套作出一連串的設計，以獲得整體的最大效益。比方說用 VCS 切 branch 可以減少介面相容問題，不過會多出維護 branch 的成本，那是另一個議題了。&lt;/p&gt;&lt;p&gt;自從意識到 DRY 帶來的成本後，我覺得有些困惑，因為它不再是 100% 正確、用了一定好的原則。在寫測試碼時，我感到更困惑，若測試碼也變複雜，之間有相依性，那誰來保證測試碼是正確的？更何況一個具有完備測試碼的專案，測試碼和產品碼的比例將近 1:1，在量如此大的情況下，測試碼的邏輯太複雜的話，測試碼容易出錯，會造成不少問題。我體驗過測試碼寫太複雜而造成測試碼有錯，因測試碼出錯而誤以為產品碼有錯，結果費了更多力氣才找出錯誤 (程式碼變兩倍)。也體驗過在 setUp 或其它初始化部份出錯，造成訊息混亂，無法掌握錯誤的源頭。後來就不知不覺地將測試碼寫得很簡單，也漸漸減少犯這些錯的機會。&lt;/p&gt;&lt;p&gt;昨天看到 Pylons (Pyramid) 的 &lt;a href="http://docs.pylonsproject.org/community/testing.html"&gt;《Unit Testing Guidelines》&lt;/a&gt;後，才串起過去的經驗，發覺問題的源頭在於 DRY 並不適合用在測試碼，但是 DRY 已成為根深蒂固的習慣，壓根兒就不會想到將重覆程式碼抽出整理成跨 method / class / module 的行為，反而是妨礙測試碼品質的元兇。該篇文章有精闢的說明和例子，推薦大家參考。其中有些規則，現在還不能掌握使用後的優缺點，之後再抽時間讀讀 Pyramid 的原始碼，應該能學到一些東西。&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/fcamel/~4/J2EiC1lpuTE" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-7490516803293708004?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/7490516803293708004/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2011/02/dry_19.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/7490516803293708004'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/7490516803293708004'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2011/02/dry_19.html' title='DRY 的缺點以及測試碼的衝突'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-2062829753734623793</id><published>2011-01-08T23:08:00.000+08:00</published><updated>2011-05-15T00:56:17.076+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Web'/><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Database'/><category scheme='http://www.blogger.com/atom/ns#' term='System'/><title type='text'>撰寫資料庫相關程式的心得</title><content type='html'>&lt;p&gt;我是用 MySQL + Django，處理的資料量有小有大。資料量大的情況下，通常有上萬筆，甚至會到上億筆。相關心得大概分成四類，依實務經驗記錄一下心得。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;h4&gt;是否應該使用 ORM？&lt;/h4&gt;&lt;p&gt;我是使用 Django ORM，以下指的 ORM 問題可能不適用全部 ORM framework，但我猜大部份應該是半斤八兩。&lt;/p&gt;&lt;p&gt;剛開始不熟 SQL 時，很喜歡用 ORM，ORM 有些學習門檻，不過習慣後用起來相當順，也容易閱讀程式。但是在好寫好讀的背後，卻犠牲掉極大的效率。原因有幾點：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;需要大概了解 ORM 產生的 SQL，才知道如何寫出有效率的操作。比方說用到 foreign key 時，可以在取物件時順便 join。若沒特別處理，預設行為是參考到關聯物件才取資料，於是取一萬個物件並讀取它們的關聯欄位，就會多下一萬次 SQL。&lt;/li&gt;&lt;li&gt;即使了解 ORM 各項操作避免一些地雷寫法，ORM 不見得能產生最快的操作方式。明顯的缺點是讀寫 N 個物件時，很可能會轉成 N 次 SQL，而不是一次。&lt;/li&gt;&lt;li&gt;ORM 為了提供一致的抽象介面，沒有支援各家 DBMS 完整的語法，減少一些最佳化的機會。如缺少批次操作，以及使用 force index、決定 join order、技巧性地用 IN 不用 range query 等。&lt;/li&gt;&lt;li&gt;即使 SQL 沒有問題，產生 object 的時間成本比自己執行 SQL 取資料來得高 (見&lt;a href="http://fcamel-life.blogspot.com/2010/06/django-python-database.html"&gt;這篇&lt;/a&gt;)，資料量大的時候會變成瓶頸。&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;我一開始寫的專案全用 ORM。第二個寫的大部份用 ORM 但遇到一堆難解的效率問題。第三個寫的開始刻意減少 ORM 操作。最後則是全面禁用 ORM。原因很簡單，弄懂 ORM 操作並做最佳化的時間，比直接寫函式封裝 SQL 操作多，而且最後達成的效率又較差。除此之外，複雜的 ORM 操作可能有 bug 或是令人誤會，導致取出不對的資料，看 ORM 產生的 SQL 才明白問題出在那。&lt;/p&gt;&lt;p&gt;愈懂 MySQL 後，愈覺得 ORM 不順手，最後就改成寫模組封裝 SQL 操作。結論是，若有意願硬啃 DBMS 相關知識的話，將時間投資在所用的 DBMS 上，會比學習 ORM 操作和理解背後運作方式划算。&lt;/p&gt;&lt;p&gt;也許有人會質疑不用 ORM 會增加換 DBMS 的成本，我沒這樣的經驗不清楚用了 ORM 能省下多少成本，相較於前述的問題，整體來說是否划算。至少我會選擇先專精一個 DBMS，還有自己寫模組隔離應用層邏輯和資料庫操作，減低轉換 DBMS 的成本。&lt;/p&gt;&lt;h4&gt;Database migration tool&lt;/h4&gt;&lt;p&gt;雖然我上面將 Django ORM 說得很慘，但是用 Django ORM 搭配 &lt;a href="http://south.aeracode.org/"&gt;South &lt;/a&gt;到是滿不錯的。South 是 Django 的 migration tool，提供一個框架維護資料庫的變動，並且可以偵測 Django model 的變化，產生對應改變 schema 的操作。在說明 South 的優點前，要先談談為何需要用 database migration tool。&lt;/p&gt;&lt;p&gt;使用 database migration tool 有兩個好處：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;記錄目前這版程式用的 database schema。既然程式碼需要版本記錄，database schema 當然也要一併記錄，才能確保每版都能正常運作。&lt;/li&gt;&lt;li&gt;方便其他組員更新資料庫。更新程式碼後執行 database migration，就能擁有和其他人同步的資料庫。&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;當然，有很多方式可以達成以上目的，像是每次更新 schema 就 dump schema，並存成一個 SQL 檔存在 VCS 裡。我沒這樣做過，不知會有什麼大問題。目前只想到幾個小問題：不方便多人同時修改 schema，之後要 merge schema 可能會比較麻煩，特別是改到同一 table 時。不方便追踪 schema 各步的轉換，像是加入 table A、B、C 以支援功能 X。但是回頭翻 VCS log 似乎也能滿足這個需求。唯一無解的大概是有些情境拆開 schema 執行會比較有效率。像是先建好 table、填完實體資料後，再建 covering index。&lt;/p&gt;&lt;p&gt;若改成維護多個 SQL 檔，第一個起始 SQL 產生基本 table，後面的 SQL 都是「schema diff」，則方便多人同時開發。但要人工產生 schema diff 有點辛苦，沒記好每個操作，手動改完 table 後，要回頭比對差別才能寫出 schema diff，容易出錯並增加確認的成本。&lt;/p&gt;&lt;p&gt;除了滿足基本需求外，South 另外提供下列功能：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;提供單線前進的 migration 方式。能在各版本之間前進、後退。&lt;/li&gt;&lt;li&gt;migration 分成 schema migration 和 data migration。並且提供偵測 Django model 變化自動產生 schema migration 的程式碼。data migration 只是空殼，由工程師自己填程式碼。&lt;/li&gt;&lt;li&gt;提供修改 schema 的 API，像是加減欄位、加減 index。&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;使用 South 的額外好處是，可以避開 Django model 的限制，像是不支援多欄 index、不支援使用不同的 MySQL Engine。用 South 的話，只要自己在 schema migration 裡用 alter table 修改即可。&lt;/p&gt;&lt;p&gt;不清楚別家 database migration tool 怎麼運做的，感覺這方面的工具有很大的發揮空間，值得了解一下各家工具提供的功能。目前遇到的最大困擾是，無法明確看出那些 migration 有相依關係，更新資料或程式時，不方便只執行有影響到的範圍，若更新在很前期的 migration，就得回溯到前面再重跑。&lt;/p&gt;&lt;h4&gt;包工具箱&lt;/h4&gt;&lt;p&gt;將資料庫操作和應用層邏輯分離的好處應該不用多說，使用統一的介面有其它好處，目前覺得最實用的是可以寫 try catch 自動記下所有出錯的 SQL，再依參數決定要吃掉 exception 或丟回應用層。由於出錯的 SQL 都有被 log，程式出錯時可以馬上找到有問題的 SQL，縮短除錯時間。&lt;/p&gt;&lt;h4&gt;單元測試&lt;/h4&gt;&lt;div&gt;我原本是用 Django 內建的方式重建測試資料庫，但是最近開始用 multiple database 後，遇到一些問題。由於我在 South 裡做了一些不合 Django 規定的操作，不想花時間理解 Django model 和 test 詳細的運作方式，最後決定自己寫簡單的模組來建置測試環境，速度也會比較快。還在小規模的試用中，看看之後能不能投多點時間打穩這塊，再來寫心得。&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/fcamel/~4/_fFQHff3pdQ" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-2062829753734623793?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/2062829753734623793/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2011/01/blog-post.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/2062829753734623793'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/2062829753734623793'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2011/01/blog-post.html' title='撰寫資料庫相關程式的心得'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-2451528197841367802</id><published>2010-11-21T22:07:00.001+08:00</published><updated>2011-09-18T10:38:43.318+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Thinking'/><title type='text'>Problem Solving 的技巧 (1)：系統設計是一連串的取捨</title><content type='html'>&lt;p&gt;費曼和杜書伍可說是影響我思考方式最深的人，即使重覆閱讀、重新思考他們話，仍能獲得不少新體悟。今天偶然發現《真正的問題是什麼？你想通了嗎？》的推薦序是杜書伍寫的，看了以後收獲良多，看了這麼多書，這還是頭一回看了推薦序而覺得有用。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;文中提到:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;釐清問題的真實性後，面對真正的問題時，須有一個認知：&lt;b&gt;甚少問題能以單一方案解決，而須由不同面向，分頭淡化問題。．．．這些不同面向的解法，單獨用都只能解決局部問題，但配套提出後，卻能大幅降低問題的嚴重性，到一可接受的範圍內。 &lt;/b&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote&gt;&lt;p&gt;企業經營無時不刻面臨問題的發生與解決，在問題決的過程中會發現，現實生活中並沒有可「百分之百」被解決的問題。誠如書中所言：「每一個解決方案都是下一個問題的根源」，一個有利於某面向的方案，代價往往是犠牲另一面向的利益。因此，如何透過溝通、妥協的過程，尋求最適的解法而非完美的解法，將問題的衝擊降到多數人可接受的範圍內，即為好的解決方式，否則反而可能適得其反，滋生新的問題。 &lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;看了這段話，更明白杜書伍花了多少歲月與心力實踐他在&lt;a href="http://www.books.com.tw/exep/prod/booksfile.php?item=0010449478"&gt;《打造將才基因》&lt;/a&gt;裡說的方法，完全是實實在在的真工夫。用精簡的文字描述出極為深刻的道理，實在是太厲害啦！&lt;/p&gt;&lt;p&gt;舉例來說，使用資料庫時，需同時擔心寫入速度和確保不會損失資料。若每次寫入時都寫入硬碟，可以確保資料正確，但卻會很慢；若先寫入記憶體，集合成一批再一起寫入硬碟，可以大幅提昇速度，但若資料庫伺服器當掉，卻會喪失大批資料。&lt;/p&gt;&lt;p&gt;一個配套解法是每次都寫入硬碟確保資料正確，接著使用 RAID 提昇寫入速度。但是用上 RAID 仍是操作硬碟，提昇有限，於是再加上帶有 write cache 的 RAID，這樣就能大幅提昇速度。但這又造成新的問題：雖然資料庫伺服器掛了不會影響資料，但若機房停電，仍會喪失 RAID cache 內的資料。於是在 RAID 裡加上電池，就能克服這問題 (附帶一提，我很喜歡這句話：&lt;b&gt;「系統設計是一連串的取捨」&lt;/b&gt;)。&lt;/p&gt;&lt;p&gt;整個串起來就是「資料庫伺服器每次都寫入硬碟，並使用帶有 write cache + 電池的 RAID 機器」，這樣能同時兼顧寫入速度並確保不會損失資料，不過就變成大幅提昇開銷了。若將支出也列入考量點的話，配套解法可能又不同，也許是降低寫入速度的要求，也許是容許損失多少時間內的資料。&lt;/p&gt;&lt;p&gt;以這麼技術的問題做為例子，其實有損杜書伍提的解題思維、以及《真正的問題是什麼？你想通了嗎？》的價值。&lt;b&gt;真正在解決問題時，需要思考多個面向，而不能只局限在定死的框架裡以技術本位思考。&lt;/b&gt;以資工的術語類比的話，用 BFS 的方式思考會比 DFS 容易找出好方案。比方說可以考慮修改規格或換方式滿足需求，或是重新定義問題，找出真正的需求和剛好到位的解法。問題往往來自於期望和感受之間出現落差，有時換個角度看問題，就能剛好繞開原本的技術難題，並且沒有犠牲任何事情。&lt;/p&gt;&lt;p&gt;以我最近在做的事為例，我在找網站壓力測試的工具，雖然能找到一些好用的工具，但它們卻沒提供良好的登入機制，讓我無法開始測試。若硬往技術方向思考，解決問題的選項不外乎再找下一個工具，或是改工具的程式碼，加入一個前置登入的機制。但其實退一步思考，我真正的需求是產生大量連線，以「如同使用者登入」的方式進行壓力測試，我並不需要用正規的方式登入自家網站，大可另寫一個網址，開後門避開繁瑣的登入流程，直接登入測試帳號。於是，我只需要評估壓力測試工具，不用在意它是否有完備的登入機制。&lt;/p&gt;&lt;p&gt;&lt;b&gt;但思考面向變廣後，比起定好其它因素、單純考量技術來說，問題也變得更複雜。要有效地在不同面向間穿梭，需要更多的經驗和練習，就像以往專精學一件事一樣，只是變得更困難。&lt;/b&gt;杜書伍在&lt;a href="http://www.books.com.tw/exep/prod/booksfile.php?item=0010449478"&gt;《打造將才基因》&lt;/a&gt;裡有不少這方面的說明，強烈推薦閱讀。概念是先專精一個技能，接著專精第二個技能，並找出和先前學習經驗的異同點，融會兩者成為自己的思考系統。接著專精第三個、第四個技能，找異同點，漸漸拼出多元且深入的思考方式。&lt;/p&gt;&lt;p&gt;最近這一年有點開竅，開始會朝多元化思考，先記錄目前的體悟。待有更多經驗後，再來寫心得吧。&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/fcamel/~4/rndR8hfzf18" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-2451528197841367802?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/2451528197841367802/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2010/11/problem-solving.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/2451528197841367802'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/2451528197841367802'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2010/11/problem-solving.html' title='Problem Solving 的技巧 (1)：系統設計是一連串的取捨'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-7709826183432194325</id><published>2010-11-04T22:57:00.001+08:00</published><updated>2011-08-09T08:36:26.480+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Software Engineering'/><category scheme='http://www.blogger.com/atom/ns#' term='Python'/><title type='text'>讓 if、else 帶有更明確的語意</title><content type='html'>&lt;p&gt;最近在維護程式時對於 if、else 有更深的體會，一但邏輯分支變多，很難釐清各種控制流程，一些簡單的習慣可以大幅簡化除錯和改程式的負擔。&lt;br /&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;h4&gt;變數的初始值&lt;/h4&gt;&lt;p&gt;一個常見的情境是有個變數會依條件而有不同的值，典型的寫法如下：&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;2&lt;br /&gt;3&lt;br /&gt;4&lt;br /&gt;5&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;&lt;span style="color:#808080;font-style:italic"&gt;# 假設後面有一長串算式會乘上 weight，這裡先決定它的值&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;if&lt;/span&gt; double：&lt;br /&gt;   weight = &lt;span style="color:#ff4500"&gt;2&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;else&lt;/span&gt;：&lt;br /&gt;   weight = &lt;span style="color:#ff4500"&gt;1&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;( 備註，在 Python 裡 if / else 裡設的變數和它的外層是同一個 scope。 )&lt;/p&gt;&lt;p&gt;或是善用程式語言提供的三元運算子設值 (即 ? : )，在 Python 裡則是這麼寫：&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;weight = &lt;span style="color:#ff4500"&gt;2&lt;/span&gt; &lt;span style="color:#ff7700;font-weight:bold"&gt;if&lt;/span&gt; double &lt;span style="color:#ff7700;font-weight:bold"&gt;else&lt;/span&gt; &lt;span style="color:#ff4500"&gt;1&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;若有多種情況，在其它語言裡可能會用 switch，我個人不喜歡 switch，覺得用起來不直覺，Python 裡也沒有 switch，但可以用 dict 代替：&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;2&lt;br /&gt;3&lt;br /&gt;4&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;weight = &lt;span style="color:black"&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span style="color:#483d8b"&gt;'double'&lt;/span&gt;: &lt;span style="color:#ff4500"&gt;2&lt;/span&gt;,&lt;br /&gt;    &lt;span style="color:#483d8b"&gt;'triple'&lt;/span&gt;: &lt;span style="color:#ff4500"&gt;3&lt;/span&gt;,&lt;br /&gt;&lt;span style="color:black"&gt;}&lt;/span&gt;.&lt;span style="color:black"&gt;get&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;condition, &lt;span style="color:#ff4500"&gt;1&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;操作複雜時可在 dict 的 value 裡改用輔助的小函式，明確的用簡短的程式表明「這區塊在決定 weight 的值」。&lt;/p&gt;&lt;p&gt;別小看這一點小改變，當程式碼很多時，看到 “value = a if condition else b” 可以立即明白這裡的判斷式是用來設值，可以省下為 if、else 這區塊煩心的時間，也可以減少消耗精神和腦內暫存記憶。&lt;/p&gt;&lt;h4&gt;提前處理簡單的分支&lt;/h4&gt;&lt;p&gt;以用遞迴的方式實作費氏數列為例：&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;2&lt;br /&gt;3&lt;br /&gt;4&lt;br /&gt;5&lt;br /&gt;6&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;def&lt;/span&gt; fib&lt;span style="color:black"&gt;(&lt;/span&gt;n&lt;span style="color:black"&gt;)&lt;/span&gt;：&lt;br /&gt;    &lt;span style="color:#ff7700;font-weight:bold"&gt;if&lt;/span&gt; n &lt;span style="color:#66cc66"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ff4500"&gt;0&lt;/span&gt;:  &lt;span style="color:#808080;font-style:italic"&gt;# Error input.&lt;/span&gt;&lt;br /&gt;        &lt;span style="color:#ff7700;font-weight:bold"&gt;raise&lt;/span&gt; &lt;span style="color:#008000"&gt;ValueError&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#483d8b"&gt;'n must be positive.'&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span style="color:#ff7700;font-weight:bold"&gt;if&lt;/span&gt; n == &lt;span style="color:#ff4500"&gt;0&lt;/span&gt; &lt;span style="color:#ff7700;font-weight:bold"&gt;or&lt;/span&gt; n == &lt;span style="color:#ff4500"&gt;1&lt;/span&gt;：&lt;br /&gt;        &lt;span style="color:#ff7700;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:#ff4500"&gt;1&lt;/span&gt;&lt;br /&gt;    &lt;span style="color:#ff7700;font-weight:bold"&gt;return&lt;/span&gt; fib&lt;span style="color:black"&gt;(&lt;/span&gt;n - &lt;span style="color:#ff4500"&gt;1&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt; + fib&lt;span style="color:black"&gt;(&lt;/span&gt;n - &lt;span style="color:#ff4500"&gt;2&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;上面的寫法先處理例外，接著就能放心處理正常的情況，再來處理特例 (初始值)，最後就能專心和主邏輯奮戰，而覺得主邏輯變得單純許多，很好處理。&lt;/p&gt;&lt;p&gt;較大的程式，就是先寫幾個簡單輔助小函式 (例如 is_invalid())，先呼叫小函式避開特殊情況，一樣可以化繁為簡。&lt;/p&gt;&lt;h4&gt;避免巢狀區塊和 continue、break&lt;/h4&gt;&lt;p&gt;常見到在多層迴圈裡呼叫 if、else，並和 continue、break 混用，我個人覺得這種寫法很亂，而傾向用小函式 + return 避開使用 continue 或 break，比方像下面的程式要從一個兩層 list 裡找出每個 list 第一個負數，並算出負數的總和：&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;2&lt;br /&gt;3&lt;br /&gt;4&lt;br /&gt;5&lt;br /&gt;6&lt;br /&gt;7&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;&lt;span style="color:#008000"&gt;sum&lt;/span&gt; = &lt;span style="color:#ff4500"&gt;0&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;for&lt;/span&gt; numbers &lt;span style="color:#ff7700;font-weight:bold"&gt;in&lt;/span&gt; a_list_of_numbers：&lt;br /&gt;    &lt;span style="color:#ff7700;font-weight:bold"&gt;for&lt;/span&gt; n &lt;span style="color:#ff7700;font-weight:bold"&gt;in&lt;/span&gt; numbers：&lt;br /&gt;        &lt;span style="color:#ff7700;font-weight:bold"&gt;if&lt;/span&gt; n &lt;span style="color:#66cc66"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ff4500"&gt;0&lt;/span&gt;：&lt;br /&gt;            &lt;span style="color:#ff7700;font-weight:bold"&gt;break&lt;/span&gt;&lt;br /&gt;    &lt;span style="color:#ff7700;font-weight:bold"&gt;if&lt;/span&gt; n &lt;span style="color:#66cc66"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ff4500"&gt;0&lt;/span&gt;：&lt;br /&gt;        &lt;span style="color:#008000"&gt;sum&lt;/span&gt; += n&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;可以改用小函式配合 return 避免使用 break 並「隱藏」分支：&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;2&lt;br /&gt;3&lt;br /&gt;4&lt;br /&gt;5&lt;br /&gt;6&lt;br /&gt;7&lt;br /&gt;8&lt;br /&gt;9&lt;br /&gt;10&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;def&lt;/span&gt; find_first_negative&lt;span style="color:black"&gt;(&lt;/span&gt;numbers&lt;span style="color:black"&gt;)&lt;/span&gt;：&lt;br /&gt;    &lt;span style="color:#483d8b"&gt;''&lt;/span&gt;&lt;span style="color:#483d8b"&gt;'Return 0 if there is no negative number.'&lt;/span&gt;&lt;span style="color:#483d8b"&gt;''&lt;/span&gt;&lt;br /&gt;    &lt;span style="color:#ff7700;font-weight:bold"&gt;for&lt;/span&gt; n &lt;span style="color:#ff7700;font-weight:bold"&gt;in&lt;/span&gt; numbers：&lt;br /&gt;        &lt;span style="color:#ff7700;font-weight:bold"&gt;if&lt;/span&gt; n &lt;span style="color:#66cc66"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ff4500"&gt;0&lt;/span&gt;：&lt;br /&gt;            &lt;span style="color:#ff7700;font-weight:bold"&gt;return&lt;/span&gt; n&lt;br /&gt;    &lt;span style="color:#ff7700;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:#ff4500"&gt;0&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;&lt;span style="color:#008000"&gt;sum&lt;/span&gt; = &lt;span style="color:#ff4500"&gt;0&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;for&lt;/span&gt; numbers &lt;span style="color:#ff7700;font-weight:bold"&gt;in&lt;/span&gt; a_list_of_numbers：&lt;br /&gt;    &lt;span style="color:#008000"&gt;sum&lt;/span&gt; += find_first_negative&lt;span style="color:black"&gt;(&lt;/span&gt;numbers&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;改寫後，兩個 if 都不見了，主邏輯很清楚地表現出「找出各 list 第一個負數並加總」。&lt;/p&gt;&lt;p&gt;同樣的，程式愈複雜時，這些寫法省下的思考時間愈可觀。&lt;/p&gt;&lt;h4&gt;明確的指明 else 的處理方式&lt;/h4&gt;&lt;p&gt;這和前面提的東西有一點相衝突，視情況而定。在任何有 if、elif 的情況，即使 else 的情況不需做任何處理，仍要明確的寫出 else 並加上註解。如下所示：&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;2&lt;br /&gt;3&lt;br /&gt;4&lt;br /&gt;5&lt;br /&gt;6&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;if&lt;/span&gt; some_condition：&lt;br /&gt;    ...&lt;br /&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;elif&lt;/span&gt; another_condition:&lt;br /&gt;    ....&lt;br /&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;else&lt;/span&gt;:  &lt;span style="color:#808080;font-style:italic"&gt;# Do nothing.&lt;/span&gt;&lt;br /&gt;    &lt;span style="color:#ff7700;font-weight:bold"&gt;pass&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;這個作法的目的是，讓其他讀這份程式的人，明白原作者沒有漏考慮 else 的情況，不處理是符合預期的作法。函式愈長時，這樣寫的好處愈明顯。別小看這個小動作，程式碼一多，回頭讀程式碼時，這點小動作可以省下不少分心的機會。&lt;/p&gt;&lt;h4&gt;結語&lt;/h4&gt;&lt;p&gt;上面提的例子背後的目的都一樣，就是避免讀程式碼的人分心在分支裡，而能專注在主邏輯上。類似的例子還有「使用 iterator 少用 for + index」，平時留意一些小細節，不但能愈寫愈快 (省去煩心細節的時間)，也能降低維護成本，讓其他人易於理解。舉手之勞做環保，大家一起來維護程式碼的品質吧！&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/fcamel/~4/T_4-OzR1Z0g" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-7709826183432194325?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/7709826183432194325/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2010/11/ifelse.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/7709826183432194325'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/7709826183432194325'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2010/11/ifelse.html' title='讓 if、else 帶有更明確的語意'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-7143118767169213531</id><published>2010-09-11T18:32:00.000+08:00</published><updated>2011-05-15T00:56:20.614+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Software Engineering'/><category scheme='http://www.blogger.com/atom/ns#' term='Thinking'/><title type='text'>每件事都有它的價值</title><content type='html'>&lt;p&gt;過去我一直在尋找捷徑，希望能找到經驗以外的價值，不然只是比年資，感覺很無趣。老手比新手強，只是因為他做得久、比較熟悉公司內的程式。待新手變強後，也只是因為他經驗變多。似乎人與人之間沒什麼差異性。改善自己學習的方式是個好主意，但有智慧的人都會不斷改善自己學習的方式，以能夠更有效率地學習。我不斷地思考自己的定位，以及如何學得更有效率。期望在天份、經驗外找到其它的聖杯。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;Steve Jobs 在對 &lt;a href="http://www.youtube.com/watch?v=UF8uR6Z6KLc"&gt;Stanford 大學的畢業演講&lt;/a&gt;裡提到：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Again, you can’t connect the dots looking forward; you can only connect them looking backwards.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;抱著半信半疑的心態，我只能先相信這個看法，繼續邊做邊想。&lt;/p&gt;&lt;p&gt;後來在杜書伍的《打造將才基因》裡看到：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;我並末想出什麼令人拍案叫絕的答案來，但當時得到的結論卻一直到現在我都認為是正確的，．．．，經驗要累積得快，除了比別人更努力工作外，似乎找不到其他的方法。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;看第一次時仍是半信半疑。然而，最近重讀這本書，對照近來的體悟，忽然間想通了這點。&lt;/p&gt;&lt;p&gt;在經歷 TDD 的洗禮後，我覺得我好像找到一個不錯的答案，至少它是我在軟體開發領域裡找到最好的答案。似乎只要練 TDD，就能在同樣時間內爆增功力。相較於過去看的 Design Pattern、程式語言實作技巧等，TDD 似乎更宏觀實用。&lt;/p&gt;&lt;p&gt;就在實踐 TDD 一年後，偶然看到別人的討論，而有不同想法。練習 TDD 後確實能寫出較好維護的軟體，不過除錯的經驗也變少了。寫了快一年半的程式下來，我一直沒有用 debugger 的需求，只開過兩三次 python debugger，碰一下就放棄它，改成補寫 unit test 偵錯。這才發覺，每段時間都有它的意義，只是練得目標不同。我明白照目前的方式走下去，再過個三年五年，我用 TDD 的功力會愈來愈深，不過用 debugger 功力仍然會是零。這沒好壞之分，端看目標為何，以及自己的定位。&lt;/p&gt;&lt;p&gt;在其它學習的經驗裡，發覺看過的知識，還是要實際操作過數次才能內化到自己的思考體系裡。操作次數愈頻繁，體悟愈深。像許多系統設計或軟體開發流程的最佳實踐 (best practice)，還是要走過不同的路，回頭才會明白最佳實踐背後的考量，才知道如何融會這些點到自己的情境裡。很多東西光用看的，或光用做的，無法體會背後的精神，遇到狀況時仍然使不出來。所以，減少嘗試多讀最佳實踐，效果有限；老是硬幹不讀別人的體悟，也是效果有限。&lt;/p&gt;&lt;p&gt;剛才在聊&lt;a href="http://www.plurk.com/p/7i4d3s"&gt;看 Python 源始碼的事&lt;/a&gt;，&lt;a href="http://scottt.tw/"&gt;Scott &lt;/a&gt;和 &lt;a href="http://www.plurk.com/Thinker"&gt;Thinker &lt;/a&gt;不約而同建議直接看原始碼，而不用看書 (即使 &lt;a href="http://www.google.com/buzz/111353793049965752735/ASTDLu9A3EZ/fcamel-%E8%AA%AA-keitheis-%E9%82%A3%E7%9C%8B%E5%88%B0%E7%9A%84-http-tinyurl"&gt;Scott 認為書本的大綱看來不錯&lt;/a&gt;)。但我覺得先看書有個概念再來看原始碼應該比較有「效率」。經 Thinker 說明，才發覺這是練得目標不同。直接硬啃 code 看來較費工，但在費工之中才會有更多練習讀碼的機會，會更熟練相關工具和技巧。而我的目的是增加系統設計經驗為主，讀原始碼為輔，自然會覺得先讀書較好。&lt;/p&gt;&lt;p&gt;每件事都有它的價值，每個技術都需要時間才能挖深。然而，要將走過的路合起來發揮最大功效，要將所學收斂在相關領域裡。這讓我不經花更多時間思考，往後自己的定位為何，我想發展什麼樣的知識網。總之，相信 Steve Jobs 和杜書伍的話，「Stay hungry. Stay foolish.」繼續邊做邊想吧。&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/fcamel/~4/TDDHel-1gts" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-7143118767169213531?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/7143118767169213531/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2010/09/blog-post.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/7143118767169213531'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/7143118767169213531'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2010/09/blog-post.html' title='每件事都有它的價值'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-560108210620449872</id><published>2010-07-18T13:12:00.000+08:00</published><updated>2011-05-15T00:56:21.601+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Software Engineering'/><category scheme='http://www.blogger.com/atom/ns#' term='Thinking'/><category scheme='http://www.blogger.com/atom/ns#' term='Computer Science'/><title type='text'>學習的自我要求</title><content type='html'>&lt;p&gt;學生時代我一直在想一個問題，究竟是問題導向的學習方式較佳，還是技術導向較佳？舉例來說，我想寫個留言版，我發現要完成最基本的需求，我得學 HTML、PHP 和 MySQL，於是我針對我要做的功能學相關的語法，最後完成留言版。可能資料庫設計沒用到正規化，網站外觀沒用到 CSS，一些動態功能用 PHP 實現而非 JavaScript。相較於從頭學寫網站所需的各項技術，這個作法比較務實，我用較少的時間完成目標。極端地看，用問題導向的解法時，我可能找到相關的程式碼，用它們拼貼出結果，但不確定各塊在做什麼。技術導向的解法，可能要先花一段時間弄清楚寫網站需要的技術，再各別學個基本能力，接著開始實作。當然，世界上不是只有 0 和 1，我們常混著用問題導向和技術導向的方式解問題。這裡我說用問題導向的意思，是指偏向用問題導向。&lt;br&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;不只做產品，做研究時也是類似的情況。問題導向就是從問題開始往下挖，看前人做那些方法，將自己不熟的部份補起來，有了必要的知識後，就能回頭解決問題。而技術導向的做法，我可能會加強基礎學科知識，視類別而定可能是機率和線代，或是計算機組織和編譯器，接著才開始思考如何解問題（或重新定義問題）。&lt;/p&gt;&lt;p&gt;以前我沒什麼時間觀念，通常是技術導向的作法，覺得有趣就看我能從多底層開始往上學。比方說使用 Java Collections Framework 時，我會弄清楚我要用的資料結構怎麼運作的的，時間複雜度是多少。實際上大部份的使用情境不需要這些知識，稍微讀一下文件的注意事項即可。要用 machine learning 的工具，就找適合的 machine learning 課程講義來讀，弄清楚要用到的 model 怎麼運作。直到現在，我無法確認這些對於使用工具究竟幫了多少忙，當初花的時間對應到目前的幫助，真的划算嗎？&lt;/p&gt;&lt;p&gt;最近我試著用問題導向的方式解問題，初期進展確實比較快，不過後來遇到一些狀況，才發覺技術導向的好處。有些情況，我們根本不明白問題出在那裡，有太多可能。舉例來說，前幾天我和同事發覺不同使用者登入同一頁面，操作速度卻差了一大截。有許多可能原因：網路連線 、網頁框架、資料庫等。做了一些初步測試，我懷疑是 MySQL 根據歷史記錄做錯 query optimization。於是用 &lt;a href="http://dev.mysql.com/doc/refman/5.0/en/using-explain.html"&gt;EXPLAIN &lt;/a&gt; 看相同的 SQL 配合不同使用者 ID，結果發現 MySQL 執行 query 的方式有細微差異，造成取出某些使用者的資料時，用較慢方式執行 SQL。於是讓 MySQL 重分析表內的資料後，問題就解決了。若不是之前稍微看過 MySQL 執行 query 的相關知識，不會這麼快就直指問題核心。也許就會用別的方式繞開這問題，一輩子都不知道怎麼解它。待發生類似問題時，又用別的方式繞開它，長遠來看浪費開發時間又增加維護成本。&lt;/p&gt;&lt;p&gt;另一個反面的例子是，我一直沒用 lock 的習慣，教科書告訴我們 deadlock 很可怕，所以我會想辦法避開用 lock。結果最近有個小專案因為沒用 lock，真的發生 race condition 造成有一點點資料不正確。實作前我明白會有這樣的狀況，但這個問題對我們的目的沒什麼影響，衡量開發時間後我決定寫下註解強調會有 race condition，而選擇不處理它。對照最近的體悟，我明白這樣下去我不可能學會用 lock，這不是個好現象，所以又找時間回頭看 MySQL 怎麼 lock table，結果比想像中來得簡單，之前多慮了。&lt;/p&gt;&lt;p&gt;有很多類似這種的逃避例子，像多人一起寫程式容易有問題，於是大家傾向將功能切乾淨，每人寫沒有交集的功能，最後再來整合。但是，對照近年來的軟體開發的趨勢，愈早整合愈容易解決問題。一個人開發容易有盲點，互相協助可以降低初期錯誤，以利後期整合。問題是，要能順利地多人共同開發，得做對不少事才行。像是版本管理系統、coding style、天天合併程式等。每一項都需要時間練習。若一個人開發時有好好練習，和別人合作時會減少許多問題，比較容易推動密集的團隊合作。&lt;/p&gt;&lt;p&gt;在面試別人時，我發覺一個問題：有些人學到的技能剛好只能應付他負責的專案。問題在於，若平時我們都處理簡單的專案，要怎麼轉去負責困難的專案？兩者之間有個斷層。像這類的例子不勝杖舉。比方說要從資料庫表 A 取出部份資料塞入表 B，最「簡單」的作法是寫個程式用 SQL 取出資料，用程式做些處理再用 SQL 一筆筆寫入表 B。另一個作法是直接用一個較複雜的 SQL 直接搞定。當資料量大時，後者執行速度會快上不少。並且，學會後者的寫法後，之後只要花一點時間就能處理類似的情況，不用再寫一個小程式。其它像寫程式的習慣、用程式工具的習慣，都是一樣的。多數情況我們可以用最「不費力」的作法滿足需求，但長遠來看卻是毫無長進。實際上有更有效率的作法，這裡的效率包含開發時間和軟體品質。&lt;/p&gt;&lt;p&gt;對照大學的程式來看，這一年來我以為自己程式已寫得頗有品質了，雖然知道一些小問題，但覺得並不迫切，也不知怎麼查相關的解法，就放著它們。最近翻了一下 &lt;a href="http://java.sun.com/docs/books/effective/"&gt;Effective Java 第二版&lt;/a&gt;和 &lt;a href="http://www.growing-object-oriented-software.com/"&gt;Growing Object-Oriented Software Guided by Tests&lt;/a&gt;，才發覺還有太多的東西要學，自己和資深的軟體工程師差了一大截，照我目前的學習方式，和他們的差距只會拉大不會縮短。若只專注完成眼前的工作，我永遠無法補足和更難工作之間的差距，這才驚覺問題導向的盲點。&lt;/p&gt;&lt;p&gt;走過天平的兩端後，我現在的體悟是，得雙向夾擊來解決問題。一方面用問題導向解問題以符合時程，確保時間有花在刀口上。另一方面再抽時間用技術導向的方式強化自己的實力。如此一來，在完成當下的專案的同時，也有一點一滴地補足技術斷層，取得挑戰更難專案的機會。題外話，英文也是很重要的「技術」，這一年來我半強迫地讓自己盡量搜英文文件，思考關鍵字比以前敏銳不少，閱讀速度也變快，獲得答案的速度比以前快、品質也較佳。&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/fcamel/~4/eRhU02I0sxQ" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-560108210620449872?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/560108210620449872/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2010/07/blog-post.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/560108210620449872'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/560108210620449872'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2010/07/blog-post.html' title='學習的自我要求'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-3061773120889184234</id><published>2010-05-29T16:54:00.003+08:00</published><updated>2011-06-22T22:24:08.661+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Software Engineering'/><title type='text'>養成寫程式的好習慣</title><content type='html'>我很喜歡 Kent Beck 說的這段話&lt;br /&gt;&lt;blockquote&gt;“I’m not a great programmer, I’m a pretty good programmer with great habits.”&lt;/blockquote&gt;從學生時代開始，我就習慣照著書上的建議寫程式，一直沒覺得什麼特別的。後來和一些人合作，或是看到別人抱怨那裡又出錯了，才驚覺那些好習慣有這麼大的影響力。試著在一些場合向別人說明如何改進寫程式的方式，才發覺很難改變寫程式的習慣 — 大概就和我一直無法早睡早起一樣難．．．。&lt;br /&gt;&lt;br /&gt;大前研一說許多人在解決問題時誤把結果當原因，沒有深入追究問題的根本，只是解決問題根源造成的表面問題。寫程式自然會有 bug，但是有許多 bug 並不是 bug，而是壞習慣造成的。像是在 PHP 或 JavaScript 裡不用 === 和 !==，而被 type casting 誤導造成 bug。或是在 if / while 裡用 assignment 又不小心漏了括號，寫出 if (a=foo() &amp;gt; 0) 這類 bug。&lt;br /&gt;好習慣得隨時間慢慢累積，做得愈多愈久，功力自然會變深。就如同龜仙人要悟空和克林送牛奶那般練基礎功，帶著好習慣寫程式，經年累月下來，學到的東西會更多。以下針對一些特定的習慣提我自己的感受。&lt;br /&gt;&lt;h4&gt;Coding style&lt;/h4&gt;網路上有不少 coding style 建議，有些如 Google coding style 甚至會解釋為何要這麼寫，這樣寫的好處和壞處為何。可以學到不少寫程式的小技巧。&lt;br /&gt;最近遇到的實例是遵守 Python coding style 所說，在 module 開頭寫 import，並照順序 import 內建、第三方模組和自己的模組。在程式寫到近兩萬行後要做些修改時，我發覺這個簡單的習慣，讓我很容易明白那些模組有關聯，很快就能找出要修改的程式。反過來說，閱讀一些 Django 程式碼時，發覺常在函式裡 import module，不易掌握模組之間的關係。&lt;br /&gt;還有控制函式的行數在螢幕的高度內、區域變數的使用範圍（要用到時再「宣告」)、避免用全域變數。遵守這些習慣使我容易掌握變數的影響範圍，除錯時可以省下不少心思思考變數是否被別的地方改到。&lt;br /&gt;最近初學 JavaScript，就在 coding style 的建議裡找到減少 global object 和管理變數 scope 的技巧：使用一個 global object 存放所有變數和函式，藉此在 JavaScript 中做出模組的效果。&lt;br /&gt;&lt;h4&gt;Version control (以 Mercurial 的指令為例)&lt;/h4&gt;即使一個人寫程式，version control 仍有很大的用處。保持每個 commit 精簡，每個 commit 只完成一個小功能，就能輕易追踪過去的改變。&lt;br /&gt;以下是幾個我常用 VCS 協助的情況：&lt;br /&gt;&lt;ul&gt;&lt;li&gt;寫程式較不怕被中斷，只要 hg diff 就知道剛才改了什麼。commit 前也能清楚明白這次做了那些修改，去掉忘了除掉的 debug code。&lt;/li&gt;&lt;li&gt;可以放心地修改，改到昏頭就 hg up -C 清掉剛才不知所云的修改，不用花費力氣將程式弄回正常的版本。&lt;/li&gt;&lt;li&gt;寫到一半發覺要先完成另一個功能，hg shelve 暫存目前的修改，接著將另一個功能做完並 commit，再 hg unshelve 回頭做原本的事，可以輕鬆地切換目標，隨時專注在目前的目標上。&lt;/li&gt;&lt;li&gt;若發覺某個功能忽然不能運作，hg up 切回舊的版本，做個 binary search (或用 hg bisect) 立即找到改出問題的 commit。由於每個 commit 都很精簡，看一下就會找到改爛的原因。&lt;/li&gt;&lt;/ul&gt;我最近用 jQuery 寫的程式，過了一陣子後發覺某個功能不能運作。用 hg up 和 binary search 的方式，很快地找到在一百多版前改爛的，而且改爛的原因很奇妙，我將目標 tag 的 id 設為 “submit” 後就爛了，但若換個名字或不設 id 就沒事。若沒有 version control，我想我在原本的程式裡找半天也不會找到，根本不會懷疑問題出在這裡。最後大概會重寫該段，然後莫明奇妙地避開這個問題。&lt;br /&gt;和人合作時 version control 就更有用了，方便和其他人共用程式、做 code review、自動跑測試確保各版運作正常，好處不勝杖舉。相較於每個人各自寫程式，多個人同寫一份程式不但方便討論，容易互相支援，開發時士氣也會較好。每次看到別人 push code，就會覺得待做事項漸漸變少，而寫得更有勁。&lt;br /&gt;&lt;h4&gt;Refactoring&lt;/h4&gt;在寫新功能或修 bug 前，若發現有重覆程式碼或一些有潛在風險的程式，先重構程式再回頭做原本該做的事。重構前記得要確保重構後行為不變。若情況太糟很難補 unit test 或是沒時間補太細，準備好幾組常用的輸入資料，記好它們對應的輸出結果，寫個自動測試的 recorded test 會比較安全，也可加快後續的重構。配合 VCS 做起來更容易，改改發現無法通過 recorded test，就 hg up -C 重頭改一次。然後別太貪心，一次改一點比較不容易犯錯。視情況寫 recorded test 的方式有所不同。通常我會用 shell script + diff 這類指令很快的拼一個可以用的小工具，重構完就丟了，節省準備 recorded test 的時間還有免除日後維護它的負擔。但若打算長久維護的話，自然是照規矩一步步做會比較穩當。&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;Unit test / Acceptance test / TDD&lt;/h4&gt;這部份在&lt;a href="http://fcamel.twbbs.org/archives/2009/06/13/849/"&gt;先前的文章&lt;/a&gt;已提了不少，隨著時間演進，&lt;a href="http://fcamel.twbbs.org/archives/2009/10/29/928/"&gt;實例&lt;/a&gt;愈來愈多。最近將 Django 1.1 昇到 1.2，跑 unit test OK, 但跑 Selenium test 時看到 &lt;a href="http://docs.djangoproject.com/en/1.2/ref/contrib/csrf/"&gt;CSRF &lt;/a&gt;的錯誤訊息。稍微修一下，測試程式全過。讓我立即確信所有用到的 plugin 都順利地在 1.2 版下運作。&lt;br /&gt;開始用 TDD 可說是我寫程式生涯中的重大里程碑，踏入完全不同的格局。讓我明白如何寫出易於長期發展的程式，不用像在玩踩地雷般辛苦。&lt;br /&gt;&lt;h4&gt;Pair programming&lt;/h4&gt;這不算是個人習慣，順便記在這裡。&lt;br /&gt;這部份我沒太多經驗，有時運作的不錯，有時不太順。執行 pair programming 前要先確保兩人的背景知識差不多，才不會有一人跟不上進度，讓另一人空轉。運作順利時，可以很快地完成較複雜的設計，並確保至少有兩個人可以繼續維護這份程式。而且程式也會較易懂：兩個人覺得好懂的程式，遠比一個人覺得好懂的程式易懂多了。&lt;br /&gt;Pair programming 比寫下規範更容易讓大家有一致的開發習慣，像是 coding style 或是 commit 的規範。藉由一人帶一人的方式連結開發習慣。也方便分享實作技巧，像操作工具的技巧、使用函式庫的經驗或是寫程式的技巧。&lt;br /&gt;&lt;h4&gt;其它&lt;/h4&gt;除養成好習慣外，偶而抽點時間學習工具的操作，像是 Linux 架站裝軟體之類的，開發軟體時很難避免這些事。像我習慣用 Linux terminal 開發程式，多熟悉&amp;nbsp;screen、bash、Vim 的設定和操作，開發速度可以快上不少。&lt;br /&gt;最近的例子是使用 Firefox 的 plugin Firebug。以前改 CSS 都笨笨地存檔、重讀網頁，用 Firebug 讓我用十倍以上的速度完工，令人不勝噓唏。&lt;br /&gt;&lt;h4&gt;結語&lt;/h4&gt;剩下的就是熟悉函式庫、框架，還有學會資訊工程一些基本知識，了解程式背後運作的原理。一但每個人都能保持寫程式的好習慣，團隊合作將會簡單許多，大家方便共用程式，方便互相支援 (寫相依的元件或除錯），既能加快開發速度，也會比較有趣。&lt;br /&gt;&lt;img height="1" src="http://feeds.feedburner.com/~r/fcamel/~4/GaQZD8slMpM" width="1" /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-3061773120889184234?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/3061773120889184234/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2010/05/blog-post.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/3061773120889184234'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/3061773120889184234'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2010/05/blog-post.html' title='養成寫程式的好習慣'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-5433422785483674844</id><published>2010-04-24T14:33:00.000+08:00</published><updated>2011-05-15T00:56:23.444+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Life'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Thinking'/><title type='text'>百人百觀 (3)</title><content type='html'>&lt;p&gt;原本我以為大部份問題有「標準答案」，而不斷地尋找每個問題的「標準解」。兩個人見解不同，必然有人有誤，也可能都不對。在&lt;a href="http://fcamel.twbbs.org/archives/2006/11/27/182/"&gt;《百人百觀》&lt;/a&gt;裡，我才明白不是這麼一回事：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;最後我發覺沒什麼最正確、最有道理的事，百人百觀，於是我不會硬把自己的想法套到別人身上，當別人和我抱怨誰的想法不合理，大家都如何，他偏偏不合群，即使我贊同大部份人的看法，卻不會像以前一樣，認為那個特立獨行的人有問題，想說服他。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;在&lt;a href="http://fcamel.twbbs.org/archives/2007/02/21/299/"&gt;《百人百觀 (2)》&lt;/a&gt;裡，我才明白不該強加自己的觀念在別人身上，即使我認為自己沒錯，也要尊重別人的見解：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;於是我不在意我是否能影響聽者的觀點，有的話，當然很高興；沒有的話，對方可能需要不同的契機來改變，那個契機不是我。也可能這個想法適用於我，不適用於對方。另一方面，我仍然熱於與人討論，百人百觀不意味交流沒有意義，只是不用過於執著自己的想法。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;但是直到最近，我才明白自己的想法常常有誤，要能接受不同的看法，從中學習。這個轉變花了我不少時間。先是看了費曼的言論而開始懷疑一切、懷疑自己。我很喜歡他在《這個不科學的年代！ 》第一篇裡說的話：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;有些人說：「你怎麼能夠活著而無知？」我不知道他們是什麼意思。我從來都活著，也從來都很無知。那容易得很。我想知道的是你如何能什麼都知道。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;後來又看了 TED Talk &lt;a href="http://www.ted.com/talks/derek_sivers_weird_or_just_different.html"&gt;《Weird, or just different?》&lt;/a&gt;，裡面有段話很棒：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Whatever brilliant ideas you have or hear, the opposite may also be true.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;我原本對此半信半疑，經過幾次實例驗證，發覺我堅信「絕對沒錯」的作法，仍有一些情境不適用。若能從自己習慣的作法中找出反面的價值，或許會大有幫助。一但認定「肯定如此」後，就失去改進的機會了。&lt;/p&gt;&lt;p&gt;至於前兩篇文章對於溝通的焦慮，現在的心得是，要能開放心胸，站在對方的角度思考。嘗試幾次後，會發覺以前沒想到的事。再來就是去除情緒，就事論事。說來容易做來難，還在持續練習中。附帶一提，站在對方的角度思考並不容易，不只是情緒上的問題，有時沒類似的經驗，無法明白對方看重的點。&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/fcamel/~4/qyuxclxL_RM" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-5433422785483674844?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/5433422785483674844/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2010/04/3.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/5433422785483674844'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/5433422785483674844'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2010/04/3.html' title='百人百觀 (3)'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-2475284774548830042</id><published>2010-04-11T21:11:00.000+08:00</published><updated>2011-05-15T00:56:24.759+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Testing'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><title type='text'>寫出容易測試的程式</title><content type='html'>&lt;p&gt;昨天和何米特、卡曼德 (不要懷疑，他們都是道地的台灣人）聊到測試，想到一個不錯的例子。這回就用實例來說明「容易測試」和「不容易測試」到底是怎麼一回事。完整的程式可以從&lt;a href="http://code.google.com/p/fc-toolkit/source/browse/#hg/test_samples/wwc"&gt;這裡&lt;/a&gt;取得。&lt;br&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;h4&gt;題目說明&lt;/h4&gt;&lt;p&gt;從參數列讀入一個網址，計算網頁內的單字數量並輸出到螢幕。單字之間用空白區隔。當網址無效時，輸出 -1。&lt;/p&gt;&lt;p&gt;為了方便說明起見，我選了個簡單但完整的例子。若要將問題變得更有說服力，不妨想像要連線到網頁伺服器，需要來回幾次的通訊和分析網頁內容以達成目的。比方說寫個程式下載漫畫或美女圖之類的。總之，程式愈複雜，下面提到的問題愈嚴重。&lt;/p&gt;&lt;h4&gt;直覺的寫法&lt;/h4&gt;&lt;p&gt;相信大家看到這題目都會覺得簡單嘛，這樣的程式有什麼難的，寫一寫、跑一跑、改一改，來回幾次就搞定了。寫出來的程式大概長這個樣子：&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;2&lt;br /&gt;3&lt;br /&gt;4&lt;br /&gt;5&lt;br /&gt;6&lt;br /&gt;7&lt;br /&gt;8&lt;br /&gt;9&lt;br /&gt;10&lt;br /&gt;11&lt;br /&gt;12&lt;br /&gt;13&lt;br /&gt;14&lt;br /&gt;15&lt;br /&gt;16&lt;br /&gt;17&lt;br /&gt;18&lt;br /&gt;19&lt;br /&gt;20&lt;br /&gt;21&lt;br /&gt;22&lt;br /&gt;23&lt;br /&gt;24&lt;br /&gt;25&lt;br /&gt;26&lt;br /&gt;27&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;import&lt;/span&gt; &lt;span style="color:#dc143c"&gt;sys&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;import&lt;/span&gt; &lt;span style="color:#dc143c"&gt;optparse&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;import&lt;/span&gt; &lt;span style="color:#dc143c"&gt;urllib2&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;def&lt;/span&gt; main&lt;span style="color:black"&gt;(&lt;/span&gt;url&lt;span style="color:black"&gt;)&lt;/span&gt;:&lt;br /&gt;    &lt;span style="color:#483d8b"&gt;''&lt;/span&gt;&lt;span style="color:#483d8b"&gt;'&lt;span style="color:#000099;font-weight:bold"&gt;\&lt;/span&gt;&lt;br /&gt;    %prog [options] &amp;lt;url&amp;gt;&lt;br /&gt;    Print the number of words in &amp;lt;url&amp;gt;.&lt;br /&gt;    Print -1 if &amp;lt;url&amp;gt; is invalid.&lt;br /&gt;    &amp;#39;&lt;/span&gt;&lt;span style="color:#483d8b"&gt;''&lt;/span&gt;&lt;br /&gt;    &lt;span style="color:#ff7700;font-weight:bold"&gt;try&lt;/span&gt;:&lt;br /&gt;        content = &lt;span style="color:#dc143c"&gt;urllib2&lt;/span&gt;.&lt;span style="color:black"&gt;urlopen&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;url&lt;span style="color:black"&gt;)&lt;/span&gt;.&lt;span style="color:black"&gt;read&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span style="color:#ff7700;font-weight:bold"&gt;print&lt;/span&gt; &lt;span style="color:#008000"&gt;sum&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#008000"&gt;len&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;line.&lt;span style="color:black"&gt;split&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt; &lt;span style="color:#ff7700;font-weight:bold"&gt;for&lt;/span&gt; line &lt;span style="color:#ff7700;font-weight:bold"&gt;in&lt;/span&gt; content.&lt;span style="color:black"&gt;split&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#483d8b"&gt;'&lt;span style="color:#000099;font-weight:bold"&gt;\n&lt;/span&gt;'&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span style="color:#ff7700;font-weight:bold"&gt;except&lt;/span&gt; &lt;span style="color:#dc143c"&gt;urllib2&lt;/span&gt;.&lt;span style="color:black"&gt;URLError&lt;/span&gt;, e:&lt;br /&gt;        &lt;span style="color:#ff7700;font-weight:bold"&gt;print&lt;/span&gt; -&lt;span style="color:#ff4500"&gt;1&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;    &lt;span style="color:#ff7700;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:#ff4500"&gt;0&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;if&lt;/span&gt; __name__ == &lt;span style="color:#483d8b"&gt;'__main__'&lt;/span&gt;:&lt;br /&gt;    &lt;span style="color:#dc143c"&gt;parser&lt;/span&gt; = &lt;span style="color:#dc143c"&gt;optparse&lt;/span&gt;.&lt;span style="color:black"&gt;OptionParser&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;usage=main.__doc__&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt;    options, args = &lt;span style="color:#dc143c"&gt;parser&lt;/span&gt;.&lt;span style="color:black"&gt;parse_args&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;    &lt;span style="color:#ff7700;font-weight:bold"&gt;if&lt;/span&gt; &lt;span style="color:#008000"&gt;len&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;args&lt;span style="color:black"&gt;)&lt;/span&gt; &lt;span style="color:#66cc66"&gt;!&lt;/span&gt;= &lt;span style="color:#ff4500"&gt;1&lt;/span&gt;:&lt;br /&gt;        &lt;span style="color:#dc143c"&gt;parser&lt;/span&gt;.&lt;span style="color:black"&gt;print_help&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span style="color:#dc143c"&gt;sys&lt;/span&gt;.&lt;span style="color:black"&gt;exit&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#ff4500"&gt;1&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;    &lt;span style="color:#dc143c"&gt;sys&lt;/span&gt;.&lt;span style="color:black"&gt;exit&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;main&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#66cc66"&gt;*&lt;/span&gt;args&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;看起來沒啥問題，但是我們要怎麼確定這份程式是對的？嗯，大概找幾個有效的網址、無效的網址，試一試，看看跑出來的數字對不對。或著專業一點，自己弄幾個簡單的網頁方便計算答案，連入自己的網頁，對看看有沒有算對字數。&lt;/p&gt;&lt;p&gt;可以想見，這個測試過程冗長乏味。之後若做些修改，像是規格改成「輸出的數字要四捨五入到百位」、「輸出全部的字和它的次數」，沒人想從頭重測一次所有情況。&lt;/p&gt;&lt;p&gt;這裡的問題是什麼？問題出在沒有做到自動測試。那就寫個 shell script 讀入一串網址依序執行並存下結果，人工確認結果無誤後，再將答案存起來。之後就執行 shell script 讀網址比對輸出。比方說像這樣：&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;2&lt;br /&gt;3&lt;br /&gt;4&lt;br /&gt;5&lt;br /&gt;6&lt;br /&gt;7&lt;br /&gt;8&lt;br /&gt;9&lt;br /&gt;10&lt;br /&gt;11&lt;br /&gt;12&lt;br /&gt;13&lt;br /&gt;14&lt;br /&gt;15&lt;br /&gt;16&lt;br /&gt;17&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;&lt;span style="color:#666666;font-style:italic"&gt;#!/bin/bash&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#666666;font-style:italic"&gt;# usage: ./check.sh &amp;lt;prog&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#000000;font-weight:bold"&gt;function&lt;/span&gt; check&lt;span style="color:#7a0874;font-weight:bold"&gt;(&lt;/span&gt;&lt;span style="color:#7a0874;font-weight:bold"&gt;)&lt;/span&gt; &lt;span style="color:#7a0874;font-weight:bold"&gt;{&lt;/span&gt;&lt;br /&gt;    python $&lt;span style="color:#000000"&gt;1&lt;/span&gt; $&lt;span style="color:#000000"&gt;2&lt;/span&gt; &lt;span style="color:#000000;font-weight:bold"&gt;&amp;gt;&lt;/span&gt; t.txt&lt;br /&gt;    &lt;span style="color:#c20cb9;font-weight:bold"&gt;read&lt;/span&gt; n &lt;span style="color:#000000;font-weight:bold"&gt;&amp;lt;&lt;/span&gt; t.txt&lt;br /&gt;    &lt;span style="color:#c20cb9;font-weight:bold"&gt;rm&lt;/span&gt; &lt;span style="color:#660033"&gt;-f&lt;/span&gt; t.txt&lt;br /&gt; &lt;br /&gt;    &lt;span style="color:#000000;font-weight:bold"&gt;if&lt;/span&gt; &lt;span style="color:#7a0874;font-weight:bold"&gt;[&lt;/span&gt; &lt;span style="color:#007800"&gt;$n&lt;/span&gt; &lt;span style="color:#660033"&gt;-eq&lt;/span&gt; $&lt;span style="color:#000000"&gt;3&lt;/span&gt; &lt;span style="color:#7a0874;font-weight:bold"&gt;]&lt;/span&gt;; &lt;span style="color:#000000;font-weight:bold"&gt;then&lt;/span&gt;&lt;br /&gt;        &lt;span style="color:#7a0874;font-weight:bold"&gt;echo&lt;/span&gt; &lt;span style="color:#ff0000"&gt;&amp;quot;Pass.&amp;quot;&lt;/span&gt;&lt;br /&gt;    &lt;span style="color:#000000;font-weight:bold"&gt;else&lt;/span&gt;&lt;br /&gt;        &lt;span style="color:#7a0874;font-weight:bold"&gt;echo&lt;/span&gt; &lt;span style="color:#ff0000"&gt;&amp;quot;Fail.&amp;quot;&lt;/span&gt;&lt;br /&gt;        &lt;span style="color:#7a0874;font-weight:bold"&gt;exit&lt;/span&gt; &lt;span style="color:#000000"&gt;1&lt;/span&gt;&lt;br /&gt;    &lt;span style="color:#000000;font-weight:bold"&gt;fi&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#7a0874;font-weight:bold"&gt;}&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;check $&lt;span style="color:#000000"&gt;1&lt;/span&gt; http:&lt;span style="color:#000000;font-weight:bold"&gt;//&lt;/span&gt;www.googel.com&lt;span style="color:#000000;font-weight:bold"&gt;/&lt;/span&gt; &lt;span style="color:#000000"&gt;257&lt;/span&gt;&lt;br /&gt;check $&lt;span style="color:#000000"&gt;1&lt;/span&gt; http:&lt;span style="color:#000000;font-weight:bold"&gt;//&lt;/span&gt;www.googel&lt;span style="color:#000000;font-weight:bold"&gt;/&lt;/span&gt; &lt;span style="color:#660033"&gt;-1&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;雖然少了冗長的手動測試，上述的測試流程仍有幾個問題： &lt;/p&gt;&lt;ul&gt;&lt;li&gt;無法保證測試結果一致。有可能連到的網站改變內容，或是剛好連不上，使得每次測試可能得到不同的結果 (即使 Google 不會斷線，它總會改變網頁內容吧)。那麼，測試失敗時，我們怎麼知道是測試過程有問題，還是被測試的程式有問題？&lt;/li&gt;&lt;li&gt;測試費時。受到網路連線的限制，測試相當費時。使得我們不會改一小段程式就執行所有測試。若我們能寫一行程式就跑一次測試，馬上能明白是那裡改出問題。&lt;/li&gt;&lt;li&gt;測試失敗無法直指錯誤的源頭。我們只知道連 A 網址沒得到預期結果，接著得進程式一步步看，輸出內部資訊才能慢慢找到寫錯的地方。&lt;/li&gt;&lt;li&gt;不易準備測試資料。若想測空網頁、有一個字的網頁和有一堆字的網頁，就得準備三個檔案。&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;目前看來，上述的問題似乎不大。但若有上萬行程式和上百個測試案例，一個案例要跑一秒，加起來就變一百秒。其中又有一兩個偶而會測試失敗，造成每次跑完測試無法相信測試結果。即使測試結果沒有疑慮，當測試失敗時，要怎麼從上萬行程式中找出錯在那裡？&lt;/p&gt;&lt;h4&gt;第二版：將計算字元數的部份獨立成函式&lt;/h4&gt;&lt;p&gt;在第一版的程式裡，為了測試是否有算對字數，得準備多份網頁和網頁伺服器再透過 HTTP 讀入內文做測試。光看這麼長的描述就會發覺那裡有些不對勁。若將計算字數的部份獨立成一個函式，就能單獨測「無內文」、「只有一個字」、「有很多字」等情況：&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;2&lt;br /&gt;3&lt;br /&gt;4&lt;br /&gt;5&lt;br /&gt;6&lt;br /&gt;7&lt;br /&gt;8&lt;br /&gt;9&lt;br /&gt;10&lt;br /&gt;11&lt;br /&gt;12&lt;br /&gt;13&lt;br /&gt;14&lt;br /&gt;15&lt;br /&gt;16&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;def&lt;/span&gt; count_lines&lt;span style="color:black"&gt;(&lt;/span&gt;lines&lt;span style="color:black"&gt;)&lt;/span&gt;:&lt;br /&gt;    &lt;span style="color:#ff7700;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:#008000"&gt;sum&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#008000"&gt;len&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;line.&lt;span style="color:black"&gt;split&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt; &lt;span style="color:#ff7700;font-weight:bold"&gt;for&lt;/span&gt; line &lt;span style="color:#ff7700;font-weight:bold"&gt;in&lt;/span&gt; lines&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;def&lt;/span&gt; main&lt;span style="color:black"&gt;(&lt;/span&gt;url&lt;span style="color:black"&gt;)&lt;/span&gt;:&lt;br /&gt;    &lt;span style="color:#483d8b"&gt;''&lt;/span&gt;&lt;span style="color:#483d8b"&gt;'&lt;span style="color:#000099;font-weight:bold"&gt;\&lt;/span&gt;&lt;br /&gt;    %prog [options] &amp;lt;url&amp;gt;&lt;br /&gt;    Print the number of words in &amp;lt;url&amp;gt;.&lt;br /&gt;    Print -1 if &amp;lt;url&amp;gt; is invalid.&lt;br /&gt;    &amp;#39;&lt;/span&gt;&lt;span style="color:#483d8b"&gt;''&lt;/span&gt;&lt;br /&gt;    &lt;span style="color:#ff7700;font-weight:bold"&gt;try&lt;/span&gt;:&lt;br /&gt;        content = &lt;span style="color:#dc143c"&gt;urllib2&lt;/span&gt;.&lt;span style="color:black"&gt;urlopen&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;url&lt;span style="color:black"&gt;)&lt;/span&gt;.&lt;span style="color:black"&gt;read&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span style="color:#ff7700;font-weight:bold"&gt;print&lt;/span&gt; count_lines&lt;span style="color:black"&gt;(&lt;/span&gt;content.&lt;span style="color:black"&gt;split&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#483d8b"&gt;'&lt;span style="color:#000099;font-weight:bold"&gt;\n&lt;/span&gt;'&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span style="color:#ff7700;font-weight:bold"&gt;except&lt;/span&gt; &lt;span style="color:#dc143c"&gt;urllib2&lt;/span&gt;.&lt;span style="color:black"&gt;URLError&lt;/span&gt;, e:&lt;br /&gt;        &lt;span style="color:#ff7700;font-weight:bold"&gt;print&lt;/span&gt; -&lt;span style="color:#ff4500"&gt;1&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;    &lt;span style="color:#ff7700;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:#ff4500"&gt;0&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;獨立出函式後，就能直接測算字數的部份：&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;2&lt;br /&gt;3&lt;br /&gt;4&lt;br /&gt;5&lt;br /&gt;6&lt;br /&gt;7&lt;br /&gt;8&lt;br /&gt;9&lt;br /&gt;10&lt;br /&gt;11&lt;br /&gt;12&lt;br /&gt;13&lt;br /&gt;14&lt;br /&gt;15&lt;br /&gt;16&lt;br /&gt;17&lt;br /&gt;18&lt;br /&gt;19&lt;br /&gt;20&lt;br /&gt;21&lt;br /&gt;22&lt;br /&gt;23&lt;br /&gt;24&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;class&lt;/span&gt; CountLinesTest&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#dc143c"&gt;unittest&lt;/span&gt;.&lt;span style="color:black"&gt;TestCase&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;:&lt;br /&gt;    &lt;span style="color:#ff7700;font-weight:bold"&gt;def&lt;/span&gt; testEmptyContent&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#008000"&gt;self&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;:&lt;br /&gt;        actual = wc.&lt;span style="color:black"&gt;count_lines&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:black"&gt;[&lt;/span&gt;&lt;span style="color:#483d8b"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span style="color:black"&gt;]&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span style="color:#008000"&gt;self&lt;/span&gt;.&lt;span style="color:black"&gt;assertEqual&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#ff4500"&gt;0&lt;/span&gt;, actual&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;    &lt;span style="color:#ff7700;font-weight:bold"&gt;def&lt;/span&gt; testAWord&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#008000"&gt;self&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;:&lt;br /&gt;        actual = wc.&lt;span style="color:black"&gt;count_lines&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:black"&gt;[&lt;/span&gt;&lt;span style="color:#483d8b"&gt;&amp;quot;camel&amp;quot;&lt;/span&gt;&lt;span style="color:black"&gt;]&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span style="color:#008000"&gt;self&lt;/span&gt;.&lt;span style="color:black"&gt;assertEqual&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#ff4500"&gt;1&lt;/span&gt;, actual&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;    &lt;span style="color:#ff7700;font-weight:bold"&gt;def&lt;/span&gt; testWords&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#008000"&gt;self&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;:&lt;br /&gt;        actual = wc.&lt;span style="color:black"&gt;count_lines&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:black"&gt;[&lt;/span&gt;&lt;span style="color:#483d8b"&gt;&amp;quot;a camel can fly&amp;quot;&lt;/span&gt;&lt;span style="color:black"&gt;]&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span style="color:#008000"&gt;self&lt;/span&gt;.&lt;span style="color:black"&gt;assertEqual&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#ff4500"&gt;4&lt;/span&gt;, actual&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;    &lt;span style="color:#ff7700;font-weight:bold"&gt;def&lt;/span&gt; testWordsWithSpaces&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#008000"&gt;self&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;:&lt;br /&gt;        actual = wc.&lt;span style="color:black"&gt;count_lines&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:black"&gt;[&lt;/span&gt;&lt;span style="color:#483d8b"&gt;&amp;quot; a camel can fly &amp;quot;&lt;/span&gt;&lt;span style="color:black"&gt;]&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span style="color:#008000"&gt;self&lt;/span&gt;.&lt;span style="color:black"&gt;assertEqual&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#ff4500"&gt;4&lt;/span&gt;, actual&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;    &lt;span style="color:#ff7700;font-weight:bold"&gt;def&lt;/span&gt; testWordsWithAdjacentSpaces&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#008000"&gt;self&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;:&lt;br /&gt;        actual = wc.&lt;span style="color:black"&gt;count_lines&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:black"&gt;[&lt;/span&gt;&lt;span style="color:#483d8b"&gt;&amp;quot; a camel  can &lt;span style="color:#000099;font-weight:bold"&gt;\t&lt;/span&gt;fly &amp;quot;&lt;/span&gt;&lt;span style="color:black"&gt;]&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span style="color:#008000"&gt;self&lt;/span&gt;.&lt;span style="color:black"&gt;assertEqual&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#ff4500"&gt;4&lt;/span&gt;, actual&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;    &lt;span style="color:#ff7700;font-weight:bold"&gt;def&lt;/span&gt; testMultiLines&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#008000"&gt;self&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;:&lt;br /&gt;        actual = wc.&lt;span style="color:black"&gt;count_lines&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:black"&gt;[&lt;/span&gt;&lt;span style="color:#483d8b"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;, &lt;span style="color:#483d8b"&gt;&amp;quot;b c&amp;quot;&lt;/span&gt;, &lt;span style="color:#483d8b"&gt;&amp;quot;d e f&amp;quot;&lt;/span&gt;&lt;span style="color:black"&gt;]&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span style="color:#008000"&gt;self&lt;/span&gt;.&lt;span style="color:black"&gt;assertEqual&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#ff4500"&gt;6&lt;/span&gt;, actual&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;看來不壞，上面的單元測試可以確保算字數的部份是對的。若上面的測試失敗，也能明白錯在那段程式，又有精簡的輸出入範例協助除錯。並且，獨立出來的函式 (count_lines) 可供其它程式使用。&lt;/p&gt;&lt;p&gt;但是對整個程式來說，我們還是擺脫不了下列問題： &lt;/p&gt;&lt;ul&gt;&lt;li&gt;無法保證測試結果一致。&lt;/li&gt;&lt;li&gt;測試費時。&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;第三版：將網路連線的部份封裝成物件，並用「傳入」的方式使用它&lt;/h4&gt;&lt;p&gt;問題出在 main() 直接用 urllib2.urlopen() 連上網路，若能在測試時替換成我們準備好的函式，就能確保測試結果一致，且減少網路連線的時間。也許有人會說，可以在測試前直接換掉 urllib2.urlopen。這是一個解法，但不建議這麼做，原因日後再說明。比較適當的作法如下：&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;2&lt;br /&gt;3&lt;br /&gt;4&lt;br /&gt;5&lt;br /&gt;6&lt;br /&gt;7&lt;br /&gt;8&lt;br /&gt;9&lt;br /&gt;10&lt;br /&gt;11&lt;br /&gt;12&lt;br /&gt;13&lt;br /&gt;14&lt;br /&gt;15&lt;br /&gt;16&lt;br /&gt;17&lt;br /&gt;18&lt;br /&gt;19&lt;br /&gt;20&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;class&lt;/span&gt; Client&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#008000"&gt;object&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;:&lt;br /&gt;    &lt;span style="color:#ff7700;font-weight:bold"&gt;def&lt;/span&gt; get&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#008000"&gt;self&lt;/span&gt;, url&lt;span style="color:black"&gt;)&lt;/span&gt;:&lt;br /&gt;        &lt;span style="color:#ff7700;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:#dc143c"&gt;urllib2&lt;/span&gt;.&lt;span style="color:black"&gt;urlopen&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;url&lt;span style="color:black"&gt;)&lt;/span&gt;.&lt;span style="color:black"&gt;read&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;def&lt;/span&gt; count_web_page&lt;span style="color:black"&gt;(&lt;/span&gt;client, url&lt;span style="color:black"&gt;)&lt;/span&gt;:&lt;br /&gt;    &lt;span style="color:#ff7700;font-weight:bold"&gt;try&lt;/span&gt;:&lt;br /&gt;        content = client.&lt;span style="color:black"&gt;get&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;url&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span style="color:#ff7700;font-weight:bold"&gt;except&lt;/span&gt; &lt;span style="color:#dc143c"&gt;urllib2&lt;/span&gt;.&lt;span style="color:black"&gt;URLError&lt;/span&gt;, e:&lt;br /&gt;        &lt;span style="color:#ff7700;font-weight:bold"&gt;return&lt;/span&gt; -&lt;span style="color:#ff4500"&gt;1&lt;/span&gt;&lt;br /&gt;    &lt;span style="color:#ff7700;font-weight:bold"&gt;return&lt;/span&gt; count_lines&lt;span style="color:black"&gt;(&lt;/span&gt;content.&lt;span style="color:black"&gt;split&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#483d8b"&gt;'&lt;span style="color:#000099;font-weight:bold"&gt;\n&lt;/span&gt;'&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;def&lt;/span&gt; main&lt;span style="color:black"&gt;(&lt;/span&gt;url&lt;span style="color:black"&gt;)&lt;/span&gt;:&lt;br /&gt;    &lt;span style="color:#483d8b"&gt;''&lt;/span&gt;&lt;span style="color:#483d8b"&gt;'&lt;span style="color:#000099;font-weight:bold"&gt;\&lt;/span&gt;&lt;br /&gt;    %prog [options] &amp;lt;url&amp;gt;&lt;br /&gt;    Print the number of words in &amp;lt;url&amp;gt;.&lt;br /&gt;    Print -1 if &amp;lt;url&amp;gt; is invalid.&lt;br /&gt;    &amp;#39;&lt;/span&gt;&lt;span style="color:#483d8b"&gt;''&lt;/span&gt;&lt;br /&gt;    client = Client&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span style="color:#ff7700;font-weight:bold"&gt;print&lt;/span&gt; count_web_page&lt;span style="color:black"&gt;(&lt;/span&gt;client, url&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span style="color:#ff7700;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:#ff4500"&gt;0&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;於是我們可以用 &lt;a href="http://martinfowler.com/articles/mocksArentStubs.html"&gt;mock&lt;/a&gt; (我用 &lt;a href="http://code.google.com/p/pymox/wiki/MoxDocumentation"&gt;pymox&lt;/a&gt;) 準備假的 Client 反應出我們期望的網頁連線結果，輕易地測試正常連線和網址無效的情況：&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;2&lt;br /&gt;3&lt;br /&gt;4&lt;br /&gt;5&lt;br /&gt;6&lt;br /&gt;7&lt;br /&gt;8&lt;br /&gt;9&lt;br /&gt;10&lt;br /&gt;11&lt;br /&gt;12&lt;br /&gt;13&lt;br /&gt;14&lt;br /&gt;15&lt;br /&gt;16&lt;br /&gt;17&lt;br /&gt;18&lt;br /&gt;19&lt;br /&gt;20&lt;br /&gt;21&lt;br /&gt;22&lt;br /&gt;23&lt;br /&gt;24&lt;br /&gt;25&lt;br /&gt;26&lt;br /&gt;27&lt;br /&gt;28&lt;br /&gt;29&lt;br /&gt;30&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;class&lt;/span&gt; CountWebPageTest&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#dc143c"&gt;unittest&lt;/span&gt;.&lt;span style="color:black"&gt;TestCase&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;:&lt;br /&gt;    &lt;span style="color:#ff7700;font-weight:bold"&gt;def&lt;/span&gt; testCountWebPage&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#008000"&gt;self&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;:&lt;br /&gt;        &lt;span style="color:#808080;font-style:italic"&gt;# Prepare the fixture&lt;/span&gt;&lt;br /&gt;        url = &lt;span style="color:#483d8b"&gt;'http://www.google.com/'&lt;/span&gt;&lt;br /&gt;        client = mox.&lt;span style="color:black"&gt;MockObject&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;wc.&lt;span style="color:black"&gt;Client&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt;        client.&lt;span style="color:black"&gt;get&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;url&lt;span style="color:black"&gt;)&lt;/span&gt;.&lt;span style="color:black"&gt;AndReturn&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#483d8b"&gt;&amp;#39;&amp;lt;html&amp;gt; ... &amp;lt;/html&amp;gt;&amp;#39;&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt;        mox.&lt;span style="color:black"&gt;Replay&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;client&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;        &lt;span style="color:#808080;font-style:italic"&gt;# Run&lt;/span&gt;&lt;br /&gt;        actual = wc.&lt;span style="color:black"&gt;count_web_page&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;client, url&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;        &lt;span style="color:#808080;font-style:italic"&gt;# Verify&lt;/span&gt;&lt;br /&gt;        &lt;span style="color:#008000"&gt;self&lt;/span&gt;.&lt;span style="color:black"&gt;assertEqual&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#ff4500"&gt;3&lt;/span&gt;, actual&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;        mox.&lt;span style="color:black"&gt;Verify&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;client&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;    &lt;span style="color:#ff7700;font-weight:bold"&gt;def&lt;/span&gt; testPageNotFound&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#008000"&gt;self&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;:&lt;br /&gt;        &lt;span style="color:#808080;font-style:italic"&gt;# Prepare the fixture&lt;/span&gt;&lt;br /&gt;        url = &lt;span style="color:#483d8b"&gt;'http://www.google/'&lt;/span&gt;&lt;br /&gt;        client = mox.&lt;span style="color:black"&gt;MockObject&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;wc.&lt;span style="color:black"&gt;Client&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt;        client.&lt;span style="color:black"&gt;get&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;url&lt;span style="color:black"&gt;)&lt;/span&gt;.&lt;span style="color:black"&gt;AndRaise&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#dc143c"&gt;urllib2&lt;/span&gt;.&lt;span style="color:black"&gt;URLError&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#483d8b"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt;        mox.&lt;span style="color:black"&gt;Replay&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;client&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;        &lt;span style="color:#808080;font-style:italic"&gt;# Run&lt;/span&gt;&lt;br /&gt;        actual = wc.&lt;span style="color:black"&gt;count_web_page&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;client, url&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;        &lt;span style="color:#808080;font-style:italic"&gt;# Verify&lt;/span&gt;&lt;br /&gt;        &lt;span style="color:#008000"&gt;self&lt;/span&gt;.&lt;span style="color:black"&gt;assertEqual&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;-&lt;span style="color:#ff4500"&gt;1&lt;/span&gt;, actual&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;        mox.&lt;span style="color:black"&gt;Verify&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;client&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;回頭看原本的程式有那些改變：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;獨立出 Client 物件，用來封裝網路連線的操作。&lt;/li&gt;&lt;li&gt;傳 client 給 count_web_page() ，而不是在 count_web_page() 內部 new 出 Client。&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;想想看，若沒有上述兩個改變，要怎麼滿足測試需求呢？該怎麼測試各種網路連線問題呢？這個問題觸及容易測試和不易測試程式的關鍵差異。&lt;/p&gt;&lt;h4&gt;結語&lt;/h4&gt;&lt;p&gt;就結論來說，一但在函式內直接用全域變數、全域函式或使用自己 new 的物件 X，就不容易測試之後的程式了，因為之後的程式邏輯受到 X 的影響，但測試程式又無法直接控制 X 的行為。當然我們無法完全避免這個情況，總會有全域函式、需要 new 物件。重點在於，我們能將程式隔離到什麼程度？有沒有留「後門」讓測試程式方便控制內部邏輯。&lt;/p&gt;&lt;p&gt;也許有人會懷疑為了測試而改變原本的寫法，是否有些本末倒置？不妨自己練習做個幾次，對照一下原本的寫法和修正後的寫法，用自己的經驗判斷：為測試而改變的結果，是否恰好讓程式變得更彈性、更易重用？&lt;/p&gt;&lt;p&gt;附帶一提，上面的範例碼是逆向寫出來的。我是先用 TDD 寫出最後的版本，再將拆開的東西塞回去 main，弄出「直覺的寫法」。習慣 TDD 後，會改變寫程式的思維。先寫測試有助於寫出易測的程式。&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/fcamel/~4/t_9-20JFwVs" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-2475284774548830042?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/2475284774548830042/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2010/04/blog-post.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/2475284774548830042'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/2475284774548830042'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2010/04/blog-post.html' title='寫出容易測試的程式'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-2544411200656951843</id><published>2010-04-05T17:23:00.000+08:00</published><updated>2011-05-15T00:56:26.158+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Testing'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><title type='text'>[譯文] 為什麼我們不好意思承認，我們不知道如何寫測試？</title><content type='html'>&lt;p&gt;原文見：&lt;a href="http://misko.hevery.com/2009/07/07/why-are-we-embarrassed-to-admit-that-we-dont-know-how-to-write-tests/"&gt;《Why are we embarrassed to admit that we don’t know how to write tests?》&lt;/a&gt;。&lt;/p&gt;&lt;h4&gt;前言&lt;/h4&gt;&lt;p&gt;這篇帶給我很大的影響。對我來說，明白「可測性是最重要的」是一大里程碑。隨著經驗累積，了解得愈深，愈明白 &lt;a href="http://misko.hevery.com/"&gt;Miško Hevery&lt;/a&gt;  寫得多有道理。就當我打算寫篇心得時，才發覺很容易變成用我的話重說 Miško Hevery 說過的東西，而且還很容易漏講。轉念一想，乾脆翻譯他的文章好了。二月底時徵得他的同意，沒想到一拖就拖了一個月半，真不好意思。&lt;/p&gt;&lt;p&gt;大家覺得那裡譯得不好或呈現方式不好，就直接反應出來 。透過 Buzz、Murmur、Plurk、Facebook 或在此留言都可。謝啦。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;h4&gt;本文&lt;/h4&gt;&lt;p&gt;Take your average developer and ask “do you know language/technology X?” None of us will feel any shame in admitting that we do not know X. After all there are so many languages, frameworks and technologies, how could you know them all? But what if X is writing testable code? Somehow we have trouble answering the question “do you know how to write tests?” Everyone says yes, whether or not  we actually know it. It is as if there is some shame in admitting that you don’t know how to write tests.&lt;/p&gt;&lt;p&gt;找出你公司內一般水準的開發者並問他們「你會語言/技術 X嗎？」沒有人會為承認自己不懂 X 而感到不好意思。畢竟有太多程式語言、框架和技術，你怎麼可能全部都會？但若 X 是寫出能被測試的程式呢？不知為何，我們很難回答這個問題：「你會寫測試嗎？」不論我們是否真的懂，每個人都回答會。就如同承認自己不懂寫測試是件很不好意思的事。&lt;/p&gt;&lt;p&gt;Now I am not suggesting that people knowingly lie here, it is just that they think there is nothing to it. We think: I know how to write code, I think my code is pretty good, therefore my code is testable!&lt;/p&gt;&lt;p&gt;我不是暗示人們故意說謊，而是他們認為測試沒什麼大不了的。我們認為「我知道如何寫程式，我覺得我的程式相當不錯，因此程式是可以測試的！」&lt;/p&gt;&lt;p&gt;I personally think that we would do a lot better if we would recognize testability as a skill in its own right. And as such skills are not innate and take years of practice to develop. We could than treat it as any other skill and freely admit that we don’t know it. We could than do something about it. We could offer classes, or other materials to grow our developers, but instead we treat it like breathing. We think that any developer can write testable code.&lt;/p&gt;&lt;p&gt;我個人認為如果我們能意識到可測試性是一個獨立的技術，我們可以做得好多了。這種技術並不是天生就會的，需要經年累月的練習來培養。我們可以將它視為另一項技術並坦率地承認我們不會這項技術。於是我們就能對它做點事。我們能提供課程或其它教材來讓開發者成長，而不是將寫測試的技術視為如同呼吸的能力，好像任何開發者都會寫可以測試的程式。&lt;/p&gt;&lt;p&gt;It took me two years of writing tests first, where I had as much tests as production code, before I started to understand what is the difference between testable and hard to test code. Ask yourself, how long have you been writing tests? What percentage of the code you write is tests?&lt;/p&gt;&lt;p&gt;在我開始了解可測試的程式和難以測試的程式的差別前，我花了兩年的時間先寫測試。在這些程式裡，測試碼的量和產品碼一樣多。問問你自己，你持續寫測試多久了？在你寫的程式裡，測試碼占了百分之多少？&lt;/p&gt;&lt;p&gt;Here is a question which you can ask to prove my point: “How do you write hard to test code?” I like to ask this question in interviews and most of the time I get silence. Sometimes I get people to say, make things private. Well if visibility is your only problem, I have a RegExp for you which will solve all of your problems. The truth is a lot more complicated, the code is hard to test doe to its structure, not doe to its naming conventions  or visibility. &lt;a href="http://misko.hevery.com/2008/07/30/top-10-things-which-make-your-code-hard-to-test/"&gt;Do you know the answer?&lt;/a&gt;&lt;/p&gt;&lt;p&gt;你可以問這個問題來證明我的觀點：「你如何寫出難以測試的程式？」我喜歡在面試時問這個問題，多數的時候我得到沉默的回應。有時有人回答「隱藏物件」。嗯，如果物件的可見範圍是唯一的問題，我可以給你一個正規表示式讓你解決這問題（譯者注：我猜是在測試程式前先用字串比對把程式內所有 private 換成 public，那就可以測了）。真正的答案複雜許多，是因為程式的結構造成它難以測試，而不是命名習慣或物件的可見範圍。&lt;a href="http://misko.hevery.com/2008/07/30/top-10-things-which-make-your-code-hard-to-test/"&gt;你知道答案嗎？&lt;/a&gt;&lt;/p&gt;&lt;p&gt;We all start at the same place. When I first heard about testing I immediately thought about writing a framework which will pretend to be a user so that I can put the app through its paces. It is only natural to thing this way. This kind of tests are called end-to-end-tests (or scenario or large tests), and they should be the last kind of tests which you write not the first thing you think of. End-to-end-tests are great for locating &lt;a href="http://misko.hevery.com/2008/11/17/unified-theory-of-bugs/"&gt;wiring bugs&lt;/a&gt; but are pretty bad at locating &lt;a href="http://misko.hevery.com/2008/11/17/unified-theory-of-bugs/"&gt;logical bugs&lt;/a&gt;. And most of your mistakes are in logical bugs, those are the hard ones to find. I find it a bit amusing that to fight buggy code we write even more complex framework which will pretends to be the user, so now we have even more code to test.&lt;/p&gt;&lt;p&gt;一開始我們都是一樣的。當我第一次聽到測試時，我立即想到寫一個框架來假裝使用者，使得我能用它來執行被測的應用程式。很自然會這麼想。這類型的測試被稱為使用者端測試（end-to-end-tests）（或是情境測試、大型測試），它們應該是你最後寫的測試，而不是一開始想到的。使用者端測試很適合找出 &lt;a href="http://misko.hevery.com/2008/11/17/unified-theory-of-bugs/"&gt;wiring bugs&lt;/a&gt;（譯者注：不知該怎麼翻，請參見 wiring bug 連結的說明），但不適合找出&lt;a href="http://misko.hevery.com/2008/11/17/unified-theory-of-bugs/"&gt;邏輯錯誤&lt;/a&gt;。並且，你的多數錯誤會是邏輯錯誤，它們才是難以找到的錯誤。我發覺這有些有趣，為了對抗有錯誤的程式我們卻寫了更複雜的框架來假裝使用者，於是我們有更多程式待測。&lt;/p&gt;&lt;p&gt;Everyone is in search of some magic test framework, technology, the know-how, which will solve the testing woes. Well I have news for you: there is no such thing. &lt;strong&gt;The secret in tests is in writing testable code,&lt;/strong&gt; not in knowing some magic on testing side. And it certainly is not in some company which will sell you some test automation framework. Let me make this super clear: &lt;strong&gt;The secret in testing is in writing testable-code! &lt;/strong&gt;You need to go after your developers not your test-organization.&lt;/p&gt;&lt;p&gt;每個人都在尋找解決測試麻煩的神奇測試框架、技術、知識。然而我有個消息要告訴你：沒有這種東西。&lt;strong&gt;測試的祕訣就是寫出能被測試的程式碼，&lt;/strong&gt;而不是明白測試領域中某種魔法。並且大概不會有某家公司賣你某種自動測試框架。讓我說得更清楚一些：&lt;strong&gt;測試的祕訣就是寫出能被測試的程式碼！&lt;/strong&gt;你需要關注你的開發者而不是你的測試組織。&lt;/p&gt;&lt;p&gt;Now lets think about this. Most organizations have developers which write code and than a test organization to test it. So let me make sure I understand. There is a group of people which write untestable code and a group which desperately tries to put tests around the untestable code. (Oh and test-group is not allowed to change the production code.) The developers are where the mistakes are made, and testers are the ones who feel the pain. Do you think that the developers have any incentive to change their behavior if they don’t feel the pain of their mistakes? Can the test-organization be effective if they can’t change the production code?&lt;/p&gt;&lt;p&gt;現在讓我們想想這點。大部份的組織讓開發者寫程式，接著讓一個測試組織來測試。讓我確保我明白這是怎麼回事，有組人馬寫出無法測試的程式，和另一組人馬分頭試著測試這些無法測試的程式（喔，而且測試小組不被允許改變產品碼）。開發者是錯誤的來源，測試者感受這些痛苦。你認為有任何誘因讓開發者改變他們的行為 — 如果他們沒有為他們製造的錯誤而感到痛苦？在不能改變產品碼的情況下，測試組織能夠有效地作事嗎？&lt;/p&gt;&lt;p&gt;It is so easy to hide behind a “framework” which needs to be built/bought and things will be better. But the root cause is the untestable code, and until we learn to admit that we don’t know how to write testable code, nothing is going to change…&lt;/p&gt;&lt;p&gt;躲在能建造／買來的框架後面是很容易的，事情也會改善。但是問題的根源是無法測試的程式，直到我們學會承認我們不懂如何寫出能被測試的程式，情況不會改變的．．．。&lt;/p&gt;&lt;h4&gt;相關閱讀&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://www.youtube.com/watch?v=wEhu57pih5w"&gt;Google Tech Talks: The Clean Code Talks — Unit Testing&lt;/a&gt;：內容主旨和這篇文章差不多。Miško Hevery 的口語表達很生動，值得一看。&lt;/li&gt;&lt;li&gt;&lt;a href="http://misko.hevery.com/2008/07/30/top-10-things-which-make-your-code-hard-to-test/"&gt;Top 10 things which make your code hard to test&lt;/a&gt;：提及文中問題的答案，值得一讀。&lt;/li&gt;&lt;/ul&gt;&lt;img src="http://feeds.feedburner.com/~r/fcamel/~4/1Q8H8x4Tq_k" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-2544411200656951843?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/2544411200656951843/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2010/04/blog-post_05.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/2544411200656951843'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/2544411200656951843'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2010/04/blog-post_05.html' title='[譯文] 為什麼我們不好意思承認，我們不知道如何寫測試？'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-7449996050637879222</id><published>2010-04-01T22:12:00.000+08:00</published><updated>2011-05-15T00:56:27.585+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='格言錦句'/><title type='text'>我們不能改變手裡的牌，但是可以決定如何出牌。</title><content type='html'>&lt;p&gt;在愚人節發文章好像怪怪的，搞不好會被人誤以為在說反話。&lt;/p&gt;&lt;p&gt;我很喜歡 &lt;a href="http://www.thelastlecture.com/"&gt;Randy &lt;/a&gt;說的這段話：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;我們不能改變手裡的牌，但是可以決定如何出牌。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;了解自己能改變什麼、不能改變什麼，專注在自己能影響的範圍裡，就沒什麼需要煩惱的事了。&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/fcamel/~4/skDc8DzLpQM" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-7449996050637879222?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/7449996050637879222/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2010/04/blog-post_01.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/7449996050637879222'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/7449996050637879222'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2010/04/blog-post_01.html' title='我們不能改變手裡的牌，但是可以決定如何出牌。'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-8980347064527153563</id><published>2010-03-01T21:36:00.000+08:00</published><updated>2011-05-15T00:56:28.431+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Machine Learning'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Computer Science'/><title type='text'>我學 Machine Learning 的方法</title><content type='html'>&lt;p&gt;有人問我這個問題，想說就寫在這吧。目前還學不到家，只有斷斷續續讀個一陣子，有錯還請大家指正。&lt;br&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;h4&gt;讀 Data mining 的書&lt;/h4&gt;&lt;p&gt;一開始我讀 data mining 之類的課本、講義，發現內容講得滿概念的。但若只是想拿 ML 來用，那看這類文件會比較容易上手。比方 &lt;a href="http://www-users.cs.umn.edu/~kumar/dmbook/index.php"&gt;Introduction to Data Mining&lt;/a&gt; 寫得挺不錯的，可謂深入淺出！官網附的投影片挺棒的，可以先「快速」 (相對於看書來說……) 看一遍，再挑有興趣的部份閱讀課本。若有耐性慢慢讀完課本，會較有系統性地認識相關內容。由於碩論做 clustering 的緣故，我有好好地&lt;a href="http://fcamel.twbbs.org/archives/2007/10/07/434/"&gt;讀書本 clustering 部份&lt;/a&gt;。不過 clustering 論文種類多到爆炸，看完這本的內容也只是苦痛的開始而已……。&lt;/p&gt;&lt;h4&gt;讀國外大學的授課講義&lt;/h4&gt;&lt;p&gt;若想進一步了解 model 的原理，那就需要讀 ML 課程用的課本和講義。這類文件滿多的，其中我強力推薦 Andrew Ng 教的 &lt;a href="http://www.stanford.edu/class/cs229/"&gt;Stanford CS229 Machine Learning&lt;/a&gt;。大部份教材在講解 model 最關鍵的數學時，只會從天空掉下來一句「套用 gradient ascending 求最大值」、「運用 EM 求最佳值」之類的話，接著結果就神奇地飛出來。這份講義卻從最基礎的數學開始講，包含需要用到的微分、線性代數、機率等，讓讀者有能力自己從頭推導數學算式，算是 ML 文件裡最親切的教材。當然，讀者也要有興趣慢慢地啃才行。我大概啃了一半多一點內容，收獲良多。可惜久一沒用也只記得概念了。有機會想再從頭自己推導一次。我覺得 Andrew Ng 數學寫得相當漂亮，這是我第一次察覺寫數學式和寫程式一樣，也有美醜、易懂難懂之分。附帶一提，當初會看這份教材是看 &lt;a href="http://mmdays.com/"&gt;Mr. Saturday&lt;/a&gt; 推薦才讀的，感謝 Mr. Saturday 推薦。&lt;/p&gt;&lt;p&gt;後來我陸續看了一些不同國外大學的 ML 課程講義，發覺大家的切入點差很多，各有不同考量。像 &lt;a href="http://ocw.mit.edu/OcwWeb/Electrical-Engineering-and-Computer-Science/6-867Fall-2006/Syllabus/index.htm"&gt;MIT OCR 6.867 Machine Learning&lt;/a&gt; 有教 HMM，Stanford CS229 沒教。&lt;a href="http://hunch.net/?p=169"&gt;A Fundamentalist Organization of Machine Learning&lt;/a&gt; 提到由 Learning theory 的方式切入 ML，而不要走馬看花講一堆 model (作者稱為 “zoo approach”，挺傳神的譬喻)。我只有從 Andrew Ng 的講義裡讀了一點 learning theory，懂些基本觀念 (bias 和 variance)，感覺挺有用的。若對 learning to theory 有興趣，印象中該文作者有推薦 &lt;a href="http://www.cs.cmu.edu/~avrim/ML09/index.html"&gt;15-859(B) Machine Learning Theory，Spring 2009&lt;/a&gt;。我看了一晚講義，內容是從簡單的假設環境中推導出明確的性質，還挺有趣的。但看到後來就看不懂了，也不明白能做什麼，就沒繼續看了。&lt;/p&gt;&lt;h4&gt;讀 Wikipedia&lt;/h4&gt;&lt;p&gt;此外，我發覺 Wikipedia 是學東西的好地方。有代表性的方法早已有系統地記錄下來，又提供詳細的參考資料可以一路讀下去。比方說想學 &lt;a href="http://en.wikipedia.org/wiki/Linear_classifier"&gt;Linear classifier &lt;/a&gt;，比起查書，查 Wikipedia 更有效率。我常拿 Wikipedia 內容和其它文件交叉對照，弄明白看不懂的部份。讀這些東西需要的是耐心以及&lt;a href="http://userstyles.org/styles/1365"&gt;不傷眼的 Wikipedia CSS 樣版&lt;/a&gt;。&lt;/p&gt;&lt;h4&gt; 讀經典課本&lt;/h4&gt;&lt;p&gt;後來又看到 &lt;a href="http://mmdays.com/"&gt;Mr. Saturday&lt;/a&gt; 提到 &lt;a href="http://www-stat.stanford.edu/~tibs/ElemStatLearn/"&gt;Elements of Statistical Learning: data mining，inference，and prediction. 2nd Edition.&lt;/a&gt;，而且官網還有附完整電子書，就載來看看。不得不說這本書數學超硬，但內容超級紮實，相當值得一讀。我挑有興趣且讀得下去的部份加減看，學到 bagging、random forest 等東西，讓我擴展眼界，明白許多原理。除數學太硬之外，這本書另一問題是實驗資料太小了，令人懷疑實驗結果是否適用於真實世界的情況。只好先暫時相信大師，之後再從實驗中確認。&lt;/p&gt;&lt;p&gt;有興趣的話可以在 Amazon 查一下其它書，或以這本書為底看相關書籍。ML 有不少經典書籍，也有針對影像處理或文字處理的。&lt;/p&gt;&lt;h4&gt;其它&lt;/h4&gt;&lt;p&gt;O’Reilly 出的 &lt;a href="http://oreilly.com/catalog/9780596529321"&gt;Programming Collective Intelligence - O’Reilly Media&lt;/a&gt; 也是不錯的書，適合初學者閱讀。配合使用情境，作者簡單地解釋 model 的概念並用 Python 說明如何實作。最令人讚賞的是，作者還有比較各個模型的優缺點。雖然分析的內容不見得正確，對初學者來說挺受用的。&lt;/p&gt;&lt;h4&gt;總結&lt;/h4&gt;&lt;p&gt;讀這些理論時，我發覺有許多文件提供不同的講解方式，可以多比較一番，找自己看得懂的。不用刻意從某個地方學會某種 model。文中提的是我學習的歷程，每個人的需求不同，數學底也不同，我的經驗不見得適用於其他人。寫在這裡供大家做個參考，有錯還請告知，感謝。&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/fcamel/~4/YAnVtAp4YPM" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-8980347064527153563?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/8980347064527153563/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2010/03/machine-learning.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/8980347064527153563'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/8980347064527153563'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2010/03/machine-learning.html' title='我學 Machine Learning 的方法'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-3144074816335727984</id><published>2010-02-10T23:07:00.000+08:00</published><updated>2011-05-15T00:56:29.785+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Tips'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Research'/><category scheme='http://www.blogger.com/atom/ns#' term='Tool'/><category scheme='http://www.blogger.com/atom/ns#' term='Python'/><category scheme='http://www.blogger.com/atom/ns#' term='System'/><title type='text'>觀察實驗資料和畫圖表的小技巧</title><content type='html'>&lt;p&gt;之前在&lt;a href="http://fcamel.twbbs.org/archives/2008/01/25/464/"&gt;《減少操作實驗浪費的時間》&lt;/a&gt;提到一些做實驗的小技巧，方便自動化重覆實驗、觀察結果和產生圖表。最近實驗做得比以前更多，意外發覺更好用的方式 。有需求就會進步，這是工程師的宿命啊！&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;主要的改變有三點：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;用 Python 寫程式執行實驗。Python 功能比 shell script 強上不少，也方便維護和增加功能。&lt;a href="http://stackoverflow.com/questions/209470/can-i-use-python-as-a-bash-replacement"&gt;「Can I use Python as a bash replacement?」&lt;/a&gt;提到這樣做的好處和簡單的實施要點。什麼？你看了上篇文章發現我好像用 Ruby 較多？這．．．總之我跳槽了。男子漢做事，有時是不需要理由的！&lt;/li&gt;&lt;li&gt;改用 &lt;a href="http://www.sqlite.org/"&gt;SQLite&lt;/a&gt; 當資料庫。使用 local database 至少有兩個好處：每次實驗都重建一個新資料庫，方便保留不同實驗結果；少掉煩人的設定，可以更彈性地使用。實驗後可以用 sqlite3 看內容，或另寫程式下 SQL 彙整結果。若有架 Wiki，直接將結果輸出成 Wiki code 也不壞，容易加上顏色、粗體和表格。&lt;/li&gt;&lt;li&gt;用 &lt;a href="http://matplotlib.sourceforge.net/"&gt;matplotlib &lt;/a&gt; 畫圖。matplotlib 是一套 Python 繪圖函式庫，能畫長方圖、折線圖、直方圖等多種類型圖片。更重要的是，它畫得還挺不賴的 (見&lt;a href="http://matplotlib.sourceforge.net/gallery.html"&gt;官網範例&lt;/a&gt;)！既可以在 matplotlib 內建的應用程式裡看結果，也能存成 PNG 檔。依我個人過去用 gnuplot 的經驗，matplotlib 好用許多。不過這可能取決於使用者有多熟悉 Python。若你也喜歡用 Python，matplotlib 無疑是最好的選擇。&lt;a href="http://floatingsun.net/2006/02/07/tools-i-use-matplotlib/"&gt;「Tools I use: matplotlib」&lt;/a&gt;提出相似的見解，有興趣的人可以參考看看。&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;不論是 SQLite 還是 matplotlib 都有嚇死人詳細的文件，配合範例程式學起來挺輕鬆的。目前唯一的不便之處是，當實驗數據更多時，有時只想看部份東西，再一步步看不同層面。若一口氣把全部資料畫成圖表，不容易閱讀。有空時想來研究看看 &lt;a href="http://www.djangoproject.com/"&gt;Django&lt;/a&gt; (殺雞用牛刀嗎?)，看能不能寫個簡單網頁，做為互動呈現數據的介面。反正資料存在 SQLite 裡，容易用各種方式存取資料。&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/fcamel/~4/9V9mZk7JrfI" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-3144074816335727984?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/3144074816335727984/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2010/02/blog-post.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/3144074816335727984'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/3144074816335727984'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2010/02/blog-post.html' title='觀察實驗資料和畫圖表的小技巧'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-3443747299376059857</id><published>2010-02-05T20:23:00.000+08:00</published><updated>2011-05-15T00:56:31.894+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Python'/><title type='text'>學 Python 的入門書</title><content type='html'>&lt;p&gt;常看到有人問這問題，想說來寫篇心得，省得重覆回一樣的問題。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;我在學 Python 前已學過別的程式語言，所以想找針對已學過程式的人的書。一開始翻許多人推薦的《Learning Python》，發現它是針對沒學過程式的人，有太多篇幅在介紹基本觀念 (如 if 是什麼 )，翻沒幾頁就沒耐性看下去。而《Programming Python》又太厚了，於是找別本書。&lt;/p&gt;&lt;p&gt;後來 &lt;a href="http://traditionalchineseblog.scottt.tw/"&gt;Scott &lt;/a&gt;推薦我看&lt;a href="http://www.amazon.com/Python-Essential-Reference-David-Beazley/dp/0672329786/"&gt;《Python Essential Reference》&lt;/a&gt; (目前出到第四版，包含 Python 2.6 的新功能)，我一看就驚為天人，「if、elif、else」只有一頁！內容包含完整語法，以及特殊情況 ( 若 if 之後想放空的 statement ，要用 pass )。沒錯，這就是我要的書，去蕪存菁地讓讀者立即掌握 Python 的語法。附帶一提，Scott 明明已學會了，竟然還買了一本用來傳教，真是面惡心善的好學長。&lt;/p&gt;&lt;p&gt;看完這本後，有時查些 Python 觀念或函式名稱，發現常連到《Dive into Python》，才發覺這本也滿有名的，而且還有完整免費的網頁版。於是又找時間看了&lt;a href="http://diveintopython3.org/"&gt;《Dive into Python 3》&lt;/a&gt;，順便了解 Python 3 有啥有趣的東西。發覺這本書超合我胃口，直接用完整的例子說明語法，在學到語法的同時，也明白怎麼實際使用這些語法。有些書就像辭典一般，提了很正式的語法，讀起來很累，讀完卻不知能怎麼用它們。《Dive into Python 3》不但用生動的實例避免這個問題，並進一步深入淺出地介紹運作細節。而且還提到如重構、單元測試等寫軟體的重要觀念，又有詳細的 Python 2 和 3 的比較。若只對 Python 2 有興趣也沒關係，大部份語法仍適用於 Python 2.6 (2.6 是承接 Python 2 和 3 的橋樑)。&lt;/p&gt;&lt;p&gt;總結來說，若你沒學過程式語言，我不知道能推薦什麼書藉。或許可以參考&lt;a href="http://www.amazon.com/gp/product/1887902996/qid=1138075467/sr=2-1/ref=pd_bbs_b_2_1/002-9444419-2516027?s=books&amp;amp;v=glance&amp;amp;n=283155"&gt;《Python Programming: An Introduction to Computer Science》&lt;/a&gt;，這是 &lt;a href="http://web.mit.edu/6.00/www/info.shtml"&gt;MIT 6.00: Introduction to Computer Science and Programming&lt;/a&gt; 的參考書，該課程用 Python 教沒寫過程式的學生學會電腦科學家的思考方式。若有學過程式語言，《Python Essential Reference》或《Dive into Python 3》都值得一看。除了學會 Python 之外，兩者提供不同的額外內容，都看也不會浪費時間。&lt;/p&gt;&lt;h4&gt;延伸閱讀&lt;/h4&gt;&lt;p&gt;學會 Python 基本語法後，可以先看這幾篇了解如何寫出更道地的 Python。道地的 Python 程式不但易讀、易維護，通常也表示更有效率：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://www.python.org/dev/peps/pep-0008/"&gt;PEP 8: Style Guide for Python Code&lt;/a&gt;&lt;/li&gt;&lt;li&gt; &lt;a href="http://jaynes.colorado.edu/PythonGuidelines.html"&gt;Python Coding Guidelines&lt;/a&gt; &lt;/li&gt;&lt;li&gt;&lt;a href="http://jaynes.colorado.edu/PythonIdioms.html"&gt;Python Idioms and Efficiency&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://python.net/~goodger/projects/pycon/2007/idiomatic/handout.html"&gt;Code Like a Pythonista: Idiomatic Python&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;再來，可以到《Python Essential Reference》作者 &lt;a href="http://www.dabeaz.com/talks.html"&gt;David Beazley 的網站&lt;/a&gt;挖寶，像是：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://www.dabeaz.com/generators-uk/index.html"&gt;Generator Tricks for Systems Programmers, v2.0&lt;/a&gt;：generator 是 Python 一個強大的功能，這篇介紹超多深入的技巧。&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.dabeaz.com/python/GIL.pdf"&gt;Inside the Python GIL&lt;/a&gt;：說明為何 Python 的 multi-thread 在多 multi-core CPU 下跑得更慢。&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;另外若對 design patterns 有興趣，可以看 Google 員工 Joe Gregorio 寫的 &lt;a href="http://en.oreilly.com/oscon2008/public/schedule/detail/2810"&gt;(The Lack of) Design Patterns in Python&lt;/a&gt;。&lt;/p&gt;&lt;p&gt;《Python Cookbook》也有不少經典寫法，不過該書有點舊了 (到 Python 2.4)，有些方法已用不到，看的時候要挑一下。&lt;/p&gt;&lt;p&gt;原以為延伸閱讀應該沒多少東西，沒想到我拉拉雜雜也看了不少文件，整理到後來就累了。不知何時 Python 才會像 Java 有本《Effective Python》，一本搞定大部份的注意事項和經典寫法。&lt;/p&gt;&lt;h4&gt;備註：設定開發環境&lt;/h4&gt;&lt;p&gt;詳見&lt;a href="http://fcamel-life.blogspot.com/2010/03/python.html"&gt;《學習和開發 Python 的好幫手》&lt;/a&gt;。&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/fcamel/~4/YXRJbo8Ss1s" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-3443747299376059857?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/3443747299376059857/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2010/02/python.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/3443747299376059857'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/3443747299376059857'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2010/02/python.html' title='學 Python 的入門書'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-1694783043741478799</id><published>2010-01-31T21:08:00.000+08:00</published><updated>2011-05-15T00:56:32.980+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='閒書'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Book'/><title type='text'>專利，就是科技競爭力</title><content type='html'>&lt;p&gt;書籍基本資料：&lt;a href="http://www.books.com.tw/exep/prod/booksfile.php?item=0010237295"&gt;專利，就是科技競爭力&lt;/a&gt;&lt;/p&gt;&lt;p&gt;這年頭專利盛行，雖說專利申請書是專利工程師在寫，&lt;strong&gt;多少懂些專利知識，才能明白何謂專利，想出有用  (防範侵權或授權獲利) 的專利。&lt;/strong&gt;這本書用白話的方式配合舉例，說明專利是什麼。藉由許多正反例子，幫我釐清不少觀念，總算有種「我好像有點明白專利」的感覺。以下為一些散亂的筆記。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;h4&gt;專利的觀念澄清&lt;/h4&gt;&lt;p&gt;專利的目的是鼓勵發明，保護發明人的權利。像寫作、程式碼屬於著作權，完成的當下作者就擁有著作權。然而，其他人只要寫法不同，即使概念和我的作法完全一樣，也不會侵犯到著作權。&lt;strong&gt;專利則是保護發明人的概念。&lt;/strong&gt;以最近當紅的 MapReduce 來說，Hadoop 沒用到 Google 的原始碼，所以沒侵犯到 Google 的著作權，但是由於&lt;a href="http://patft.uspto.gov/netacgi/nph-Parser?Sect1=PTO1&amp;amp;Sect2=HITOFF&amp;amp;d=PALL&amp;amp;p=1&amp;amp;u=/netahtml/PTO/srchnum.htm&amp;amp;r=1&amp;amp;f=G&amp;amp;l=50&amp;amp;s1=7,650,331.PN.&amp;amp;OS=PN/7,650,331&amp;amp;RS=PN/7,650,331"&gt; Google 已申請到 MapReduce 的專利&lt;/a&gt;，Hadoop 就侵犯到 Google 的專利權　(Google 要不要告 Hadoop 是另一回事啦)。&lt;/p&gt;&lt;p&gt;但也&lt;strong&gt;不是光提概念就能申請專利，比方說申請「讓人類飛在空中」就不會通過，除提出概念外，至少還有提及一種實現方法。&lt;/strong&gt;不過專利的重點是保護概念，而非實現方法。但這也得看專利的寫法，其他人可能以不同的實現方式迴避專利。我還沒搞清楚這部份差異。&lt;/p&gt;&lt;p&gt;閱讀專利書時，&lt;strong&gt;最重要的部份是申請專利範圍 (claim)&lt;/strong&gt;，其它部份只是交代相關知識，並無法律效果。&lt;/p&gt;&lt;p&gt;專利訴訟時會以專利範圍去比對侵權產品，比方說專利 A 包含 A1、A2 兩個元件，產品 B 包含 A1、B2，那麼產品 B 就沒有侵權。若產品 C 包含 A1、A2、C3，那產品 C 有侵權。用數學式來表示就是：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;if A ^ B == A, then&lt;br&gt;    B 侵犯到 A 專利&lt;br&gt;else&lt;br&gt;    無侵權&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;若從字意上的解讀無法看出 B 包含 A 的專利範圍，可進一步做推論，判斷兩者是否有同意的詞。書中以鐵槌為例，若專利 A 和產品 B 只差在 A 用拉環來掛鐵槌，B 用挖洞。這就看法官是否認為這兩者是同樣的概念。&lt;/p&gt;&lt;p&gt;專利範圍的條件愈多，範圍愈小。所以若有兩個限制 A1、A2，若能拆開各別申請，效果會更好，但也會比較難申請。&lt;/p&gt;&lt;p&gt;由以上的描述可知，專利說明書的品質相當重要。現今廣為使用在滑鼠的滾輪，並不是第一個發明滾輪的人。在那之前也有人申請專利，只是他聲言的專利範圍除「輔助操作件」 (比方滾輪) 外，還多提到「彈性元件」，所以後來為滾輪申請專利就沒有侵權。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;即使申請到專利 A，也不見得可以行使專利 A。&lt;/strong&gt;比方說專利 A 包含 A1、A2 兩點，但可能 A1、A2 各有一個專利，於是要行使專利 A 的話，還得先和 A1、A2 的所有人購買專利權。&lt;/p&gt;&lt;h4&gt;那些項目可以申請專利？&lt;/h4&gt;&lt;p&gt;去除是否能獲利，&lt;strong&gt;申請專利其實是很簡單的事，只要滿足專利三要素即可&lt;/strong&gt;：&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;有用性：&lt;/strong&gt;&lt;strong&gt;提出對發明人而言有用的理由即可，&lt;/strong&gt;比方「狗手錶」聲稱給狗帶的手錶，實際上就是快七倍時間的手錶。申請人聲稱這可讓狗的主人明白狗的生命短暫，更珍惜狗。&lt;/li&gt;&lt;li&gt;&lt;strong&gt;新穎性：&lt;/strong&gt;&lt;strong&gt;過去全世界文獻 (通常是索引專利資料庫) 中沒有出現過的方法。&lt;/strong&gt;除不重要的細節外，只要有一絲不同，就有新穎性。所以有時即使是相當直覺的東西，沒有文獻就無法退回申請。另外若審查官懶得看他國的專利資料庫，也可能發生日本已有案例，卻仍在台灣申請成功。但這樣的專利也可能無用，事後若有人發現這點，可申請專利無效 (打官司時對手最常幹的事)。&lt;/li&gt;&lt;li&gt;&lt;strong&gt;進步性 (非顯而易見性)：&lt;/strong&gt;新穎性是一對一比方，而進步性是拿過去多件作品和目前的申請比對。申請人要證明過去任何方法都無法組出申請方法能達到的效果。那怕是 90% 的效果變成 90.1%，都可以申請。只要該領域的技術人士無法輕易想出你的作法，就有非顯而易見性。為免事後諸葛，審查時會比較寬鬆。進步性也不是非要「進步」不可，重點是「非顯而易見」。有許多高爾夫球專利，差別在於表面的洞有不同排列方式。到底有沒有飛得比較遠？那不是重點，&lt;strong&gt;只要不是「輕易」想到即可。&lt;/strong&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;比方說現在已有電視這種東西，只要隨便把電視和另一個不相干的東西組在一起，就能機會申請專利。像是兼具暖爐功能的電視、能養魚的電視等。雖然拆開來看都是習以為常的產品，合起來卻是「沒有人想過」的概念 (無文獻記載)。書中舉了許多有趣的例子，像是避免忘了帶筆，把筆放在鑰匙圈裡、放在手錶裡、放在項鍊裡都被申請成專利。&lt;/p&gt;&lt;p&gt;新用途可以申請專利，像是阿司匹林原本是感冒藥，卻發現也有防治心血管疾病效果。後來這個新用途就被申請成專利。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;雖然數學公式無法申請專利，使用數學的應用方式卻可以。&lt;/strong&gt;所以無法申請傅立葉轉換，但可以申請「讀入一串資料，做傅立葉轉換，接著輸出」。可以歸可以，會不會過是另一回事 (比方說已有論文或書籍提及這種作法，那就不具新穎性了）。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;軟體也可以申請專利，&lt;/strong&gt;像美國賭場的吃角子老虎，以前是各台單獨累積獎金。有人提出讓全部機器連線，統一累積獎金，並用軟體控制產生的結果，確保中獎機率不會過高。這人有成功地申請專利，後來賣給賭場。還有像 Yahoo 也有申請到個人化網頁的專利：&lt;a href="http://www.google.com/patents/about?id=H8QWAAAAEBAJ&amp;amp;dq=5983227"&gt;Dynamic page generator&lt;/a&gt;：&lt;br&gt;&lt;blockquote&gt;An custom page server is provided with user preferences organized into templates stored in compact data structures and the live data used to fill the templates stored local to the page server which is handing user requests for custom pages. One process is executed on the page server for every…&lt;/blockquote&gt;&lt;/p&gt;&lt;p&gt;注意，這裡保護的重點仍是概念，不是程式碼。一個大型軟體專案包含許多概念，可能會抽出其中幾點符合專利三要素的元件來申請專利，而不會申請整個軟體專案。作者以 e-mail 系統舉的例子滿不錯的，一個新的 e-mail 系統包含更好的編輯器和更好的偵測機制，編輯器和 Word 相比可能不具進步性 (使用者可以用 Word 編好內容，再用貼到舊的 e-mail 編輯器)，然而偵測垃圾郵件的機制可能有進步性。&lt;/p&gt;&lt;p&gt;原本我也挺&lt;a href="http://en.wikipedia.org/wiki/Software_patent_debate"&gt;反對軟體專利&lt;/a&gt; (應該說我還沒搞懂它的含蓋範圍，萬一 design pattern 被專利了，會發生什麼事？)，不過作者舉的一些例子也滿有道理的，像是支援附加功能的傳真機，附加功能可能是傳真結束後輸出一張紙表示上個操作有無成功。這不需更新硬體即可達成，是很有用的功能 (現今有電子螢幕就不需要啦)。若沒有軟體專利，競爭對手即可抄走這個概念，違反了專利保護發明人概念的原意。&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/fcamel/~4/gpFS2LGKQP0" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-1694783043741478799?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/1694783043741478799/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2010/01/blog-post.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/1694783043741478799'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/1694783043741478799'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2010/01/blog-post.html' title='專利，就是科技競爭力'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-6711848365156091419</id><published>2010-01-05T21:35:00.000+08:00</published><updated>2011-05-15T00:56:34.811+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Python'/><title type='text'>Python 加速的方法</title><content type='html'>&lt;p&gt;這一年來用 Python 開發滿爽的，用久後發覺 Python 真的是不夠快啊。雖說這就是 trade-off，拿 CPU 執行時間換工程師開發的時間，但關鍵時刻跑得不夠快，還是得投入時間研究如何加速。程式開發就是一連串的 trade-off，人生也是如此啊！…………………還是少嘴炮，切入正題吧。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;h4&gt;用 Profiling 測全體執行時間&lt;/h4&gt;&lt;p&gt;去除演算法、資料結構的問題外 (像是 O(N log(N)) vs. O(N^2) 的方法 )，千萬別自己亂猜效能瓶頸就一頭熱地改程式。隨便想想都能舉出一堆自以為聰明的反例。我印象最深的經驗是，有一次我在寫資料結構作業答案，題目是實作 hash table 來統計文章裡單字出現的次數，身為課程助教，程式當然不能跑得太慢啊。所以調了一下效能，結果發覺改了一些演算法 (如 hash table 的 「chain」用 binary tree 而不用 linked-list )，卻沒快多少 (變慢是常有的事)，一用 profiler 量，卻發現 1/3 的時間花在把一行文字切成單字上。我原本用 Java 的 split 一次切完回傳一個 List，改用 StringTokenizer 後就省下整體 1/3 的時間。&lt;/p&gt;&lt;p&gt;Python 有內建 profiling 的工具，叫做 &lt;a href="http://docs.python.org/library/profile.html"&gt;profile&lt;/a&gt;，可以透過命令列直接下參數使用：&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;python &lt;span style="color:#660033"&gt;-m&lt;/span&gt; cProfile &lt;span style="color:#660033"&gt;-s&lt;/span&gt; &lt;span style="color:#000000;font-weight:bold"&gt;time&lt;/span&gt; MODULE.py ARGUMENTS&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;這樣會執行 MODULE 再將測量結果輸出到螢幕，時間依單一函式執行時間來排序。引用&lt;a href="http://docs.python.org/library/profile.html#module-pstats"&gt;官網的例子&lt;/a&gt;，cProfile 的輸出格式如下：&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;2&lt;br /&gt;3&lt;br /&gt;4&lt;br /&gt;5&lt;br /&gt;6&lt;br /&gt;7&lt;br /&gt;8&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;     &lt;span style="color:#000099"&gt;2706&lt;/span&gt; function calls &lt;span style="color:#000099"&gt;(&lt;/span&gt;&lt;span style="color:#000099"&gt;2004&lt;/span&gt; primitive calls&lt;span style="color:#000099"&gt;)&lt;/span&gt; in &lt;span style="color:#000099"&gt;4.504&lt;/span&gt; CPU seconds&lt;br /&gt; &lt;br /&gt;Ordered by: standard name&lt;br /&gt; &lt;br /&gt;ncalls  tottime  percall  cumtime  percall filename:lineno&lt;span style="color:#000099"&gt;(&lt;/span&gt;function&lt;span style="color:#000099"&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span style="color:#000099"&gt;2&lt;/span&gt;    &lt;span style="color:#000099"&gt;0.006&lt;/span&gt;    &lt;span style="color:#000099"&gt;0.003&lt;/span&gt;    &lt;span style="color:#000099"&gt;0.953&lt;/span&gt;    &lt;span style="color:#000099"&gt;0.477&lt;/span&gt; pobject.py:&lt;span style="color:#000099"&gt;75&lt;/span&gt;&lt;span style="color:#000099"&gt;(&lt;/span&gt;save_objects&lt;span style="color:#000099"&gt;)&lt;/span&gt;&lt;br /&gt; &lt;span style="color:#000099"&gt;43&lt;/span&gt;/&lt;span style="color:#000099"&gt;3&lt;/span&gt;    &lt;span style="color:#000099"&gt;0.533&lt;/span&gt;    &lt;span style="color:#000099"&gt;0.012&lt;/span&gt;    &lt;span style="color:#000099"&gt;0.749&lt;/span&gt;    &lt;span style="color:#000099"&gt;0.250&lt;/span&gt; pobject.py:&lt;span style="color:#000099"&gt;99&lt;/span&gt;&lt;span style="color:#000099"&gt;(&lt;/span&gt;evaluate&lt;span style="color:#000099"&gt;)&lt;/span&gt;&lt;br /&gt; ...&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;前面的 tottime 是該函式自己執行的時間；後面的 cumtime 則是該函式整體 (包含它呼叫其它函式) 的執行時間。&lt;/p&gt;&lt;p&gt;附帶一提，以前試 Java profiling 工具時，執行速度會慢個數倍，但用 Python 的 cProfile 卻只變成一倍多慢而已，也可能我那時用的 Java 工具太糟，或參數沒設好吧。&lt;/p&gt;&lt;h4&gt;用 timeit 來量單一 expression 的速度&lt;/h4&gt;&lt;p&gt;timeit 也是內建模組，也可以用命令列執行：&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;python &lt;span style="color:#660033"&gt;-m&lt;/span&gt; timeit EXPRESSION&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;下面是執行的例子：&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;2&lt;br /&gt;3&lt;br /&gt;4&lt;br /&gt;5&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;$ python &lt;span style="color:#660033"&gt;-m&lt;/span&gt;  timeit &lt;span style="color:#ff0000"&gt;'range(10)'&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#000000"&gt;1000000&lt;/span&gt; loops, best of &lt;span style="color:#000000"&gt;3&lt;/span&gt;: &lt;span style="color:#000000"&gt;0.3&lt;/span&gt; usec per loop&lt;br /&gt; &lt;br /&gt;$ python &lt;span style="color:#660033"&gt;-m&lt;/span&gt;  timeit &lt;span style="color:#ff0000"&gt;'xrange(10)'&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#000000"&gt;10000000&lt;/span&gt; loops, best of &lt;span style="color:#000000"&gt;3&lt;/span&gt;: &lt;span style="color:#000000"&gt;0.166&lt;/span&gt; usec per loop&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;我比較常寫程式來測 timeit，透過命令列執行 expression 相當受限，還是自己寫段程式包成函式後，再用 timeit 執行較方便。附上一個例子：&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;2&lt;br /&gt;3&lt;br /&gt;4&lt;br /&gt;5&lt;br /&gt;6&lt;br /&gt;7&lt;br /&gt;8&lt;br /&gt;9&lt;br /&gt;10&lt;br /&gt;11&lt;br /&gt;12&lt;br /&gt;13&lt;br /&gt;14&lt;br /&gt;15&lt;br /&gt;16&lt;br /&gt;17&lt;br /&gt;18&lt;br /&gt;19&lt;br /&gt;20&lt;br /&gt;21&lt;br /&gt;22&lt;br /&gt;23&lt;br /&gt;24&lt;br /&gt;25&lt;br /&gt;26&lt;br /&gt;27&lt;br /&gt;28&lt;br /&gt;29&lt;br /&gt;30&lt;br /&gt;31&lt;br /&gt;32&lt;br /&gt;33&lt;br /&gt;34&lt;br /&gt;35&lt;br /&gt;36&lt;br /&gt;37&lt;br /&gt;38&lt;br /&gt;39&lt;br /&gt;40&lt;br /&gt;41&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;&lt;span style="color:#808080;font-style:italic"&gt;#!/usr/bin/env python&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#808080;font-style:italic"&gt;# Count numbers in a list.&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#808080;font-style:italic"&gt;# Demonstrate built-in functions' efficiency.&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;import&lt;/span&gt; &lt;span style="color:#dc143c"&gt;timeit&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;import&lt;/span&gt; &lt;span style="color:#dc143c"&gt;random&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;&lt;span style="color:#808080;font-style:italic"&gt;# prepare test data&lt;/span&gt;&lt;br /&gt;numbers = &lt;span style="color:#008000"&gt;range&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#ff4500"&gt;2&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt;a = &lt;span style="color:black"&gt;[&lt;/span&gt;&lt;span style="color:black"&gt;]&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;for&lt;/span&gt; n &lt;span style="color:#ff7700;font-weight:bold"&gt;in&lt;/span&gt; numbers:&lt;br /&gt;   a += &lt;span style="color:black"&gt;[&lt;/span&gt;n&lt;span style="color:black"&gt;]&lt;/span&gt; &lt;span style="color:#66cc66"&gt;*&lt;/span&gt; &lt;span style="color:#ff4500"&gt;10000&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#dc143c"&gt;random&lt;/span&gt;.&lt;span style="color:black"&gt;shuffle&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;a&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;def&lt;/span&gt; &lt;span style="color:#dc143c"&gt;test&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;:&lt;br /&gt;   c = &lt;span style="color:black"&gt;{&lt;/span&gt;&lt;span style="color:black"&gt;}&lt;/span&gt;&lt;br /&gt;   &lt;span style="color:#ff7700;font-weight:bold"&gt;for&lt;/span&gt; x &lt;span style="color:#ff7700;font-weight:bold"&gt;in&lt;/span&gt; a:&lt;br /&gt;       c&lt;span style="color:black"&gt;[&lt;/span&gt;x&lt;span style="color:black"&gt;]&lt;/span&gt; = c.&lt;span style="color:black"&gt;get&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;x, &lt;span style="color:#ff4500"&gt;0&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt; + &lt;span style="color:#ff4500"&gt;1&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;def&lt;/span&gt; test2&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;:&lt;br /&gt;   c = &lt;span style="color:black"&gt;{&lt;/span&gt;&lt;span style="color:black"&gt;}&lt;/span&gt;&lt;br /&gt;   &lt;span style="color:#ff7700;font-weight:bold"&gt;for&lt;/span&gt; n &lt;span style="color:#ff7700;font-weight:bold"&gt;in&lt;/span&gt; numbers:&lt;br /&gt;       c&lt;span style="color:black"&gt;[&lt;/span&gt;n&lt;span style="color:black"&gt;]&lt;/span&gt; = &lt;span style="color:#ff4500"&gt;0&lt;/span&gt;&lt;br /&gt;   &lt;span style="color:#ff7700;font-weight:bold"&gt;for&lt;/span&gt; x &lt;span style="color:#ff7700;font-weight:bold"&gt;in&lt;/span&gt; a:&lt;br /&gt;       c&lt;span style="color:black"&gt;[&lt;/span&gt;x&lt;span style="color:black"&gt;]&lt;/span&gt; += &lt;span style="color:#ff4500"&gt;1&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;def&lt;/span&gt; test3&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;:&lt;br /&gt;   c = &lt;span style="color:black"&gt;{&lt;/span&gt;&lt;span style="color:black"&gt;}&lt;/span&gt;&lt;br /&gt;   &lt;span style="color:#ff7700;font-weight:bold"&gt;for&lt;/span&gt; n &lt;span style="color:#ff7700;font-weight:bold"&gt;in&lt;/span&gt; numbers:&lt;br /&gt;       c&lt;span style="color:black"&gt;[&lt;/span&gt;n&lt;span style="color:black"&gt;]&lt;/span&gt; = a.&lt;span style="color:black"&gt;count&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;n&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt; &lt;br /&gt; &lt;br /&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;if&lt;/span&gt; __name__==&lt;span style="color:#483d8b"&gt;'__main__'&lt;/span&gt;:&lt;br /&gt;   t = &lt;span style="color:#dc143c"&gt;timeit&lt;/span&gt;.&lt;span style="color:black"&gt;Timer&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#483d8b"&gt;&amp;quot;test()&amp;quot;&lt;/span&gt;, &lt;span style="color:#483d8b"&gt;&amp;quot;from __main__ import test&amp;quot;&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt;   &lt;span style="color:#ff7700;font-weight:bold"&gt;print&lt;/span&gt; &lt;span style="color:#483d8b"&gt;'test'&lt;/span&gt;, t.&lt;span style="color:#dc143c"&gt;timeit&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;number=&lt;span style="color:#ff4500"&gt;1000&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;   t = &lt;span style="color:#dc143c"&gt;timeit&lt;/span&gt;.&lt;span style="color:black"&gt;Timer&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#483d8b"&gt;&amp;quot;test2()&amp;quot;&lt;/span&gt;, &lt;span style="color:#483d8b"&gt;&amp;quot;from __main__ import test2&amp;quot;&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt;   &lt;span style="color:#ff7700;font-weight:bold"&gt;print&lt;/span&gt; &lt;span style="color:#483d8b"&gt;'test2'&lt;/span&gt;, t.&lt;span style="color:#dc143c"&gt;timeit&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;number=&lt;span style="color:#ff4500"&gt;1000&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;   t = &lt;span style="color:#dc143c"&gt;timeit&lt;/span&gt;.&lt;span style="color:black"&gt;Timer&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#483d8b"&gt;&amp;quot;test3()&amp;quot;&lt;/span&gt;, &lt;span style="color:#483d8b"&gt;&amp;quot;from __main__ import test3&amp;quot;&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt;   &lt;span style="color:#ff7700;font-weight:bold"&gt;print&lt;/span&gt; &lt;span style="color:#483d8b"&gt;'test3'&lt;/span&gt;, t.&lt;span style="color:#dc143c"&gt;timeit&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;number=&lt;span style="color:#ff4500"&gt;1000&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;有興趣的人可以猜猜看那個方法最快 [1]。&lt;/p&gt;&lt;h4&gt;找出瓶頸後加速的方法&lt;/h4&gt;&lt;p&gt;用 profiling 找出瓶頸後，大概有幾種作法：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;用 &lt;a href="http://docs.python.org/library/multiprocessing.html"&gt;multiprocessing&lt;/a&gt; 平行化：這年頭 CPU 不用錢，隨便買都是 multi-core。若程式可以單純平行化，這個作法比用 C/C++ 方便一些，至少還是在寫 python code。但若程式稍微複雜，寫起來會很辛苦，且容易因 fork 或 lock 的 overhead 而沒快多少。&lt;/li&gt;&lt;li&gt;將 CPU bound 的地方透過 &lt;a href="http://docs.python.org/library/ctypes.html"&gt;ctypes&lt;/a&gt; 改寫成 C/C++：雖說 ctypes 很好用，不過寫 C/C++ 得注意的事比寫 Python 多太多了，所以我滿少這麼幹的。&lt;/li&gt;&lt;li&gt;從 Python interpreter 下手，改用較快的寫法：這個作法需要一些 Python 運作的知識，才知道怎麼改會比較快。比方若不需用到整個 list 可以改用 generator 減少產生 list 的時間、使用 member field 存取資料以減少 function call、改用 built-in 函式組出要做的事 (如上面附的例子)。這時 timeit 就很好用了，可以先測好關鍵部份，減少重跑整個 profiling 的時間。&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;不勞而獲的加速方法&lt;/h4&gt;&lt;p&gt;我只有偶而注意相關消息，還沒試過，只知道各家方法有些不適用的問題，這裡記些關鍵字。&lt;/p&gt;&lt;p&gt;&lt;a href="http://psyco.sourceforge.net/"&gt;Psyco&lt;/a&gt; 是老字號工具，大家都說讚，對數值運算特別有效，但它只支援 32-bit CPU，作者目前在開發 &lt;a href="http://codespeak.net/pypy/dist/pypy/doc/"&gt;PyPy&lt;/a&gt;。另外 Google 開發的 &lt;a href="http://code.google.com/p/unladen-swallow/"&gt;unladen-swallow&lt;/a&gt; 也令人期待。也許在不久的將來，兩邊都會有足以實用的成果吧。&lt;/p&gt;&lt;h4&gt;延伸閱讀&lt;/h4&gt;&lt;p&gt;經 &lt;a href="http://victor.csie.org/"&gt;Victor&lt;/a&gt; 告知，Python 官方 Wiki 有幾篇超棒的文件，相當值得一看：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://wiki.python.org/moin/PythonSpeed/"&gt;Python speed&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://wiki.python.org/moin/PythonSpeed/PerformanceTips"&gt;Python Speed - Performance Tips&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt; 備註 &lt;/h4&gt;&lt;ol&gt;&lt;li&gt;這是我執行出來的數據：&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;2&lt;br /&gt;3&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;test &lt;span style="color:#000099"&gt;5.41639614105&lt;/span&gt;&lt;br /&gt;test2 &lt;span style="color:#000099"&gt;2.70715403557&lt;/span&gt;&lt;br /&gt;test3 &lt;span style="color:#000099"&gt;0.619280815125&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;即使走訪 list 兩次，方法三還是比前兩個方法 (只走訪一次) 快上不少，這是因為方法三用 C 實作的程式走訪迴圈，而前兩個方法用到 Python 的迴圈。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;img src="http://feeds.feedburner.com/~r/fcamel/~4/faYD0hiE7PU" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-6711848365156091419?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/6711848365156091419/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2010/01/python.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/6711848365156091419'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/6711848365156091419'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2010/01/python.html' title='Python 加速的方法'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-3252818381943807520</id><published>2009-11-08T11:02:00.000+08:00</published><updated>2011-05-15T00:56:36.755+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Refactoring'/><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Testing'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><title type='text'>我學 TDD 的方式</title><content type='html'>&lt;p&gt;學 &lt;a href="http://fcamel.twbbs.org/archives/2009/03/09/747/"&gt;TDD ( 測試驅動開發 )&lt;/a&gt; 的動機請見&lt;a href="http://fcamel.twbbs.org/archives/2009/06/13/849/"&gt;《為什麼要寫 unit test？為什麼要先寫測試？》&lt;/a&gt;。這篇簡記一下我學習 TDD 的方式：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;TDD 的流程、精神：網路上零散的短文 [1] 和 Kent Beck 的書&lt;a href="http://www.amazon.com/Test-Driven-Development-Kent-Beck/dp/0321146530"&gt;《Test Driven Development: By Example 》&lt;/a&gt;。&lt;/li&gt;&lt;li&gt;Testing 的技巧：從 Google 的 Agile Coach &lt;a href="http://misko.hevery.com/"&gt;Miško Hevery 的 Blog&lt;/a&gt; 學。&lt;/li&gt;&lt;li&gt;Refactoring 的技巧：從 Martin Fowler 的書&lt;a href="http://www.books.com.tw/exep/prod/booksfile.php?item=0010231852"&gt;《重構：改善既有程式的設計》&lt;/a&gt;以及他的 &lt;a href="http://martinfowler.com/articles.html"&gt;Blog &lt;/a&gt;學&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;看完書後，大致上是從大師的 Blog 跳著看有興趣（或著說，讀起來比較有感覺）的部份。自己寫個一陣子就看個幾篇，待思考消化後再回頭試著用自己的方式寫出來。如此反覆進行。&lt;strong&gt;最重要的是在自己的環境裡，用自己的方式進行 TDD，感受會更明確。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;TDD 的概念很簡單，看過例子後應該能馬上使用，但無法立即精通 TDD 。&lt;strong&gt;它是一個習慣，而培養習慣需要時間累積。&lt;/strong&gt;養成習慣後，接著&lt;strong&gt;需要提昇測試和重構的技巧，才能逐步提昇效果並降低使用 TDD 的成本。&lt;/strong&gt;如同過去學 Design Pattern 或其它寫程式的技巧一般，需要讀書和時間練習。我在前幾次使用 TDD 時有犯一些錯，使得效果打折，有感受到一些好處，但也對一些壞處感到疑惑。直到寫了&lt;br&gt;五、六個小專案後，才釐清一些疑慮，確信 TDD 是很划算的取捨，從而決定持續使用 TDD。最近用它&lt;a href="http://fcamel.twbbs.org/archives/2009/10/29/928/"&gt;寫超過一萬行程式&lt;/a&gt;，更加感受到它的威力。&lt;/p&gt;&lt;h4&gt;備註&lt;/h4&gt;&lt;ol&gt;&lt;li&gt;Java 的例子可見 &lt;a href="http://butunclebob.com/ArticleS.UncleBob.TheBowlingGameKata"&gt;The Bowling Game Kata&lt;/a&gt;；Python 的例子可見 Dive into Python 3 &lt;a href="http://diveintopython3.org/unit-testing.html"&gt;介紹 unittest module 的章節&lt;/a&gt;和&lt;a href="http://diveintopython3.org/refactoring.html"&gt;介紹 refactoring 的章節&lt;/a&gt;。《Dive into Python 3》的例子比較不像真實的 TDD，為了教學方便，作者直接寫最終版的測試碼。若要看原汁原味的演化過程，看 Java 的例子或 Kent Beck 的書較適當。&lt;/li&gt;&lt;/ol&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-3252818381943807520?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/3252818381943807520/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2009/11/tdd.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/3252818381943807520'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/3252818381943807520'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2009/11/tdd.html' title='我學 TDD 的方式'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-2170088979610199087</id><published>2009-10-29T23:40:00.000+08:00</published><updated>2011-05-15T00:56:37.748+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Testing'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Python'/><title type='text'>賀！Python 寫破萬行記念！</title><content type='html'>&lt;p&gt;這是我獨力寫過最大的專案了，從八月初開始寫，不知不覺寫到破萬行啊，而且其中有四千多行是測試碼，貫徹當初 TDD 到底的決心。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;不得不說，幸虧有用 TDD，不然測試碼大概會丟三落四的，愈後面會愈痛苦，最後就會想砍掉重練。開發過程裡遇過幾次很難改的情況（第一次發生在寫到兩千多行時），幸好仗著測試碼夠齊全，將相關程式重構後，不知不覺就把新功能寫好了。&lt;/p&gt;&lt;p&gt;有時沒啥想法，不知怎麼改較好，就想說「總之就先重構，船到橋頭自然直」。結果這招矇對的機率還滿高的，有時還會發覺比原本構想更簡單的做法。有些人認為重構成本很高，且短期沒有產值，似乎不適合常做。事實上，在測試碼充沛且時常重構的情況下，可以三兩下完成重構，再加上版本管理系統的輔助，重構失敗立即重來，十分方便。&lt;/p&gt;&lt;p&gt;使用 TDD 偶而會有意外收獲，發覺巧妙設計。有時即使看個數次，還是學不會自己完成目標用上的新技巧，感覺好像用外掛過關，卻不知道自己怎麼過的。最後只能期望下回遇到類似的處理，TDD 仍能導引出良好的設計。對照&lt;a href="http://ihower.idv.tw/blog/archives/1960"&gt;《物件導向程式的九個體操練習》&lt;/a&gt;來看，發現在 TDD 的導引之下，自然地會做出較佳的設計（因為較差的設計不好測），而且會因地制宜，做出貼近需求的做法，不會 overdesign。&lt;/p&gt;&lt;p&gt;雖說在很久以前就確定 TDD 是正確的路，也領悟出可測性 (testability) 是首要之務，為了可測性而改變原本的設計也是合適的決定。還是需要許多實際經驗，才能更充份地體會這些原則帶來的影響。我喜歡 freedom 說過的一句話「 system design 一直都是 trade-off」，經過這一年多的歷練 [1]，我更肯定 TDD 帶來的 trade-off 是值得的，加強寫測試碼的技術絕對是值回票價的投資。&lt;/p&gt;&lt;p&gt;咦？這篇好像離題成行銷 TDD，回頭來補講 Python 的心得。寫這個專案的途中接觸到比較深的 Python 議題，像是用 ctypes 包 C/C++ API 很方便；好用的畫圖函式庫 matplotlib；還有一拖拉庫的速度問題讓我學了不少，比方說：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;cProfile 絕對是找瓶頸的頭號幫手，做 profiling 時也不會拖慢多少。除演算法的考量外，別自己亂猜浪費時間。&lt;/li&gt;&lt;li&gt;list 很大但不需用到 sublist 或 index 的功能時，可以用 generator 改寫。&lt;/li&gt;&lt;li&gt;若有個欄位被存取數百萬次以上，用 data field 會比用 method 快上不少，因為 Python 的 function call 不快。&lt;/li&gt;&lt;li&gt;collections.defaultdict 比 dict.get(key, default_value) 快上一些。&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.dabeaz.com/blog/2009/08/inside-inside-python-gil-presentation.html"&gt;CPython 使用 GIL 造成 multi-thread 比 single-thread 還慢&lt;/a&gt;，要想用多 CPU 加速，可以用 2.6 內建的 multiprocessing。multiprocessing 用法不難，可是有一堆小細節會爆炸，要仔細讀&lt;a href="http://docs.python.org/library/multiprocessing.html"&gt;官方文件&lt;/a&gt;。&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;雖說調速度很辛苦，整體來看還是值得的，而且 Python 和 C/C++ 之間溝通很容易，針對效能改寫的成本不高，更何況有測試碼撐著，修改很容易。讓我覺得用 Python 快速開發原型，針對瓶頸用 C/C++ 改寫或平行化的流程還挺不錯的。&lt;/p&gt;&lt;p&gt;除了以上這堆拉里拉雜的收獲，更大的收獲是學到了一個專案的演化過程。過去自己太心急，什麼都想一開始做到最好，擔心後期會愈來愈難改，災難一發不可收拾。結果是搞得自己很累，開發效率很差、程式品質漸漸變糟也不知道從何改善。後來放開心胸，延伸 TDD 的精神，先做最主要的部份，再慢慢修補小問題：像是註解、模組文件格式、行寛該定多少 [2]、某設計會不會太慢、會不會太髒等。稍微轉換思維、改變自己的習慣，會發現過去許多困擾都不見了，似乎多明白了一點「擁抱變化」的含意了 [3]。&lt;/p&gt;&lt;h4&gt;備註&lt;/h4&gt;&lt;ol&gt;&lt;li&gt;接觸 TDD 的概念；用它實作近十個小專案包含 C++、Java、Python 三種語言；單一專案寫了上萬行的程式。&lt;/li&gt;&lt;li&gt;我後來習慣用 85。這樣用 24″ 螢幕配 putty 預設字型大小，用 VIM 剛好可以垂直分割成兩個視窗而不會折行。&lt;/li&gt;&lt;li&gt;話說我以前一直覺得這個詞很嘴炮啊。&lt;/li&gt;&lt;/ol&gt;&lt;img src="http://feeds.feedburner.com/~r/fcamel/~4/q17zYSbKu5U" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-2170088979610199087?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/2170088979610199087/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2009/10/python.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/2170088979610199087'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/2170088979610199087'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2009/10/python.html' title='賀！Python 寫破萬行記念！'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-51962912737368630</id><published>2009-09-22T22:42:00.000+08:00</published><updated>2011-05-15T00:56:38.793+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Testing'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Python'/><title type='text'>寫 Python 測試碼的好幫手</title><content type='html'>&lt;p&gt;前陣子用 &lt;a href="http://fcamel.twbbs.org/archives/2009/03/09/747/"&gt;TDD&lt;/a&gt; 寫了個六千多行的工具，這篇記錄一下過程中惠我良多的好幫手。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;h4&gt;unittest&lt;/h4&gt;&lt;p&gt;內建模組。&lt;/p&gt;&lt;p&gt;用法見&lt;a href="http://docs.python.org/library/unittest.html"&gt;官網介紹&lt;/a&gt;或&lt;a href="http://diveintopython3.org/unit-testing.html"&gt;《Dive into Python 3》的第九章 Unit Testing&lt;/a&gt; ( 有附帶介紹 TDD ) ，如同 Java 的 JUnit 3.x，用 unittest.TestCase 來寫 test case 很方便。&lt;/p&gt;&lt;h4&gt;coverage&lt;/h4&gt;&lt;p&gt;安裝方式：easy_install coverage。&lt;/p&gt;&lt;p&gt;&lt;a href="http://en.wikipedia.org/wiki/Code_coverage"&gt;Code coverage&lt;/a&gt; 是分析測試碼品質的方法，標示出測試碼沒有執行到那幾行程式碼。使用 &lt;a href="http://en.wikipedia.org/wiki/Code_coverage"&gt;Code coverage&lt;/a&gt; 的基本精神是：&lt;a href="http://en.wikipedia.org/wiki/Code_coverage"&gt;Code coverage&lt;/a&gt; 的數據高不表示測試碼有效，但數據低的話，測試品質必定不好。至於高低要如何界定？這得看專案的類型，比方科學計算型的程式，要求 80% 以上並不過份；而有一堆圖形介面的程式，可能連到 60% 都很難。&lt;/p&gt;&lt;p&gt;&lt;a href="http://nedbatchelder.com/code/coverage/"&gt;coverage 官網&lt;/a&gt;有簡單易懂的使用例子。由於 Python 的功能限制，&lt;a href="http://nedbatchelder.com/code/coverage/"&gt;coverage&lt;/a&gt; 無法作到像 Java 那麼全面。&lt;a href="http://nedbatchelder.com/code/coverage/"&gt;coverage&lt;/a&gt; 的限制以及 &lt;a href="http://en.wikipedia.org/wiki/Code_coverage"&gt;Code coverage&lt;/a&gt; 的注意事項，詳見作者的說明：&lt;a href="http://us.pycon.org/2009/conference/schedule/event/26/"&gt;“Coverage testing, the good and the bad.”&lt;/a&gt;&lt;/p&gt;&lt;h4&gt;nose&lt;/h4&gt;&lt;p&gt;安裝方式： easy_install nose。&lt;/p&gt;&lt;p&gt;寫 unittest 時，管理 test suit 是件很瑣碎又易犯錯的事，相信很多人會想說，能不能跑個程式，自行搜集目錄下全部的測試碼並自動執行。沒錯，大家的心聲 &lt;a href="http://somethingaboutorange.com/mrl/projects/nose/0.11.1/index.html"&gt;nose&lt;/a&gt; 聽到了！這裡直接用例子說明 &lt;a href="http://somethingaboutorange.com/mrl/projects/nose/0.11.1/index.html"&gt;nose&lt;/a&gt; 的使用方式：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;執行目前目錄下所有測試：&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;nosetests&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;執行目前目錄下所有測試並附上子目錄 pkg1、pkg2 的 &lt;a href="http://en.wikipedia.org/wiki/Code_coverage"&gt;Code coverage&lt;/a&gt; 資訊：&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;nosetests --with-coverage --cover-package=pkg1,pkg2 --cover-erase&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;不要執行 slow_test.py：&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;nosetests -e slow_test.py&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;使用四個 CPU 平行執行測試：&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;nosetests --processes=4&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;–with-coverage 需要先裝 &lt;a href="http://nedbatchelder.com/code/coverage/"&gt;coverage&lt;/a&gt;；–process 得另裝 package multiprocessing ( easy_install multiprocessing )，相關說明詳見 &lt;a href="http://somethingaboutorange.com/mrl/projects/nose/0.11.1/plugins/multiprocess.html"&gt;Multiprocess: parallel testing&lt;/a&gt;。&lt;/p&gt;&lt;p&gt;另外，若要讓 &lt;a href="http://somethingaboutorange.com/mrl/projects/nose/0.11.1/index.html"&gt;nose&lt;/a&gt; 跳過物件 A 的測試，就在程式裡寫上&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;A.__test__ = &lt;span style="color:#008000"&gt;False&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;比方若不想測模組 mod，就在 mod.py 裡寫上&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;__test__ = &lt;span style="color:#008000"&gt;False&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;h4&gt;sqlite3&lt;/h4&gt;&lt;p&gt;內建模組。&lt;/p&gt;&lt;p&gt;用法見&lt;a href="http://docs.python.org/library/sqlite3.html"&gt;官網介紹&lt;/a&gt;。在測試資料庫時，個人覺得 local database 比 &lt;a href="http://martinfowler.com/articles/mocksArentStubs.html"&gt;mock&lt;/a&gt; 好用，方便準備資料，測起來也比較踏實，而且使用 memory mode 可大幅減少執行時間。附帶一提，&lt;a href="http://www.sqlite.org/"&gt;sqlite&lt;/a&gt; 跨 C、Python、Java 等語言，支援 SQL 92，執行速度又快，相當好用。&lt;/p&gt;&lt;h4&gt;PyMox&lt;/h4&gt;&lt;p&gt;安裝方式： 用 SVN 從&lt;a href="http://code.google.com/p/pymox/source/checkout"&gt;官網&lt;/a&gt; checkout 原始碼，再用 setup.py 安裝：&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;2&lt;br /&gt;3&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;&lt;span style="color:#c20cb9;font-weight:bold"&gt;svn&lt;/span&gt; checkout http:&lt;span style="color:#000000;font-weight:bold"&gt;//&lt;/span&gt;pymox.googlecode.com&lt;span style="color:#000000;font-weight:bold"&gt;/&lt;/span&gt;&lt;span style="color:#c20cb9;font-weight:bold"&gt;svn&lt;/span&gt;&lt;span style="color:#000000;font-weight:bold"&gt;/&lt;/span&gt;trunk&lt;span style="color:#000000;font-weight:bold"&gt;/&lt;/span&gt; pymox-read-only&lt;br /&gt;&lt;span style="color:#7a0874;font-weight:bold"&gt;cd&lt;/span&gt; pymox-read-only&lt;span style="color:#000000;font-weight:bold"&gt;/&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#c20cb9;font-weight:bold"&gt;sudo&lt;/span&gt; python setup.py &lt;span style="color:#c20cb9;font-weight:bold"&gt;install&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;用法見&lt;a href="http://code.google.com/p/pymox/wiki/MoxDocumentation"&gt;官網文件&lt;/a&gt;。PyMox 是 Google 開發的 Python 版 &lt;a href="http://easymock.org/"&gt;EasyMock&lt;/a&gt;，我試用過 Java 的 EasyMock 後覺得用法挺直覺的，就決定用它，如此一來學一套工具可以同時用在 Java 和 Python 上。使用 &lt;a href="http://martinfowler.com/articles/mocksArentStubs.html"&gt;mock&lt;/a&gt; 的好處是簡化測試碼，更完備地測試程式，像是代換掉處理資料庫、網路連線的物件。如此一來，連測試「讀資料到一半卻斷網」的應對情形，都是輕而易舉的事。若想立即體會 mock 的功效，不妨配合 mock 用 top-down 的方式作 &lt;a href="http://fcamel.twbbs.org/archives/2009/03/09/747/"&gt;TDD&lt;/a&gt;，會發覺不同的程式開發思維。&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/fcamel/~4/RLjR-HyZQTM" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-51962912737368630?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/51962912737368630/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2009/09/python.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/51962912737368630'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/51962912737368630'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2009/09/python.html' title='寫 Python 測試碼的好幫手'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-3139453317678328413</id><published>2009-09-20T23:17:00.001+08:00</published><updated>2011-05-15T01:08:13.005+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Animation'/><category scheme='http://www.blogger.com/atom/ns#' term='ACG'/><title type='text'>Honey &amp; Clover I, II 觀後感</title><content type='html'>&lt;p&gt;&lt;a href="http://www.flickr.com/photos/12203797@N00/3937596080/" title="Honey &amp;amp; Clover by fcamel, on Flickr"&gt;&lt;img src="http://farm3.static.flickr.com/2573/3937596080_6a4750bf54.jpg" width="500" height="281" alt="Honey &amp;amp; Clover"&gt;&lt;/a&gt;&lt;br /&gt;又看了一次 Honey &amp;amp; Clover 第一部和第二部，已經明白劇情發展的我，這次還是一樣被情節給牽動著，忍不住一集集地看下去，而且更能注意細節，更能感受到作者細膩的鋪陳。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;看完許久，仍然無法用文筆表達情緒的我，只好照老樣子看著別人的心得，和著劇中插曲讓感覺漸漸褪去。最後的結局，究竟小育是抱著什麼樣的心情，將那份點心交給竹本的呢？&lt;/p&gt;&lt;p&gt;小育想傳達的心意，竹本確實地收到了。一再地重覆看著這段，我不知道自己感受到的是什麼，劇中插曲的歌詞是這麼寫著的：&lt;/p&gt;&lt;p&gt;&lt;embed src="http://www.youtube.com/v/E_qcHT9tSXI&amp;amp;hl=en&amp;amp;fs=1&amp;amp;" allowScriptAccess="never" allowFullScreen="true" width="425" height="344" wmode="transparent" type="application/x-shockwave-flash"&gt;&lt;/embed&gt;&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;   平靜清澈的溪水，毫不躊躇地向前流淌。&lt;br /&gt;令人懷念的午後熏風，吹拂著濕透的脖頸。&lt;br /&gt;野兔不停奔跑的樣子，白百合的耀眼花姿，&lt;br /&gt;夜空中閃耀的星群，都理所當然地在我眼中。&lt;/p&gt;&lt;p&gt;相信一定會觸碰到的幻影，&lt;br /&gt;充斥著言語的底片中的街道延續著。&lt;br /&gt;再見吧，再見吧，向著不知何時的你。&lt;br /&gt;如果還能遇見你就好了。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;只能不斷地聽著插曲，試著沉浸在兩人的心情中，隨手寫出片段的思緒。&lt;/p&gt;&lt;p&gt;小育和竹本彼此的心意，不是友情、愛情之類的字眼可以表示的，那究竟是什麼呢？對於他們而言，這樣的分別又代表什麼呢？為什麼如此的不捨？為什麼如此地感傷？滿腹疑問的我似乎明白答案為何，卻無法述諸言語，或是不願述諸言語。於是不斷重覆聽著插曲、重覆想著兩人的心情、隨手敲著鍵盤，讓這份情感深刻地注入內心深處。對於祝福的人以及被祝福的人都能幸福，感到無比地欣慰，只是感傷也隨著揮之不去。&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/fcamel/~4/e4PLDRdUIiI" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-3139453317678328413?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/3139453317678328413/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2009/09/honey-clover-i-ii.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/3139453317678328413'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/3139453317678328413'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2009/09/honey-clover-i-ii.html' title='Honey &amp; Clover I, II 觀後感'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://farm3.static.flickr.com/2573/3937596080_6a4750bf54_t.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-4547852461187949266</id><published>2009-09-06T14:13:00.000+08:00</published><updated>2011-05-15T00:56:40.480+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Research'/><category scheme='http://www.blogger.com/atom/ns#' term='Book'/><category scheme='http://www.blogger.com/atom/ns#' term='Fiction'/><title type='text'>康特的難題</title><content type='html'>&lt;p&gt;書籍基本資料：&lt;a href="http://www.books.com.tw/exep/prod/booksfile.php?item=0010021477"&gt;《康特的難題》&lt;/a&gt;。&lt;/p&gt;&lt;p&gt;看到中後半時，由於相當在意某位角色出奇的應對方式，讓我不得不硬衝了兩小時看到結尾，導致我破例到淩晨兩點才睡，上回這樣挑燈夜戰讀小說，應該是國高中時看金庸小說吧？&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;若以一般小說來說，算是普普吧。但這本揭開科學界的生活，並用非科學界人士質疑科學界視為常識的文化，還有點出女性科學界的困境，都很有意思，可滿足我對科學界人性的好奇心。像是學界慣於使用的「我們」，究竟意指為何？作者順序的含意？提出想法和執行實驗何者較為重要？實驗室成員是合作者，還是手下？害怕被盜用想法而不敢發表，以及急於發表以追求第一發現者榮譽的衝突？實驗數據不如預期，做些「適當」的調整，是修正誤差還是照假？女性學者在追求終身職和小孩之間的抉擇？書中點出許多與科學誠實、無私、公正、理性、分享等特質矛盾的人性弱點，有許多令人深省的地方。角色的佈局也滿有意思的，將幾位看似無關的核心角色，慢慢地交錯在一起，若起頭和結尾能更圓潤就更棒了。&lt;/p&gt;&lt;p&gt;看完這本書後，讓我更不想走學界路線，我明白自己很難抗拒種種誘惑堅持自己。即使能堅持自己，也很難要求合作者照自己的要求進行，有太多灰色地帶，很難令合作者與自己達成共識。還是遠離利益得失，才能自在地以自己的步調前進。&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/fcamel/~4/t81k40a-x_k" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-4547852461187949266?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/4547852461187949266/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2009/09/blog-post.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/4547852461187949266'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/4547852461187949266'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2009/09/blog-post.html' title='康特的難題'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-3653184958590613355</id><published>2009-09-05T16:14:00.000+08:00</published><updated>2011-05-15T00:56:41.373+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Testing'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Research'/><title type='text'>驗證第一、想法第二</title><content type='html'>&lt;p&gt;看到這篇&lt;a href="http://www.codinghorror.com/blog/archives/001297.html"&gt;《The Only Truly Failed Project》&lt;/a&gt;，其中有兩段話寫得很棒：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Failure is a wonderful teacher. But there’s no need to seek out failure. It will find you.&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote&gt;&lt;p&gt;The only truly failed project is the one where you didn’t learn anything along the way.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;最近觀察不少數據和實驗結果，推翻之前許多猜想，不過我沒有像碩士做研究時那般痛苦，反而有些興奮。大概是因為沒有發論文的壓力吧，加上先前讀過費曼的言論，覺得這是理所當然的情況，我們知道得本來就很少，想法通常也是錯的。然而，重點在於如何從錯誤的嘗試中學習？&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;昨天晚上不斷思索這堆錯誤的嘗試告訴我什麼，仍未有定論。忽然想到愛迪生的話，當他實驗千種材料失敗後，他說，至少我明白這千種材料不能作為燈炮的材料。&lt;br&gt;於是心裡又更踏實了。但這不表示可以隨意的嘗試，在沒有有效的驗證方式之前，無謂的嘗試無法提供任何訊息。&lt;/p&gt;&lt;p&gt;印象中費曼提過，他不和人討論無法驗證的想法，那是沒有意義的行為。提出猜想後，我會思考如何驗證。直到有辦法進行驗證前，我會先保留想法，改試別的作法。或是將原問題先拆成幾個小問題，讓小問題能夠被驗證。待各個擊破搜集到一些資訊後，再回頭看是否能驗證原本的想法。&lt;/p&gt;&lt;p&gt;能夠驗證的想法，才能從中學習。不然結果出來後，我們無從判斷結果有多接近目標，自然也無法從中學習，進而做修正。舉例來說，如果要做 AI 下棋的搜尋，要怎麼知道目前的策略有效？和人下棋是個辦法，可是沒有無法頻覆進行，也就不能確保方法的可靠程度。另一方面，光看評估盤面函式的輸出值也無用，沒法確認分數高確實是有利的盤面。&lt;/p&gt;&lt;p&gt;換個想法，提兩個策略，互相對戰。至少可以確定每次都贏的策略，是相對來說較好的策略。一個策略可以用 greedy algorithm，另一個用自己想的特別方式。雖然我們預期特別的方法會贏，但若結果相反，也能從中明白新東西：像是 greedy algorithm 比想像中的有效，可從中找到好點子；或是特別的方法不如預期地有效，也就不用再和人下棋，減少後續測試的成本。若擔心兩個方法自動對戰變化有限，可以引用不同棋譜的中盤局面，再引入兩個方法擔任不同角色，觀察好的方法是否在各種相同局面裡，無論擔任那一方，都能下得較好。&lt;/p&gt;&lt;p&gt;再舉解魔術方塊的例子。為了能夠驗證，可以先將解好的盤面一步步弄亂，記下正確的還原步驟和所需步數。準備好多組測試盤面（即最後弄亂的樣子），測試演算法解的效果，步數和預期步數相差多少？那一步發展變得不同？於是有確實的數據可以分析，明白問題出在那。有未知的大進步時（只花了預期的一半步數），也方便觀察出原因（如弄亂盤面時做了不當的重覆操作），不會不小心高估方法的成效。&lt;/p&gt;&lt;p&gt;看起來理所當然的事，沒想到我這麼遲才明白，驗證是如此的重要。而這個觀點卻是&lt;a href="http://fcamel.twbbs.org/archives/2009/06/13/849/"&gt;從 coding 那邊先萌芽&lt;/a&gt;，才接著在研究這邊確實實行。不論是研究還是coding，我認為重要性是: 需求 (動機) &amp;gt; 驗證 &amp;gt; 想法 (解法) &amp;gt; 實作。如《管理是什麼》書裡提到，管理即為客戶創造價值 －－ 相當含糊的定義，可是卻非常精闢。不論是研究還是寫程式，也要先確保能滿足某種需求，之後的發展才有意義。接著，在天馬行空地想解法前，先確保有方法驗證方法的好壞、達到目標的程度、有辦法分析結果，之後才能確實地落實想法。於是，即使失敗仍能有所收獲。&lt;/p&gt;&lt;h4&gt;2009-09-06 Update&lt;/h4&gt;&lt;p&gt;經 &lt;a href="http://www.plurk.com/LCamel"&gt;LCamel&lt;/a&gt; 一提，發現英文的用詞很有趣，&lt;a href="http://en.wikipedia.org/wiki/Verification_and_Validation"&gt;Verification and Validation &lt;/a&gt; 裡這麼解釋：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;It is sometimes said that validation can be expressed by the query “Are you building the right thing?” and verification by “Are you building the thing right?” “Building the right thing” refers back to the user’s needs, while “building it right” checks that the specifications be correctly implemented by the system.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;意即：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;滿足需求 = validation = do the right thing.&lt;/li&gt;&lt;li&gt;驗證作法 = verification = do the thing right.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;語言真是奇妙啊，用一句話來總結，就是「Are the do you right thing right?」&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/fcamel/~4/XSpnjRZb9ck" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-3653184958590613355?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/3653184958590613355/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2009/09/blog-post_05.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/3653184958590613355'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/3653184958590613355'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2009/09/blog-post_05.html' title='驗證第一、想法第二'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-4296501533084898434</id><published>2009-08-10T23:37:00.000+08:00</published><updated>2011-05-15T00:56:42.420+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='閒書'/><category scheme='http://www.blogger.com/atom/ns#' term='Web'/><category scheme='http://www.blogger.com/atom/ns#' term='Diary'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Research'/><category scheme='http://www.blogger.com/atom/ns#' term='Book'/><category scheme='http://www.blogger.com/atom/ns#' term='Blog'/><title type='text'>《別鬧了，費曼先生》閱讀中的雜記</title><content type='html'>&lt;p&gt;讀《別鬧了，費曼先生》的過程裡，偶而有寫片段感想。這篇是這些短記整理後的記錄。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;h4&gt;通用的學習法則&lt;/h4&gt;&lt;p&gt;《別鬧了，費曼先生》提到費曼常跨領域學些不同東西，不管是哲學、數學、生物學，費曼都用他學習科學的習慣判別對錯，掌握基本原理。最近做研究也有這種體會。即使沒有背景知識，有良好邏輯，掌握假設和觀察結果，通常能做出正確的判斷。前提是我們能明確判讀那些是客觀事實，那些是主觀判斷，數據不會騙人，可是人會，而且我們自己也可能會騙自己。&lt;/p&gt;&lt;p&gt;舉例來說，看到別人的實驗結果和實驗報告，一定要自己讀一遍數據，不然無法判斷別人說明中那些部份為主觀推測。像是兩個方法有效程度差了千分之一，是否可說有顯著差異？去除人有私心的因素外，我們也沒有想像中的聰明，容易將一些相似的資訊混在一起，不知不覺認定是事實。「誤把有相關性視為有因果關係」是常見的例子，有興趣的人可以看些寫給大眾看的統計書籍。反用「判讀個人觀點和事實」的原則在自己身上也很有效，可以找到自己思路的盲點，不會過於武斷主張自己的看法。&lt;/p&gt;&lt;p&gt;此外，舉例說明是最佳的討論方式。費曼提到他和數學家討論數學原理時，他的判斷方式是心中想像一些實例，對方描述問題時，他就想像心裡那些物體會怎麼變化，於是當對方說某某性質如何，他發覺在他想像世界裡有矛盾時，就能做出辯駁。另一方面，我相信只有當我們能舉例說明時，我們才算真的明白，藉由逼自己舉例，可以思考的更靈活，找出忽略的細節。&lt;/p&gt;&lt;h4&gt;別死記名詞&lt;/h4&gt;&lt;p&gt;另一方面，費曼也指出許多人記了一堆名詞，卻沒掌握基本精神，他在 MIT 的大學同學如此，愛因斯坦的研究助理也會犯這種錯。費曼一再提到很多學生只會背名詞，不會理解背後的含意，不明白他們在學什麼。這種精神從他小時候就存在，和父親在林中散步時，費曼的父親向他解釋生物行為而不背名字，費曼將自己的成就歸功於父親童年時的教導，不無道理。&lt;/p&gt;&lt;p&gt;費曼在問學生問題時，會舉例討論，而不問教科書式的問題。我在確認別人能力時也是這麼做，不問專有名詞解釋（像是請說明 X 演算法為何），盡量用應用題直接看對方怎麼運用知識，這樣能立即明白對方有沒有理解背後含意。反過來看，不知道某些名詞不代表什麼，就算對方不知道 Dijkstra’s Algorithm，但若能提出相似的解法，反而更高明。只要和對方說明這些知識，他們立即能掌握精神加以運用。反之，知道 Dijkstra’s Algorithm 卻無法活用，知道更多演算法、累積更多經驗，成長也有限。&lt;/p&gt;&lt;p&gt;名詞只是方便溝通的工具，若不能理解背後的含意，光記許多名詞毫無意義。在資訊爆炸的時代，每天產生一堆新的名詞，吸收這堆雜亂的詞彙時，得時時提醒自己，是否明白背後含意。以前我曾落入背誦知識為樂的陷阱，後來發現知道很多皮毛卻無法做進一步運用，才驚覺自己浪費不少時間。舉例來說，現在當紅的 Android，即使我知道 Android 是 Google 發佈的手機平台，也沒任何幫助，只是一堆名詞堆砌。到不如了解為何會有 Android？適合解什麼樣的問題？需要時可視需求對相關知識做進一步研究。 (備註：我並不了解這兩個問題，只是舉例而已。）&lt;/p&gt;&lt;p&gt;並不是說記名詞毫無意義，費曼童年時自己發明一套數學符號，他覺得讀起來比較好理解，很高興地用了一陣子。但後來發現他無法和人溝通，就把這些符號丟了，改用共通符號。與人溝通需要專有名詞，不然難以進入問題核心，講半天還在說「你是說如果有 n 個點，點可以是任意東西，像是城市，或想像成座標也行，這 n 個點之中部份兩點有互相連接，連接的距離長短不一，而你想從某個起點 A 走到終點 B，想問如何走才能花最少的步數嗎？」（即最短路徑問題）。&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/fcamel/~4/jmy35yxZ9jQ" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-4296501533084898434?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/4296501533084898434/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2009/08/blog-post.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/4296501533084898434'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/4296501533084898434'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2009/08/blog-post.html' title='《別鬧了，費曼先生》閱讀中的雜記'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-6651287389559826973</id><published>2009-08-05T20:39:00.000+08:00</published><updated>2011-05-15T00:56:43.301+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='閒書'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Research'/><category scheme='http://www.blogger.com/atom/ns#' term='Book'/><title type='text'>《別鬧了，費曼先生》讀後感 －－ 關於科學的品德</title><content type='html'>&lt;p&gt;小時候曾讀過一次，當時只覺得費曼這人真有趣。走過一遭研究的生活後，再回來讀這本書，幾乎每部份都令我深思許久。特別是最後一篇《草包族科學》讓我有更深的體會。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;這篇是費曼於1974年在加州理工學院的畢業典禮演講，有別於其它故事，單刀直入說明科學研究最重要的課題－－科學的品德，也就是對自己誠實，對大眾誠實。費曼舉了個反例說明何謂不誠實：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;有個朋友在上電台節目之前跟我聊起來，他是研究宇宙學及天文學的，而他很感困惑，不知該如何談論這些工作的應用。我說：「根本就沒有應用可言。」，他回答說：「沒錯，但如果這麼說，我們這類研究工作就更不受支持了。」我覺得很意外，我想那是一種不誠實。如果你以科學家的姿態出現，那麼你應該向所有非科學家的大眾說明你的工作－－如果他們不願意支持你的研究，那是他們的決定。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;自己做研究也是一樣，該同時說明支持和不支持的數據和觀點，而非只報告支持的部份。要延伸別人的研究前，要先重製對方的實驗，如此才知道兩者在自己的實驗環境下差多少，而不能為了急著產生報告，省略前人的實驗，直接做自己的部份。&lt;/p&gt;&lt;p&gt;做研究必須客觀，早就同意某種觀點而開始做實驗，和從實驗中找出支持自己觀點的證據，是截然不同的行為。在利益衝突下，我們很容易欺騙自己，得非常非常細心且耐心地排除任何有利於自己發展的想法。很多時候我們難以把持，或是不自覺地騙了自己。舉例來說，看到違反預期 (不利) 的結果，會不斷思考那個環節有誤，嘗試修正「不當變因」；看到符合預期 (有利) 的結果卻會很興奮，忘了要進一步確保沒有「不當變因」影響結果。&lt;/p&gt;&lt;p&gt;藉由費曼生活中的插曲，看到一位真正科學家自然行事的風格，令我受益良多。在看到這麼多令人困惑的情況後，費曼的身教無疑是個強心劑。我想我不會再困惑了，若有那麼一天我打算讀博士，除了找到非作不可的研究外，還要確定我能把持住想法，能完全地誠實才會成行。不然，我還是遠離利害關係，至少在沒有利害關係的影響下，我能較誠實地進行研究。&lt;/p&gt;&lt;p&gt;費曼演講的最後一段如此說道：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;因此我只有一個希望：你們能夠找到一個地方，在那裡自由自在的堅持我提到過的品德；而且不會由於要維持你在組織裡的地位，或是迫於經濟壓力，而喪失你的品德。&lt;/p&gt;&lt;p&gt;我誠心祝福，你們能獲得這樣的自由。&lt;/p&gt;&lt;p&gt;So I have just one wish for you—the good luck to be somewhere where you are free to maintain the kind of integrity I have described, and where you do not feel forced by a need to maintain your position in the organization, or financial support, or so on, to lose your integrity. May you have that freedom.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;看到這段話，我鼻頭都酸了，祝福自己，也祝福所有對科學有興趣的人。&lt;/p&gt;&lt;p&gt;ps. 英文原文講稿&lt;a href="http://yost.com/misc/cargocult.html"&gt;在此&lt;/a&gt;。&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/fcamel/~4/_Ej9jEUjB-Q" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-6651287389559826973?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/6651287389559826973/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2009/08/blog-post_05.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/6651287389559826973'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/6651287389559826973'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2009/08/blog-post_05.html' title='《別鬧了，費曼先生》讀後感 －－ 關於科學的品德'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-7825349927300011813</id><published>2009-07-25T20:59:00.000+08:00</published><updated>2011-05-15T00:56:44.282+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Life'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='格言錦句'/><title type='text'>學而不思則罔，思而不學則殆</title><content type='html'>&lt;p&gt;現在回顧兩年前思考未來出路的文章，覺得孔子說的「學而不思則罔，思而不學則殆。」真是太有道理了。再多規劃，聽再多情報，若沒有親身體驗，只會對紛亂的情報感到困惑。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;現在回頭比對當時搜集的情報和現在知道的情報，發現有許多出入，甚至連對自己的認知都有誤。一開始我不知道自己喜歡做研究還是做產品。結果經過四個月全心投入研究後，覺得做研究滿有趣的，和正在業界的學長聊天，覺得自己似乎沒那麼適合當工程師。結果去一家公司實習四個月後，又覺得自己比較喜歡做產品。現在做了半年半研究半產品的工作，又覺得好像都很有趣。到頭來是看目標是否明確有意義、組織是否能給我彈性的空間發展，以及不需要為了衝積效而做無意義的事。那麼，不論是研究還是產品，都能從中找到樂趣。若用負面例子來看，寫不踏實的學術報告，和做難用的產品，都一樣乏味。&lt;/p&gt;&lt;p&gt;需求不明確，情報又不見得正確，怎麼能做出有效的決定？&lt;/p&gt;&lt;p&gt;所以，若碩士畢業生沒有先到業界實習過，不清楚自己的喜好，聽再多情報也難以判斷。另外，若不是和公司內的人直接一對一聊，情報價值也難以判斷。和一家公司內的一個人聊，也只能了解該組詳細情況和公司概況，容易以偏概全。就像好學校有壞實驗室，壞學校會有好實驗室一樣，和自身最相近的案例才有幫助。&lt;/p&gt;&lt;p&gt;類似的例子俯拾皆是，像是高中生選大學，若自身沒有針對有興趣的科系先嘗試一下，和他說明學校科系的特色，也難以定奪。若用系統來解釋，沒有寫過系統程式直接讀作業系統的書，只會覺得抽象得令人不知所云。寫一堆系統程式不看書消化整理知識，需求稍微變更，又不知該如何有效處理。&lt;/p&gt;&lt;p&gt;學技術時我明白孔子這兩句話的意思，但在人生方向的課題裡，我卻到現在才明白：原來我沒搞懂。話說回來，接觸新領域時，總會想省時間偷吃步，不做基礎練習，看來在技術方面，我也沒明白這兩句話的重要性。&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/fcamel/~4/DE53ys3mBIg" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-7825349927300011813?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/7825349927300011813/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2009/07/blog-post.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/7825349927300011813'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/7825349927300011813'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2009/07/blog-post.html' title='學而不思則罔，思而不學則殆'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-9041442857476941522</id><published>2009-07-22T22:42:00.000+08:00</published><updated>2011-05-15T00:56:46.914+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Java'/><category scheme='http://www.blogger.com/atom/ns#' term='Ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Python'/><title type='text'>學程式語言的樂趣</title><content type='html'>&lt;p&gt;國三時為了做北市數學科展，實習老師教我使用 Microsoft Word，這是我首次體會到電腦在遊戲之外的樂趣。為了學會怎麼灌 Windows，高一時參加了社團，結果社團學長 Scott 說:「灌 Windows 太簡單了，你來學 C 吧。不過我們這裡只有 Red Hat Linux，就邊學 C 邊學 Linux 吧。」傻傻的我，以為 Linux 裡只有 Vi，就四處翻 Linux 入門書，看完各本書裡 Vi 部份，還寫了篇 Vi 的教學。日漸熟悉 Vi、bash 指令後，覺得玩系統、寫程式真有趣。於是上課無聊就偷看電腦書，下課耍自閉繼續看書，中午放學都跑去社團混，就這樣渡過我的高一。不知不覺，我打下日後學習能力最重要的基礎。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;高一一時想不開，同時學 C 和 Perl，用 Perl 寫東西頗有趣的，特別是費盡心思把程式擠到一行作完一件煩人的小工作時，有說不出來的成就感。可是我基礎太弱，無法明白兩者更深層的差異，只能當作兩個不同工具看。兩者同時學的下場，學一陣子仍不熟兩邊的語法。於是我先專心學 C，之後 Perl 也忘光了。那段日子最大的收獲是，不要一次同時學兩個語言。另外，多虧 Scott 心理不正常，用 K&amp;amp;R 的《C 程式語言》當入門書，我直接學會標準語法，和 C 的典型寫法。這個習慣一直伴隨我到現在，每學一個新語言時，我傾向直接找能教我典型寫法的書，省去先花時間學一堆語法，再去蕪存菁地寫出典型風格。&lt;/p&gt;&lt;p&gt;寫了三年的 C，大一接觸到 Java 後，覺得 OOP 很有趣，這麼簡單又優雅地，讓我能同時使用兩個 Stack！雖然一開始寫著滿滿的 C-like Java，程式裡都是 static method，看了些 OOP 的書，開始轉型寫著自以為是 OOP 的怪東西。時至今日，我仍然不明白要怎麼寫出真正的 OOP。至少我發現自己寫得不是 OOP，和大學時相比，算是進步不少。&lt;/p&gt;&lt;p&gt;有些課要求用 C++，我就會用 Java 學到的 OOP 觀念和 C 的經驗來寫。每次要寫 C++ 就重翻一下 C++ 快速入門的書，寫完後又忘光一切。大三做專題時，我和朋友們打算參加一個國外的比賽，由於主要贊助商是 Microsoft，我們決定用 .NET Framework 開發。就這樣，我有個好機會寫 C#。亂翻一些 ASP.NET 的書把殼刻一刻後，我找了《C# Essentials》來看。這本書如其名，薄又都是重點，很快地明白 C# 的特色，讓我覺得 C# 挺漂亮的。不過寫完專題後沒繼續寫 C#，現在什麼也不記得了。而且，當時我的底不夠深，仍無法感受到各語言的特色。&lt;/p&gt;&lt;p&gt;除 C++、C# 兩個插曲外，大學時主要用 Java 和 PHP。用途很簡單，接網站案子用 PHP，其它用 Java。我練習 PHP 的方式是不斷改寫自己的網站，還有接些小案子，可是我覺得 PHP 實在不怎麼美，沒動力學好它，能用就好。&lt;/p&gt;&lt;p&gt;大學中途有試著寫了一點 Python、Scheme，可惜無法持續。一直到碩一一時興起，一方面是厭煩了用 PHP 寫重覆的東西，另一方面想試看看當紅的 Ruby on Rails，就邊查邊寫完成一個小案子。寫得過程裡，覺得 Ruby 很奇妙，於是在寒假花了一週讀完《Programming Ruby》第一部份，這才正式開始我學的第三個語言。&lt;/p&gt;&lt;p&gt;自那之後，我只有寫 Ruby，Java、PHP和其它語言碰也沒碰，這樣直到碩士畢業。沒錯，我的碩士論文也是用 Ruby 寫的，這才深深體會到 Ruby 有多慢有多肥，幸好實驗室的機器很壯，不然我得改用 Java 完成碩論。這段期間，把一個論文裡用到的程式零零總總地加起來，大概有兩千多行，也驚覺 Ruby 幫我省了不少寫程式的時間，相信用 Java 的話，我無法在不到一個月的時間裡寫完自己的演算法、數個比較對象的演算法、前置處理、評估分數等程式。不過卻辛苦交接的學妹，這樣瘋狂用一個不熟語言趕出來的程式碼，原以為不會繼續用的說。&lt;/p&gt;&lt;p&gt;用 Ruby 讓我有了第二次衝擊，就像當年從 C 到 Java 發現可以同時有兩個 Stack 那般驚奇，沒想到 iterator、code block 這麼好用，腦裡想什麼，手就可以直接敲出來，不用寫一寫還要把游標移來移去。而且，Ruby 可以寫出很短又好讀的程式，我從來沒想過程式可以寫到這麼精簡，而寫到這麼精簡後，又更容易理解。&lt;/p&gt;&lt;p&gt;碩士畢業去一家公司實習時，該公司不用 Ruby，我藉機來試看看 Python。於是，對照先前一年多寫 Ruby 的經驗，我明白很多事。有時選擇某個語言，沒有什麼明確的理性依據，單純是個人偏好。高中時我一直想知道 Python 和 Perl 的差異，到底學那個比較划算，如今又多了一個 Ruby 列入抗爭。現在來看，即使我可以找到三者在語言設計、執行速度、社群、函式庫、上手度等各方面的比較，到頭來，選擇何者反而像是信仰。&lt;/p&gt;&lt;p&gt;附帶一提，若要推廣一個程式語言，良好的互動直譯器、快速隨手查的文件、精簡易讀的入門書，三者缺一不可。 Ruby 大概就互動直譯器弱了一些，而 Python 三者都完美無缺，非常容易上手。至於 Java，即使到現在，我仍不知道要推薦別人看什麼書入門。最後附上我對各語言入門方式的看法，入門書書單是針對已學過一種語言的人：&lt;/p&gt;&lt;table&gt;&lt;tr&gt;&lt;tr&gt;&lt;th&gt;語言&lt;/th&gt;&lt;th&gt;互動式直譯器&lt;/th&gt;&lt;th&gt;隨手查的文件&lt;/th&gt;&lt;th&gt;入門書&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;th&gt;C&lt;/th&gt;&lt;td&gt;無&lt;/td&gt;&lt;td&gt;man page&lt;/td&gt;&lt;td&gt;《C 程式語言》&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;th&gt;Ruby&lt;/th&gt;&lt;td&gt;設定後的 irb&lt;/td&gt;&lt;td&gt;ri&lt;/td&gt;&lt;td&gt;《Programming Ruby》&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;th&gt;Python&lt;/th&gt;&lt;td&gt;ipython&lt;/td&gt;&lt;td&gt;ipython&lt;/td&gt;&lt;td&gt;《Python Essential Reference》&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;th&gt;Java&lt;/th&gt;&lt;td&gt;無&lt;/td&gt;&lt;td&gt;CHM格式的 Java doc &lt;/td&gt;&lt;td&gt;無&lt;/td&gt;&lt;/tr&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;( 待續 )&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/fcamel/~4/awxdbIYx4io" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-9041442857476941522?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/9041442857476941522/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2009/07/blog-post_22.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/9041442857476941522'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/9041442857476941522'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2009/07/blog-post_22.html' title='學程式語言的樂趣'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-3481953237218050506</id><published>2009-06-13T02:00:00.000+08:00</published><updated>2011-05-15T00:56:48.347+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Testing'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Software Engineering'/><title type='text'>為什麼要寫 unit test？為什麼要先寫測試？</title><content type='html'>&lt;p&gt;這篇說明寫 &lt;a href="http://en.wikipedia.org/wiki/Unit_testing"&gt;unit test&lt;/a&gt;的好處，以及為何先寫 &lt;a href="http://en.wikipedia.org/wiki/Unit_testing"&gt;unit test&lt;/a&gt; 再實作，比實作完再補 &lt;a href="http://en.wikipedia.org/wiki/Unit_testing"&gt;unit test&lt;/a&gt; 更好。這裡先列出摘要：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;測試碼可以簡省手動測試的時間，但有錯時無法告訴我們錯誤的源頭在那。&lt;/li&gt;&lt;li&gt;&lt;a href="http://en.wikipedia.org/wiki/Unit_testing"&gt;unit test&lt;/a&gt; 可以告訴我們錯誤的源頭在那，可是 &lt;a href="http://en.wikipedia.org/wiki/Unit_testing"&gt;unit test&lt;/a&gt; 有時間成本和維護成本。寫過多 &lt;a href="http://en.wikipedia.org/wiki/Unit_testing"&gt;unit test&lt;/a&gt; 反而有害。&lt;/li&gt;&lt;li&gt;&lt;a href="http://fcamel.twbbs.org/archives/2009/03/09/747/"&gt;TDD&lt;/a&gt; 藉由先寫測試避開 &lt;a href="http://en.wikipedia.org/wiki/Unit_testing"&gt;unit test&lt;/a&gt; 的成本問題，並帶來其它好處。&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;h4&gt;為什麼要寫測試碼？&lt;/h4&gt;&lt;p&gt;吃燒餅那有不掉芝麻，寫程式自然要驗證程式是否正確。想像平時我們如何驗證程式？大概就是寫一寫，執行看看，看看輸出符不符合預期，換些不同輸入，再看看結果，有錯再改，改完再重跑．．．。&lt;strong&gt;測試碼可以協助我們自動化驗證&lt;/strong&gt;，省時省力。&lt;/p&gt;&lt;p&gt;也許有人會懷疑寫測試碼不划算，功能常改變，到時測試碼又要重改。這個問題視情況有不同種解法，比方用較「便宜」的方式寫測試，像是準備好輸出入檔，用 shell script 執行，配合 diff 看輸出是否一致。或是用 scripting language (如 Python) 寫簡單測試。而更根本的解法，個人認為是配合 &lt;a href="http://fcamel.twbbs.org/archives/2009/03/09/747/"&gt;TDD&lt;/a&gt;，待後面說明。&lt;/p&gt;&lt;p&gt;手動執行測試，執行個三十年，每次花的時間還是一樣，也許還會因為手酸變慢。但寫測試碼則相反，我們會愈寫愈快，愈寫功能愈好。&lt;/p&gt;&lt;h4&gt;為什麼要寫 unit test (單元測試)？&lt;/h4&gt;&lt;p&gt;首先介紹不同級別的測試碼，由小到大依序為：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;unit test：測試對象為單一函式。&lt;/li&gt;&lt;li&gt;integration test：測試對象為數個單元函式的複合體。我沒特別區分這個和 &lt;a href="http://en.wikipedia.org/wiki/Unit_testing"&gt;unit test&lt;/a&gt; [*1]，以下兩者一起用 &lt;a href="http://en.wikipedia.org/wiki/Unit_testing"&gt;unit test&lt;/a&gt; 表示。&lt;/li&gt;&lt;li&gt;system test：測試整個系統。&lt;/li&gt;&lt;li&gt;acceptance test：客戶驗收用的測試。將使用情節或使用需求轉成 acceptance test，藉以確認產品滿足客戶要求，方便起見，以下以 system test 統稱。&lt;/li&gt;&lt;/ul&gt;&lt;blockquote&gt;&lt;p&gt;備註 *1：在談論 mock 時，unit test 和 integration test 的差異較明顯，本文不談這個議題，總得讓大家有意願寫測試，再來談如何寫測試才有意義，你說對吧。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;以測試的觀點來看，system test 的 test case 夠多，system test 應該能涵蓋到大範圍的產品碼，如此一來也達成我們一開始提的「驗證」目的。從實務觀點來看，寫愈多測試碼，負擔愈多，規格變動時要改的東西也多。更何況 &lt;a href="http://en.wikipedia.org/wiki/Unit_testing"&gt;unit test&lt;/a&gt; 著眼點更細，變更實作方式也有可能得改 &lt;a href="http://en.wikipedia.org/wiki/Unit_testing"&gt;unit test&lt;/a&gt;。如此一來，為什麼要寫 &lt;a href="http://en.wikipedia.org/wiki/Unit_testing"&gt;unit test&lt;/a&gt;？&lt;/p&gt;&lt;p&gt;首先，&lt;a href="http://en.wikipedia.org/wiki/Unit_testing"&gt;unit test&lt;/a&gt; 著眼點比 system test 小，也意味著&lt;strong&gt;當測試結果不對時，unit test 可以指出更明確的問題點，&lt;/strong&gt;而 system test 只能粗略地和我們說輸出不對，接著我們得比對正確和錯誤的輸出，開始推測 bug 在那，並用 debugger 設中斷，或在程式內輸出訊息一步步找出問題源頭。相信大家都能明白除錯的痛苦。而 &lt;a href="http://en.wikipedia.org/wiki/Unit_testing"&gt;unit test&lt;/a&gt; 可以協助我們釐清那些程式是對的，那些是錯的，將問題範圍縮小。若 &lt;a href="http://en.wikipedia.org/wiki/Unit_testing"&gt;unit test&lt;/a&gt; 寫得好，幾乎用不到 debugger 和輸出訊息，光看那個 &lt;a href="http://en.wikipedia.org/wiki/Unit_testing"&gt;unit test&lt;/a&gt; 錯誤，就知道 bug 在那。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;除了幫自己省時間外，unit test 也可幫助別人維護和理解程式。&lt;/strong&gt;維護的人不如自己熟悉，難免不小心改爛程式，&lt;a href="http://en.wikipedia.org/wiki/Unit_testing"&gt;unit test&lt;/a&gt; 可以減少這類問題，也讓原作者安心給其他人修改。新手要閱讀程式時，&lt;a href="http://en.wikipedia.org/wiki/Unit_testing"&gt;unit test&lt;/a&gt; 可看成是各函式的使用範例，相信大家都同意讀例子比讀全部程式碼來得容易吧。好的範例有時比註解還有用。&lt;/p&gt;&lt;h4&gt;為什麼要先寫測試？&lt;/h4&gt;&lt;p&gt;好吧，若讀者大人耐著性子看到這裡，想必已對測試碼已有些心動，也認為寫 &lt;a href="http://en.wikipedia.org/wiki/Unit_testing"&gt;unit test&lt;/a&gt; 有那麼幾分道理，那先寫測試和後寫測試有什麼差別？若後來寫的程式不管用，需求變更，先寫測試不是搬磚砸自己的腳，自找麻煩嗎？事實上正好相反。&lt;/p&gt;&lt;p&gt;我們得先問自己，為什麼程式會不管用？為什麼需求會變更？去除客戶或主管找麻煩的因素外，一部份原因為思慮不週，寫的功能不夠貼近目標。&lt;strong&gt;如同產品一般，沒試用過我們不會明白產品的缺點在那，函式也是一樣。&lt;/strong&gt;先寫測試碼就是先想像要如何使用即將要寫的函式，&lt;strong&gt;在寫測試碼的同時，我們同時也在設計函式。&lt;/strong&gt;有時會發現難以測試，而想出更簡潔的介面。當我們能輕鬆寫出測試碼時，也意味著目標函式易於使用，之後才方便重用。&lt;/p&gt;&lt;p&gt;再者，先寫測試，相當於先列需求，規範中的輸入為何？預期輸出為何？可能有什麼例外情況？於是，我們接著寫出的程式，一定是有用的。我們不會忽然想說「要不要加個 foo()，之後大概會用到吧。」&lt;strong&gt;先寫測試，確保之後寫出的每一行產品碼都是有意義的。&lt;/strong&gt;同時，我們寫的測試碼也是有意義的，若事後再補測試，不明白加入一個 &lt;a href="http://en.wikipedia.org/wiki/Unit_testing"&gt;unit test&lt;/a&gt; 有何幫助，有時會多寫不必要的 unit test，如同前面提過，測試碼不是萬靈丹，它也同時是負擔，需求變更時，有時也得一併改測試碼。再者，&lt;strong&gt;先寫測試表示有先考慮程式的可測性 (testability)，使得程式容易測試，&lt;/strong&gt;通常會將程式拆得很細，待寫產品碼時容易將寫好的小函式組成目標功能；完成產品碼後再來補測試則困難許多，由於寫產品碼時沒有考慮到寫測試的需求，結果是影響測試碼的品質，甚至變成過於難寫而不補上測試。&lt;/p&gt;&lt;p&gt;除幫助設計、減少寫冗碼的機會外，&lt;strong&gt;先寫測試還方便我們之後進行重構。&lt;/strong&gt;沒有測試碼的重構是很危險的，而沒重構的程式碼也是很危險的 [*2]，就像在底層不穩的地基上不斷加蓋偷工減料的高樓一般。先寫測試的好處還有一點，&lt;strong&gt;早寫早享受，&lt;/strong&gt;先寫測試，就能先自動化驗證，省去所有手動驗證。&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;備註 *2：為什麼要重構？這是一個值得獨立討論的議題，主因為減少隱藏的 bug 和強化程式碼的可讀性，並更容易重用，以及增加新功能。詳見 &lt;a href="http://martinfowler.com"&gt;Martin Fowler&lt;/a&gt; 寫的 &lt;a href="http://www.books.com.tw/exep/prod/booksfile.php?item=0010411649"&gt;《重構：改善既有程式的設計》&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;綜合以上所言，先寫測試花費的時間，可以攤算在設計時間，以及減少寫無用程式、減少手動驗證、縮短除錯的時間，所以&lt;strong&gt;先寫測試不見得會增加額外時間，我們只是把投入不划算事上的時間，先拿來寫測試。&lt;/strong&gt;&lt;/p&gt;&lt;h4&gt;特別加贈：何時該寫新的測試碼？&lt;/h4&gt;&lt;p&gt;雖然寫測試好處多多，也別卯起勁來狂寫，最後卻覺得投資沒回本，浪費過多時間寫測試。遵照 &lt;a href="http://fcamel.twbbs.org/archives/2009/03/09/747/"&gt;TDD&lt;/a&gt; 的原則，先依規格寫測試，別浪費時間寫也許有用的測試。&lt;/p&gt;&lt;p&gt;那麼，何時才是跳出 &lt;a href="http://fcamel.twbbs.org/archives/2009/03/09/747/"&gt;TDD&lt;/a&gt; 迴圈，另外加寫測試的時機呢？依我個人經驗，我發現以下三個情況，都是寫新測試碼的好時機：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;在程式裡輸出訊息找錯誤&lt;/strong&gt;：與其花時間寫 print、看輸出訊息、再回頭砍掉 print，不如寫個 unit test，之後能持續受惠。&lt;/li&gt;&lt;li&gt;&lt;strong&gt;執行 debugger&lt;/strong&gt;：同上，你還是得花時間設中斷，一步步看訊息，不如寫個 unit test，之後能持續受惠。&lt;/li&gt;&lt;li&gt;&lt;strong&gt;不知錯誤在那，該如何進行下一步&lt;/strong&gt;：這表示一次貪心實作太多功能，把目標縮小，一步步補 &lt;a href="http://en.wikipedia.org/wiki/Unit_testing"&gt;unit test&lt;/a&gt; 吧。&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;另外，&lt;strong&gt;在解 bug 時，先寫個 &lt;a href="http://en.wikipedia.org/wiki/Unit_testing"&gt;unit test&lt;/a&gt; 重製問題，再開始除錯。&lt;/strong&gt;如此一來可確保自己明白成因，之後同樣的 bug 也不會再出現。&lt;/p&gt;&lt;h4&gt;超級加贈：若 TDD 這麼好用，為啥它不遍及？&lt;/h4&gt;&lt;p&gt;施主，這個問題得問你自己啊，快來加入推廣 &lt;a href="http://fcamel.twbbs.org/archives/2009/03/09/747/"&gt;TDD&lt;/a&gt; 的行列吧！&lt;/p&gt;&lt;p&gt;說正經的，雖然上面將寫 &lt;a href="http://en.wikipedia.org/wiki/Unit_testing"&gt;unit test&lt;/a&gt; 和 &lt;a href="http://fcamel.twbbs.org/archives/2009/03/09/747/"&gt;TDD&lt;/a&gt; 說得如此美好，寫 &lt;a href="http://en.wikipedia.org/wiki/Unit_testing"&gt;unit test&lt;/a&gt; 不是簡單的事。若寫得不好，就會有「改變實作，也得改變測試」的副作用，或是測試執行過久，不方便反覆執行，或是寫出無用的測試。如同學習 design pattern 一般，unit test 也有 design pattern，對沒接觸過寫測試碼的人來說，相當於要花「兩倍」時間學習。加上前面提到大家可能有的誤解，使得 &lt;a href="http://fcamel.twbbs.org/archives/2009/03/09/747/"&gt;TDD&lt;/a&gt; 難以落實。&lt;/p&gt;&lt;p&gt;另一個可能是，若你的組織同時有兩批人馬對同一產品進行開發和除錯，程式碼分成兩個 &lt;a href="http://www.ericsink.com/scm/scm_branches.html"&gt;branch&lt;/a&gt;：一個加入新功能，一個除錯。重構會造成程式碼難以合併，決策者因而選擇不頻繁重構。並且，這類組織可能有龐大的人力另外進行測試 (可看成是 system / acceptance test)，而誤以為不需要 unit test。&lt;/p&gt;&lt;p&gt;然而，長遠來看，第一個理由顯然是偷懶，只是把現在的問題延後到日後再解決。趁早把除錯的時間省下來，投資到 &lt;a href="http://fcamel.twbbs.org/archives/2009/03/09/747/"&gt;TDD&lt;/a&gt; 這積優股吧。第二個情況可能是公司考量，專業分工使得公司容易找人和培訓，我沒在這類公司工作的經驗，不知能如何對應。&lt;/p&gt;&lt;p&gt;詳細的 &lt;a href="http://fcamel.twbbs.org/archives/2009/03/09/747/"&gt;TDD&lt;/a&gt; 風險，可參照 &lt;a href="http://stackoverflow.com/questions/64333/what-is-the-downside-to-test-driven-development/64696#64696"&gt;What is the downside to Test Driven Development?&lt;/a&gt;。結論是團隊要有熱情學一堆新技術，或是有個經驗老道的開發者帶才適合用 &lt;a href="http://fcamel.twbbs.org/archives/2009/03/09/747/"&gt;TDD&lt;/a&gt;。不過，我個人的觀點是，現實世界的選擇不是非一即零，不是所有程式都能 &lt;a href="http://fcamel.twbbs.org/archives/2009/03/09/747/"&gt;TDD&lt;/a&gt;，若成本過高用 &lt;a href="http://fcamel.twbbs.org/archives/2009/03/09/747/"&gt;TDD&lt;/a&gt; 不划算，那就暫時別花時間學相關技巧，挑軟柿子吃。至少，有用 &lt;a href="http://fcamel.twbbs.org/archives/2009/03/09/747/"&gt;TDD&lt;/a&gt; 的部份就能享受到 &lt;a href="http://fcamel.twbbs.org/archives/2009/03/09/747/"&gt;TDD&lt;/a&gt; 帶來的好處。別因為一些可能想見的問題，而全盤否定使用 &lt;a href="http://fcamel.twbbs.org/archives/2009/03/09/747/"&gt;TDD&lt;/a&gt;。&lt;/p&gt;&lt;h4&gt;我對 TDD 充滿熱血，請問如何入門？&lt;/h4&gt;&lt;p&gt;好吧，這個標題是我寫來自 high 的。若有興趣的話，可以參考 &lt;a href="http://butunclebob.com/ArticleS.UncleBob.TheBowlingGameKata"&gt;The Bowling Game Kata&lt;/a&gt; 的&lt;a href="http://butunclebob.com/files/downloads/Bowling%20Game%20Kata.ppt"&gt;投影片&lt;/a&gt;，由設計保齡球計分程式的小例子，一步步展示 &lt;a href="http://fcamel.twbbs.org/archives/2009/03/09/747/"&gt;TDD&lt;/a&gt; 進行的過程，相信跟著例子走一次必能有所啟發，親身體驗 &lt;a href="http://fcamel.twbbs.org/archives/2009/03/09/747/"&gt;TDD&lt;/a&gt; 如何帶來新設計。若能自己先寫一遍，再來看投影片，感受會更深。或是參閱我之前寫的介紹文：&lt;a href="http://fcamel.twbbs.org/archives/2009/03/09/747/"&gt;《TDD 推廣：背景知識和簡介》&lt;/a&gt;以及&lt;a href="http://fcamel.twbbs.org/archives/2009/04/19/784/"&gt;《最近用 Python + TDD 心得（與 Java 做對照）》&lt;/a&gt;。&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/fcamel/~4/kfK5j4orxHo" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-3481953237218050506?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/3481953237218050506/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2009/06/unit-test.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/3481953237218050506'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/3481953237218050506'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2009/06/unit-test.html' title='為什麼要寫 unit test？為什麼要先寫測試？'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-2073165880995074289</id><published>2009-05-11T23:05:00.000+08:00</published><updated>2011-05-15T00:56:49.553+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Life'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='格言錦句'/><title type='text'>以有涯逐無涯，殆矣。</title><content type='html'>&lt;p&gt;近幾個月學習狀況不錯，學習效率好也能持久，但總覺得愈學愈多，愈學愈累，偶爾會有種茫然的挫折感。忽然想到莊子的這句：&lt;br&gt;&lt;blockquote&gt;吾生也有涯，而知也無涯。以有涯逐無涯，殆矣。&lt;/blockquote&gt;&lt;/p&gt;&lt;p&gt;原本我曲解了這段話，還振奮了一下，想說要更認真地追求知識。忽然，我發現莊子的本意反而是點出恰到好處的生活方式，於是我的世界改變了。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;體悟到這點後，我決定晚上十點後停止電腦科學相關的學習，改看看動畫、或翻翻閒書，或偶而像這樣寫寫 Blog 也不壞。待之後多存點錢，買了電鋼琴還可以來個&lt;a href="http://back-big5.zhengjian.org/articles/2007/9/27/48562.html"&gt;「彈琴復長嘯」&lt;/a&gt;。不知為何，我覺得自己可以用更自在的方式追求更深的知識，讓生活過得趨於平衡。&lt;/p&gt;&lt;p&gt;忽然，又有&lt;a href="http://fcamel.twbbs.org/archives/2007/02/16/284/"&gt;兩年半前相似的體悟&lt;/a&gt;，到外邊小晃了一圈，我似乎走回正確的路了。即使不知道方向，不知道終點何在，我明白自己正踏在想走的路。&lt;/p&gt;&lt;p&gt;題外話，看到現在學習程式語言和機器學習的進展，不禁懷疑過去為何期望這麼久，卻遲遲沒有行動。才想起以前還有許多想做的事，像是增加電腦科學的廣度認知、寫 3D 遊戲、參加各式比賽、做研究寫論文等。許多事若沒有親身嘗試，只會念念不忘，難以打消念頭。經過前幾年的嘗試，才能像現在般沉澱心靈，專心在最有興趣的幾個點上。&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/fcamel/~4/AAmWs2l3ivM" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-2073165880995074289?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/2073165880995074289/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2009/05/blog-post.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/2073165880995074289'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/2073165880995074289'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2009/05/blog-post.html' title='以有涯逐無涯，殆矣。'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-7759948679718033676</id><published>2009-05-02T13:32:00.000+08:00</published><updated>2011-05-15T00:56:50.531+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Game'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='ACG'/><title type='text'>空之軌跡</title><content type='html'>&lt;p&gt;如果不全破的話，是不是故事就可以持續下去永不結束？玩完《空之軌跡》系列後，這樣的想法浮現在我腦裡。這是去年十一月、十二月的事了，愈是喜歡一個作品，愈難下筆。&lt;/p&gt;&lt;p&gt;&lt;a href="http://www.flickr.com/photos/12203797@N00/3493169060/" title="FC by fcamel, on Flickr"&gt;&lt;img src="http://farm4.static.flickr.com/3655/3493169060_32d21b820c.jpg" width="500" height="375" alt="FC"&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;《空之軌跡》是 &lt;a href="http://www.falcom.co.jp/"&gt;Falcom&lt;/a&gt; 公司出的系列作，分別為 《空之軌跡 FC》、《空之軌跡 SC》、《空之軌跡 The 3rd》。FC 和 SC 的劇情直接相連，當初全破 FC 後發現劇情急轉直下，嘴裡雖然碎碎唸罵著 &lt;a href="http://www.falcom.co.jp/"&gt;Falcom&lt;/a&gt; 沒天良，還是立即買了 SC 回來玩。沒想到 SC 故事又比 FC 更龐大，算算兩部玩下來，花了我一百小時，這輩子從來沒這麼密集地玩同一作品。&lt;/p&gt;&lt;p&gt;&lt;a href="http://www.flickr.com/photos/12203797@N00/3492351015/" title="SC by fcamel, on Flickr"&gt;&lt;img src="http://farm4.static.flickr.com/3620/3492351015_243f0182f7.jpg" width="500" height="375" alt="SC"&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;全破 SC 後，全身充斥著滿滿的感動，又一次地為自己無法以言語表達感動而感到無力，很想分享澎湃的情感，卻擠不出一個字來。就像做了一個很長很美好的夢一般，夢醒後又回到現實世界，接著是深沉的失落感。為了紓解這種失落感，只好上 ptt 看著大家的心得，放著《空之軌跡》的背景音樂，透過不同人從鍵盤下傳達過來的感動，暫時沉浸在《空之軌跡》的世界，直到心情恢復平靜。&lt;/p&gt;&lt;p&gt;只是，一聽到劇中音樂時，又會陷入短暫地感傷，懷念著約書亞、艾絲蒂爾等人。劇中主題曲的歌詞寫得很好，配合歌詞聽音樂，很自然地融入當時的情景。想說的話太多卻無法述諸文字，只好藉由音樂穿插帶出我片段的感受。&lt;/p&gt;&lt;p&gt;《銀の意志、金の翼》，這首當作早晨起床音樂挺不錯的，情緒會很 high。FC 尾聲出現這首時，立即提昇戰鬥氣氛至最高點。&lt;br&gt;&lt;embed src="http://www.youtube.com/v/15nhOeQQkR4&amp;amp;hl=zh_TW&amp;amp;fs=1" allowScriptAccess="never" allowFullScreen="true" width="560" height="340" wmode="transparent" type="application/x-shockwave-flash"&gt;&lt;/embed&gt;&lt;/p&gt;&lt;p&gt;《星の在り処》 (星之所在)，FC 片尾曲。配合歌詞聽著，想到艾絲蒂爾當時的感受，眼眶情不自盡地溼了。 ( 含歌詞的版本見&lt;a href="http://www.youtube.com/watch?v=xIA-BLl6mdw"&gt;這裡&lt;/a&gt;，畫質音質較差。 )&lt;br&gt;&lt;embed src="http://www.youtube.com/v/MMj0rFTybQ8&amp;amp;hl=zh_TW&amp;amp;fs=1" allowScriptAccess="never" allowFullScreen="true" width="425" height="344" wmode="transparent" type="application/x-shockwave-flash"&gt;&lt;/embed&gt;	&lt;/p&gt;&lt;p&gt;《I Swear》，SC 片尾曲。SC 的結束，暗示著艾絲蒂爾等人的旅程暫時到一段落，今後她們也會繼續活躍著。整首歌充斥著希望，但想到故事到此完結，失落感也隨之來襲。&lt;br&gt;&lt;embed src="http://www.youtube.com/v/fndkQ6SpG7M&amp;amp;hl=zh_TW&amp;amp;fs=1" allowScriptAccess="never" allowFullScreen="true" width="425" height="344" wmode="transparent" type="application/x-shockwave-flash"&gt;&lt;/embed&gt;&lt;/p&gt;&lt;p&gt;於是我忍不住買了 The 3rd，只為多看他們幾眼。劇情方面，The 3rd 穿插許多支線劇情，帶出各角色之後的發展，大大滿足玩完 SC 的失落感。不過主線劇情相較 FC + SC，遜色不少。在看完 The 3rd 的片尾，畫面上映出「Thank you for playing」的字樣時，我更想和制作小組道謝，謝謝他們做出這樣感人的作品。也不禁想著，那一天我才能參與這樣的大作，將我的感動傳出去呢？&lt;/p&gt;&lt;p&gt;最後，以 The 3rd 的片尾曲，《空を见上げて》，做為結尾吧，可惜只有音樂無畫面：&lt;br&gt;&lt;embed src="http://www.youtube.com/v/X0-PMjN0LTk&amp;amp;hl=zh_TW&amp;amp;fs=1" allowScriptAccess="never" allowFullScreen="true" width="425" height="344" wmode="transparent" type="application/x-shockwave-flash"&gt;&lt;/embed&gt;&lt;/p&gt;&lt;p&gt;&lt;a href="http://www.flickr.com/photos/12203797@N00/3493427720/" title="sora-no-kiseki-3rd-ed-image by fcamel, on Flickr"&gt;&lt;img src="http://farm4.static.flickr.com/3326/3493427720_01e8f3d519.jpg" width="500" height="494" alt="sora-no-kiseki-3rd-ed-image"&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;再見了，《空之軌跡》，謝謝你帶給我這段美好的日子。期許著有那麼一天，我也能將你傳達給我的感動，透過我的雙手，制作出足以感動他人的作品。&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/fcamel/~4/t_Vt3K6feII" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-7759948679718033676?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/7759948679718033676/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2009/05/blog-post_02.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/7759948679718033676'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/7759948679718033676'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2009/05/blog-post_02.html' title='空之軌跡'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://farm4.static.flickr.com/3655/3493169060_32d21b820c_t.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-7568737500905552518</id><published>2009-05-01T20:18:00.000+08:00</published><updated>2011-05-15T00:56:51.616+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Python'/><title type='text'>Python 排序的技巧</title><content type='html'>&lt;p&gt;用 Python 排序時相較於 C、Java 多了效率議題。由於 Python 內建函數 (built-in function) 是用 C 實作的，排序時盡可能用內建函數效率會快上不少，詳細的數據比較可以參見 &lt;a href="http://oreilly.com/catalog/9780596001674/"&gt;Python Cookbook&lt;/a&gt;，這篇做個簡單的整理。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;h4&gt;背景知識&lt;/h4&gt;&lt;p&gt;本篇需要 &lt;a href="http://docs.python.org/howto/functional.html#generator-expressions-and-list-comprehensions"&gt;list comprehension&lt;/a&gt; 和 &lt;a href="http://docs.python.org/howto/functional.html#built-in-functions"&gt;map&lt;/a&gt; 的基本觀念，若不清楚的話可以先了解它們再往下看。&lt;/p&gt;&lt;h4&gt;排序 list 和 dict&lt;/h4&gt;&lt;p&gt;先來看程式碼：&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;2&lt;br /&gt;3&lt;br /&gt;4&lt;br /&gt;5&lt;br /&gt;6&lt;br /&gt;7&lt;br /&gt;8&lt;br /&gt;9&lt;br /&gt;10&lt;br /&gt;11&lt;br /&gt;12&lt;br /&gt;13&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;d = &lt;span style="color:black"&gt;{&lt;/span&gt; &lt;span style="color:#483d8b"&gt;'a'&lt;/span&gt;: &lt;span style="color:#ff4500"&gt;2&lt;/span&gt;, &lt;span style="color:#483d8b"&gt;'b'&lt;/span&gt;: &lt;span style="color:#ff4500"&gt;1&lt;/span&gt; &lt;span style="color:black"&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#808080;font-style:italic"&gt;# return keys ordered by key&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;print&lt;/span&gt; &lt;span style="color:#008000"&gt;sorted&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;d&lt;span style="color:black"&gt;)&lt;/span&gt;                                      &lt;span style="color:#808080;font-style:italic"&gt;# ['a', 'b']&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#808080;font-style:italic"&gt;# return values ordered by key&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;print&lt;/span&gt; &lt;span style="color:#008000"&gt;map&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;d.&lt;span style="color:black"&gt;get&lt;/span&gt;, &lt;span style="color:#008000"&gt;sorted&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;d&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;                          &lt;span style="color:#808080;font-style:italic"&gt;# [2, 1]&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#808080;font-style:italic"&gt;# return keys ordered by value&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;print&lt;/span&gt; &lt;span style="color:#008000"&gt;sorted&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;d, key=d.&lt;span style="color:black"&gt;get&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;                           &lt;span style="color:#808080;font-style:italic"&gt;# ['b', 'a']&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#808080;font-style:italic"&gt;# return values ordered by value&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;print&lt;/span&gt; &lt;span style="color:#008000"&gt;sorted&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;d.&lt;span style="color:black"&gt;values&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;                             &lt;span style="color:#808080;font-style:italic"&gt;# [1, 2]&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#808080;font-style:italic"&gt;# return (key, value) pairs order by key&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;print&lt;/span&gt; &lt;span style="color:#008000"&gt;sorted&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;d.&lt;span style="color:black"&gt;items&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;                              &lt;span style="color:#808080;font-style:italic"&gt;# [('a', 2), ('b', 1)]&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#808080;font-style:italic"&gt;# return (key, value) pairs order by value&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;print&lt;/span&gt; &lt;span style="color:#008000"&gt;sorted&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;d.&lt;span style="color:black"&gt;items&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;, key=&lt;span style="color:#dc143c"&gt;operator&lt;/span&gt;.&lt;span style="color:black"&gt;itemgetter&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#ff4500"&gt;1&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;  &lt;span style="color:#808080;font-style:italic"&gt;# [('b', 1), ('a', 2)]&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;上面的程式列出所有排序 dict ( 在一些語言裡稱為 hash table) 的方法，這裡用到幾個技巧，分別說明一下。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;return values ordered by key：&lt;/strong&gt;Python 的 method call 其實和 function call 差不多，obj.func(x) 相當於傳回 func(obj, x)，所以 d.get 一樣可以傳進 map 裡，結果是會取回 d 的 value。關於 function 和 method 的說明，可以參考&lt;a href="http://codeplayer.blogspot.com/2006/12/python-method-function-descriptor.html"&gt;《理解 python 的 method 和 function 兼谈 descriptor 》&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;return keys ordered by value：&lt;/strong&gt;同上的技巧，注意參數 key 有做最佳化處理，使得 d 的每個元素只會呼叫一次 d.get ( 而不是比較次數 O(N*logN) 次 )，不用擔心取 key 的函式會被重覆執行。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;return (key, value) pairs order by value：&lt;/strong&gt;這部份的 code 和排序 list 一樣，關鍵在於用了 operator.itemgetter，它的文件說明摘錄如下：&lt;br&gt;&lt;blockquote&gt;Return a callable object that fetches the given item(s) from its operand.&lt;br&gt;After, f=itemgetter(2), the call f(r) returns r[2].&lt;/blockquote&gt;&lt;/p&gt;&lt;p&gt;以程式碼來表示的話，類似於這麼寫：&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;2&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;def&lt;/span&gt; itemgetter&lt;span style="color:black"&gt;(&lt;/span&gt;n&lt;span style="color:black"&gt;)&lt;/span&gt;:&lt;br /&gt;    &lt;span style="color:#ff7700;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:#ff7700;font-weight:bold"&gt;lambda&lt;/span&gt; r: r&lt;span style="color:black"&gt;[&lt;/span&gt;n&lt;span style="color:black"&gt;]&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;只要資料轉成 list of lists 的形式，就可以用 operator.itemgetter 處理。轉成 list of lists 的時間和資料數量成正比，相對於套入用 python 寫的 comparator 來說，快上不少。module operator 裡有許多好用的函式，在套用 map、max、sort 等內建函式時，可以配合使用，一來不用自己重寫小函式，二來內建函式用 C 實作，速度快上許多。&lt;/p&gt;&lt;h4&gt;Decorate-Sort-Undecorate (DSU)&lt;/h4&gt;&lt;p&gt;若要比較的方式比較複雜，像是希望有 &lt;a href="http://en.wikipedia.org/wiki/Sorting_algorithm#Stability"&gt;stable sort&lt;/a&gt;，或是比較的 key 不只一個，可以套用 DSU 的解法。這裡以 stable sort 為例 (程式引用自 &lt;a href="http://oreilly.com/catalog/9780596001674/"&gt;Python Cookbook&lt;/a&gt;)：&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;2&lt;br /&gt;3&lt;br /&gt;4&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;def&lt;/span&gt; stable_sorted_copy&lt;span style="color:black"&gt;(&lt;/span&gt;alist, _indices=&lt;span style="color:#008000"&gt;xrange&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#dc143c"&gt;sys&lt;/span&gt;.&lt;span style="color:black"&gt;maxint&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;:&lt;br /&gt;    decorated = &lt;span style="color:#008000"&gt;zip&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;alist, _indices&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt;    decorated.&lt;span style="color:black"&gt;sort&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span style="color:#ff7700;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:black"&gt;[&lt;/span&gt; item &lt;span style="color:#ff7700;font-weight:bold"&gt;for&lt;/span&gt; item, index &lt;span style="color:#ff7700;font-weight:bold"&gt;in&lt;/span&gt; decorated &lt;span style="color:black"&gt;]&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;DSU 的關鍵在於先把每筆資料轉成 [key1, key2, ..., original item] 的形式，如此一來內建的 sort 就會依 key1, key2, … 的順序排序，排好後再取回 original item。同樣的，排序 object 依欄位值或 method 傳回值，都可以套用 DSU。而 &lt;a href="http://en.wikipedia.org/wiki/Sorting_algorithm#Stability"&gt;stable sort&lt;/a&gt; 的情況比較不同，是將資料轉成 [original item, original position ]，排序後再取回 original item。&lt;/p&gt;&lt;p&gt;附帶一提，xrange 是 &lt;a href="http://docs.python.org/howto/functional.html#generators"&gt;generator&lt;/a&gt;，使用 generator 好處可以參見 &lt;a href="http://www.python.org/dev/peps/pep-0289/"&gt;PEP 289: Generator Expressions&lt;/a&gt;。&lt;/p&gt;&lt;h4&gt;參考資料&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://oreilly.com/catalog/9780596001674/"&gt;Python Cookbook&lt;/a&gt; 第二章 Searching and Sorting：有詳細的例子一步步解釋各種寫法，並分析優劣。&lt;/li&gt;&lt;li&gt;&lt;a href="http://wiki.python.org/moin/HowTo/Sorting#Sortingbykeys"&gt;Sorting Mini-HOW TO - Sorting by keys&lt;/a&gt;：這篇比 Cookbook 的介紹精簡許多。&lt;/li&gt;&lt;li&gt;&lt;a href="http://docs.python.org/howto/functional.html"&gt;Functional Programming HOWTO&lt;/a&gt;：這篇清楚地說明許多重要的基本觀念。&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;備註&lt;/h4&gt;&lt;p&gt;發表這篇後，才發現去年的今天我在寫 Ruby。物換星移，忽然有淡淡的懷舊感傷。&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/fcamel/~4/9dHp-s4iCII" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-7568737500905552518?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/7568737500905552518/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2009/05/python.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/7568737500905552518'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/7568737500905552518'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2009/05/python.html' title='Python 排序的技巧'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-4811458972180017582</id><published>2009-04-28T21:44:00.000+08:00</published><updated>2011-05-15T00:56:52.660+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Travel'/><title type='text'>關西行：廁所大賞</title><content type='html'>&lt;p&gt;從日本玩回來都快過一年了遊記還沒完成，還是直接寫最有興趣的這篇，剩下的就隨意吧。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;說到旅遊方面的事，我可能沒什麼特別自信，但去一個旅遊景點&lt;strong&gt;先找廁所這件事，我可是很在行呢！&lt;/strong&gt;這回去關西特別搜集了多所精美廁所外觀，在這裡和大家分享一下。為什麼沒拍廁所內部呢？一來是內部沒差太多，都是現代化設施；二來是我不想在一個人旅遊時以妨礙風化的罪行被逮補。這次的特集裡，最可惜的是沒拍到&lt;a href="http://zh.wikipedia.org/wiki/%E6%B8%85%E6%B0%B4%E5%AF%BA"&gt;清水舞台&lt;/a&gt;下面的廁所，那間可說是此行中的冠亞軍之選，但人潮太多，我在那等了十多分鐘都抓不到拍照的時機。&lt;/p&gt;&lt;p&gt;廢話不多說，首先是&lt;a href="http://zh.wikipedia.org/wiki/%E9%87%91%E9%96%A3%E5%AF%BA"&gt;金閣寺&lt;/a&gt;的廁所：&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212127929.jpg"&gt;&lt;/p&gt;&lt;p&gt;所照片所示，以&lt;a href="http://zh.wikipedia.org/wiki/%E9%87%91%E9%96%A3%E5%AF%BA"&gt;金閣寺&lt;/a&gt;的名聲來說，未免太普通了點，失敗失敗。&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212127978.jpg"&gt;&lt;/p&gt;&lt;p&gt;上面這張是&lt;a href="http://zh.wikipedia.org/wiki/%E9%BE%8D%E5%AE%89%E5%AF%BA"&gt;龍安寺&lt;/a&gt;石庭旁的廁所，也是相當普通。再來這張就有點意思了：&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128029.jpg"&gt;&lt;/p&gt;&lt;p&gt;仁和寺的廁所看來比較有點格調，為了怕我自己認不出來是廁所，故意選照得到水管的角度。以在關西的第一天來說，就仁和寺的廁所有在水準之內。希望這些水準低下的廁所外觀沒有掃了大家逛大阪的興致，比較正常的風景照和介紹請參見&lt;a href="http://fcamel.twbbs.org/archives/2008/06/21/491/"&gt;《關西行：抵達京都、前往洛西》&lt;/a&gt;。&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128158.jpg"&gt;&lt;/p&gt;&lt;p&gt;再來這張是風俗博物館內部的廁所，由於該館很小，外觀．．．就是一扇門，只好改拍內部意思意思，可以看到中間還放了幾個小裝飾。喜歡源氏物語的人可以去看看，詳見之前寫的&lt;a href="http://fcamel.twbbs.org/archives/2009/02/01/591/"&gt;《關西行：京都洛中》&lt;/a&gt;。&lt;/p&gt;&lt;p&gt;再貼這類照片下去，相信大家很想關網頁了，只好提前先拿出&lt;strong&gt;最有冠軍相的&lt;a href="http://zh.wikipedia.org/wiki/%E5%B9%B3%E7%AD%89%E9%99%A2"&gt;平等院&lt;/a&gt;廁所！&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128295.jpg"&gt;&lt;/p&gt;&lt;p&gt;是不是看起來相當雅緻呢？而我個人相當中意的&lt;a href="http://zh.wikipedia.org/wiki/%E4%BC%8F%E8%A6%8B%E7%A8%BB%E8%8D%B7%E5%A4%A7%E7%A4%BE"&gt;伏見稻荷大社&lt;/a&gt;，相當地可惜，山中的還算上得了檯面，可是在人潮多的神社旁邊，卻是相當現代化的外觀，真是太不專業了。&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128276.jpg"&gt;&lt;br&gt;( 神社外的廁所 )&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128245.jpg"&gt;&lt;br&gt;( 山中的廁所，拍這張花了我一點時間，雖然在山中，人潮也不算少 )&lt;/p&gt;&lt;p&gt;關於上兩間廁所之外的正常介紹，請見舊文&lt;a href="http://fcamel.twbbs.org/archives/2009/02/02/611/"&gt;《關西行：京都洛南》&lt;/a&gt;。&lt;/p&gt;&lt;p&gt;下面這兩張是二条城的廁所，馬馬虎虎，將就將就啦，護城河和城牆這麼美，為什麼廁所卻沒那種霸氣呢？&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128364.jpg"&gt;&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128414.jpg"&gt;&lt;/p&gt;&lt;p&gt;而下面這張&lt;strong&gt;現代化的廁所，竟然位在&lt;a href="http://zh.wikipedia.org/wiki/%E6%B8%85%E6%B0%B4%E5%AF%BA"&gt;清水寺&lt;/a&gt;，裝個屋瓦並不會改變現代化外觀的事實，&lt;/strong&gt;真是太令人失望了。所幸在&lt;a href="http://zh.wikipedia.org/wiki/%E6%B8%85%E6%B0%B4%E5%AF%BA"&gt;清水寺&lt;/a&gt;下面的廁所相當雅緻，為&lt;a href="http://zh.wikipedia.org/wiki/%E6%B8%85%E6%B0%B4%E5%AF%BA"&gt;清水寺&lt;/a&gt;扳回一些面子。只可惜人潮過多我沒能照成，可說是本次旅遊的一大憾事。&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128483.jpg"&gt;&lt;/p&gt;&lt;p&gt;這部份的相關介紹，請見&lt;a href="http://fcamel.twbbs.org/archives/2009/02/04/634/"&gt;《關西行：京都洛中二条城及洛東清水寺》&lt;/a&gt;&lt;/p&gt;&lt;p&gt;最後這張是&lt;a href="http://ja.wikipedia.org/wiki/%E5%A4%A7%E9%98%AA%E5%9F%8E%E5%85%AC%E5%9C%92"&gt;大阪城公園&lt;/a&gt;的廁所，外觀看來頗為氣派，但如同水柱邊泛黃的痕跡所示，廁所內部實在太髒啦！&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128931.jpg"&gt;&lt;/p&gt;&lt;p&gt;就這樣，本次的廁所大賞到一段落，感謝各位帶著被玷污的眼睛耐心看完。不知大家心目中關西最漂亮的廁所是那間呢？&lt;/p&gt;&lt;p&gt;最後的最後，我不得不說，&lt;a href="http://fcamel.twbbs.org/archives/2009/04/25/809/"&gt;TD-Post&lt;/a&gt;真是好用啊！&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/fcamel/~4/7WAXIkuKTVY" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-4811458972180017582?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/4811458972180017582/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2009/04/blog-post.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/4811458972180017582'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/4811458972180017582'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2009/04/blog-post.html' title='關西行：廁所大賞'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-8131991327664564354</id><published>2009-04-25T23:05:00.000+08:00</published><updated>2011-05-15T00:56:53.514+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Wiki'/><category scheme='http://www.blogger.com/atom/ns#' term='Web'/><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Testing'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Python'/><category scheme='http://www.blogger.com/atom/ns#' term='Blog'/><title type='text'>寫 Blog 好幫手 TD-Post！</title><content type='html'>&lt;p&gt;為了方便寫 Blog，寫了個小程式讀 wiki code 產生 &lt;a href="http://wordpress.org/"&gt;WordPress&lt;/a&gt; 吃的格式，順便藉機練習 &lt;a href="http://en.wikipedia.org/wiki/Test-driven_development"&gt;TDD&lt;/a&gt;，沒想到寫了十小時才完成。剛好最近在看&lt;a href="http://zh.wikipedia.org/wiki/%E8%99%8E%E8%88%87%E9%BE%8D_(%E5%B0%8F%E8%AA%AA)"&gt;虎x龍&lt;/a&gt;的動畫，就將它命名為&lt;a href="http://code.google.com/p/td-post/"&gt;TD-Post&lt;/a&gt; (Tiger x Dragon Post) 吧。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;h4&gt;緣起&lt;/h4&gt;&lt;p&gt;換過多家 &lt;a href="http://wordpress.org/"&gt;WordPress&lt;/a&gt; 用的編輯器後，我還是找不到滿意的工具。為縮短寫 Blog 的時間，我決定自己做一個。&lt;/p&gt;&lt;p&gt;寫 Blog 最惱人的事有幾點：&lt;/p&gt;&lt;ol&gt;&lt;li&gt;得輸入麻煩的 HTML，特別是要匹配結束標籤特別麻煩。而且 HTML code 不易閱讀，改文章時很不方便。&lt;/li&gt;&lt;li&gt;加超鏈結很麻煩，常用的幾個超鏈結，得重附貼多次，像 &lt;a href="http://en.wikipedia.org/wiki/Test-driven_development"&gt;TDD&lt;/a&gt; 我就重貼了許多次。&lt;/li&gt;&lt;li&gt;無法用自己慣用的編輯器。若能用 &lt;a href="http://www.vim.org/"&gt;Vim&lt;/a&gt; 寫 Blog，速度必能大增啊！&lt;/li&gt;&lt;/ol&gt;&lt;h4&gt;功能簡介&lt;/h4&gt;&lt;p&gt;對我來說，寫 wiki code 相當方便，所以輸入格式決定用 wiki code。但 wiki 格式有許多種，我比較常用的有 &lt;a href="http://www.pmwiki.org/"&gt;PmWiki&lt;/a&gt;、&lt;a href="http://www.dokuwiki.org/"&gt;DokuWiki&lt;/a&gt;、&lt;a href="http://twiki.org/"&gt;TWiki&lt;/a&gt;，其中以 &lt;a href="http://www.pmwiki.org/"&gt;PmWiki&lt;/a&gt; 語法最簡單，但原始碼沒後兩者好讀。最後聽從 York 的建議，採用 &lt;a href="http://trac.edgewall.org/"&gt;Trac&lt;/a&gt; 的 &lt;a href="http://trac.edgewall.org/wiki/WikiFormatting"&gt;wiki 格式&lt;/a&gt;。選它的主要原因除好讀好寫外，我特別喜歡它 Preformatted Text 的格式，很適合用來貼程式碼。&lt;/p&gt;&lt;p&gt;接著，針對第二點問題，加上自動補超鏈結的功能，只要寫過一次超鏈結，&lt;a href="http://code.google.com/p/td-post/"&gt;TD-Post&lt;/a&gt; 就會自動記下來，自動補上對應的位置。像在這段裡，因為我在第一段已寫過 &lt;a href="http://code.google.com/p/td-post/"&gt;TD-Post&lt;/a&gt; 的位置， &lt;a href="http://code.google.com/p/td-post/"&gt;TD-Post&lt;/a&gt; 的超鏈結都會自動補上。初步估計，以後寫一篇 Blog 至少可以省十分鐘，所以差不多用個六十次．．．這幾天的辛勞就．就回本啦。&lt;/p&gt;&lt;h4&gt;程式下載&lt;/h4&gt;&lt;p&gt;本程式採 &lt;a href="http://zh.wikipedia.org/w/index.php?title=BSD%E8%AE%B8%E5%8F%AF%E8%AF%81&amp;amp;variant=zh-tw"&gt;BSD License&lt;/a&gt; ( 簡單說就是隨便使用 )，歡迎大家玩玩。雖然沒什麼註解，但我照 &lt;a href="http://en.wikipedia.org/wiki/Test-driven_development"&gt;TDD&lt;/a&gt; 流程寫的，測試碼應該很完整。這裡是一些相關資訊：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;下載：&lt;a href="http://code.google.com/p/td-post/downloads/list"&gt;http://code.google.com/p/td-post/downloads/list&lt;/a&gt;&lt;/li&gt;&lt;li&gt;程式語言：&lt;a href="http://www.python.org/"&gt;Python&lt;/a&gt;&lt;/li&gt;&lt;li&gt;程式碼總行數：682 行&lt;/li&gt;&lt;li&gt;測式碼：346 行 ( 主要是準備測試的例子 )&lt;/li&gt;&lt;li&gt;非測試碼：336 行&lt;/li&gt;&lt;li&gt;實作時間：原本預估兩小時完成，卻花了約十小時。大概是一小時查語法、一到兩小時寫測試。其中花最多的時間在解決 list 和 performatted text 的衝突，這部份從開始實作到結束花了三小時才解決。&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;其它&lt;/h4&gt;&lt;p&gt;有原始碼有真相，最後附上本文的原始碼：&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;2&lt;br /&gt;3&lt;br /&gt;4&lt;br /&gt;5&lt;br /&gt;6&lt;br /&gt;7&lt;br /&gt;8&lt;br /&gt;9&lt;br /&gt;10&lt;br /&gt;11&lt;br /&gt;12&lt;br /&gt;13&lt;br /&gt;14&lt;br /&gt;15&lt;br /&gt;16&lt;br /&gt;17&lt;br /&gt;18&lt;br /&gt;19&lt;br /&gt;20&lt;br /&gt;21&lt;br /&gt;22&lt;br /&gt;23&lt;br /&gt;24&lt;br /&gt;25&lt;br /&gt;26&lt;br /&gt;27&lt;br /&gt;28&lt;br /&gt;29&lt;br /&gt;30&lt;br /&gt;31&lt;br /&gt;32&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;為了方便寫 Blog，寫了個小程式讀 wiki code 產生 [http://wordpress.org/ WordPress] 吃的格式，順便藉機練習 [http://en.wikipedia.org/wiki/Test-driven_development TDD]，沒想到寫了十小時才完成。剛好最近在看[http://zh.wikipedia.org/wiki/虎與龍_(小說) 虎x龍]的動畫，就將它命名為[http://code.google.com/p/td-post/ TD-Post] (Tiger x Dragon Post) 吧。&lt;br /&gt; &lt;br /&gt;==== 緣起 ====&lt;br /&gt;換過多家 [WordPress] 用的編輯器後，我還是找不到滿意的工具。為縮短寫 Blog 的時間，我決定自己做一個。&lt;br /&gt; &lt;br /&gt;寫 Blog 最惱人的事有幾點：&lt;br /&gt; 1. 得輸入麻煩的 HTML，特別是要匹配結束標籤特別麻煩。而且 HTML code 不易閱讀，改文章時很不方便。&lt;br /&gt; 1. 加超鏈結很麻煩，常用的幾個超鏈結，得重附貼多次，像 [TDD] 我就重貼了許多次。&lt;br /&gt; 1. 無法用自己慣用的編輯器。若能用 [http://www.vim.org/ Vim] 寫 Blog，速度必能大增啊！&lt;br /&gt; &lt;br /&gt;==== 功能簡介 ====&lt;br /&gt;對我來說，寫 wiki code 相當方便，所以輸入格式決定用 wiki code。但 wiki 格式有許多種，我比較常用的有 [http://www.pmwiki.org/ PmWiki]、[http://www.dokuwiki.org/ DokuWiki]、[http://twiki.org/ TWiki]，其中以 [PmWiki] 語法最簡單，但原始碼沒後兩者好讀。最後聽從 York 的建議，採用 [http://trac.edgewall.org/ Trac] 的 [http://trac.edgewall.org/wiki/WikiFormatting wiki 格式]。選它的主要原因除好讀好寫外，我特別喜歡它 Preformatted Text 的格式，很適合用來貼程式碼。&lt;br /&gt; &lt;br /&gt;接著，針對第二點問題，加上自動補超鏈結的功能，只要寫過一次超鏈結，[TD-Post] 就會自動記下來，自動補上對應的位置。像在這段裡，因為我在第一段已寫過 [TD-Post] 的位置， [TD-Post] 的超鏈結都會自動補上。初步估計，以後寫一篇 Blog 至少可以省十分鐘，所以差不多用個六十次．．．這幾天的辛勞就．就回本啦。&lt;br /&gt; &lt;br /&gt;==== 程式下載 ====&lt;br /&gt;本程式採 [http://zh.wikipedia.org/w/index.php?title=BSD许可证&amp;amp;variant=zh-tw BSD License] ( 簡單說就是隨便使用 )，歡迎大家玩玩。雖然沒什麼註解，但我照 [TDD] 流程寫的，測試碼應該很完整。這裡是一些相關資訊：&lt;br /&gt; * 下載：http://code.google.com/p/td-post/downloads/list&lt;br /&gt; * 程式語言：[http://www.python.org/ Python]&lt;br /&gt; * 程式碼總行數：682 行&lt;br /&gt; * 測式碼：346 行 ( 主要是準備測試的例子 )&lt;br /&gt; * 非測試碼：336 行&lt;br /&gt; * 實作時間：原本預估兩小時完成，卻花了約十小時。大概是一小時查語法、一到兩小時寫測試。其中花最多的時間在解決 list 和 performatted text 的衝突，這部份從開始實作到結束花了三小時才解決。&lt;br /&gt; &lt;br /&gt;==== 其它 ====&lt;br /&gt; &lt;br /&gt;有原始碼有真相，最後附上本文的原始碼：{{{&lt;br /&gt;#!html&lt;br /&gt;_QUINE_&lt;br /&gt;}}}&lt;br /&gt; &lt;br /&gt;身為資工人，這段原始碼當然也是自動貼上的啦，不過我沒用像 [http://en.wikipedia.org/wiki/Quine_(computing) Quine] 那樣的技巧，只是單純地取代關鍵字。&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;身為資工人，這段原始碼當然也是自動貼上的啦，不過我沒用像 &lt;a href="http://en.wikipedia.org/wiki/Quine_(computing)"&gt;Quine&lt;/a&gt; 那樣的技巧，只是單純地取代關鍵字。&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/fcamel/~4/71_XscnBTkw" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-8131991327664564354?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/8131991327664564354/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2009/04/blog-td-post.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/8131991327664564354'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/8131991327664564354'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2009/04/blog-td-post.html' title='寫 Blog 好幫手 TD-Post！'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-1594140343300005659</id><published>2009-04-22T22:47:00.000+08:00</published><updated>2011-05-16T23:40:20.415+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='中文'/><category scheme='http://www.blogger.com/atom/ns#' term='Fun'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Google'/><category scheme='http://www.blogger.com/atom/ns#' term='English'/><title type='text'>中翻英，線上翻譯測試 － Google Strikes Back！</title><content type='html'>&lt;p&gt;( 這篇寫於 2008-10-26，不知為啥從 Blog 裡消失了，所幸 Google Reader 裡有備份。 )&lt;/p&gt;&lt;p&gt;剛才無聊看看 &lt;a href="https://www.google.com/analytics/"&gt;Google Analytics &lt;/a&gt;，發現本站最熱門的文章還是那篇&lt;a href="http://fcamel.twbbs.org/archives/2006/10/19/114/"&gt;《中翻英，線上翻譯測試》&lt;/a&gt;，我不知該說什麼，這不是件令人高興的事啊。總之，一時興起又來測看看。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;最近 Google 翻譯似乎變強了，果然有在學習啊！輸入問題如下：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;我要代替月亮來懲罰你&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;兩年前 Google 的回報令我很失望：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;I want to punish you replace Moon&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;沒想到現在 &lt;a href="http://translate.google.com/translate_t?hl=zh-TW#zh-CN%7Cen%7C%E6%88%91%E8%A6%81%E4%BB%A3%E6%9B%BF%E6%9C%88%E4%BA%AE%E4%BE%86%E6%87%B2%E7%BD%B0%E4%BD%A0"&gt;Google 幾乎翻對了！&lt;/a&gt;&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;I want to punish you in place of the moon&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;google 一下會發現有不少人提到類似的句子，附帶一提，個人心目中最完美的翻譯是：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;In the name of the moon, I will punish you!&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;其它家翻譯結果沒什麼變，就不再貼了。&lt;/p&gt;&lt;p&gt;另外再測了&lt;a href="http://fcamel.twbbs.org/archives/2006/10/19/115/"&gt;之前亂測的中文詩詞，&lt;/a&gt;有些變化，不過說不上是變好變壞，都很糟就是了。看來博大精測的中文仍是機器翻譯無法跨越的高牆，也是&lt;a href="http://blog.chinatimes.com/sow/Archive/2008/10/14/334349.html"&gt;國高中生們無法跨越的牆啦。&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/fcamel/~4/2OmzmY8xqAI" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-1594140343300005659?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/1594140343300005659/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2009/04/google-strikes-back.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/1594140343300005659'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/1594140343300005659'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2009/04/google-strikes-back.html' title='中翻英，線上翻譯測試 － Google Strikes Back！'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-1172071481664829160</id><published>2009-04-22T21:59:00.000+08:00</published><updated>2011-05-16T23:40:21.465+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Python'/><category scheme='http://www.blogger.com/atom/ns#' term='VIM'/><category scheme='http://www.blogger.com/atom/ns#' term='System'/><title type='text'>強化 Python 在 Vim 裡的顏色</title><content type='html'>&lt;p&gt;我習慣用 putty 連 Unix server 開 screen，再用 vim 寫 Python。這篇記錄如何改善 Python 的顏色。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;h4&gt;啟動 256 色 terminal&lt;/h4&gt;&lt;p&gt;首先將可用的色彩數增加為 256 色，先確定 putty 為新版 ( 舊版 putty 沒支援 256 色)。接著參照&lt;a href="http://plog.longwin.com.tw/my_note-unix/2008/12/19/vim-screen-set-support-256-color-2008"&gt;《讓 Vim、Screen 支援 256 色》&lt;/a&gt;將 Screen 和 Vim 設好，記得先用文末提到的 &lt;a href="http://www.vim.org/scripts/script.php?script_id=1349"&gt;Colortest&lt;/a&gt; 測試是否有符合 256 色，成功的話，數字 0 ~ 15 為原本的 16 色，16 ~ 231 為新的顏色 (6 x 6 x 6)，232 ~ 255 為新的灰階色。可配合 Colortest 看到數字值和顏色。&lt;/p&gt;&lt;p&gt;這裡備忘該篇提到的步驟：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;在 ~/.vimrc 加入：&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;&lt;span style="color:#000000;font-weight:bold"&gt;set&lt;/span&gt; &lt;span style="color:#007800"&gt;t_Co&lt;/span&gt;=&lt;span style="color:#000000"&gt;256&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;在 ~/.screenrc 加入：&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;termcapinfo xterm &lt;span style="color:#ff0000"&gt;'Co#256:AB=\E[48;5;%dm:AF=\E[38;5;%dm'&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;更新 Python script 偵測的格式&lt;/h4&gt;&lt;p&gt;更新 syntax/python.vim，讓 Vim 偵測出更多種格式，像是行末多的空白，或是空白行有含空格都會被偵測出來。做法如下：&lt;/p&gt;&lt;ol&gt;&lt;li&gt;下載最新的 &lt;a href="http://www.vim.org/scripts/download_script.php?src_id=9293"&gt;python.vim&lt;/a&gt; 並放到 ~/.vim/syntax/ 下。  &lt;/li&gt;&lt;li&gt;編輯 ~/.vimrc，加入&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;&lt;span style="color:#7a0874;font-weight:bold"&gt;let&lt;/span&gt; python_highlight_all = &lt;span style="color:#000000"&gt;1&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;以顯示所有 python.vim 有偵測的格式。參照 python.vim 開頭的註解，了解細部選項。 &lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h4&gt;替換顯示的顏色&lt;/h4&gt;&lt;ol&gt;&lt;li&gt;下載 Vim color scheme: &lt;a href="http://www.vim.org/scripts/download_script.php?src_id=9587"&gt;Wombat256.vim&lt;/a&gt;，並放到 ~/.vim/colors/ 下。&lt;/li&gt;&lt;li&gt;在 ~/.vimrc 裡加入&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;colorscheme wombat256&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;如此一來就有漂亮的顏色啦，wombat256 的 screenshot 見&lt;a href="http://dengmao.wordpress.com/2007/01/22/vim-color-scheme-wombat/"&gt;這裡&lt;/a&gt;。&lt;/p&gt;&lt;h4&gt;備註&lt;/h4&gt;&lt;p&gt;&lt;a href="http://www.cs.cmu.edu/~maverick/VimColorSchemeTest/"&gt;這個網站&lt;/a&gt;有提供許多不同的 color scheme screenshot，可惜沒附 Python 的 screenshot。&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-1172071481664829160?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/1172071481664829160/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2009/04/python-vim.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/1172071481664829160'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/1172071481664829160'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2009/04/python-vim.html' title='強化 Python 在 Vim 裡的顏色'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-4164100738181946191</id><published>2009-04-19T23:46:00.000+08:00</published><updated>2011-05-16T23:40:22.411+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Refactoring'/><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Testing'/><category scheme='http://www.blogger.com/atom/ns#' term='Python'/><title type='text'>最近用 Python + TDD 心得（與 Java 做對照）</title><content type='html'>&lt;p&gt; 最近又用 &lt;a href="http://www.python.org/"&gt;Python&lt;/a&gt; 寫了些程式，剛好和之前用 Java 進行 &lt;a href="http://en.wikipedia.org/wiki/Test-driven_developmen"&gt;TDD &lt;/a&gt;做個對照。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;若不知道 TDD 的人，可以先參考&lt;a href="http://fcamel.twbbs.org/archives/2009/03/09/747/" title="/archives/2009/03/09/747/"&gt;這篇&lt;/a&gt;，TDD 的概念是依以下三個步驟寫程式 ( &lt;strong&gt;順序相當重要&lt;/strong&gt; )：&lt;/p&gt;&lt;ol&gt;&lt;li&gt;先寫簡單的單元測試，並執行它。&lt;/li&gt;&lt;li&gt;用最簡單的方法實作需要的功能，讓程式能通過測試。&lt;/li&gt;&lt;li&gt;重構程式，並確保重構後的程式仍能通過測試。&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;實作部份不需多談，這裡先分別對步驟一「寫測試碼」和步驟三「重構」討論，再分享一般性的一點心得。&lt;/p&gt;&lt;h4&gt;寫測試碼&lt;/h4&gt;&lt;p&gt;Python 寫起測試碼比 Java 簡單許多，可以&lt;strong&gt;輕易地抽換所有既有物件&lt;/strong&gt;，如 module、function、class、method。下面是一個簡單的抽換 method 的例子：&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;2&lt;br /&gt;3&lt;br /&gt;4&lt;br /&gt;5&lt;br /&gt;6&lt;br /&gt;7&lt;br /&gt;8&lt;br /&gt;9&lt;br /&gt;10&lt;br /&gt;11&lt;br /&gt;12&lt;br /&gt;13&lt;br /&gt;14&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;class&lt;/span&gt; MyClass&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#008000"&gt;object&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;:&lt;br /&gt;    &lt;span style="color:#ff7700;font-weight:bold"&gt;def&lt;/span&gt; hello&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#008000"&gt;self&lt;/span&gt;, msg&lt;span style="color:black"&gt;)&lt;/span&gt;:&lt;br /&gt;        &lt;span style="color:#ff7700;font-weight:bold"&gt;print&lt;/span&gt; msg&lt;br /&gt; &lt;br /&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;def&lt;/span&gt; new_hello&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#008000"&gt;self&lt;/span&gt;, msg&lt;span style="color:black"&gt;)&lt;/span&gt;:&lt;br /&gt;    &lt;span style="color:#ff7700;font-weight:bold"&gt;print&lt;/span&gt; msg + &lt;span style="color:#483d8b"&gt;&amp;quot; (by replaced method)&amp;quot;&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;obj = MyClass&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt;obj.&lt;span style="color:black"&gt;hello&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#483d8b"&gt;'Testing rocks!'&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;  &lt;span style="color:#808080;font-style:italic"&gt;# Testing rocks!&lt;/span&gt;&lt;br /&gt;mystub = stub.&lt;span style="color:black"&gt;Stub&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt;mystub.&lt;span style="color:black"&gt;replace&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;MyClass, &lt;span style="color:#483d8b"&gt;'hello'&lt;/span&gt;, new_hello&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt;obj.&lt;span style="color:black"&gt;hello&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#483d8b"&gt;'Testing rocks!'&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;  &lt;span style="color:#808080;font-style:italic"&gt;# Testing rocks! (by replaced method)&lt;/span&gt;&lt;br /&gt;mystub.&lt;span style="color:black"&gt;restore&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;MyClass, &lt;span style="color:#483d8b"&gt;'hello'&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt;obj.&lt;span style="color:black"&gt;hello&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#483d8b"&gt;'Testing rocks!'&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;  &lt;span style="color:#808080;font-style:italic"&gt;# Testing rocks!&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;註解表示該行輸出結果，其中 Stub 是我寫得一個簡單 class，只有十行多，可以替換和還原物件。可以從 MyClass.hello() 的輸出看出，stub.replace() 後 MyClass.hello() 被換為 new_hello()，stub.restore() 後則換了回來。完整的程式碼請看&lt;a href="http://pastie.textmate.org/451440"&gt;這裡&lt;/a&gt;。&lt;/p&gt;&lt;p&gt;方便抽換物件有什麼好處呢？舉例來說，若 class A 有兩個 method B 和 C，其中 B 用到 C ( B 會視 C 傳回結果改變程式流程 )。&lt;strong&gt;為了方便測試 B 的行為，得先控制 C 的傳回結果。&lt;/strong&gt;若是 Python 的話，照上面的例子透過 Stub ( setattr() ) 替換 A.C 即可。但在 Java 的情況，除非將 C 當作參數傳給 B，讓測試程式有機會傳「假的 C 」給 B，不然難以控制 B 內部行為。寫新程式時還有機會改設計，對於舊的程式，修改程式是個災難。用到別人的函式庫或沒程式碼就無解了。不論 Java 有何解法 ( 如採用 &lt;a href="http://en.wikipedia.org/wiki/Dependency_injection"&gt;injection&lt;/a&gt; 的方式 )，&lt;strong&gt;解法愈麻煩，表示大眾愈不願意做，因此降低測試碼的品質，連帶影響 TDD 效果。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;a href="http://www.martinfowler.com/articles/mocksArentStubs.html"&gt;Mock &lt;/a&gt;library 部份，Java 有 &lt;a href="http://easymock.org/"&gt;EasyMock&lt;/a&gt;，Python 有 &lt;a href="http://code.google.com/p/pymox/"&gt;Mox&lt;/a&gt;，兩者用法差不多。後者是前者改來的，學一套兩邊都可以用。&lt;/p&gt;&lt;h4&gt;重構&lt;/h4&gt;&lt;p&gt;由於 Java 的特性 ( &lt;a href="http://en.wikipedia.org/wiki/Type_system"&gt;static type&lt;/a&gt; )，重構工具相當成熟，我慣用 &lt;a href="http://www.eclipse.org/"&gt;Eclipse&lt;/a&gt; 的重構功能，離開它就不太想寫 Java。其中我最常用的功能是改名稱，包含 variable、method 、 class、 package 等。&lt;strong&gt;好的名稱是好程式碼的必要條件之一&lt;/strong&gt;，好的名稱可以省去冗長的註解、縮短理解程式的時間，同時也有助於作者釐清邏輯，減少犯錯的機會。若發現名稱很難取，也許表示該 object（method / class / package）功能沒規範好，之後容易遇到問題。寫程式難免會犯錯，寫一段時間後才發現命名不夠精確，得回頭修改。&lt;strong&gt;若有好的重構工具，改起來快又沒風險，可以提高重構的意願。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;反觀 Python 因為 dynamic type，程式碼本身提供的訊息不足（得執行後才清楚全貌），難以透過工具重構。我找了一下相關工具後沒看到滿意的。Eclipse 的 &lt;a href="http://pydev.sourceforge.net/"&gt;PyDev&lt;/a&gt; plugin 有提供重構，但試用後結果是錯的，改 module 名稱時沒動到檔名，執行後才會發現程式碼爛了。另外試了 &lt;a href="http://rope.sourceforge.net/"&gt;Rope&lt;/a&gt; ，可以正確改名，可是速度有點慢，操作相當不直覺 ( 之後再來試看看 &lt;a href="http://rope.sourceforge.net/ropevim.html"&gt;ropevim&lt;/a&gt; )。為了長遠發展考量，或許可以試看看自己弄個簡單版的改名工具或改 Rope，工具的功能可以少，但要快、容易操作且結果正確。&lt;/p&gt;&lt;h4&gt;結語&lt;/h4&gt;&lt;p&gt;只有在三個步驟都確實做到時，TDD 才能發揮應有的威力。依我目前一點點的實作心得，Python 容易寫測試碼、卻不方便重構；而 Java 正好相反。現在我大多有照 TDD 的流程寫程式，即使寫個一小時的小程式，也會用 TDD 。有時寫寫覺得卡卡的，才發現忘了先寫測試碼。&lt;/p&gt;&lt;p&gt;另外，&lt;strong&gt;測試碼的範圍抓得準 ( 別測太細，也別懶得測核心) ，效果才會好。&lt;/strong&gt;我初用 TDD + Python 時就矯枉過正，寫太多測試變成 「over-testing」，省了 over-design 的負擔，卻多花時間寫不必要的測試碼、又增加日後維護的成本 。&lt;/p&gt;&lt;p&gt;養成 TDD 習慣的關鍵，在於寫測試碼的功力，像是如何準備 fixture ，如何改善函式介面以利測試。&lt;strong&gt;為了寫測試而改變函式介面並不是本末倒置&lt;/strong&gt;，通常這會降低函式之間的關聯性，將功能明確切開，讓每個函式的輸入輸出都很乾淨（有簡單的輸入，才方便準備 fixture）。而乾淨的輸入輸出意味著函式更容易被組合使用。&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/fcamel/~4/tIW9OkgvcfA" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-4164100738181946191?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/4164100738181946191/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2009/04/python-tdd-java.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/4164100738181946191'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/4164100738181946191'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2009/04/python-tdd-java.html' title='最近用 Python + TDD 心得（與 Java 做對照）'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-4550190128159657684</id><published>2009-03-15T13:05:00.000+08:00</published><updated>2011-05-16T23:40:23.359+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Refactoring'/><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Java'/><category scheme='http://www.blogger.com/atom/ns#' term='Testing'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Software Engineering'/><title type='text'>軟體開發技巧的待讀書單</title><content type='html'>&lt;p&gt;經過這陣子的評估，終於列出了夢幻般 (?) 的讀書清單。我的做法是先在網上看到有人推薦，到書局翻一陣子，再回來查 Amazon 的評論，最後決定是否有必要看。附帶一提，待這書單決定後又查了一下，結果發現每本書都有得到 &lt;a href="http://www.joltawards.com/"&gt;Jolt Award&lt;/a&gt;，一瞬間好像以為 Jolt Award 不值錢了 :-)。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;以下依暫定的閱讀順序依序說明，除前兩本外，後三本都有中文版： &lt;/p&gt;&lt;h4&gt;1. Test Driven Development by Examples&lt;/h4&gt;&lt;p&gt;&lt;a href="http://www.amazon.com/Test-Driven-Development-Addison-Wesley-Signature/dp/0321146530"&gt;Amazon 評論 4 顆星&lt;/a&gt;，由 Kent Beck 所著。全書只有 200+ 頁，用大量的例子，一步一步說明怎麼進行 TDD，相當易讀。這裡有別人寫的&lt;a href="http://www.kenming.idv.tw/index.php?title=ithome_a_cec_a_11_test_driven_developmen&amp;amp;more=1&amp;amp;c=1&amp;amp;tb=1&amp;amp;pb=1"&gt;詳細書評&lt;/a&gt;。&lt;/p&gt;&lt;h4&gt;2. xUnit Test Patterns: Refactoring Test Code&lt;/h4&gt;&lt;p&gt;&lt;a href="http://www.amazon.com/xUnit-Test-Patterns-Refactoring-Addison-Wesley/dp/0131495054"&gt;Amazon 評論 4.5 顆星&lt;/a&gt;，不過只有七筆評論，樣本略嫌不足。 在書店試翻的感想是：好書但不易讀，而且實在是太厚了。&lt;a href="http://www.thoughtworks.com/"&gt;ThoughtWorks&lt;/a&gt; 專出這種書嗎？&lt;/p&gt;&lt;h4&gt;3. Refactoring to Patterns&lt;/h4&gt;&lt;p&gt;&lt;a href="http://www.amazon.com/Refactoring-Patterns-Addison-Wesley-Signature-Kerievsky/dp/0321213351"&gt;Amazon 評論 4 顆星&lt;/a&gt;，但評論兩極化落在 3 和 5，最中肯的評論為： &amp;quot;Good ideas, but needs refactoring&amp;quot;，值得一讀，但不好消化。讀過 Martin Fowler 的 &lt;a href="http://www.books.com.tw/exep/prod/booksfile.php?item=0010411649"&gt;Refactoring&lt;/a&gt; 並有一段實戰經驗後的最佳書藉。本書中文版&lt;a href="http://www.books.com.tw/exep/prod/booksfile.php?item=0010399556"&gt;《重構－向範式前進》&lt;/a&gt;，且是侯捷合譯的 。 &lt;/p&gt;&lt;h4&gt;4. Head First - Head First Object-Oriented Analysis and Design&lt;/h4&gt;&lt;p&gt;&lt;a href="http://www.amazon.com/Head-First-Object-Oriented-Analysis-Design/dp/0596008678"&gt;Amazon 評論 4 顆星&lt;/a&gt;，評論裡指出本書適合初學者，另外&lt;a href="http://www.amazon.com/review/R3E3M5KIN2NYA1/ref=cm_cr_pr_cmt?ie=UTF8&amp;amp;ASIN=0596008678&amp;amp;nodeID=#wasThisHelpful"&gt;最多人同意的評論 (Decent Introduction to OOA&amp;amp;D) &lt;/a&gt;指出：本書不夠簡潔，並有不少小錯，若有第二版才值得推薦。 &lt;br&gt;&lt;h4&gt;5. Head First - Design Pattern&lt;/h4&gt;&lt;p&gt;&lt;a href="http://www.amazon.com/First-Design-Patterns-Elisabeth-Freeman/dp/0596007124"&gt;Amazon 評論 4.5 顆星&lt;/a&gt;，且是壓倒性的一堆 5 顆星。Head First 的書以易讀出名，但通常也寫得很厚，需要花不少時間消化 。 對照過於精簡典雅的 &lt;a href="http://www.amazon.com/Design-Patterns-Object-Oriented-Addison-Wesley-Professional/dp/0201633612"&gt;Design Pattern Bible Book&lt;/a&gt;，這本書親切不少。附帶一提，在書店還有看到 &lt;a href="http://www.amazon.com/Head-First-Algebra-Learners-Brain-Friendly/dp/0596514867"&gt;Head First - Algebra&lt;/a&gt;，真是太有趣了！可惜是教國中代數，不是教線性代數。&lt;/p&gt;&lt;p&gt;依目前心得來說，有四件事要學：&lt;/p&gt;&lt;ol&gt;&lt;li&gt;寫出良好的測試程式 - book 2 &lt;/li&gt;&lt;li&gt;寫出良好的 OOP- book 4, 5 &lt;/li&gt;&lt;li&gt;提昇重構技巧 - book 3 &lt;/li&gt;&lt;li&gt;TDD (其實就是上述三者的綜合體) - book 1 &lt;/li&gt;&lt;/ol&gt;&lt;p&gt;前往軟體開發聖殿之路是很遠的，希望一個月至少能解決一本，並持續地應用到實戰中，半年後就出師啦！ &lt;/p&gt;&lt;h4&gt;備註&lt;/h4&gt;&lt;p&gt;這裡補充我曾看過且大力推薦的書藉：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://www.books.com.tw/exep/prod/booksfile.php?item=0010233987"&gt;Effective Java Programming Language Guide&lt;/a&gt;：Joshua Bloch 所著。中文版只有第一版，但原文已有第二版，我只看過中譯第一版。本書適合偶而拿出來重習學習，薄又易讀。若不認識 Joshua Bloch，至少也應該用過他做的 &lt;a href="http://java.sun.com/docs/books/tutorial/collections/index.html"&gt;Java Collections Framework&lt;/a&gt;。 &lt;/li&gt;&lt;li&gt;&lt;a href="http://www.books.com.tw/exep/prod/booksfile.php?item=0010411649"&gt;重構：改善既有程式的設計&lt;/a&gt;：Martin Fowler 所著，淺顯易懂的好書。說明何謂壞程式碼，以及如何一小步一小步安全地修正壞程式碼。&lt;/li&gt;&lt;/ul&gt;&lt;img src="http://feeds.feedburner.com/~r/fcamel/~4/_1ISHWzX9vs" height="1" width="1"&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-4550190128159657684?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/4550190128159657684/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2009/03/blog-post.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/4550190128159657684'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/4550190128159657684'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2009/03/blog-post.html' title='軟體開發技巧的待讀書單'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-8858207963565850285</id><published>2009-03-10T00:38:00.000+08:00</published><updated>2011-05-16T23:40:24.158+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Testing'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Software Engineering'/><title type='text'>TDD 推廣：結語和參考資料</title><content type='html'>&lt;p&gt;續&lt;a href="http://fcamel.twbbs.org/archives/2009/03/10/757/"&gt;前篇&lt;/a&gt;，這篇說明我和組員近兩週開發過程中的心得，並附上幾篇不錯的參考資料。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;h4&gt;結語&lt;/h4&gt;&lt;p&gt;目前我和組員只有做到半套的 TDD，有時有照三步驟的順序進行，有時是先寫程式碼，再補測試碼，或兩者交替寫。目前組員有感受到測試程式的好處，例如對程式有信心、避免因修改而破壞既有功能。組員也漸漸感受到重構的必要性。但對於先寫測試碼這點，仍有疑慮而難以進行。至少，我們盡量遵守 TDD 的結果，在寫另一個 class 時，會先補好測試碼，保持程式碼的品質。附帶一提，配合 IDE 的功能，先寫測試碼可以輕鬆地建出產品用程式的框架。像 Eclipse 可以自動產生不存在的 class、method、field，所以我常先寫測試，再用 Eclipse 建出這些東西的殼，之後再填入內容，寫起來相當快。&lt;/p&gt;&lt;p&gt;開發過程也不盡是順利。藉由頻繁的 code review 組員的程式碼，還有回顧自己的程式碼，我發現許多問題。像是單一測試函式太大、測試碼和程式關聯性過高、測試時間過長，若沒有第一時間更正，後果不堪設想，也會使得組員對 TDD 有不當的負面認知。這方面的相關知識可以搜尋 &lt;a href="http://www.google.com.tw/search?q=tdd+antipattern&amp;amp;sourceid=navclient-ff&amp;amp;ie=UTF-8&amp;amp;rlz=1B3GGGL_enTW308TW309"&gt;TDD antipattern&lt;/a&gt;，或 &lt;a href="http://www.google.com.tw/search?q=junit+antipattern&amp;amp;sourceid=navclient-ff&amp;amp;ie=UTF-8&amp;amp;rlz=1B3GGGL_enTW308TW309"&gt;JUnit antipattern&lt;/a&gt;。&lt;/p&gt;&lt;p&gt;此外，由於組員少了前備知識，進行開發過程裡會看到許多 &lt;a href="http://en.wikipedia.org/wiki/Code_smell"&gt;code smell&lt;/a&gt;，常常在重構一段程式前，必須先重構另一段程式，而重構另一段程式前，又得先補對應的測試程式或重構測試程式。這才體會到良好品質的程式碼得來不易，需要全組成員一同堅持和維護，大家的相關知識也需要一同逐步提昇，才能達到良好的軟體開發流程。雖然初期摸索很花時間，相信日後會有相當高的回報。&lt;/p&gt;&lt;p&gt;說了這麼多，其實仍抵不過自己親身嘗試，&lt;strong&gt;強力建議親身找個小專案，用 TDD 的三個步驟寫個千行程式，&lt;/strong&gt;相信程式設計思維會有很大的轉換。&lt;/p&gt;&lt;h4&gt;參考資料&lt;/h4&gt;&lt;p&gt;我到 &lt;a href="http://www.slideshare.net/"&gt;slideshare &lt;/a&gt;找了一陣子，看到三份不錯的教學，但各有些不足，沒有一份完全符合我預期的入門文件。若有時間的話，自己再來作份投影片。&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://www.slideshare.net/dnoble/testing-in-java"&gt;Testing In Java&lt;/a&gt;：提供簡單清楚的 TDD 流程圖，並附有多個工具連結供日後參考。&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.slideshare.net/Skud/test-driven-development-tutorial"&gt;Test Driven Development Tutorial&lt;/a&gt;：提供詳細的介紹，包含相關工具的參考。&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.slideshare.net/viget/changing-your-mindset-tdd-introduction"&gt;Changing Your Mindset: Getting Started With Test-Driven Development&lt;/a&gt;：實戰是最容易了解 TDD 的方法，這篇直接展示 TDD 過程中程式碼的變化。若看得懂 Ruby 的話，是很不錯的介紹。&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;相關閱讀&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://fcamel.twbbs.org/archives/2009/03/09/747/"&gt;TDD 推廣：背景知識和簡介&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://fcamel.twbbs.org/archives/2009/03/10/752/"&gt;TDD 推廣：影響和個案心得&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://fcamel.twbbs.org/archives/2009/03/10/757/"&gt;TDD 推廣：相關工具&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;img src="http://feeds2.feedburner.com/~r/fcamel/~4/HFh2WMkCV8Y" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-8858207963565850285?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/8858207963565850285/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2009/03/tdd.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/8858207963565850285'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/8858207963565850285'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2009/03/tdd.html' title='TDD 推廣：結語和參考資料'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-8368366291734369620</id><published>2009-03-10T00:21:00.000+08:00</published><updated>2011-05-16T23:40:25.070+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Testing'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Software Engineering'/><title type='text'>TDD 推廣：相關工具</title><content type='html'>&lt;p&gt;續&lt;a href="http://fcamel.twbbs.org/archives/2009/03/10/752/"&gt;前篇&lt;/a&gt;，這篇說明我近兩週開發過程中選擇工具的心得。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;h4&gt;開發環境&lt;/h4&gt;&lt;p&gt;我採用 &lt;a href="http://fcamel.twbbs.org/archives/2009/03/10/757/www.eclipse.org/"&gt;Eclipse&lt;/a&gt;，配合 Eclipse 的 refactoring tool，大幅加快重構的速度。人都有惰性，好工具讓我們更願意重構，而且降低重構過程除錯的風險。對照之前我自己寫 C++ 和 Python 的經驗，現在我更常做 rename variable/method、change method signature 之類的操作。&lt;/p&gt;&lt;h4&gt;Unit Test&lt;/h4&gt;&lt;p&gt;經過評估後，我決定用 JUnix 4.x。JUnit 4.x 改用 &lt;a href="http://java.sun.com/j2se/1.5.0/docs/guide/language/annotations.html"&gt;annotation&lt;/a&gt; 的方式表示 test method，可以去除必須繼承 TestCase 的限制。如此一來 test method 命名彈性更大，而且也可以直接在 class 裡加 test method 測 private method，解決因 private 不方便測試的困擾。不過我不確定這種作法是否正確，仍需多點經驗判斷。其它 JUnix 4.x 的好處可以參照&lt;a href="http://www.ibm.com/developerworks/java/library/j-junit4.html"&gt;《An early look at JUnit 4》&lt;/a&gt;。&lt;/p&gt;&lt;p&gt;關於 unit test 的技巧， &lt;a href="http://thoughtworks.com/"&gt;ThoughtWorks&lt;/a&gt; 有出相關的書：&lt;a href="http://www.amazon.com/xUnit-Test-Patterns-Refactoring-Addison-Wesley/dp/0131495054"&gt;《xUnit Test Patterns: Refactoring Test Cod》&lt;/a&gt;。有機會再去書店翻看看。&lt;/p&gt;&lt;h4&gt;Mock lib&lt;/h4&gt;&lt;p&gt;關於 mock 的知識可以參見 Martin Fowler 的&lt;a href="http://martinfowler.com/articles/mocksArentStubs.html"&gt;《Mocks Aren’t Stubs》&lt;/a&gt;，解釋得鞭闢入理，除介紹 mock 外，更進一步指出&lt;strong&gt;用與不用 mock，其實測試思維是截然不同的（確認物件行為或確認物件狀態），&lt;/strong&gt;有機會的話再對此另寫心得。文中的範例程式有點過時（畢竟是舊文章），現在 mock lib 的用法更方便一些。Java 的 mock lib 有許多選擇，最後我選用 &lt;a href="http://www.easymock.org/"&gt;EasyMock&lt;/a&gt;Z。除了好用外，主要是因為 Python 的 &lt;a href="http://code.google.com/p/pymox/"&gt;Mox&lt;/a&gt; 是從 EasyMock 來的，這樣寫 Python 時可以花較少心力學 Mox。&lt;/p&gt;&lt;h4&gt;Database unit testing&lt;/h4&gt;&lt;p&gt;若只是用到簡單的 database 操作，可以用 EasyMock 處理，。測試前準備好 mock connection、mock statment、mock resutSet 即可。一方面是初學 mock 的緣故，另一方面也是 database 相關操作太瑣碎，我花了一整個下午才用 EasyMock 完成測試程式並寫好對應的功能。事後修改挺複雜的，比方說我程式裡補了 close()，mock statement/connection 也要記得加。感覺不是理想的作法，這方面經驗尚淺，需要多點實戰。&lt;/p&gt;&lt;p&gt;若要測得更深更全面一些，測試對 &lt;a href="http://en.wikipedia.org/wiki/Database_management_system"&gt;DBMS&lt;/a&gt; 本身的操作（ insert / delete / update）的話，用 &lt;a href="http://www.dbunit.org/"&gt;DBUnit&lt;/a&gt; 會方便許多。DBUnit 的基本精神是幫測試程式準備好已知狀態的資料庫，以及比對兩個資料庫是否相等。它的核心功能是將資料庫內容和 XML 檔案互轉。所以，先建好一個資料庫，透過 DBUnit 將整個資料庫存成一個 XML 檔，日後隨時都能用該 XML 檔還原回原樣。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;對測試程式而言，執行速度是非常重要的，執行速度快才能反覆地執行多次，才方便反覆開發、重構以及整合測試。&lt;/strong&gt;所以我改用 &lt;a href="http://www.sqlite.org/"&gt;SQLite&lt;/a&gt; 做為測試用的資料庫，真正的產品再用 MySQL 之類的 DBMS。SQLite 的好處是支援 SQL92 的語法，整個資料庫只要一個檔案即可。如此一來只需要讀寫本機檔案，省去網路連線的負擔，也方便建置測試環境。&lt;/p&gt;&lt;p&gt;由於使用 DBUnit 前得先準備好資料庫才行，我得先從既有的 MySQL database 建出同樣 schema 的 SQLite database，才能用 DBUnit 做後續的操作。原本打算寫個程式讀出 MySQL database schema 再建出 SQLite database schema。但兩者建 table 語法差太多，無法直接套用。最後我是利用 DBUnit 產生的 XML 檔，寫程式從 XML 檔中讀出 table 和 column 名稱，再建出簡單的 SQL 指令來建出 SQLite database。如此一來，每次自動測試前我先用 DBUnit 將 SQLite database 填入備好的資料，就能快速地重覆在同樣的狀態下測試資料庫操作的程式碼。待有更多相關經驗後，再針對此議題寫篇心得。&lt;/p&gt;&lt;h4&gt;相關閱讀&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://fcamel.twbbs.org/archives/2009/03/09/747/"&gt;TDD 推廣：背景知識和簡介&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://fcamel.twbbs.org/archives/2009/03/10/752/"&gt;TDD 推廣：影響和個案心得&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://fcamel.twbbs.org/archives/2009/03/10/758/"&gt;TDD 推廣：結語和參考資料&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;img src="http://feeds2.feedburner.com/~r/fcamel/~4/F1xPlL91g0M" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-8368366291734369620?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/8368366291734369620/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2009/03/tdd_10.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/8368366291734369620'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/8368366291734369620'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2009/03/tdd_10.html' title='TDD 推廣：相關工具'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-8753252586680999157</id><published>2009-03-10T00:11:00.000+08:00</published><updated>2011-05-16T23:40:25.981+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Testing'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Software Engineering'/><title type='text'>TDD 推廣：影響和個案心得</title><content type='html'>&lt;p&gt;續&lt;a href="http://fcamel.twbbs.org/archives/2009/03/09/747/"&gt;前篇&lt;/a&gt;，這篇談 TDD 帶來的改變，以及我自己的個案心得。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;h4&gt;TDD 的影響&lt;/h4&gt;&lt;p&gt;只要用 TDD 重寫以前寫過的小專案，就會發現最後的產出，和原本寫的程式不同。舉例來說，會發現少了很多 setter function，因為大多情況只要用 constructor 放值即可。更進一步，&lt;strong&gt;會發現少寫了許多功能，而這些功能其實是原本不需用到的。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;TDD 同時也幫助設計出易用的介面，&lt;/strong&gt;透過寫測試程式的過程，可以釐清程式的使用方式。當發現程式不好測試時，通常意味著程式設計不良。測試程式也可以當作文件使用，提供使用程式的範例程式碼。&lt;/p&gt;&lt;p&gt;除此之外，&lt;strong&gt;在 TDD 的過程中，開發的速度很順暢，很少會落入長時間的除錯裡，&lt;/strong&gt;因為程式被強迫切成許多小單位，並且每一單位都有被測試。一但出錯馬上會發現，而且知道錯在那裡。不像以往得用 debugger 追個老半天，最後發現 bug 在很遠的一小段程式碼裡，有時還是簡單的打錯字。在除錯的過程，藉由自動化測試的幫助，修改和執行可以快速地反覆交替進行，減少除錯以外的精神損耗（如手動準備執行需要的資料、設置和操作 debugger ）。即使寫測試程式花的時間和除錯一樣多（我相信會是較少），至少以 TDD 的方式進行，心情會比較愉快，不會卡在一個地方太久。&lt;/p&gt;&lt;h4&gt;TDD 的個案心得&lt;/h4&gt;&lt;p&gt;以我最近的實作案例來說，我一開始知道會用到數個算數函數，而且這些函數日後需要抽換為不同的算法，方便我比較何種組合效果最好。舉例來說，我需要實作排序演算法 Sort()，由於資料分佈特性的改變，我可能需要實作各種演算法如 quick sort、heap sort、insertion sort、radix sort 等，並讓這些演算法的輸入輸出介面一致，方便視情況抽換不同的演算法。在這個案例裡，我需要寫多個這類型的演算法，把它們整合成一個較大的程式。&lt;/p&gt;&lt;p&gt;若照我原本的習慣，大概就全部套 &lt;a href="http://en.wikipedia.org/wiki/Strategy_pattern"&gt;strategy pattern&lt;/a&gt;，於是我可能會先寫數個 interface 或是寫個 abstract class 再套多型。至少會花半天完成這些工作，並且增加一堆「未來可能會用到」的 class。但這回我忍住了，我遵從 TDD 的三個步驟，我先統統用 static method 寫，因為這是最簡單的實作。結果寫個兩天後，我發現這幾個算數函數，其實只有一個真的需要套 strategy pattern，其它用 static method 就夠了。也許那一個最複雜的函數也不需用到 strategy pattern，總之，由於目前仍無需求，我全部都保留原樣。於是我將時間花在刀口上，優先完成必要的事。並且，我的 class space 沒有被一堆「也許有用」的 interface 和 class 汙染，&lt;strong&gt;避免 over-design，提高整體的可讀性。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;相信大家看到這都會大喊，&lt;strong&gt;全部都等用到才寫，日後難道不會付出更大的代價做變動？&lt;/strong&gt;改程式碼的代價很高，所以應該多費些心力設計好才對吧？但別忘了&lt;strong&gt;，「改程式碼的代價很高」是一般的情況，在有充沛的測試程式做為後盾的情況下，改程式的代價其實沒那麼高。&lt;/strong&gt;況且，程式碼隨時都有重構，程式應該是保持在容易修改的情況，改程式的代價又更低了。&lt;/p&gt;&lt;p&gt;至於初期的設計要精確到什麼程度才開始寫程式，仍需經驗拿捏。我目前的作法是先有個大概的整體設計，再開始用 TDD 的方式實作各個 class，並完成細部的設計。&lt;/p&gt;&lt;h4&gt;相關閱讀&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://fcamel.twbbs.org/archives/2009/03/09/747/"&gt;TDD 推廣：背景知識和簡介&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://fcamel.twbbs.org/archives/2009/03/10/757/"&gt;TDD 推廣：相關工具&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://fcamel.twbbs.org/archives/2009/03/10/758/"&gt;TDD 推廣：結語和參考資料&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;img src="http://feeds2.feedburner.com/~r/fcamel/~4/hMCi7a1sU7w" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-8753252586680999157?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/8753252586680999157/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2009/03/tdd_6543.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/8753252586680999157'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/8753252586680999157'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2009/03/tdd_6543.html' title='TDD 推廣：影響和個案心得'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-5436054690209051247</id><published>2009-03-09T19:35:00.000+08:00</published><updated>2011-05-16T23:40:27.161+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Testing'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Software Engineering'/><title type='text'>TDD 推廣：背景知識和簡介</title><content type='html'>&lt;p&gt;之前我曾獨自一人用 TDD (&lt;a href="http://en.wikipedia.org/wiki/Test-driven_development"&gt;Test Driven Development&lt;/a&gt;，中譯為測試驅動開發) 的方式，分別用 Python 和 C++ 各寫了一千多行的小程式，感覺滿好的。最近剛好有機會寫一個新專案，就趁這機會開始第三次的 TDD 練習。和前兩次不同的是，這次要和一位組員合作，以 Java 開發。看來正是測試 TDD 威能的最好時機。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;由於我的組員缺乏寫測試程式的經驗，剛好可以用來評估是否能在組內推廣 TDD。雖然我之前有過兩次 TDD 開發經驗，但也只是自己邊看文件邊摸索，經驗仍嫌不足。這次進行 TDD 讓我多花了不少心力查相關資料。但是為了擺脫軟體開發後期的泥沼，現在先下點苦工是值得的。這裡記錄一下過程中的心得。&lt;/p&gt;&lt;h4&gt;背景知識&lt;/h4&gt;&lt;p&gt;了解 TDD 前，得先知道重構 ( &lt;a href="http://www.refactoring.com/"&gt;Refactoring&lt;/a&gt; ) 和單元測試 ( &lt;a href="http://en.wikipedia.org/wiki/Unit_testing"&gt;Unit Testing&lt;/a&gt; )。個人大力推薦 &lt;a href="http://martinfowler.com/"&gt;Martin Fowler&lt;/a&gt; 寫的&lt;a href="http://www.books.com.tw/exep/prod/booksfile.php?item=0010411649"&gt;《重構：改善既有程式的設計 》&lt;/a&gt;，可以了解何謂好的程式碼、如何找出該改善的程式碼以及如何改善它。書中列出多種簡單的操作流程，相當淺顯易懂。附帶一提，&lt;a href="http://martinfowler.com/"&gt;Martin Fowler 的個人站&lt;/a&gt;有許多經典好文，值得細細咀嚼。&lt;/p&gt;&lt;h4&gt;TDD 簡介&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;TDD 是個知難行易的道理。知難，是因為我們很難相信它，&lt;/strong&gt;到不是因為 TDD 概念很複雜，只要三句話就可以交待完 TDD：&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;先寫簡單的單元測試，並執行它。&lt;/strong&gt; &lt;/li&gt;&lt;li&gt;&lt;strong&gt;用最簡單的方法實作需要的功能，讓程式能通過測試。&lt;/strong&gt; &lt;/li&gt;&lt;li&gt;&lt;strong&gt;重構程式，並確保重構後的程式仍能通過測試。&lt;/strong&gt; &lt;/li&gt;&lt;/ol&gt;&lt;p&gt;實務上，這三條規則蘊含了經年累月的經驗法則。若沒親身體驗過，再怎麼解釋它們帶來的好處，也很難令人信服。&lt;strong&gt;這三條規則是環環相扣的，&lt;/strong&gt;由於有先寫測試，才能安心地重構；由於有重構，才能方便地擴充功能；由於專注於最簡單的實作，省掉 over-design 的時間，才能用更快的速度完成該作的事，並有多餘時間寫測試程式和重構。&lt;strong&gt;藉由測試程式，程式設計師可以提高對程式碼的信心；透過重構，程式碼易於修改和重用。&lt;/strong&gt;注意這裡&lt;strong&gt;強調「最簡單」的實作，&lt;/strong&gt;這正是 TDD 精神所在，簡單的實作易於進行，複雜的考量如程式碼是否能重用、是否易於擴充，都留待第三步再做。專心正是將事做得又快又好的祕訣。&lt;/p&gt;&lt;p&gt;天下沒有白吃的午餐，&lt;strong&gt;要做到如上所述的理想世界，必須學許多寫測試程式的技巧。&lt;/strong&gt;如同平常寫程式有輔助工具、函式庫、設計模式等，測試程式亦有對應的東西要學。舉例來說，產品用的程式需要重構，測試用的程式也需要重構。&lt;strong&gt;關鍵在於&lt;/strong&gt;&lt;a href="http://www.stickyminds.com/sitewide.asp?Function=edetail&amp;amp;ObjectId=11298"&gt;&lt;strong&gt;「treating their tests like first-class citizens. 」&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;。&lt;/strong&gt;反之若將測試程式視為可有可無的附帶程式，只會陷入惡性循環，改變產品用程式的細節得改變測試用程式，反而變成得改兩倍的程式，以及在兩份程式中除錯。結果變得更糟，並誤認為測試程式是累贅。我剛用 TDD 時就因為沒把測試程式寫成許多小巧的函式，或是沒有寫成正確的檢查方式，造成產品用的程式是對的，卻常在測試程式裡除錯，造成無謂的損耗。&lt;/p&gt;&lt;h4&gt;相關閱讀&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://fcamel.twbbs.org/archives/2009/03/10/752/"&gt;TDD 推廣：影響和個案心得&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://fcamel.twbbs.org/archives/2009/03/10/757/"&gt;TDD 推廣：相關工具&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://fcamel.twbbs.org/archives/2009/03/10/758/"&gt;TDD 推廣：結語和參考資料&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;img src="http://feeds2.feedburner.com/~r/fcamel/~4/ba-RPsBkcug" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-5436054690209051247?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/5436054690209051247/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2009/03/tdd_09.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/5436054690209051247'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/5436054690209051247'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2009/03/tdd_09.html' title='TDD 推廣：背景知識和簡介'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-1446514614885965123</id><published>2009-03-08T20:48:00.000+08:00</published><updated>2011-05-16T23:40:28.400+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Testing'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Python'/><title type='text'>讓 Python 的 unittestgui 可以載入 test suite</title><content type='html'>&lt;p&gt;由於 pyUnit 附的 unittestgui 怎麼試都沒成功，我改用這個 unittestgui.py：&lt;a href="http://homepage.hispeed.ch/py430/python/unittestgui.py"&gt;GUI test runner using wxPython&lt;/a&gt;。相關說明請見&lt;a href="http://fcamel.twbbs.org/archives/2009/02/18/666/"&gt;之前的文章&lt;/a&gt;。美中不足的是，它不方便載入 test suite，所以我修改了一下這份程式。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;h4&gt;程式下載和用法&lt;/h4&gt;&lt;p&gt;&lt;a href="http://fcamel.twbbs.org/wp-content/uploads/2009/03/unittestgui.py"&gt;點此&lt;/a&gt;下載修改後的程式碼。使用的範例程式碼如下：&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;2&lt;br /&gt;3&lt;br /&gt;4&lt;br /&gt;5&lt;br /&gt;6&lt;br /&gt;7&lt;br /&gt;8&lt;br /&gt;9&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;&lt;span style="color:#808080;font-style:italic"&gt;# all_test_suite.py&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;import&lt;/span&gt; &lt;span style="color:#dc143c"&gt;unittest&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;import&lt;/span&gt; catsinbag.&lt;span style="color:black"&gt;card_factory_test&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;import&lt;/span&gt; catsinbag.&lt;span style="color:black"&gt;cash_test&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;def&lt;/span&gt; suite&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;:&lt;br /&gt;    suites = &lt;span style="color:black"&gt;[&lt;/span&gt;&lt;span style="color:black"&gt;]&lt;/span&gt;&lt;br /&gt;    suites.&lt;span style="color:black"&gt;append&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#dc143c"&gt;unittest&lt;/span&gt;.&lt;span style="color:black"&gt;TestLoader&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;.&lt;span style="color:black"&gt;loadTestsFromTestCase&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;some_module.&lt;span style="color:black"&gt;SomeTestCase&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span style="color:#ff7700;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:#dc143c"&gt;unittest&lt;/span&gt;.&lt;span style="color:black"&gt;TestSuite&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;suites&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;執行 unittestgui.py 後，按 Open 選擇 all_test_suite.py 即可載入。&lt;/p&gt;&lt;p&gt;目前這個工具仍有些問題，像是原始碼改過後，得重開檔案載入後才會生效，所以無法一邊改程式碼一邊按 Run Test。還有錯誤訊息顯示得不夠清楚，而且還是顯示在 console 上，不是顯示在 GUI 上。&lt;/p&gt;&lt;h4&gt;修改過程心得&lt;/h4&gt;&lt;p&gt;參照 &lt;a href="http://docs.python.org/library/unittest.html"&gt;unittest官方文件&lt;/a&gt;，最方便載入多個 test cases 和 test suites 的方法，是採用下面這段 code：&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;2&lt;br /&gt;3&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;suite1 = &lt;span style="color:#dc143c"&gt;unittest&lt;/span&gt;.&lt;span style="color:black"&gt;TestLoader&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;.&lt;span style="color:black"&gt;loadTestsFromTestCase&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;SomeTestCase1&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt;suite2 = &lt;span style="color:#dc143c"&gt;unittest&lt;/span&gt;.&lt;span style="color:black"&gt;TestLoader&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;.&lt;span style="color:black"&gt;loadTestsFromTestCase&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;SomeTestCase2&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt;alltests = &lt;span style="color:#dc143c"&gt;unittest&lt;/span&gt;.&lt;span style="color:black"&gt;TestSuite&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:black"&gt;[&lt;/span&gt;suite1, suite2&lt;span style="color:black"&gt;]&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;若自己用 console 執行 unittest，只要把 alltests 交給 TextTextRunner 即可，例如：&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;&lt;span style="color:#dc143c"&gt;unittest&lt;/span&gt;.&lt;span style="color:black"&gt;TextTestRunner&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;verbosity=&lt;span style="color:#ff4500"&gt;2&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;.&lt;span style="color:black"&gt;run&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;alltests&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;但是，一個熱愛寫 testing 程式的程式設計師，除了想要有更高品質的程式碼外，內心更渴望看到 Green Bar啊！所以非得用 GUI 版的 unittest 不可。而 unittestgui.py 可以載入 test case、test suite，卻沒辦法載入 module 內產生的 test suite。所以只好直接改 unittestgui.py 的程式碼。關鍵在於 OnMenuOpen() 如何取得 test suite。原本的程式碼如下：&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;2&lt;br /&gt;3&lt;br /&gt;4&lt;br /&gt;5&lt;br /&gt;6&lt;br /&gt;7&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;modname, modtype = &lt;span style="color:#dc143c"&gt;os&lt;/span&gt;.&lt;span style="color:black"&gt;path&lt;/span&gt;.&lt;span style="color:black"&gt;splitext&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#dc143c"&gt;os&lt;/span&gt;.&lt;span style="color:black"&gt;path&lt;/span&gt;.&lt;span style="color:black"&gt;basename&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#008000"&gt;self&lt;/span&gt;.&lt;span style="color:black"&gt;filename&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;if&lt;/span&gt; modtype.&lt;span style="color:black"&gt;lower&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt; == &lt;span style="color:#483d8b"&gt;'.py'&lt;/span&gt;:&lt;br /&gt;    moduleToTest = &lt;span style="color:#dc143c"&gt;imp&lt;/span&gt;.&lt;span style="color:black"&gt;load_source&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;modname, &lt;span style="color:#008000"&gt;self&lt;/span&gt;.&lt;span style="color:black"&gt;filename&lt;/span&gt;, &lt;span style="color:#008000"&gt;file&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#008000"&gt;self&lt;/span&gt;.&lt;span style="color:black"&gt;filename&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;elif&lt;/span&gt; modtype.&lt;span style="color:black"&gt;lower&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt; &lt;span style="color:#ff7700;font-weight:bold"&gt;in&lt;/span&gt; &lt;span style="color:black"&gt;{&lt;/span&gt;&lt;span style="color:#483d8b"&gt;'.pyc'&lt;/span&gt;:&lt;span style="color:#ff4500"&gt;0&lt;/span&gt;, &lt;span style="color:#483d8b"&gt;'.pyo'&lt;/span&gt;:&lt;span style="color:#ff4500"&gt;0&lt;/span&gt;&lt;span style="color:black"&gt;}&lt;/span&gt;:&lt;br /&gt;    moduleToTest = &lt;span style="color:#dc143c"&gt;imp&lt;/span&gt;.&lt;span style="color:black"&gt;load_compiled&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;modname, &lt;span style="color:#008000"&gt;self&lt;/span&gt;.&lt;span style="color:black"&gt;filename&lt;/span&gt;, &lt;span style="color:#008000"&gt;file&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#008000"&gt;self&lt;/span&gt;.&lt;span style="color:black"&gt;filename&lt;/span&gt;, &lt;span style="color:#483d8b"&gt;'rb'&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#808080;font-style:italic"&gt;#print moduleToTest, dir(moduleToTest)&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#008000"&gt;self&lt;/span&gt;.&lt;span style="color:black"&gt;suite&lt;/span&gt; = &lt;span style="color:#dc143c"&gt;unittest&lt;/span&gt;.&lt;span style="color:black"&gt;defaultTestLoader&lt;/span&gt;.&lt;span style="color:black"&gt;loadTestsFromModule&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;moduleToTest&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;修改後的程式碼如下：&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;2&lt;br /&gt;3&lt;br /&gt;4&lt;br /&gt;5&lt;br /&gt;6&lt;br /&gt;7&lt;br /&gt;8&lt;br /&gt;9&lt;br /&gt;10&lt;br /&gt;11&lt;br /&gt;12&lt;br /&gt;13&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;modname, modtype = &lt;span style="color:#dc143c"&gt;os&lt;/span&gt;.&lt;span style="color:black"&gt;path&lt;/span&gt;.&lt;span style="color:black"&gt;splitext&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#dc143c"&gt;os&lt;/span&gt;.&lt;span style="color:black"&gt;path&lt;/span&gt;.&lt;span style="color:black"&gt;basename&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#008000"&gt;self&lt;/span&gt;.&lt;span style="color:black"&gt;filename&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;if&lt;/span&gt; modtype.&lt;span style="color:black"&gt;lower&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt; == &lt;span style="color:#483d8b"&gt;'.py'&lt;/span&gt;:&lt;br /&gt;    moduleToTest = &lt;span style="color:#dc143c"&gt;imp&lt;/span&gt;.&lt;span style="color:black"&gt;load_source&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;modname, &lt;span style="color:#008000"&gt;self&lt;/span&gt;.&lt;span style="color:black"&gt;filename&lt;/span&gt;, &lt;span style="color:#008000"&gt;file&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#008000"&gt;self&lt;/span&gt;.&lt;span style="color:black"&gt;filename&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;elif&lt;/span&gt; modtype.&lt;span style="color:black"&gt;lower&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt; &lt;span style="color:#ff7700;font-weight:bold"&gt;in&lt;/span&gt; &lt;span style="color:black"&gt;{&lt;/span&gt;&lt;span style="color:#483d8b"&gt;'.pyc'&lt;/span&gt;:&lt;span style="color:#ff4500"&gt;0&lt;/span&gt;, &lt;span style="color:#483d8b"&gt;'.pyo'&lt;/span&gt;:&lt;span style="color:#ff4500"&gt;0&lt;/span&gt;&lt;span style="color:black"&gt;}&lt;/span&gt;:&lt;br /&gt;    moduleToTest = &lt;span style="color:#dc143c"&gt;imp&lt;/span&gt;.&lt;span style="color:black"&gt;load_compiled&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;modname, &lt;span style="color:#008000"&gt;self&lt;/span&gt;.&lt;span style="color:black"&gt;filename&lt;/span&gt;, &lt;span style="color:#008000"&gt;file&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#008000"&gt;self&lt;/span&gt;.&lt;span style="color:black"&gt;filename&lt;/span&gt;, &lt;span style="color:#483d8b"&gt;'rb'&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#808080;font-style:italic"&gt;#print moduleToTest, dir(moduleToTest)&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;if&lt;/span&gt; &lt;span style="color:#008000"&gt;hasattr&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;moduleToTest, &lt;span style="color:#483d8b"&gt;'suite'&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;:  &lt;span style="color:#808080;font-style:italic"&gt;# use the prepared suite if it exists.&lt;/span&gt;&lt;br /&gt;    &lt;span style="color:#ff7700;font-weight:bold"&gt;if&lt;/span&gt; &lt;span style="color:#008000"&gt;callable&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;moduleToTest.&lt;span style="color:black"&gt;suite&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;:&lt;br /&gt;        &lt;span style="color:#008000"&gt;self&lt;/span&gt;.&lt;span style="color:black"&gt;suite&lt;/span&gt; = moduleToTest.&lt;span style="color:black"&gt;suite&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span style="color:#ff7700;font-weight:bold"&gt;else&lt;/span&gt;:&lt;br /&gt;        &lt;span style="color:#008000"&gt;self&lt;/span&gt;.&lt;span style="color:black"&gt;suite&lt;/span&gt; = moduleToTest.&lt;span style="color:black"&gt;suite&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;else&lt;/span&gt;:&lt;br /&gt;    &lt;span style="color:#008000"&gt;self&lt;/span&gt;.&lt;span style="color:black"&gt;suite&lt;/span&gt; = &lt;span style="color:#dc143c"&gt;unittest&lt;/span&gt;.&lt;span style="color:black"&gt;defaultTestLoader&lt;/span&gt;.&lt;span style="color:black"&gt;loadTestsFromModule&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;moduleToTest&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;利用已讀到的 module: moduleToTest，增加判斷它是否有提供 function suite() 或是 attribute suite，若有的話，直接使用準備好的 test suite，而非從 TestCase 或 TestSuite 中產生。&lt;/p&gt;&lt;p&gt;最後附上執行後的精美圖片，辛苦了這麼久，就是為了看到這 Green Bar 啊！&lt;/p&gt;&lt;p&gt;&lt;a href="http://www.flickr.com/photos/12203797@N00/3337986184/" title="unittestgui_with_suite by fcamel, on Flickr"&gt;&lt;img src="http://farm4.static.flickr.com/3568/3337986184_c03803a774.jpg" alt="unittestgui_with_suite" width="500" height="448"&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds2.feedburner.com/~r/fcamel/~4/m3ECnFpyZFI" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-1446514614885965123?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/1446514614885965123/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2009/03/python-unittestgui-test-suite.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/1446514614885965123'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/1446514614885965123'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2009/03/python-unittestgui-test-suite.html' title='讓 Python 的 unittestgui 可以載入 test suite'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://farm4.static.flickr.com/3568/3337986184_c03803a774_t.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-3423568053607891078</id><published>2009-03-08T14:56:00.000+08:00</published><updated>2011-05-16T23:40:29.460+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Web'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Tool'/><category scheme='http://www.blogger.com/atom/ns#' term='System'/><category scheme='http://www.blogger.com/atom/ns#' term='Blog'/><title type='text'>裝了 ScribeFire Blog Editor</title><content type='html'>&lt;p&gt;由於 &lt;a href="http://windowslivewriter.spaces.live.com/"&gt;Windows Live Editor&lt;/a&gt; 對 WordPress 的支援不夠完整，這篇改用 &lt;a href="http://www.scribefire.com/"&gt;Scribefire&lt;/a&gt; 試看看。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;Scribefire 也有很炫的預覽功能，而且多了可以將網頁的標題和連結取出加入編輯內容內，挺方便的。Category 部份到是有支援一半，可點選要加入那些目錄，但沒有顯示出原本目錄的階層關係。唯一的缺點是不能加入&amp;lt;!–more–&amp;gt;。或許之後可以改用 ScribeFire 看看。&lt;/p&gt;&lt;h4&gt;Update&lt;/h4&gt;&lt;p&gt;ScribeFire 也加了部份討厭的 tag，像是 &amp;lt;br&amp;gt; 和 &amp;lt;div&amp;gt;，這樣日後要回頭手動編輯時，文章內容挺醜的。&lt;/p&gt;&lt;img src="http://feeds2.feedburner.com/~r/fcamel/~4/12ynSMXNcJ0" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-3423568053607891078?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/3423568053607891078/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2009/03/scribefire-blog-editor.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/3423568053607891078'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/3423568053607891078'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2009/03/scribefire-blog-editor.html' title='裝了 ScribeFire Blog Editor'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-2287564966159169545</id><published>2009-03-08T14:35:00.000+08:00</published><updated>2011-05-16T23:40:30.040+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Web'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Tool'/><category scheme='http://www.blogger.com/atom/ns#' term='System'/><category scheme='http://www.blogger.com/atom/ns#' term='Blog'/><title type='text'>裝了 Windows Live Writer</title><content type='html'>&lt;p&gt;剛才不小心關掉網頁而遺失了正在寫的文章，一怒之下就裝了 &lt;a href="http://windowslivewriter.spaces.live.com/"&gt;Windows Live Writer&lt;/a&gt;。現在這篇就是用它來試寫。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;初步用起來還挺方便的，不過每打一個字畫面就閃一下，感覺挺怪的。還有不能插入 WordPress 專用的一些 tag，得自己切到 Source mode 輸入（例如&amp;lt;!—more—&amp;gt;）。 Preview mode 看起來挺屌的，可以直接看到文章送出後，在自己的 Blog theme 下看起來是什麼樣子。&lt;/p&gt;&lt;h4&gt; Update &lt;/h4&gt;&lt;p&gt;Windows Live Writer 竟然加了多餘的 tag，像是 &amp;lt;p&amp;gt;，而且不能用&amp;lt;!—more—&amp;gt;，發文章時找不到選 category 的地方，只有看到加 tag。改來試用別套好了。&lt;/p&gt;&lt;img src="http://feeds2.feedburner.com/~r/fcamel/~4/C3WLNFL9kLw" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-2287564966159169545?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/2287564966159169545/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2009/03/windows-live-writer.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/2287564966159169545'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/2287564966159169545'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2009/03/windows-live-writer.html' title='裝了 Windows Live Writer'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-1219859133137654312</id><published>2009-03-07T19:34:00.000+08:00</published><updated>2011-05-16T23:40:30.760+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Fun'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><title type='text'>甲土豆症候群</title><content type='html'>&lt;p&gt;這個可怕的病得從小黃（匿名）去一趟系計中後說起。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;有天小黃買了新硬碟，卻發現自己的電腦無法使用，只好去系計中借些工具處理一下。結果當時系計中的部份人士正瘋狂地看《甲土豆系列》影片，小黃不小心中標了，遂把影片也帶回家裡推廣。&lt;/p&gt;&lt;p&gt;不久後&lt;a href="http://taoyangtrivia.blogspot.com/"&gt;小羊&lt;/a&gt;（匿名）也中招了，兩人還常常你一句我一句地接裡面的台詞，這時小駱（匿名）才發覺事態不妙。但小駱經不起誘惑，仍將它介紹給&lt;a href="http://ieon.pixnet.net/blog"&gt;小豬&lt;/a&gt;（匿名）、小德（匿名）、&lt;a href="http://tbri.pixnet.net/blog"&gt;小王&lt;/a&gt;（還是匿名）等人，結果不幸地，小王也中標了。&lt;/p&gt;&lt;p&gt;在此記錄此種症狀的感染過程：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;通常看完第一遍時，是一臉茫然不知所措。&lt;/li&gt;&lt;li&gt;看第二遍時，開始有噗嗤一聲。&lt;/li&gt;&lt;li&gt;看第三遍時，開始斷斷續續地笑。&lt;/li&gt;&lt;li&gt;看第四遍時，開始莫明地狂笑，但要說為何好笑，卻怎麼也說不上來。&lt;/li&gt;&lt;li&gt;看第五遍時，忍不住跟著念起劇中台詞，而且沒有播放時還會不停地覆誦劇中台詞，遇到其他發病者時，會自然地你一句我一句地接完全部台詞。&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;為證明我不是無端唬爛，以下為小王發病過程的個案記錄：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;這是啥鬼….XD “靠北我就說前面那一句給你…”&lt;/li&gt;&lt;li&gt;後勁真的蠻強的…&lt;/li&gt;&lt;li&gt;甲土豆!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!&lt;/li&gt;&lt;li&gt;甲土豆!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!&lt;/li&gt;&lt;li&gt;甲土豆!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!&lt;/li&gt;&lt;li&gt;…&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;若還是不信邪的話，請直接觀看《甲土豆》一片：&lt;br&gt;&lt;embed src="http://www.youtube.com/v/r2t9fSrWmGM&amp;amp;hl=en&amp;amp;fs=1" allowScriptAccess="never" allowFullScreen="true" width="425" height="344" wmode="transparent" type="application/x-shockwave-flash"&gt;&lt;/embed&gt;&lt;/p&gt;&lt;p&gt;另外讓小黃和小羊中毒最深則是這篇《歐巴馬》（內有髒話，不喜者勿點）：&lt;br&gt;&lt;embed src="http://www.youtube.com/v/jMkWfMt1j08&amp;amp;hl=en&amp;amp;fs=1" allowScriptAccess="never" allowFullScreen="true" width="425" height="344" wmode="transparent" type="application/x-shockwave-flash"&gt;&lt;/embed&gt;&lt;/p&gt;&lt;p&gt;我自己是覺得《聯合國》也不錯啦（內有髒話，不喜者勿點）：&lt;br&gt;&lt;embed src="http://www.youtube.com/v/MGyY-nIbnPI&amp;amp;hl=en&amp;amp;fs=1" allowScriptAccess="never" allowFullScreen="true" width="425" height="344" wmode="transparent" type="application/x-shockwave-flash"&gt;&lt;/embed&gt;&lt;/p&gt;&lt;p&gt;僅以此篇，為這些患者留個記錄。若看完上面兩篇還無感覺的話，恭喜你，你目前對《甲土豆》免役，別再多看幾遍，快快離開《甲土豆》的世界吧。&lt;/p&gt;&lt;h4&gt;備註&lt;/h4&gt;&lt;p&gt;我忘了在那看到別人介紹，這系列原本的影片是教青少年正確的災難處理方式，像是「失火了怎麼逃生」，「有人在冰湖上跌倒了，要怎麼救？」，對照改版後的台詞，真是太 XD 啦，讓我想到台詞翻譯的重要性。而說到台詞翻譯的重要性，不得不提這篇&lt;a href="http://blog.xuite.net/rookierookie/moe/8028897"&gt;《史上最爛翻譯: 港版「風之谷」完整考證》&lt;/a&gt;，相信自此之後，大家會對譯者們致上最高的敬意。&lt;/p&gt;&lt;img src="http://feeds2.feedburner.com/~r/fcamel/~4/1Mi-39N5WLE" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-1219859133137654312?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/1219859133137654312/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2009/03/blog-post_07.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/1219859133137654312'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/1219859133137654312'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2009/03/blog-post_07.html' title='甲土豆症候群'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-4781128839170165560</id><published>2009-03-07T18:59:00.000+08:00</published><updated>2011-05-16T23:40:31.795+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='格言錦句'/><title type='text'>路漫漫其修遠兮，吾將上下而求索</title><content type='html'>&lt;p&gt;忽然發現很久沒更新&lt;a href="http://fcamel.twbbs.org/archives/category/%e6%a0%bc%e8%a8%80%e9%8c%a6%e5%8f%a5/"&gt;「格言綿句」&lt;/a&gt;了，來補上去年十月末的感觸。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;原文寫於 2008-10-21，下文為稍微修改後的版本。&lt;/p&gt;&lt;p&gt;在家和老爸老媽一起看&lt;a href="http://zh.wikipedia.org/w/index.php?title=%E4%B9%94%E5%AE%B6%E5%A4%A7%E9%99%A2_(%E7%94%B5%E8%A7%86%E5%89%A7)&amp;amp;variant=zh-tw"&gt;《喬家大院》&lt;/a&gt;，這部戲還滿好看的。當喬致庸決定做票號 (相當於現今的銀行) 時，他的得力助手孫茂才大力勸阻，而喬致庸引用&lt;a href="http://zh.wikipedia.org/w/index.php?title=%E9%9B%A2%E9%A8%B7&amp;amp;variant=zh-tw"&gt;《離騷》&lt;/a&gt;回了這段話：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;亦余心之所善兮，雖九死其猶未悔。&lt;br&gt;路漫漫其修遠兮，吾將上下而求索。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;真是感動到心坎裡了！雖然我還不知道自己要追求什麼，也許過陣子會重想起學生時代的熱情。&lt;/p&gt;&lt;h4&gt;備註&lt;/h4&gt;&lt;p&gt;離騷全文見&lt;a href="http://zh.wikisource.org/w/index.php?title=%E9%9B%A2%E9%A8%B7&amp;amp;variant=zh-tw"&gt;這裡&lt;/a&gt;，這輩子大概一次也不會仔細讀完吧。文學氣息這種東西，小時候沒好好培養，長大後就是那幅德性啦。要學的東西只會愈來愈多，學不完的東西也只會愈來愈多。&lt;/p&gt;&lt;img src="http://feeds2.feedburner.com/~r/fcamel/~4/Hph61js5htI" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-4781128839170165560?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/4781128839170165560/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2009/03/blog-post_6817.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/4781128839170165560'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/4781128839170165560'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2009/03/blog-post_6817.html' title='路漫漫其修遠兮，吾將上下而求索'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-6773864441758416185</id><published>2009-02-23T22:13:00.000+08:00</published><updated>2011-05-16T23:40:32.875+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Travel'/><title type='text'>關西行：大阪海遊館</title><content type='html'>&lt;p&gt;2008/05/26 Mon.&lt;/p&gt;&lt;p&gt;早上 checout 離開住了三天的京都旅館，要回大阪住商務旅館啦。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;一早 7:30 起床，從京都坐電車回大阪。途中沒搶到座位，得站個半小時，真累。到大阪站後不知道怎麼買一日卷，用英文和車站服員用英文求助，大車站的服務員英文很好，真方便。還有在大阪站看到大型行李鎖箱，對觀光客來說挺方便的。沒想到回台灣後發現台北車站也有，之前都沒注意到。&lt;/p&gt;&lt;p&gt;早上先花了日幣 2,000 參觀&lt;a href="http://www.kaiyukan.com/chinese_traditional/index.htm"&gt;海遊館&lt;/a&gt;，號稱全世界最大的水族館，裡面值得看的東西滿多的，大概可以逛個兩小時，對照京都一些寺廟要價近千元逛不了一小時，C/P 值還算 OK 啦。&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128845.jpg"&gt;&lt;br&gt;( 這入口超炫的，我在這足足看了十分鐘多。原本想拍下一些大魚的樣子，可是游太快了，完全沒照成功。 )&lt;/p&gt;&lt;p&gt;過了入口隧道後，沿路的景象也很壯觀，像是下圖這隻海遊館最大的魚：&lt;br&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128885.jpg"&gt;&lt;/p&gt;&lt;p&gt;照片看起來很普通，現場則是隔著一片玻璃看著三層樓高的水池，裡面一群奇形怪狀的魚游來游去。&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128912.jpg"&gt;&lt;br&gt;( 長得很像香菇的水母。 )&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128918.jpg"&gt;&lt;br&gt;( 超可愛的娃娃。880日幣，買不下手啊。 )&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128920.jpg"&gt;&lt;br&gt;( 可愛的企鵝娃娃，真正的企鵝也很有趣，可惜都沒照到好鏡頭。 )&lt;/p&gt;&lt;p&gt;逛完海遊館後，到旁邊的 GREENS 吃午餐，在大阪靠它吃了三天的午餐。可能是因為 buffet 式的，店內大多為歐美來的客人。接著前往 &lt;a href="http://zh.wikipedia.org/wiki/%E6%97%A5%E6%9C%AC%E7%92%B0%E7%90%83%E5%BD%B1%E5%9F%8E"&gt;Universe City&lt;/a&gt;，才剛下電車就能聽到熱鬧的音樂。愈走近入口，氣氛愈熱鬧。和現場熱鬧氣氛相對，我忽然有股強烈的孤寂感。在入口看了一陣子後，我轉身離開。這大概是在日本自由行九天裡，繼在 PAKDD 晚宴後，感受到最強的一次孤寂感。於是我修改行程，改成先去&lt;a href="http://zh.wikipedia.org/wiki/%E5%A4%A7%E9%98%AA%E5%9F%8E"&gt;大阪城&lt;/a&gt;公園。&lt;/p&gt;&lt;p&gt;( 未完待續 )&lt;/p&gt;&lt;h4&gt;備註&lt;/h4&gt;&lt;p&gt;本篇文章是在 herbage 借我的 &lt;a href="http://www.cherrycorp.com/english/keyboards/index.htm"&gt;Cherry &lt;/a&gt;茶軸下打完的，無奈今天沒啥 fu 寫 blog，本篇無法打完。鍵盤打起來打起來挺舒服的，卻讓我意識到椅子不舒服啊．．．。&lt;/p&gt;&lt;img src="http://feeds2.feedburner.com/~r/fcamel/~4/sy7fnWwIXfA" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-6773864441758416185?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/6773864441758416185/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2009/02/blog-post.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/6773864441758416185'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/6773864441758416185'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2009/02/blog-post.html' title='關西行：大阪海遊館'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-3059353949100982096</id><published>2009-02-20T01:16:00.000+08:00</published><updated>2011-05-16T23:40:33.733+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Machine Learning'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Computer Science'/><title type='text'>Naive Bayes Classifier 的原理（單刀直入版）</title><content type='html'>&lt;p&gt;這篇內容和上篇&lt;a href="http://fcamel.twbbs.org/archives/2009/02/19/680/"&gt;《Naive Bayes Classifier 的原理》&lt;/a&gt;一樣，只是改採簡明厄要的寫法。話說我以前都寫這種風格的說。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;h4&gt;問題描述&lt;/h4&gt;&lt;p&gt;給定由屬性 A1, A2, A3 和類別 C1, C2 組成的 training data（例如 (A1, A2, C1) 或 (A1, A3, C2)）。求 A1, A2, A3 三者任意組合的情況下，應該分類到 C1 還是 C2。&lt;/p&gt;&lt;h4&gt;Naive Bayes Classifier 定義&lt;/h4&gt;&lt;p&gt;以 (A1, A2) 為例說明：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;P(C1 | A1, A2) = P(A1, A2 | C1) * P(C1) / P(A1, A2)&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;比較 P(C1 | A1, A2) 和 P(C2 | A1, A2) ，若前者較高，就將 (A1, A2) 分類到 C1，反之分到 C2。&lt;/p&gt;&lt;p&gt;注意 P(C1 | A1, A2) 和 P(C2 | A1, A2) 分母一樣，可以忽略不算。&lt;/p&gt;&lt;h4&gt;Naive Bayes Classifier 原理說明&lt;/h4&gt;&lt;p&gt;相較於直接數 training data 裡各種組合和對應到類別的數量，&lt;a href="http://en.wikipedia.org/wiki/Naive_Bayesian_classification"&gt;Naive Bayes Classifier &lt;/a&gt;的優勢是在 training data 不足時，仍能處理各種屬性組合。也就是說，即使 training data 內沒有出現過 (A1, A2, C1) 和 (A1, A2, C2)，Naive Bayes Classifier 仍可估計出 P(C1 | A1, A2) 和 P(C2 | A1, A2) 何者機率較高。&lt;/p&gt;&lt;p&gt;原因是 Naive Bayes Classifier 假設所有屬性對其類別具有 &lt;a href="http://en.wikipedia.org/wiki/Conditional_independence"&gt;conditional independence &lt;/a&gt;，所以  P(C1 | A1, A2)  可拆成如下的算法：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;P(A1, A2 | C1) = P(A1 | C1) * P(A2 | C1)&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;同理可算出 P(A1, A2 | C2) ，所以能比較 P(C1 | A1, A2) 和 P(C2 | A1, A2) 的值。注意真實情況下 conditional independence 不見得會成立。&lt;/p&gt;&lt;img src="http://feeds2.feedburner.com/~r/fcamel/~4/4DKs_5kD6F4" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-3059353949100982096?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/3059353949100982096/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2009/02/naive-bayes-classifier.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/3059353949100982096'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/3059353949100982096'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2009/02/naive-bayes-classifier.html' title='Naive Bayes Classifier 的原理（單刀直入版）'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-8004734095432184496</id><published>2009-02-19T23:54:00.000+08:00</published><updated>2011-05-16T23:40:34.680+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Machine Learning'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Computer Science'/><title type='text'>Naive Bayes Classifier 的原理</title><content type='html'>&lt;p&gt;以前讀 &lt;a href="http://en.wikipedia.org/wiki/Bayesian_probability"&gt;Bayesian probability&lt;/a&gt; 時一直有個疑問，&lt;strong&gt;為什麼要這樣繞彎來計算？&lt;/strong&gt;這疑問在讀 &lt;a href="http://en.wikipedia.org/wiki/Naive_Bayesian_classification"&gt;Naive Bayes Classifier &lt;/a&gt;時更大了。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;假設輸入為屬性 A1、A2、A3 和類別 C1, C2，為什麼不從 training data 裡直接計算出 P(C1 | A1, A2, A3) ？如下所示：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;P(C1 | A1, A2, A3) = P(A1, A2, A3, C1) / P(A1, A2, A3) — &lt;strong&gt;(1)&lt;/strong&gt;&lt;br&gt;P(A1, A2, A3, C1) = N(A1, A2, A3, C1) / N(U) &lt;br&gt;P(A1, A2, A3) = N(A1, A2, A3) / N(U) &lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;其中 N(.) 表示計算出現次數，U 是宇集。或是更直接一點，直接算數量：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;P(C1 | A1, A2, A3) = N(A1, A2, A3, C1) / N(A1, A2, A3) — &lt;strong&gt;(2)&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;可是 Naive Bayes Classifier 卻採用下面這種繞彎的方式子計算：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;P(C1 | A1, A2, A3) = P(A1, A2, A3 | C1) * P(C1) / P(A1, A2, A3) — &lt;strong&gt;(3)&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;顯然 (2) 看來直接易懂，為什麼 Naive Bayes Classifier 要用 (3) 的算法呢？畢竟等式右邊各項，也是用 N(.) / N(U) 組出來的。&lt;/p&gt;&lt;p&gt;今天想了許久，終於想通了！&lt;strong&gt;關鍵在於 training data 不見得能完整涵蓋所有可能。&lt;/strong&gt;比方從來沒出現過 (A1, A2, C1) 或 (A1, A2, C2) ，在這種情況下，無法從 training data 裡直接用計數的方式求出 P(C1 | A1, A2) 和 P(C2 | A1, A2) 。於是疑問轉成：&lt;strong&gt;「為何 Naive Bayes Classifier 可以計算沒出現過的組合？」&lt;br&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;先從定義來看：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;P(C1 | A1, A2) = P(A1, A2 | C1) * P(C1) / P(A1, A2)&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;由於 P(C1 | A1, A2) 和 P(C2 | A1, A2) 的分母一樣，分母可以忽略不算（事實上也算不出來），所以問題簡化為計算上式分子的值。P(C1) 或 P(C2) 可從 training data 裡算估出，&lt;strong&gt;但在沒有 P(A1, A2) 的情況下，難道 P(A1, A2 | C1) 可以估的出來？&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;答案是：&lt;strong&gt;「在 &lt;a href="http://en.wikipedia.org/wiki/Conditional_independence"&gt;conditional independence &lt;/a&gt;的假設下可以！」&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;假設 A1, A2 在 C1 發生的情況下為 conditional independence，則可得到如下的結果：&lt;/strong&gt;&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;P(A1, A2 | C1) = P(A1 | C1) * P(A2 | C1)&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;原本要求 A1, A2 在 C1 條件下同時出現的機率，成功地轉換為計算各別在 C1 條件下的機率！於是在 A1, …, Ak 對 Ci 都具有 conditional independence 的假設下，&lt;strong&gt;只要 training data 內可估出全部的 P(Aj | Ci)，就能計算各種 Aj 組合的條件機率！&lt;/strong&gt;&lt;/p&gt;&lt;h4&gt;結論&lt;/h4&gt;&lt;p&gt;相較於直接在 training data 內算次數猜類別，&lt;strong&gt;Naive Bayes Classifier 的特色是用極少的機率函數 [*1] 表示出所有屬性組合的機率。但是前提是 conditional independence 的假設要成立。&lt;/strong&gt;若 Ai 和 Aj 有&lt;a href="http://en.wikipedia.org/wiki/Correlation"&gt;關聯性&lt;/a&gt; ( Ai and Aj are correlate ），會算出錯誤的數據，這時可能得用 &lt;a href="http://en.wikipedia.org/wiki/Bayesian_network"&gt;Bayesian network&lt;/a&gt;。&lt;/p&gt;&lt;h4&gt;更新 (2009-02-21)&lt;/h4&gt;&lt;p&gt;經祖佑 [*2] 說明，在這補充幾點：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;我誤用 &lt;a href="http://en.wikipedia.org/wiki/Missing_values"&gt;missing value&lt;/a&gt; 一詞，文中已改正為「training data 不完備」。&lt;/li&gt;&lt;li&gt;修改結論，將 Naive Bayes Classifier 的特色寫得更明確。&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;備註&lt;/h4&gt;&lt;ol&gt;&lt;li&gt;若有 k 個屬性和 c 個類別，只要從 training data 裡估出 k * c 個機率函數，就能估計出所有組合（(2^k - 1) * c）的機率。&lt;/li&gt;&lt;li&gt;祖佑回覆的原文如下（從 BBS 複製過來的，排版有點亂）：&lt;br&gt;&lt;blockquote&gt;&lt;p&gt;一點個人感想&lt;/p&gt;&lt;p&gt;NBC假設conditional independence的優勢是&lt;br&gt;把很大的機率表拆成許多小的機率表相乘，所以減少了機率參數&lt;br&gt;因此減少了training data的需求量，或反過來說讓同一筆training data資料意義變大&lt;br&gt;而文中所提的”missing value”只是這個優勢下的一個特例而已&lt;br&gt;事實上就算完全沒有”missing value”的情況，NBC還是可能有他的優勢存在&lt;/p&gt;&lt;p&gt;因為自己說要舉例，所以只好舉例&lt;br&gt;假設要判斷一部動畫camel喜不喜歡&lt;br&gt;觀察的attribute是有沒有蘿莉跟有沒有身材好的女角&lt;br&gt;同樣一筆training data [(有蘿莉，沒身材好的女角) =&amp;gt; camel喜歡]&lt;br&gt;在沒有假設conditional independence的情況下&lt;br&gt;就只代表(有，無)這樣的組合camel可能會喜歡&lt;br&gt;但是在假設conditional independence的情況下&lt;br&gt;代表的是”有蘿莉=&amp;gt;camel可能會喜歡”以及”沒身材好的女角=&amp;gt;camel可能會喜歡”&lt;br&gt;所以說同一筆資料的information變大&lt;/p&gt;&lt;p&gt;但是很明顯的這樣假設的危險就在於也許camel只是不喜歡蘿莉身材好XD&lt;br&gt;所以(無蘿莉，無身材好的女角)可能在NBC會被誤判成camel喜歡&lt;br&gt;這就是有denpendency的情況下，錯誤假設造成的謬誤&lt;br&gt;順帶一提的是我覺得missing value這個詞很怪XD&lt;br&gt;因為有另一類問題是在處理某input attribute不存在的情況&lt;br&gt;例如training data出現(有蘿莉，”不知道”) =&amp;gt; camel喜歡這樣的情況&lt;/p&gt;&lt;p&gt;前文所說的missing value與其說是missing value&lt;br&gt;不如說是training data不足&lt;/p&gt;&lt;/blockquote&gt;&lt;/li&gt;&lt;/ol&gt;&lt;img src="http://feeds2.feedburner.com/~r/fcamel/~4/9zC98J6YOus" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-8004734095432184496?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/8004734095432184496/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2009/02/naive-bayes-classifier_19.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/8004734095432184496'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/8004734095432184496'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2009/02/naive-bayes-classifier_19.html' title='Naive Bayes Classifier 的原理'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-8065902969878640927</id><published>2009-02-18T22:23:00.000+08:00</published><updated>2011-05-16T23:40:35.779+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Testing'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Python'/><title type='text'>Windows 下初寫 Python 心得</title><content type='html'>&lt;p&gt;今天下午用了一下 &lt;a href="http://www.python.org/"&gt;Python&lt;/a&gt; 處理簡單的文字檔，結果激起我對 Python 沉封已久的渴望，晚上看個半集動畫後，就試著在 Windows 下寫看看 Python。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;除 Java 外，多年來我都習慣在 Unix 下寫程式，用 Windows 時也是透過 putty 連入 Unix，開 screen + vim split 來寫。這次轉念一想，乾脆來試看看在 Windows 下開發的情況，順便增長見聞。&lt;/p&gt;&lt;p&gt;保險起見，我選擇用 &lt;a href="http://www.python.org/ftp/python/2.6.1/python-2.6.1.msi"&gt;Python 2.6 &lt;/a&gt;而不是最近剛出來的 Python 3000。安裝時會要求選個路徑，我先開個 D:Python，之後打算把東西都丟在這下面，所以就裝在 D:PythonPython26下。裝好後的第一印象是，Windows 版有精美的 &lt;a href="http://zh.wikipedia.org/w/index.php?title=CHM&amp;amp;variant=zh-tw"&gt;chm 檔&lt;/a&gt;真好，索引文件超方便！&lt;/p&gt;&lt;p&gt;再來是設定 VIM：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;下載 &lt;a href="http://www.vim.org/scripts/script.php?script_id=974"&gt;python.vim&lt;/a&gt;到 C:Documents and SettingsUSERvimfilesindent ( 需先自行新增目錄 vimfiles 和 vimfilesindent )，並在 C:Documents and SettingsUSER_vimrc 裡加上：&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;&lt;span style="color:#668080"&gt;autocmd&lt;/span&gt; BufRead,BufNewFile &lt;span style="color:#000000"&gt;*.&lt;/span&gt;py set sw=&lt;span style="color:#000000;font-weight:bold"&gt;4&lt;/span&gt; tabstop=&lt;span style="color:#000000;font-weight:bold"&gt;4&lt;/span&gt; smarttab smartindent expandtab&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;如此一來才能方便地縮排和有漂亮的色彩。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;為了方便執行，在 _vimrc 裡加上：&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;&lt;span style="color:#668080"&gt;autocmd&lt;/span&gt; BufRead,BufNewFile &lt;span style="color:#000000"&gt;*.&lt;/span&gt;py &lt;span style="color:#25BB4D"&gt;map&lt;/span&gt; &lt;span style="color:#000000"&gt;&amp;lt;&lt;/span&gt;F10&lt;span style="color:#000000"&gt;&amp;gt;%&lt;/span&gt; w &lt;span style="color:#000000"&gt;!&lt;/span&gt;python&lt;span style="color:#000000"&gt;&amp;lt;&lt;/span&gt;CR&lt;span style="color:#000000"&gt;&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;這樣就能按 F10 執行目前編輯的 script。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;編輯環境弄好後開始試 &lt;a href="http://en.wikipedia.org/wiki/Test-driven_development"&gt;Test Driven Development &lt;/a&gt;，來看 &lt;a href="http://pyunit.sourceforge.net/"&gt;pyUnit &lt;/a&gt;怎麼用。pyUnit 有含在預設的 modules 裡，抄幾段範例碼就能用了：&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;2&lt;br /&gt;3&lt;br /&gt;4&lt;br /&gt;5&lt;br /&gt;6&lt;br /&gt;7&lt;br /&gt;8&lt;br /&gt;9&lt;br /&gt;10&lt;br /&gt;11&lt;br /&gt;12&lt;br /&gt;13&lt;br /&gt;14&lt;br /&gt;15&lt;br /&gt;16&lt;br /&gt;17&lt;br /&gt;18&lt;br /&gt;19&lt;br /&gt;20&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;&lt;span style="color:#808080;font-style:italic"&gt;#-------------------------------------------------&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#808080;font-style:italic"&gt;# Press F10 to execute the test case directly in VIM&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#808080;font-style:italic"&gt;#-------------------------------------------------&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;import&lt;/span&gt; &lt;span style="color:#dc143c"&gt;unittest&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;class&lt;/span&gt; MainTestCase&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#dc143c"&gt;unittest&lt;/span&gt;.&lt;span style="color:black"&gt;TestCase&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;:&lt;br /&gt;    &lt;span style="color:#ff7700;font-weight:bold"&gt;def&lt;/span&gt; setUp&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#008000"&gt;self&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;:&lt;br /&gt;        &lt;span style="color:#ff7700;font-weight:bold"&gt;pass&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;    &lt;span style="color:#ff7700;font-weight:bold"&gt;def&lt;/span&gt; tearDown&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#008000"&gt;self&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;:&lt;br /&gt;        &lt;span style="color:#ff7700;font-weight:bold"&gt;pass&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;    &lt;span style="color:#ff7700;font-weight:bold"&gt;def&lt;/span&gt; testAMethod&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#008000"&gt;self&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;:&lt;br /&gt;        &lt;span style="color:#008000"&gt;self&lt;/span&gt;.&lt;span style="color:black"&gt;assertTrue&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#008000"&gt;True&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span style="color:#008000"&gt;self&lt;/span&gt;.&lt;span style="color:black"&gt;assertEqual&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#ff4500"&gt;2&lt;/span&gt;, &lt;span style="color:#ff4500"&gt;1&lt;/span&gt; + &lt;span style="color:#ff4500"&gt;1&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt; &lt;br /&gt; &lt;br /&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;if&lt;/span&gt; __name__ == &lt;span style="color:#483d8b"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;:&lt;br /&gt;    &lt;span style="color:#dc143c"&gt;unittest&lt;/span&gt;.&lt;span style="color:black"&gt;main&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;結果沒找到 GUI 版的 pyUnit，少了精美的綠色進度表就不是 xUnit 了啊！&lt;/p&gt;&lt;p&gt;接下來演變為長期抗戰，裝好的 Python26 裡沒有 pyUnit 上提到的 unittestgui.py，而官網上的載點也失效了，好在可以從 code search site 找到： &lt;a href="http://www.koders.com/python/fidF93B7FC620F2804257C9D7B56B584B1B72113E78.aspx"&gt;unittestgui.py&lt;/a&gt;。可是怎麼試都是載入失敗。最後在 pyUnit 官網找到另一個方案：&lt;a href="http://homepage.hispeed.ch/py430/python/unittestgui.py"&gt;GUI test runner using wxPython&lt;/a&gt;。&lt;/p&gt;&lt;p&gt;這份 unittestgui.py 用到 &lt;a href="http://www.wxpython.org/"&gt;wxPython&lt;/a&gt;，所以得下載對應到 &lt;a href="http://downloads.sourceforge.net/wxpython/wxPython2.8-win32-unicode-2.8.9.1-py26.exe"&gt;Win32 Python 2.6 的 wxPython&lt;/a&gt;，執行安裝檔後會自動找到 Python 2.6 的位置，而將預設路徑設為 Python26Libsite-packages，真是方便啊。原本還在擔心 Windows 下 Python module 要怎麼管理，結果很簡單。果然無知會造成恐懼。裝好 wxPython 後，再改一下 wxPython 版的 unittestgui.py (加上 import sys），就可以用漂亮的 GUI 版 Unit test 工具啦！而且是選檔案直接載入 test case，不用辛苦地填入 package/module path。&lt;/p&gt;&lt;p&gt;&lt;a href="http://www.flickr.com/photos/12203797@N00/3289830677/" title="unittestgui_wxPython by fcamel, on Flickr"&gt;&lt;img src="http://farm4.static.flickr.com/3135/3289830677_804da171e7.jpg" width="500" height="446" alt="unittestgui_wxPython"&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;附帶一提，&lt;a href="http://docs.python.org/library/site.html"&gt;site-package &lt;/a&gt;的設計簡單易懂，了解後以後要手動裝 module 也不成問題啦！&lt;/p&gt;&lt;p&gt;最後提一下 &lt;a href="http://ipython.scipy.org/"&gt;IPython &lt;/a&gt;的相關訊息。IPython 比原本 python 內建的 interpreter 強太多了，是寫 python 的必備工具之一。IPython 首頁有列出下載的頁面，點 xxx.exe 下載執行，自然會裝到 site-packages 裡。官網首頁下方有指出 Windows 下要另外裝 &lt;a href="http://ipython.scipy.org/moin/PyReadline/Intro"&gt;PyReadline&lt;/a&gt; 才能正確執行 IPython，不然會沒顏色，且按 Tab 補字時會丟出 exception。IPython 的執行方式有幾種，這裡列出其中兩種：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;console mode：python Python26Scriptsipython&lt;/li&gt;&lt;li&gt;GUI mode：python Python26Libsite-packagesIPythonfrontendwxipythonx.py&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;雖然是一樣的東西，總覺得 Unix 下用起來比較方便。&lt;/p&gt;&lt;h4&gt;2009-02-23 更新&lt;/h4&gt;&lt;p&gt;經 &lt;a href="http://scotttsaitraditionalchinese.blogspot.com/"&gt;Scott Tsai &lt;/a&gt;提醒，修正文中和 vim 相關的說明。沒想到才剛在 &lt;a href="http://www.plurk.com/p/h6dz3"&gt;Plurk 提到&lt;/a&gt;「以後提到 Scott Tsai 可以加超連結了」，馬上就用到了。&lt;/p&gt;&lt;h4&gt;2009-03-08 更新&lt;/h4&gt;&lt;p&gt;修正 vim indent 的目錄路徑，並補上 IPython 的相關訊息。&lt;/p&gt;&lt;img src="http://feeds2.feedburner.com/~r/fcamel/~4/tLodmOL_iB4" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-8065902969878640927?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/8065902969878640927/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2009/02/windows-python.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/8065902969878640927'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/8065902969878640927'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2009/02/windows-python.html' title='Windows 下初寫 Python 心得'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://farm4.static.flickr.com/3135/3289830677_804da171e7_t.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-8741393854762565798</id><published>2009-02-07T00:07:00.000+08:00</published><updated>2011-05-16T23:40:36.700+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Research'/><category scheme='http://www.blogger.com/atom/ns#' term='Communication'/><title type='text'>溝通的技巧</title><content type='html'>&lt;p&gt;以前我覺得全世界的人都講話很直的話，這世界就少很多問題，大家都可以有效率地溝通了。不過《&lt;a href="http://zh.wikipedia.org/wiki/%E5%A5%87%E8%AB%BE%E4%B9%8B%E6%97%85"&gt;奇諾之旅》&lt;/a&gt;有一話就是類似的故事，有一個國家透過科學的方式改造人體，讓兩人在一定的距離內就能直接聽到對方的心思，結果在全國人民同時改造完後，全國人民都分散獨居避不見面。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;我曾有兩個錯誤觀念：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;說話直才有效率。&lt;/li&gt;&lt;li&gt;「笑著同意對方又接著反駁」是繞圈子又假的事。&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;後來有段時間有幸和強者共事，在對方身上學到不少為人處理的技巧，才明白自己原本的認知有多不成熟。我從對方身上親身體驗到教科書上講的方法，如&lt;strong&gt;「先贊同對方意見的優點，後提出不同意見。」&lt;/strong&gt;說來容易做來難，&lt;strong&gt;要做到這點，得先真得聽進別人的話，認真地從對方的觀點思考。&lt;/strong&gt;基本上，&lt;strong&gt;大部份思考後的發言有一部份價值，只是不同的背景考量下，而有利害得取捨。&lt;/strong&gt;從對方的觀點思考是很難很累的事，要靜下心做到這點，相當不容易，但也唯有做到這點後，才能有效地溝通。&lt;/p&gt;&lt;p&gt;再來是要能做到&lt;strong&gt;對事不對人。&lt;/strong&gt;相信大家都喜歡就事論事。也希望自己能就事論事，可是溝通時難免會帶有情緒，這可以有技巧地避免。舉例來說，在和 A、B 共事時，先和 A 討論過再和 B 談，發現兩人意見不同，在和 B 討論時可以這麼說：「之前我和 A 討論過，他的觀點類似****，我覺得****，不知 B 你覺得如何？」看起來很正常直接的方式，來個失敗的對照組會更顯示出它的價值：「我聽 A 說是****，和 B 你說的不一樣，&lt;strong&gt; A 弄錯了嗎？&lt;/strong&gt;」後面這段的問題，在於隱含探討到個人名譽，一個弄得不好，讓人覺得不是 A 錯就是 B 錯，可能會讓 A、B 感情用事，提供不理智的判斷。&lt;/p&gt;&lt;p&gt;從事實的觀點來看，真相只有一個（都是&lt;a href="http://zh.wikipedia.org/wiki/%E5%90%8D%E5%81%B5%E6%8E%A2%E6%9F%AF%E5%8D%97"&gt;柯南&lt;/a&gt;害的，講出這句就覺得很聳），的確有一人是錯的，或大家都不對。可是，重要的是正確地把事做好，讓團體獲利。所以，不小心帶出誰對誰錯的議題，是不明智的行為。更高明的手腕是帶入一點幽默讓談話氣氛輕鬆些。人不是天生的理性生物，愉快的談話氣氛可以協助大家理性地談話。&lt;/p&gt;&lt;p&gt;很多類似這樣的溝通議題，可以有效率不直接傷人的達成，端看自己願不願意學習，揣摩和練習出更好的方式。我還太嫩了，沒法舉出太多例子。&lt;/p&gt;&lt;img src="http://feeds2.feedburner.com/~r/fcamel/~4/XeIu2_-fAdM" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-8741393854762565798?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/8741393854762565798/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2009/02/blog-post_07.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/8741393854762565798'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/8741393854762565798'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2009/02/blog-post_07.html' title='溝通的技巧'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-2823229506918482811</id><published>2009-02-05T23:32:00.000+08:00</published><updated>2011-05-16T23:40:37.600+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Wiki'/><category scheme='http://www.blogger.com/atom/ns#' term='Web'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><title type='text'>如何啟動 TWiki Debug 模式</title><content type='html'>&lt;p&gt;這篇的另一個標題是「TWiki Plugin 讀取和設定 Preference 的方法」，因為要成功地啟動 TWiki Debug 模式，得弄清楚 Preference 設值和讀值的方式才行。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;我想這樣的標題就和有人寫了篇&lt;a href="http://clicheideas.com/amazon.htm"&gt;《The Amazon.com Customer Service Phone Number》&lt;/a&gt;一樣蠢，但實情就是如此糟。一位網友&lt;a href="http://twiki.org/cgi-bin/view/Plugins/ExcelImportExportPluginDev"&gt;完全道出&lt;/a&gt;我摸索這過程的心聲：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;TWIKI is SO DAMN CONFUSING on how to debug. It has taken me 4 hours of reading everything and all the docs and I still don’t understand how to turn on debugging. I set DEBUG = 1 in twiki/bin/view/TWiki/ExcelImportExportPlugin, like described in another place (that took me hours to find) http://twiki.org/cgi-bin/view/Support/HeadLinePluginDebugLog&lt;/p&gt;&lt;p&gt;So, the plugin does not work. Twiki documentation assumes you have a PHD in Twiki configuration in order to debug. Seriously it is so confusing. Please someone point out where the docs clearly state where you turn on/off plugin configurations? I have seen docs that say “you do it in configuration” FALSE, you only turn on or off plugins there (and that took me 2 hours to find after installing the plugin and wondering why it did not work)&lt;/p&gt;&lt;p&gt;Will someone please take the to write some documentation that makes sense? &lt;/p&gt;&lt;p&gt;– &lt;a href="http://twiki.org/cgi-bin/view/Main/JeffEller"&gt;JeffEller &lt;/a&gt;- 30 Nov 2007&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;問題原因在於 TWiki plugin 的變數設法和取法有兩種，得先弄清楚 plugin 作者是用那個方式取變數值，才有可能把值傳到 plugin 裡。&lt;/p&gt;&lt;p&gt;TWiki 設變數方式很簡單，在任何一頁裡寫如下的 wiki code 即可，存檔後立即生效（注意 * 前有三個空白，後有一個空白）：&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;   &lt;span style="color:#339933"&gt;*&lt;/span&gt; Set VAR &lt;span style="color:#339933"&gt;=&lt;/span&gt; VALUE&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;當這行 wiki code 寫在 TWiki.TWikiPreference 時，就是全站都讀得到，TWiki script 可透過如下的方式取得值並存在 $preferenceVar 裡：&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;&lt;span style="color:#0000ff"&gt;$preferenceVar&lt;/span&gt; &lt;span style="color:#339933"&gt;=&lt;/span&gt; TWiki&lt;span style="color:#339933"&gt;::&lt;/span&gt;&lt;span style="color:#006600"&gt;Func&lt;/span&gt;&lt;span style="color:#339933"&gt;::&lt;/span&gt;&lt;span style="color:#006600"&gt;getPreferencesValue&lt;/span&gt;&lt;span style="color:#009900"&gt;(&lt;/span&gt;&lt;span style="color:#ff0000"&gt;'VAR'&lt;/span&gt;&lt;span style="color:#009900"&gt;)&lt;/span&gt;&lt;span style="color:#339933"&gt;;&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;然而，Plugin 有另一個讀值的方法，Plugin 可以用如下的 code 取值：&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;2&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;&lt;span style="color:#0000ff"&gt;$preferenceVar&lt;/span&gt; &lt;span style="color:#339933"&gt;=&lt;/span&gt; TWiki&lt;span style="color:#339933"&gt;::&lt;/span&gt;&lt;span style="color:#006600"&gt;Func&lt;/span&gt;&lt;span style="color:#339933"&gt;::&lt;/span&gt;&lt;span style="color:#006600"&gt;getPluginPreferencesValue&lt;/span&gt;&lt;span style="color:#009900"&gt;(&lt;/span&gt;&lt;span style="color:#ff0000"&gt;'VAR'&lt;/span&gt;&lt;span style="color:#009900"&gt;)&lt;/span&gt;&lt;span style="color:#339933"&gt;;&lt;/span&gt;  &lt;br /&gt;&lt;span style="color:#0000ff"&gt;$preferenceVar&lt;/span&gt; &lt;span style="color:#339933"&gt;=&lt;/span&gt; TWiki&lt;span style="color:#339933"&gt;::&lt;/span&gt;&lt;span style="color:#006600"&gt;Func&lt;/span&gt;&lt;span style="color:#339933"&gt;::&lt;/span&gt;&lt;span style="color:#006600"&gt;getPreferencesValue&lt;/span&gt;&lt;span style="color:#009900"&gt;(&lt;/span&gt;&lt;span style="color:#ff0000"&gt;'PLUGINNAME_VAR'&lt;/span&gt;&lt;span style="color:#009900"&gt;)&lt;/span&gt;&lt;span style="color:#339933"&gt;;&lt;/span&gt; &lt;span style="color:#666666;font-style:italic"&gt;# 效果同上行&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;用 get&lt;strong&gt;Plugin&lt;/strong&gt;PreferencesValue 讀到的變數名稱，實際上是對應到 &lt;strong&gt;PLUGINNAME_&lt;/strong&gt;VAR，比方說在 EditTablePlugin 裡執行上面這行 code，讀到的是 EDITTABLEPLUGIN_VAR，所以得在 TWiki.&lt;strong&gt;TWikiPreference &lt;/strong&gt;寫成：&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;   &lt;span style="color:#339933"&gt;*&lt;/span&gt; Set EDITTABLEPLUGIN_VAR &lt;span style="color:#339933"&gt;=&lt;/span&gt; VALUE&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;或是在 TWiki.&lt;strong&gt;EditTablePluginTopic &lt;/strong&gt;寫成：&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;   &lt;span style="color:#339933"&gt;*&lt;/span&gt; Set VAR &lt;span style="color:#339933"&gt;=&lt;/span&gt; VALUE&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;總而言之，最保險的作法是稍微 trace plugin code，確定它怎麼輸出除錯訊息，得設那個變數名稱才能啟動它，以及它用那個函數取得 preference variable。設對的話會立即生效，也就是將除錯訊息寫入 twiki/data/debug.txt 裡。Code 的改變也是立即在下次讀網頁時生效。&lt;/p&gt;&lt;p&gt;附帶一提，我改 code 時犯了兩個小錯，結果一直沒有效果（也沒看到錯誤訊息）。其一是行末忘了打分號（寫 Python 的習慣．．．）；其二是我在 variable name 後面不小心多加了一個空白，即「getPluginPreferencesValue(’VAR ‘);」，結果就沒取到 ‘VAR’ 的值，浪費了我不少時間啊。&lt;/p&gt;&lt;h4&gt;備註&lt;/h4&gt;&lt;p&gt;&lt;a href="http://foswiki.org/"&gt;Foswiki &lt;/a&gt;的 &lt;a href="http://foswiki.org/Development/ReleasePlan#Release_1_1"&gt;ReleasePlan &lt;/a&gt;上指出，本季會出 1.1 版，提高 Foswiki 的品質，我想等那之後再轉用 Foswiki 吧。新版 WYSIWYG 修了一些 bug，而且 NatSkin 很帥，超想用的啦。&lt;/p&gt;&lt;img src="http://feeds2.feedburner.com/~r/fcamel/~4/5RYrupe4_1k" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-2823229506918482811?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/2823229506918482811/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2009/02/twiki-debug.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/2823229506918482811'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/2823229506918482811'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2009/02/twiki-debug.html' title='如何啟動 TWiki Debug 模式'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-6479304419741896171</id><published>2009-02-04T22:13:00.000+08:00</published><updated>2011-05-16T23:40:38.459+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Travel'/><title type='text'>關西行：京都洛中二条城及洛東清水寺</title><content type='html'>&lt;p&gt;2008/05/25 Sun.&lt;/p&gt;&lt;p&gt;與其為了交通方便在同一地區逛較中上的景點，不如到各地只逛頂級的景點。今天決定比照昨天下午的作法，依想去的點排行程，不管地點遠近。這就是自助旅行彈性的地方啊，行程想怎麼改就怎麼改（事實上是行前都在弄論文，只好都在前一天晚上規劃隔天行程，泣）！&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128513.jpg"&gt;&lt;br&gt;( 先來張照得醜醜的名勝：&lt;a href="http://images.google.com.tw/images?q=%E6%B8%85%E6%B0%B4%E8%88%9E%E5%8F%B0&amp;amp;um=1&amp;amp;hl=zh-TW&amp;amp;rlz=1B2GGFB_enTW211TW211&amp;amp;sa=N&amp;amp;imgsz=huge"&gt;清水舞台&lt;/a&gt;。 )&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;今天又不小心睡過頭，八點半才起來。早上高興地搭 JR 到二条城站，出站時隱隱感到不對，怎麼出站旅客這麼少，走出車站也沒看到明確的指示。結果發現應該要搭烏丸線到二条城前駅才對。走路太遠，只好再花巴士錢搭到目的地，浪費了不少時間。&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128325.jpg"&gt;&lt;br&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128380.jpg"&gt;&lt;br&gt;( 護城河超帥的！我是小路控兼護城河控。 )&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128354.jpg"&gt;&lt;br&gt;( 城內遍地碎石，走起路來沙沙作響，為了防刺客嗎？ )&lt;/p&gt;&lt;p&gt;二条城在日本歷史上有重要的意義，是當年德川政府宣布大政奉還給天皇的地點，城內有許多人像擺設用以說明當時的情景。我偷跟著歐美旅行團，還站在最前面的位置聽導遊解說。可惜城內不能拍照，城外也沒啥好拍的，古城就那個樣子。&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128372.jpg"&gt;&lt;br&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128435.jpg"&gt;&lt;br&gt;( 城內有廣大的庭園。 )&lt;/p&gt;&lt;p&gt;在逛庭園時，遇到一對旅行團內的夫婦，請我幫他們拍照。拍完後還誇我英文好，問說台灣是什麼時候開始學英文的。我想比較對象大概是日本人，所以也沒啥好高興的啦。&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128400.jpg"&gt;&lt;br&gt;( 從因雷擊而燒掉的天守閣往下拍，我最喜歡這張。 )&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128405.jpg"&gt;&lt;br&gt;( 到處都有語音解說板，有日英中韓文四種發音。好孩子不要亂按還按了就跑。 )&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128440.jpg"&gt;&lt;br&gt;( 其中一塊園地，城內有不少隔起來不能進去草地，看起來很舒服。 )&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128443.jpg"&gt;&lt;br&gt;( 是不是挺有&lt;a href="http://zh.wikipedia.org/w/index.php?title=%E5%8D%83%E4%B8%8E%E5%8D%83%E5%AF%BB&amp;amp;variant=zh-tw"&gt;《神隱少女》&lt;/a&gt;片頭的感覺啊？ )&lt;/p&gt;&lt;p&gt;中午回到京都車站吃義大利麵，今天人超多的，幸好有找到前兩天幫我的那位服務生，不然其他人不知道我要吃什麼樣的素（日本人對於素食的觀念和台灣差很多）。最後有點可惜，沒和那位服務生多說一聲謝謝並道聲再見。接著買了巴士一日卷，前往清水寺。附帶一提，若一天會搭三四次以上巴士，一日卷比較划算。京都寺廟分得很散，基本上一天會搭很多次。&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128455.jpg"&gt;&lt;br&gt;( 小路控的最愛：三年’土反’。 )&lt;/p&gt;&lt;p&gt;清水寺可是京都最著名的地點，清水舞台真的很讚，昨天遇到那位中東正妹最推的也是此處，還有奈良的大佛。為了買送給爸媽的禮物，我往三年’土反’走下去，到清水燒專賣店買了組對杯，要價3,150日幣，結果兩老用來喝茶喝了一陣子，又換用他們後來新買的杯子喝茶。買禮物最重要的是心意嘛，實用與否還是得看當事者。&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128491.jpg"&gt;&lt;br&gt;( 被遺忘的景色：在清水舞台上看到的樣子。 )&lt;/p&gt;&lt;p&gt;上面這張很可惜，其實站在清水舞台往外看很壯觀，一望無際的山和樹林，普通相機拍不出那種遼闊。日本很多景觀都很精致，但很少有較大的視野。&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128527.jpg"&gt;&lt;br&gt;( 被遺忘的景色之二：清水舞台的底部。 )&lt;/p&gt;&lt;p&gt;離開清水寺後前往知恩院看全世界最大的木門，「三門」。感想是：「啊，挺大的木門。」回京都車站時大概四五點吧，前往&lt;a href="http://fcamel.twbbs.org/archives/2009/02/04/634/www.kyotomm.com/international/chinese"&gt;京都國際漫畫博物館&lt;/a&gt;。&lt;/p&gt;&lt;p&gt;京都國際漫畫博物館聽起來很屌，可惜不夠有特色，還有這裡不能拍照有點可惜。館內有個人工草皮，不少人躺在那裡看書，我也小試了一下，挺舒服的。地下室有全部的 &lt;a href="http://zh.wikipedia.org/wiki/%E9%80%B1%E5%88%8A%E5%B0%91%E5%B9%B4Jump"&gt;Jump&lt;/a&gt;，封在玻璃門之內無法翻閱，一堆週刊長長地排在一起挺壯觀的，只能望刊興嘆。館內有時會有某大學漫畫系的人來現場示範漫畫，可惜我太晚來錯過這活動。全館逛完後，我到世界漫畫區找了中文版的&lt;a href="http://zh.wikipedia.org/wiki/%E9%8B%BC%E4%B9%8B%E9%8D%8A%E9%87%91%E8%A1%93%E5%B8%AB"&gt;《鋼之鍊金術師》&lt;/a&gt;看，待天黑差不多要閉館後，就回旅館休息了。&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128550.jpg"&gt;&lt;br&gt;( 最後別忘了拍一下京都鐵塔。 )&lt;/p&gt;&lt;p&gt;晚上看到慈善拍賣節目，是&lt;a href="http://zh.wikipedia.org/wiki/%E5%B3%B6%E7%94%B0%E7%B4%B3%E5%8A%A9"&gt;島田紳助&lt;/a&gt;為了籌建學校而發起的，請了多位名人（不一定是畫家）畫一副畫來義賣。結果原哲夫的「希望の种」賣了350萬日幣！節目的影片見&lt;a href="http://tieba.baidu.com/f?kz=471381662"&gt;這裡&lt;/a&gt;。&lt;/p&gt;&lt;p&gt;更驚人的是，島田紳助本人畫的賣了500萬，是該次義賣最高金額。買者說他以前曾做過很不好的事，如今事業有成想贖罪。附帶一提，島田紳助講話超有趣的，可惜很多內容沒有聽懂，不知道台灣看不看得到他的節目。&lt;/p&gt;&lt;p&gt;最後附上在京都買的門票殘骸，這些都是白花花的銀票啊。&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128553.jpg"&gt;&lt;br&gt;( 左邊那張符是金閣去的門票，挺有意思的。 )&lt;/p&gt;&lt;img src="http://feeds2.feedburner.com/~r/fcamel/~4/e9k7nT_SR8w" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-6479304419741896171?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/6479304419741896171/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2009/02/blog-post_04.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/6479304419741896171'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/6479304419741896171'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2009/02/blog-post_04.html' title='關西行：京都洛中二条城及洛東清水寺'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-257450625844047091</id><published>2009-02-03T22:14:00.000+08:00</published><updated>2011-05-16T23:40:39.373+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Music'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><title type='text'>最近常聽的幾首歌</title><content type='html'>&lt;p&gt;大多是從 &lt;a href="http://www.plurk.com/fcamel"&gt;Plurk &lt;/a&gt;或 BBS 上看到別人推薦，結果一聽就欲罷不能。&lt;/p&gt;&lt;p&gt;首先是 &lt;a href="http://rainstring.blogspot.com/"&gt;rainstring &lt;/a&gt;推的「Libertango」：&lt;/p&gt;&lt;p&gt;&lt;embed src="http://www.youtube.com/v/RUAPf_ccobc&amp;amp;hl=en&amp;amp;fs=1" allowScriptAccess="never" allowFullScreen="true" width="425" height="344" wmode="transparent" type="application/x-shockwave-flash"&gt;&lt;/embed&gt;&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;據 rainstring 所言，這位作曲家 &lt;a href="http://en.wikipedia.org/wiki/%C3%81stor_Piazzolla"&gt;Astor Piazzolla &lt;/a&gt;的曲子裡大多有手風琴出現，他本身也會演奏手風琴（Wikipedia 上有詳細的介紹）。上面這首YouTube 裡的相關作品有不少其它版本，像是&lt;a href="http://tw.youtube.com/watch?v=WMrODjusHlI&amp;amp;feature=related"&gt;四位正妹的演奏版 (Vanilla Mood )&lt;/a&gt;，不過聽來聽去，還是有&lt;a href="http://zh.wikipedia.org/wiki/%E9%A6%AC%E5%8F%8B%E5%8F%8B"&gt;馬友友&lt;/a&gt;演奏的這版本最動聽。一開始大提琴出現時立刻劃破其它配器建立出的世界，配合馬友友的演奏神情，動聽程度加倍。後面接手的手風琴也是一絕，特別是 1:34 時忽然轉到弱聲的弦律，再搭上大提琴的輔助，嗚啊～～～，實在太享受了！&lt;/p&gt;&lt;p&gt;再來是 &lt;a href="http://blog.hubert.tw/"&gt;Hubert&lt;/a&gt; 推薦&lt;a href="http://zh.wikipedia.org/wiki/%E7%B5%A2%E9%A6%99%C3%97%E5%8F%AF%E8%8B%A6%E5%8F%AF%E6%A8%82"&gt;絢香×可苦可樂&lt;/a&gt;的「WINDING ROAD」：&lt;/p&gt;&lt;p&gt;&lt;embed src="http://www.youtube.com/v/5mRbc0xUYkw&amp;amp;hl=en&amp;amp;fs=1" allowScriptAccess="never" allowFullScreen="true" width="480" height="295" wmode="transparent" type="application/x-shockwave-flash"&gt;&lt;/embed&gt;&lt;/p&gt;&lt;p&gt;歌手們唱起來都很享受的樣子，很喜歡這種氣氛，關於這首歌的詳細介紹可以看&lt;a href="http://mint0909.pixnet.net/blog/post/20650309"&gt;《Music。絢香x可苦可樂。WINDING ROAD》&lt;/a&gt;。這裡借引用中譯的副歌歌詞：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;彎彎曲曲的道路的前端&lt;br&gt;有幾道小小的光正等待著&lt;br&gt;雖然還很遠 還看不到&lt;br&gt;一步一步 只要深信光芒 向前走吧&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;真是勵志的好歌詞啊，&lt;del&gt;可惜現實不是如此&lt;/del&gt; 讓心飛躍了起來呢！這首曲子我最喜歡的是可苦可樂衝出來的第一句弦律：「即使無法深愛一切，只要徹底以自己的心 」（約 0:40 開始）。&lt;/p&gt;&lt;p&gt;絢香的另一首「今夜も星に抱かれて」就沒前兩首這麼 high 了，配合&lt;a href="http://gnn.gamer.com.tw/3/30973.html"&gt;The Sky Crawlers&lt;/a&gt;的畫面，更是悲到極點啊．．．。&lt;/p&gt;&lt;p&gt;&lt;embed src="http://www.youtube.com/v/SG_FGcQZ0jY&amp;amp;hl=en&amp;amp;fs=1" allowScriptAccess="never" allowFullScreen="true" width="480" height="295" wmode="transparent" type="application/x-shockwave-flash"&gt;&lt;/embed&gt;&lt;/p&gt;&lt;p&gt;不知為何，最近兩天早上上班時腦裡不斷地浮現這首曲子的弦律，但下午時就不會有了，早起上班有這麼悲嗎？這首最喜歡的地方自然是副歌最高音的轉折，「What I need now is you」這句。&lt;/p&gt;&lt;p&gt;最後是 echo 推薦 &lt;a href="http://en.wikipedia.org/wiki/Muse_(band)"&gt;Muse&lt;/a&gt; 的「Supermassive Black Hole 」：&lt;/p&gt;&lt;p&gt;&lt;embed src="http://www.youtube.com/v/8tugqHunwDA&amp;amp;hl=en&amp;amp;fs=1" allowScriptAccess="never" allowFullScreen="true" width="425" height="344" wmode="transparent" type="application/x-shockwave-flash"&gt;&lt;/embed&gt;&lt;/p&gt;&lt;p&gt;上面這首是 live 版，配合影片看才 high！最 high 的是前奏部份，1:18 開始「Ooh」那個神情真是超機車的，不知為何很喜歡看這一段的表情。中間開始就沒那麼精彩，就整體來說，&lt;a href="http://tw.youtube.com/watch?v=Xsp3_a-PMTw"&gt;MTV版&lt;/a&gt;的和 live 版有點不同，有興趣的人可以比較看看自己比較喜歡那版。至於畫面．．．MTV版的看個幾秒就轉看其它畫面了。歌詞見&lt;a href="http://www.metrolyrics.com/supermassive-black-hole-lyrics-muse.html"&gt;這裡&lt;/a&gt;。&lt;/p&gt;&lt;p&gt;話說我原本只打算貼四個連結，簡單提歌名和推薦人就結束這篇的說。這樣可以達成一篇一Blog還可以省下時間來學 &lt;a href="http://code.google.com/android/"&gt;Android&lt;/a&gt;，沒想到碎碎念習慣發作，又花了不少時間。&lt;/p&gt;&lt;img src="http://feeds2.feedburner.com/~r/fcamel/~4/50J4egYIiTA" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-257450625844047091?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/257450625844047091/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2009/02/blog-post_03.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/257450625844047091'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/257450625844047091'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2009/02/blog-post_03.html' title='最近常聽的幾首歌'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-2845371087092032125</id><published>2009-02-02T22:36:00.000+08:00</published><updated>2011-05-16T23:40:40.331+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Travel'/><title type='text'>關西行：京都洛南</title><content type='html'>&lt;p&gt;2008/05/24 Sat. 細雨&lt;/p&gt;&lt;p&gt;續&lt;a href="http://fcamel.twbbs.org/archives/2009/02/01/591/"&gt;前篇&lt;/a&gt;。&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128236.jpg"&gt;&lt;br&gt;( 先來張超帥氣的千鳥居吧！ )&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;吃完午餐後到Kyoto 手塚治虫 World 逛，這回我付了門票錢，看了三集一共半小時的動畫，分別是「黑傑克」、「平安遷都」、「獅子王」。大概是因為這些動畫是針對小孩子而作的，對白挺簡單的，大致上都聽得懂。看完動畫後，憑著門票可在小小的會場裡盡情看手塚治虫的漫畫。在那裡硬啃了幾本獅子王，也休息夠了，動身前往下一站。附帶一提，在這裡看到&lt;a href="http://www.kyotomm.com/international/chinese/"&gt;京都國際漫畫博物館&lt;/a&gt;的介紹，想說找一天去那看看。&lt;/p&gt;&lt;p&gt;下午的第一站是 York 大推的伏見稻荷大社，看膩了外貌差不多的寺廟後，來這剛好。雖然可以坐巴士來，但還是搭電車比較好掌握時間。&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128191.jpg"&gt;&lt;br&gt;( 連車站出站處都很有名勝地點的味道。 )&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128248.jpg"&gt;&lt;br&gt;( 帥氣的千鳥居！有些似乎是企業或個人捐的，算變相賣廣告吧。 )&lt;/p&gt;&lt;p&gt;動畫裡常看類似這樣綿綿不絕的場景（例如 &lt;a href="http://zh.wikipedia.org/wiki/%E6%B0%B4%E6%98%9F%E9%A0%98%E8%88%AA%E5%93%A1"&gt;ARIA &lt;/a&gt;狐狸娶新娘那集），原本我有錄下第一人稱視野走了四十多秒穿過一長串鳥居的影片，結果在相機裡整理照片時手賤誤刪。&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128253.jpg"&gt;&lt;br&gt;( 難得看到貓，不免俗地要照個一張。 )&lt;/p&gt;&lt;p&gt;逛完這裡後，由於暫時想不到要去那，就先搭電車回京都車站。結果回到京都車站後想說去宇治逛好了，再笨笨地坐車二度經過稻荷到達宇治。&lt;/p&gt;&lt;p&gt;出車站時看到一位中東正妹，拿著英文的旅遊書正在問車站人員要怎麼前往旅遊景點。抱著&lt;a href="http://zh.wikipedia.org/wiki/%E4%B8%80%E6%9C%9F%E4%B8%80%E6%9C%83"&gt;一期一會&lt;/a&gt;的爽快心態，我上前問她要去那裡。結果剛好和我一樣是&lt;a href="http://www.byodoin.or.jp/"&gt;平等院&lt;/a&gt;，順道問她要不要一起同行，雖然我日文也不行，至少看得懂漢字，可以認出平等院。結果對方爽快地答應。&lt;/p&gt;&lt;p&gt;路上閒聊中才發現她真是不簡單，她說剛從澳洲過來，已在日本自助旅行十多天了，之後還要在日本玩十多天。原本她不是要來日本，但在澳洲時發現她的盤纏不夠去太遠（或是大貴？）的地方，所以臨時起意來日本。又提到她回國後要當兵，是義務役，待當完兵後再讀大學，她們國家的人都是這樣。可惜我看她回話都挺冷淡的，當時太孬不敢要求和照，所以這裡就沒有真相啦。&lt;/p&gt;&lt;p&gt;到平等院後我們就各走各的，這樣才能專心欣賞景色。為了不要路上走走又碰到（相信大家都有說完再見沒多見又碰到對方的尷尬情況吧），我故意先到平等院外繞繞，風景也滿不錯的。&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128292.jpg"&gt;&lt;br&gt;( 我真是愛拍路啊！ )&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128281.jpg"&gt;&lt;br&gt;( 附近的河。 )&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128304.jpg"&gt;&lt;br&gt;( 最聳的標準拍攝角度，這就是&lt;a href="http://zh.wikipedia.org/wiki/%E5%B9%B3%E7%AD%89%E9%99%A2"&gt;平等院的鳳凰堂&lt;/a&gt;啦。 )&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128298.jpg"&gt;&lt;br&gt;( 太晚來了，鍊條已扣上，不能參觀鳳凰堂了。 )&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128301.jpg"&gt;&lt;br&gt;( 院內一角。 )&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128309.jpg"&gt;&lt;br&gt;( 顏色和形狀很奇特的樹，看起來真不像活生生的植物。 )&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128320.jpg"&gt;&lt;br&gt;( 傘一放進去就會自動加上套子並並釘好封口哦！在台灣好像沒看過自動加封的裝置。 )&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128323.jpg"&gt;&lt;br&gt;( 冷清的宇治街道。其實我是小路控吧。 )&lt;/p&gt;&lt;p&gt;逛完平等院後，就回京都休息了，日本的寺廟在下午四五點前就都關了，一天能看的點不多啊。在旅館時照例又亂轉電視，看了忍者亂太郎、棒球大聯盟，還有 &lt;a href="http://zh.wikipedia.org/wiki/%E9%9B%BB%E8%85%A6%E7%B7%9A%E5%9C%88"&gt;Coil &lt;/a&gt;呢。&lt;/p&gt;&lt;img src="http://feeds2.feedburner.com/~r/fcamel/~4/PwNSVsUBGNM" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-2845371087092032125?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/2845371087092032125/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2009/02/blog-post_02.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/2845371087092032125'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/2845371087092032125'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2009/02/blog-post_02.html' title='關西行：京都洛南'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-5019058796326810456</id><published>2009-02-01T21:51:00.000+08:00</published><updated>2011-05-16T23:40:41.535+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Travel'/><title type='text'>關西行：京都洛中</title><content type='html'>&lt;p&gt;沒想到去年的遊記拖了一年沒寫完．．．。&lt;/p&gt;&lt;p&gt;2008/05/24 Sat. 細雨&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128172.jpg"&gt;&lt;br&gt;( 先來張精美的六條院模型吧！)&lt;/p&gt;&lt;p&gt;由於太累的緣故，早上 8:30 才起來。吃早餐時看著&lt;a href="http://zh.wikipedia.org/wiki/%E5%AE%88%E8%AD%B7%E8%80%85%E7%B3%BB%E5%88%97"&gt;精靈守護者&lt;/a&gt;，能看動畫的感覺真好。早上打算先步行到車站附近的東本願寺，再前往風俗博物館看&lt;a href="http://zh.wikipedia.org/wiki/%E6%BA%90%E6%B0%8F%E7%89%A9%E8%AA%9E"&gt;《源氏物語》&lt;/a&gt;展。京都路上不少人穿和服，可惜大部份是中年婦女，較少年輕人穿。（附帶一提，我當時的筆記片段地寫著「巫女服不萌了」，現在完全不能理解這句是啥意思。）&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128077.jpg" alt="東本願寺其中一個門口"&gt;&lt;br&gt;( 東本願寺其中一個門口 )&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128082.jpg" alt=""&gt;&lt;br&gt;( 寺裡有成群的鴿子 )&lt;/p&gt;&lt;p&gt;東本願寺的御影堂為世界最大的木造建築，不過看起來也沒什麼特別感覺，大概就是「啊，都是木頭啊」的感想。離開寺廟後，愚蠢的我步行十多分鐘到風俗博物館看《源氏物語》展。不知為何，我很喜歡走在小路間，在台灣搭公車時常看著路邊的小巷子，想著這條路究竟通到那呢？偶而也會騎著車在小路亂開，沒目的地前進。兩個地點之間這段短暫的步行，反而比在名勝地點參觀還要愉快。&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128101.jpg" alt=""&gt;&lt;br&gt;( 令人愉快的平凡小巷 )&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128117.jpg" alt=""&gt;&lt;br&gt;( 一看就明白但很難找的「風俗博物館」 )&lt;/p&gt;&lt;p&gt;風俗博物館位在小巷子的某棟建築物的五樓，館內空間很小，對照台灣的屋子來看，相當於一個客廳一個房間大而已。在客廳裡展出迷你版的六條院，由於我曾看過一段時間&lt;a href="http://zh.wikipedia.org/wiki/%E6%BA%90%E6%B0%8F%E7%89%A9%E8%AA%9E"&gt;《源氏物語》&lt;/a&gt;的&lt;a href="http://zh.wikipedia.org/w/index.php?title=%E6%BA%90%E6%B0%8F%E7%89%A9%E8%AA%9E_(%E6%BC%AB%E7%95%AB)&amp;amp;variant=zh-tw"&gt;少女漫畫&lt;/a&gt;（小說看幾頁就沒看下去），來這參觀相當有趣。&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128164.jpg" alt=""&gt;&lt;br&gt;( 精美的六條院啊！ )&lt;/p&gt;&lt;p&gt;問過客服人員這裡可以拍照後，我才欣然地付了400日幣的門票。之後先悠閒地參觀過一遍，再開始猛烈拍照。&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128120.jpg" alt=""&gt;&lt;br&gt;( 喜歡嗎？可是不能帶回家。 )&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128131.jpg" alt=""&gt;&lt;br&gt;( 超多層的衣服，穿這樣的衣服要怎麼行動啊？ )&lt;/p&gt;&lt;p&gt;逛完迷你版的模型後，進入小房間裡看一比一的模型。還有提供試穿，若有女伴的話，一定要帶來這試穿拍照的啦！&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128175.jpg"&gt;&lt;br&gt;( 為了不要拍到其他遊客，只好拍直的。 )&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128150.jpg"&gt;&lt;br&gt;( 精美的房間。 )&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128142.jpg" alt=""&gt;&lt;br&gt;( 它不是&lt;a href="http://zh.wikipedia.org/wiki/%E6%A3%8B%E9%AD%82"&gt;佐為&lt;/a&gt;哦。 )&lt;/p&gt;&lt;p&gt;&lt;a href="http://www.flickr.com/photos/12203797@N00/3243413989/" title="佐為Q版 by fcamel, on Flickr"&gt;&lt;img src="http://farm4.static.flickr.com/3518/3243413989_2321027218.jpg" width="500" height="379" alt="佐為Q版"&gt;&lt;/a&gt;&lt;br&gt;( 佐為對照組。 )&lt;/p&gt;&lt;p&gt;離開這裡後，前往附近的西本願寺，但因為沒申請無法參觀書院和飛雲閣（我日文很差啊），看看門口就離開了。走到這時腳已差不多掛了，回京都車站吃午餐去。&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128180.jpg"&gt;&lt;br&gt;( 途中看到的幼稚園招牌，學費會不會很貴啊。 )&lt;/p&gt;&lt;p&gt;&lt;img src="http://p4.p.pixnet.net/albums/userpics/4/9/253349/normal_1212128183.jpg"&gt;&lt;/p&gt;&lt;p&gt;和昨天一樣，在Kyoto 手塚治虫 World 隔壁的義式餐廳吃義大利麵。義大利麵真是素食者的救星，在京都三天午餐都是吃這個。廚師超貼心的，三次料都不同。&lt;/p&gt;&lt;p&gt;（未完待續）&lt;/p&gt;&lt;img src="http://feeds2.feedburner.com/~r/fcamel/~4/aq9KjOYG2H8" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-5019058796326810456?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/5019058796326810456/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2009/02/blog-post_01.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/5019058796326810456'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/5019058796326810456'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2009/02/blog-post_01.html' title='關西行：京都洛中'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://farm4.static.flickr.com/3518/3243413989_2321027218_t.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-6101284002942200267</id><published>2009-01-31T14:45:00.000+08:00</published><updated>2011-05-16T23:40:42.608+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Web'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><title type='text'>多一點點的好服務</title><content type='html'>&lt;p&gt;最近用了 &lt;a href="http://readitlaterlist.com/"&gt;Read it later &lt;/a&gt;後，讀起文章方便許多，點出來的一堆文章挑重要的看，看累了就把仍有興趣未看的文章收到 Read it Later 裡，輕輕點一下 &lt;a href="http://www.moztw.org/firefox/"&gt;Firefox &lt;/a&gt;網址列右側紅色小勾即可。在不同電腦間還可透過 Read it Later 同步，看起文章來輕鬆自在。大幅減輕文章看不完的焦慮，反正有留著，不怕忘了看（&lt;del&gt;其實是自欺欺人&lt;/del&gt;)。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;看到 &lt;a href="http://rainstring.blogspot.com/"&gt;rainstring&lt;/a&gt; 說以前他都用 bookmark 做這件事，有 Read it Later 後方便多了。轉念一想，對哦，用隨便一家可同步的 bookmark tool （例如 &lt;a href="https://addons.mozilla.org/zh-TW/firefox/addon/2410"&gt;Foxmarks&lt;/a&gt;、&lt;a href="http://www.google.com/bookmarks/"&gt;Google Bookmark&lt;/a&gt;、&lt;a href="http://delicious.com/"&gt;Delicious&lt;/a&gt;），自己定一個 &lt;em&gt;read_it_later &lt;/em&gt;的 tag，不就是一樣的東西？至於 read / unread 的區別，自己多加個 tag 管理就行，剩下的文章排序也只是一點小差異，到不是我選用 Read it Later 的原因。更何況，用 bookmark 的話，分類可以更仔細，像是 &lt;em&gt;read_it_later_must_read&lt;/em&gt;、&lt;em&gt;read_it_later_someday&lt;/em&gt;，為什麼會選用 Read it Later 呢？&lt;/p&gt;&lt;p&gt;大概是 Read it Later 快了那麼一點點吧。&lt;/p&gt;&lt;p&gt; Read it Later 針對文章讀不完的問題，讓標記文章只要一個點擊，不像 bookmark 要「點擊 -&amp;gt; 下 tag -&amp;gt; 確定儲存」。看完的文章，也是一個點擊完成，不用到 bookmark tool 的管理介面改 tag。若在 Read it Later 這服務出來前，就算有想到這個點子，也許我會懷疑地問「有必要專門做一個工具嗎？又沒快多少」，但真的試過後，確實體會到這必要。插個題，這讓我聯想到 Paul Buchheit 寫的&lt;a href="http://paulbuchheit.blogspot.com/2009/01/communicating-with-code.html"&gt;《Communicating with code》&lt;/a&gt;，與其爭論不休功能好壞，不如用最簡單快速的方式寫個 prototype 給大家玩玩，才有具體的經驗可討論未來發展。&lt;/p&gt;&lt;p&gt;&lt;a href="http://www.getdropbox.com/"&gt;Dropbox &lt;/a&gt;也是一個類似的服務，它的功能就是同步檔案，相信大家可以想到許多替代方案，像是架個 &lt;a href="http://subversion.tigris.org/"&gt;SVN&lt;/a&gt; server，並在每個人電腦上裝 &lt;a href="http://tortoisesvn.tigris.org/"&gt;TortoiseSVN&lt;/a&gt; ，大家就能在不同電腦上分享檔案。既然如此，Dropbox 看來似乎也是有些多餘？答案如同 Read it Later 的情況，Dropbox 針對分享同步檔案而設計，操作就是簡單了一點，快了一點，而有它存在的必要。&lt;/p&gt;&lt;h4&gt;相關介紹&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://www.goston.net/2008/07/24/1483/"&gt;[FF] 這篇文章我等一下再看 - Read it Later&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://fred.ipod.to/blog/m?20081216/1770/"&gt;真好用的「Dropbox」！&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;img src="http://feeds2.feedburner.com/~r/fcamel/~4/m-ObcHBMhqw" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-6101284002942200267?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/6101284002942200267/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2009/01/blog-post.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/6101284002942200267'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/6101284002942200267'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2009/01/blog-post.html' title='多一點點的好服務'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-298809329221454068</id><published>2009-01-31T00:55:00.000+08:00</published><updated>2011-05-16T23:40:43.469+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Life'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><title type='text'>打電話的理由？</title><content type='html'>&lt;p&gt;有時忽然想到誰誰誰好久不見，不知最近過得如何，卻怎麼也無法按下撥號鍵，開頭輕鬆地說句「好久不見，最近如何啊？」&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;過年時接到 &lt;a href="http://hawx-engineer.blogspot.com/"&gt;kgo &lt;/a&gt;的電話，有點驚呀，但稍微想想，這也沒什麼特別的，只是我們沒這習慣而已。偶而想到久久沒見的國中小學同學，其實要連絡一下大家不會太難，但想不到什麼動機，又懶得辦同學會。說來說去，大概還是不夠熱情吧。一直覺得想做什麼就可以做，沒有人可以阻擋自己，無法行動只是因為渴望不夠強烈。&lt;/p&gt;&lt;p&gt;另一個原因是距離和時間，隨著年紀增長，認識的人愈來愈多，有了過去的經驗，已明白不可能什麼朋友都保持連繫。到頭來除了較熟的那一群人外，大部份人都不會有所連絡，也提不起勁主動連絡。或是想特別連絡誰時，又想到還有一堆人也久久沒連絡，全連絡完正事也不用做了，而變得連原本想到的人也懶得連絡了。就和看到一堆動畫未看，卻變得一部也不想看的心情相似。明明一開始只是一個單純的念頭，唉呀呀。&lt;/p&gt;&lt;p&gt;三十不到，就覺得朋友之間的往來如此悲情，這樣不行啊！最近應該趕緊做點正事，並挪出時間找些很久不見的人聚聚。&lt;/p&gt;&lt;img src="http://feeds2.feedburner.com/~r/fcamel/~4/DqwflZNbnYk" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-298809329221454068?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/298809329221454068/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2009/01/blog-post_31.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/298809329221454068'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/298809329221454068'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2009/01/blog-post_31.html' title='打電話的理由？'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-5662293200429230525</id><published>2009-01-25T14:21:00.000+08:00</published><updated>2011-05-16T23:40:44.252+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Blog'/><title type='text'>修改 One year ago plugin</title><content type='html'>&lt;p&gt;效果見本篇文章下方，剛好去年和前年的今天有發文章。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;原本 plugin 可從&lt;a href="http://www.lamateporunyogur.net/wp-plugins/one-year-ago/#english"&gt;這裡下載&lt;/a&gt; 。我把輸出格式改成條列式，並把每年同一天的文章都取出來（原本是取出過去每年同一天的文章）。改完的檔案可從&lt;a href="http://fcamel.twbbs.org/wp-content/uploads/2009/01/oneyearago.phps"&gt;這裡下載&lt;/a&gt;。&lt;/p&gt;&lt;img src="http://feeds2.feedburner.com/~r/fcamel/~4/R8dFy3jWCgE" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-5662293200429230525?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/5662293200429230525/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2009/01/one-year-ago-plugin.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/5662293200429230525'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/5662293200429230525'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2009/01/one-year-ago-plugin.html' title='修改 One year ago plugin'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-7107175912873290281</id><published>2009-01-25T13:25:00.000+08:00</published><updated>2011-05-16T23:40:44.981+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Blog'/><title type='text'>幾篇文章不小心變 private post</title><content type='html'>&lt;p&gt;用 WordPress 2.7 才發現，幸好有 quick edit，很輕鬆地把它們設為 public。Po這篇另一個目的是要測 &lt;a href="http://www.lamateporunyogur.net/wp-plugins/one-year-ago/#english"&gt;One Year Ago 的 plugin&lt;/a&gt; XD。&lt;/p&gt;&lt;img src="http://feeds2.feedburner.com/~r/fcamel/~4/V9jpdLdCLkE" height="1" width="1"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/451051488891335923-7107175912873290281?l=fcamel-fc.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fcamel-fc.blogspot.com/feeds/7107175912873290281/comments/default' title='張貼意見'/><link rel='replies' type='text/html' href='http://fcamel-fc.blogspot.com/2009/01/private-post.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/7107175912873290281'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/451051488891335923/posts/default/7107175912873290281'/><link rel='alternate' type='text/html' href='http://fcamel-fc.blogspot.com/2009/01/private-post.html' title='幾篇文章不小心變 private post'/><author><name>fcamel</name><uri>http://www.blogger.com/profile/04792244455260595133</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-451051488891335923.post-6942218866849220549</id><published>2009-01-24T23:06:00.000+08:00</published><updated>2011-05-16T23:40:45.818+08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Web'/><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='All'/><category scheme='http://www.blogger.com/atom/ns#' term='Blog'/><title type='text'>更新 WordPress！</title><content type='html'>&lt;p&gt;看了 &lt;a href="http://wordpress.org/development/2008/12/coltrane/"&gt;WordPress 2.7 的介紹&lt;/a&gt;後超心動的，趁過年回家陪爸媽看電視的時間來更新！&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;與其說是更新，不如說是砍掉重練吧。舊的 plugin 全丟掉，反正重要的也只有那幾個，為了省事，重找和 2.7 相容的 plugin 比較方便。新版面看來還不壞，後台又超好用的，留言速度好像有比較快（還是我的錯覺 XD）。看看之後回新竹時能不能把欠一堆的文章慢慢補齊吧。&lt;/p&gt;&lt;p&gt;文末來測一下常用標籤的外觀吧&lt;/p&gt;&lt;h3&gt;我是 H3&lt;/h3&gt;&lt;h4&gt;我是 H4&lt;/h4&gt;&lt;p&gt;很久很久以前寫的片段 python code：&lt;/p&gt;&lt;div&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1&lt;br /&gt;2&lt;br /&gt;3&lt;br /&gt;4&lt;br /&gt;5&lt;br /&gt;6&lt;br /&gt;7&lt;br /&gt;8&lt;br /&gt;9&lt;br /&gt;10&lt;br /&gt;11&lt;br /&gt;12&lt;br /&gt;13&lt;br /&gt;14&lt;br /&gt;15&lt;br /&gt;16&lt;br /&gt;17&lt;br /&gt;18&lt;br /&gt;19&lt;br /&gt;20&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre style="font-family:monospace"&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;import&lt;/span&gt; &lt;span style="color:#dc143c"&gt;sys&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;def&lt;/span&gt; decode&lt;span style="color:black"&gt;(&lt;/span&gt;n, &lt;span style="color:#008000"&gt;len&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;:&lt;br /&gt;        &lt;span style="color:#483d8b"&gt;&amp;quot;&amp;quot;&amp;quot;Hello, this is document string. Happy Chinese new year!!.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;br /&gt;        &lt;span style="color:#008000"&gt;input&lt;/span&gt; = &lt;span style="color:black"&gt;[&lt;/span&gt;&lt;span style="color:black"&gt;]&lt;/span&gt;&lt;br /&gt;        &lt;span style="color:#ff7700;font-weight:bold"&gt;for&lt;/span&gt; i &lt;span style="color:#ff7700;font-weight:bold"&gt;in&lt;/span&gt; &lt;span style="color:#008000"&gt;range&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#ff4500"&gt;0&lt;/span&gt;, &lt;span style="color:#008000"&gt;len&lt;/span&gt;, &lt;span style="color:#ff4500"&gt;1&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;:&lt;br /&gt;                &lt;span style="color:#ff7700;font-weight:bold"&gt;if&lt;/span&gt; n &lt;span style="color:#66cc66"&gt;&amp;amp;&lt;/span&gt; &lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#ff4500"&gt;1&lt;/span&gt;&lt;span style="color:#66cc66"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#66cc66"&gt;&amp;lt;&lt;/span&gt;i&lt;span style="color:black"&gt;)&lt;/span&gt; &lt;span style="color:#66cc66"&gt;!&lt;/span&gt;= &lt;span style="color:#ff4500"&gt;0&lt;/span&gt;:&lt;br /&gt;                        &lt;span style="color:#008000"&gt;input&lt;/span&gt;.&lt;span style="color:black"&gt;append&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#ff4500"&gt;1&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt;                &lt;span style="color:#ff7700;font-weight:bold"&gt;else&lt;/span&gt;:&lt;br /&gt;                        &lt;span style="color:#008000"&gt;input&lt;/span&gt;.&lt;span style="color:black"&gt;append&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#ff4500"&gt;0&lt;/span&gt;&lt;span style="color:black"&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span style="color:#ff7700;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:#008000"&gt;input&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#808080;font-style:italic"&gt;# No comment.&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#808080;font-style:italic"&gt;# No comment.&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#808080;font-style:italic"&gt;# No comment.&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;&lt;span style="color:#ff7700;font-weight:bold"&gt;if&lt;/span&gt; &lt;span style="color:#008000"&gt;len&lt;/span&gt;&lt;span style="color:black"&gt;(&lt;/span&gt;&lt;span style="color:#dc143c"&g
