Category Archives: Books

實戰敏捷開發 Practices of an Agile Developer (5) 除錯篇

Update(2009/9/22): 看到另一篇說明 bug report 該有什麼內容

debugging 最大問題在於,它是一個未知的時間箱子,不知道要花多久才能 debug 完。這章談的是幾個除錯上的技巧。

說到除錯,我也推薦閱讀如何有效地報告錯誤這一篇文章,談的是如何正確回報錯誤,好的錯誤回報可以節省很多時間,而不僅僅只是一句 “程式不會動” 而已。

Keep a Solutions Log (紀錄下解決問題的辦法)

解決問題一直是軟體開發者的工作項目(這裡指的問題比較像是軟體安裝、版本、函式庫使用等等問題,例如 windows 上如何把 MySQL Ruby gem 裝起來等等),不過有時候會碰到似曾相似的問題,疑?我以前是怎麼解決的?拜網路發達之賜,Google 搜尋一下通常會有不少幫助,但是還是得花不少時間找尋解答。

自己維護一份簡單的 solutions log 吧,紀錄下日期、問題描述、解法、參考網址等等資訊,之後碰到類似的問題就可以搜尋的到。將這份 log 用 wiki 維護也是不錯的主意。或是寫在 Blog 上,這樣 google 搞不好還會搜尋到自己以前寫的解法…XD

Warnings Are Really Errors (把警告當真)

編譯器(這裡泛指 complier 或 interpreter) 的警告常常有人是忽略不看的,反正可以編譯執行不會 error 就好了?書上舉了 C++ 的例子,編譯器的警告還是蠻有用的(我想特別是 static 語言)可以幫助你找到跟避免潛在的 Bugs,請不要忽略。請將編譯器的警告也當作程式錯誤或無法通過測試的程式一樣認真處理。

我自己寫 Ruby/Rails 的經驗,最常見的警告像是有:


link_to ( 'login', login_path ) # warning: don't put space before argument parentheses

因為中間多了一個沒用的空白。或是

p Array.new 3, 1 # warning: parenthesize argument(s) for future version

因為 interpreter 覺得 ambiguities 不清楚,這時要請你括號括清楚。另外就是 DEPRECATION WARNING 警告了,告訴你這函式將來的版本將會移除,請不要再用了 :p

Attack Problems in Isolation (隔離除錯)

要在一個大系統找特定問題,不要整個系統一起測,而是將分開隔離找出病因。如果有做 unit tests,我們知道可以將 dependence 的東西用 mock object 隔離出來,這樣測試的時候就不會被外部因素干擾。同理在抓整個系統中的特定 Bugs 的時候,必要的話可以做一些假的 prototype 元件用以隔離抓出問題所在,同時也比較好除錯測試。

我自己土法煉鋼除錯 CSS 也是如此,先移除 CSS 檔案的一半,看看問題是不是出在這裡,然後再砍一半直到找到有問題的那一行。(Binary search?)

Report All Exceptions (回報所有的例外)

如果呼叫一個可能會丟例外的函式,當碰到例外時,盡量可以處理就處理,不然請讓他自然傳播(Propagate)上去給上一層 Caller 處理,不要攔下來又什麼都不做。如果在整個 call stack 中有個傢伙寫了個空個 catch 所有例外,然後 return null 什麼都不做,到時候要找 Bugs 你就會很想殺人了,因為非得 trace 進去 call stack 才能找到原因。

不要覺得自己不會這樣寫,其實常常我們會想”暫時”不想處理這個例外,於是就留下這樣空的例外處理程式碼(然後繼續留到 Production code)。決定要在哪一層處理例外是個設計上的考量,但是如果呼叫一個函式卻要考慮處理二三十種以上的例外可能就有設計上的問題了。

Provide Useful Error Messages (提供使用者有用的錯誤訊息)

一般性的錯誤訊息如 “Something went wrong” 對使用者來說,可說是一點幫助也沒有,既沒有告訴用戶可以怎麼解決,就算使用者想要求助也不知道發生什麼事情。所以盡量提供詳細的訊息告訴使用者發生什麼事情。

當然,如果是比較嚴重的程式例外錯誤,對開發者來說,因為有 logging 的關係,所以看 log 應該可以知道大概的情況並排除,這類的錯誤對使用者來說也沒辦法自行排除,也只能告訴用戶說這不是因為他們操作錯誤的原因。

關於這個議題這一節講的不是很清楚,我個人推薦有本 37 signals 的小書可以翻翻:Defensive Design for the Web 圖文並茂共有40條 Guideline 告訴你如何改進網站的錯誤提示訊息、表單填寫等等,例如:

  • 錯誤訊息要明顯,使用顏色、Icons 等效果
  • 錯誤訊息包含這是什麼錯誤以及如何修復
  • 整個網站用一致的錯誤提示方式
  • 在錯誤提示後,不需要再回上一頁才能修正問題
  • 用易懂、禮貌的字句

實戰敏捷開發 Practices of an Agile Developer (4) 程式篇

趁著新年連續假期,終於把這一章念完了:隨著開發的進展,程式逐漸變得怪獸。這章談幾個重點幫助你讓程式容易了解、擴充及維護。

Program Intently and Expressively (寫清楚了解的程式)

厲害的程式是沒有人可以看懂的程式?程式的可閱讀性比寫得方便還重要,寫只會寫一遍,看卻會看很多次。很多時候會有機會要修 Bugs 或是新增功能,這時搞懂本來的程式常常是一開始最困難的地方,如果一開始寫的人就以可讀性為重要目標,那你就會輕鬆的多。

舉個我自己常見的例子就是 Magic Number,很多人喜歡是一個數字來代表 Type code。例如 1 代表 foo、2 代表 bar、3代表…etc,然後程式裡面就直接寫 1, 2, 3。方便是方便,但是沒多久就會忘記這個 1,2,3 各代表什麼意義。最簡單的解決手法就是請改用字串 constant 處理,或者是用 class 來表示 type。

透過程式語言本身的特色可以寫出更有表達力的程式,函數名稱應該傳達出意圖、參數名稱應該表達出他們的用途,寫出好閱讀的程式我們可以避免很多不必要的註解跟說明文件。一些你覺得明顯簡單的地方,不一定對別人也是如此明顯,甚至是幾個月後的你自己。請想想看幾個月後自己來看還會看得懂嗎?

Communicate in Code (寫出可以閱讀的程式)

我們都討厭寫文件的一大理由是:所謂的文件跟程式碼常是分開的兩碼子事,因此很難讓文件跟的上程式的更新,最終變得難以維護。因此最好的寫文件方式,就是透過程式本身和程式中的註解。

註解是用來寫目的、限制條件跟預期結果等,而不是寫出以下這些程式一行行會做些什麼(請不要拿註解再寫一次程式碼在寫的事情)。一個簡單的判斷方式是:寫在函式 “裡面” 的註解多半是不需要的 (尤其是註解寫 what? 而不是註解 why?)。在重構一書更直言回答哪裡需要重構?那就是有註解的地方:如果程式碼前方有一行註釋,就是在提醒你,可以將這段程式碼替換成一個函式,而且可以在註釋的基礎上給這個函式命名。

你要做的事情應該是讓 source code 本身容易了解,而不是透過註解。透過正確的變數命名、好的空白間隔、邏輯分離清楚的執行路徑等手法,我們很少需要在函式裡面註明這些程式在做什麼,我自己的經驗除非是有參考外部的程式碼或複雜演算法,我會多註明參考來源(網址)。否則沒有必要的註解對我來說就像噪音一樣,妨礙閱讀。

變數跟函式的命名是件非常重要的事情,有意義的命名可以傳達出程式怎麼進行,我舉個 Rails 例子:

p = Post.find(param[:id])
if p
   p.destroy
else
   p = Post.new( param[:post] )
   p.save
end


可以改寫成以下這樣具有清楚意含:

existed_post = Post.find(param[:id])
if existed_post
  existed_post.destroy
else
  new_post = Post.new( param[:post] )
  new_post.save
end

有一種 Job security 的方式就是把程式中所有的變數名稱都代換成亂碼,保證拿到的人超級難看得懂。這就是為什麼手工打造的程式碼有其不可取代的重要性,只要拿到整合型 IDE 像是 Dreamweaver 產生出來的 HTML/CSS code 都很想殺人,因為裡面的變數都是編號產生出來的,根本沒辦法閱讀及擴充。

另外就是大家很喜歡用縮寫,這在我之前寫的文章 物件導向程式的九個體操練習 也有提到,這些只有一個字母的暫時變數並沒有辦法傳達出任何可以幫助了解的資訊。

Class, module, method 前面是個寫註解的好地方,而且每個語言都有工具(例如 RDoc, Javadoc… 等)可以幫忙從程式碼中整理出好看的純文件,這些說明通常有:

  • Purpose 目的
  • Requirements(pre-conditions) 預期的輸入是什麼
  • Promises(post-conditions) 什麼樣的輸入,會有什麼樣的預期輸出
  • Exceptions: 有哪些例外(exceptions)情況

這些說明可以幫助我們由大局觀了解程式是怎麼運作的,而不需要仔細看其中每個 method。
Continue reading 實戰敏捷開發 Practices of an Agile Developer (4) 程式篇

實戰敏捷開發 Practices of an Agile Developer (3) 測試篇

上一章談了來自客戶的 Feedback,這一章作者談另一種回饋機制:透過程式測試得知目前開發的情形(Coding Feedback!)。

Put Angels on Your Shoulders (讓測試當你的天使)

Code 一直在變,我們需要一種持續又快速的 feedback 告訴我們這些程式碼建不健康:是我想要的程式嗎? 最後的 commit 有沒有弄壞任何東西? 這時你需要的一種機制可以一直幫你確保這些,也就是自動化的單元測試(unit tests)。

因為測試的流程總是 1.Setup(設定初始資料) 2.Exercise(執行某些操作) 3. Verify(驗證輸出結果與預期相同) 4.Teardown(清空資料),所以目前每套程式語言一定都會有一套 xUnit framework 的測試框架可供使用。

另外還可以加裝一套 continuous build 工具(例如 CruiseControl),它可以自動 checkout 最新的程式碼並測試,一旦有人送交了有錯的程式碼,馬上就可以通知並修正(這時候fix bug最簡單且成本最低)。

當你的 unit tests 達到 Regression tests 程度時,這時候做重構(refactoring)時就有更大的自由(自信),因為不需要擔心改爛了什麼你不知道的東西。

另外搭配 test coverage 工具可以幫助你有個大概的鳥瞰。寫 test 像是一個聰明的投資,因此報酬效率也很重要,trivial method 不需要寫,把火力放在你最擔心出錯的地方。

Use It Before You Build It (用之前先測試怎麼用)

這節進一步談測試驅動開發(TDD),在寫程式前先寫好測試程式。透過先寫測試程式,可以從使用API的觀點來思考怎麼寫程式、怎麼設計API,因而寫出更好用、一致及簡潔的程式介面。

Different Makes a Difference (差一點差很多)

在我這台沒問題啊,為什麼換一台電腦就不對了? 只要換一個平台(即使是不同版本的作業系統),不同就是不同,而太晚發現這樣的問題要修理是非常麻煩的,尤其是正要 release 的時候才發現它沒辦法在某某你想要的平台上執行。

我們當然可以要求 QA team 要在不同平台上測試,但是如果他們只是手動測試,就不如自動測試來的及早發現。我們已經會在 commit 程式前在自己電腦上執行一次 unit testing,現在要做的事情是要在你所要支援的不同平台、不同版本上再測試一次。不過好累啊,這時候就必須靠 continuous integration 工具來幫忙,每當有人 commit 時,這個工具就會自動 checkout 出來執行測試,我們分別在不同平台(例如可以使用 VMware 或 Virtual PC)上設定好這個工具來做自動化測試。

Automate Acceptance Testing (自動化驗收測試)

重要的商業邏輯除了 unit testing,還需要驗收測試獨立於所有程式之外,而且客戶必須親自驗證這個結果。也因為因此,我們需要一個工具讓客戶也可以新增編輯跟修改驗收測試(without coding),

這本書介紹的工具是 fit.c2.com/,它使用 HTML table 當作定義的介面,客戶可以直接編輯加入新的功能,接著加入可能的輸入與正確的預期值。然後 developer 再根據這個 table 產生 fixtures 資料,並比對測試結果是否成功。

感覺像是協力式的 unit testing,你還是必須自己寫測試,只是必須有別人提供你什麼是正確答案。

Measure Real Progress (測量真正的進度)

紀錄花了多久做事提供了很好的 feedback,可以幫助專案計畫、估計跟效率測量。但是一般常用的 timesheet 多半用於薪水用途而非真正花費的時間,這點要特別注意。另外常用的紀錄完成百分比,作者也認為很沒用。一個比較好的方式是紀錄 1.估計多少時間 2.已經花了多少時間 3.估計還需要多少時間來完成。根據真正花了多少時間的經驗,加以比對一開始的估計值,可以獲得很好的經驗用於估計下一個類似的任務。

紀錄真正的時數對於使用 backlog(要完成的任務清單)和 Iteration 也非常重要,如果剩餘的時間超過這次 Iteration 了,就知道必須移動某些任務到下一個 Iteration,反之則有時間多加工作。

最後是注意紀錄的時間單位,以分鐘太細了,以週就太粗囉。我們的經驗是超過一天的 Ticket(Issue) 應該都可以想辦法拆小,1hr、2hr、4hr、8hr 應該是不錯的範圍。

Listen to Users (聆聽使用者)

作者在這章最後特別提了使用者的回饋。即使有寫測試程式,我們還是很容易忽略來自真正使用者的聲音。每一個使用者的抱怨都有其道理,盡量試著找出真正的問題,而不是怪罪使用者不懂、不了解系統如何操作。
程式Bug、文件Bug、使用者會認知錯誤也要算是Bug。而不是讓客服人員回答說:喔,那不是Bug, 你犯了一個大家都會犯的錯誤。即使聽起來很笨,也應該去試著聆聽。

誰說人是理性的


好久沒看經濟類的書,這本書從人的非理性層面切入,並用有趣的實驗導出令人驚奇的結果,還蠻有趣的。

  1. 比較才能做決定。提供 A,-A, B 三種選項,其中 A,B 無法比較,A 比 -A 好。實驗出人們會 75% 去選A,即使 A,B 無法比較。這個 -A 的效果稱作誘餌效應。
  2. 定錨點的影響,隨機的定錨點先入為主影響你的決策。
  3. 只要有”免費”這個字眼,就難以抗拒。2塊降價變1塊跟1塊變免費的效果是天差地遠。
  4. 社會規範與市場規範,只要一提到錢,就變成市場規範。(錢不是萬能)
  5. 不同的情緒狀態所做的決定會不同,高張的情緒容易失去理智。
  6. 預先承諾機制可以幫助解決拖延問題
  7. 如果擁有某樣東西,就會對這樣東西估價較高。所有權會讓你的損失趨避心理變得強烈,只想到失去時的損失而忘記會獲得的利益。(由簡入奢易,由奢入簡難)
  8. 人會盡量保留多一點選擇,即使這樣得付出極高的代價。就像父母匆忙帶著小孩到處學才藝,四處奔走不僅讓人緊張,也不符合經濟效益。很多時候在兩個相似的選項中做選擇,猶豫的時間代價比兩者細微的差異損失更多。
  9. 預期心理影響感官認知。但若事後知道內情也不會影響一開始的經驗。
  10. 安慰劑的暗示效果,因為人們相信它有效,所以也出現治療效果。價格因素也會影響效用,貴的比較有效。關於醫療與安慰劑的道德問題是個很大的議題。
  11. 一旦有機會,許多誠實的人都會欺騙,一旦決定作弊,卻不會受被逮到的風險高低所影響,對小的欺騙來說不會思考成本效益分析。有趣的是如果有任何誠實概念進到腦子(例如回憶十戒或簽署榮譽制度),就會容易保持誠實。
  12. 當人們騙的不是現金的時候,欺騙行為較容易發生。甚至即使改成用代幣(多一層轉換手續而已,還是可以換現金),欺騙的比例也會大增。當交易媒介與貨幣無關時,我們就更容易合理化自己的行為。
  13. 行為經濟學了解人容易受到情境、不相關的情緒等不理性因素影響,所以希望利用工具、方法與制度來幫助我們提高決策品質。

我想這本書真正精采的地方在作者設計的這些有趣實驗,作為一本通俗讀物還蠻值得推薦的。不過我在想如果是科普觀點:認知心理學家、生物學家等科學家應該會有更好的解釋為什麼人類會這麼做吧,我自己是認為”人” 的這些非理性決策不是為了找到最佳解,而只是極小化選到最差解的可能 (也許就代表死亡)罷了。

實戰敏捷開發 Practices of an Agile Developer (2) 需求篇

這一章是 Delivering What Users Want。我們總是幻想客戶一開始就確切告訴我們需要什麼,然後做完收工拿錢即可。不過事實總是中途加入一開始沒有提到的功能跟規格變更。敏捷開發不去試圖”擊敗”這些變更,而是把重點放在如何快速辨認及適應這些改變,並在時程及預算內做出真正符合用戶需求的軟體。
Continue reading 實戰敏捷開發 Practices of an Agile Developer (2) 需求篇

實戰敏捷開發 Practices of an Agile Developer (1) 專業態度篇

敏捷軟體開發一直是我們內部開發的核心概念,強調個人、合作、回應和使用工作軟體(wiki, version control, unit testing, build automaion),2001年由一群軟體開發者發表宣言如下:

  1. 個人及互動勝於流程與工具
  2. 可用的軟體勝於詳盡的文件
  3. 與客戶合作勝於合約談判
  4. 回應變化勝於墨守計畫

軟體開發是連續(continuous)的,不是最後才測試,也不是最後才佈署,更不會停止收集需求跟feedback。正是因為開發軟體是如此複雜的活動,任何種類的錯誤如果不儘快修正,往往最後就會無法控制的失敗,因此唯有每天不斷的一點一滴的去修正,每天解決一些比較小的問題而不是最後脫韁野馬的大問題,這才是解決的辦法。

何謂 Agility 的定義,作者給了:
“Agile development uses feedback to make constant adjustments in a highly collaborative environment.” (敏捷開發是一種在高度合作的環境中不斷根據回應來做修正的開發方式)

透過經常性地建構出可以實際使用的軟體,我們持續得到 feedback。程式碼會因為需求擴充而不斷地被修改重構演進。工作的流程被拆成一至四周的短 iterations,每次透過 demo 得到 feedback,確保方向正確。

敏捷開發最大的不同到底是什麼呢? 這本書不談方法論流程(XP、Scurm等),而是談人本身,談團隊本身,談如何成為一個敏捷的開發人員。書的每一章由數個 Tips 組成,整本書共45個 Tips 來敘述什麼是敏捷的做法。這本書也得到2007年的 Jolts Productivity Award

前兩章 Beginning Agility 跟 Feeding Agility 講的是基本的專業態度:

Continue reading 實戰敏捷開發 Practices of an Agile Developer (1) 專業態度篇