Category Archives: Agile

採用敏捷方法的軟體開發合約該怎麼簽?

補充(2016/1/16): Software Costs Estimation In Agile Project Management

一般在台灣簽軟體開發合約,最常見的就是固定價格、固定規模的合約(最晚何時要交出符合這些規格的軟體和文件),但是碰到的問題就是兩造雙方都覺得需求定不清楚,而陷入對立的情形。接案的希望少做、出錢的希望多要一點,所以往往都在討價還價中試圖在期限中完成。如果實作後發現一開始的估計太保守、開發中途客戶又不斷加規格、需求頻繁變更或是任何意外,往往就就會開始 Delay,一旦專案延遲,第一個犧牲的就是開發品質了,例如省掉測試作業等。變成開發範圍太大 -> 無法如此交貨必須加班 -> 品質降低的惡性循環。

但是要採用敏捷開發,這種合約要怎麼寫啊?敏捷開發不就說專案一開始不需對規格詳細內容做決定嗎?

XP 方法論提出四個互相影響的專案變數:成本、時間、品質、開發範圍。也就是如果定死價格、時間和範圍,品質就會變成可犧牲的變數。XP 派別認為這四個變數,最常、最應該可以變動的就是開發範圍,也就是定好價格、時程和品質,但是要做什麼再談。回到合約上看,也就定期限、定總價但不定規格,例如 Kent Beck 建議的:『供應商將提供八個程式設計師為顧客服務,並且兩個月的金額是 $1,000,000。規模每兩週協商一次,協商時根據 XP 方法進行』。

但是不定規模對顧客而言最大的擔心,就是他想要知道他花了 $1,000,000 到底可以得到什麼,因此在台灣還很難接受這種合約方式,照 Kent Beck 的說法,只能玩成價格固定、交期不變、規模求個大概。例如我們和多就使用 User Story (註)的方式來定規模,保留與客戶溝通及實作的彈性,又有可以拿來估價的基準。雖然都會將專案時程切到至多三個月(一季)來做最大的估價範圍,不過還是會有一開始低估規模而造成專案 delay 風險(所以加強專案的估計技巧也是個努力的方向)。

另一種在台灣常見合約的作法,是分成兩次簽約跟報價,第一次簽約預備開發期,目的是要完整討論出需求跟規格,這部份就會先收取一次費用。第二次簽的約才根據詳細規格再報一次總價。不過這種方式基本上就把敏捷開發一開始『規格不確定也無妨』的優點給犧牲掉了。

還有一種簽法就是計時合約(顧問費),花了多少時間就請多少款,但是基本上這跟固定價格、固定規模型的合約還是有一樣的利益矛盾問題:供應商為了多賺點錢,會希望多加人、多加班多報一點時數。客戶則想要在人力、時間盡可能少的情況下,做出盡可能多的功能。

各位接案的前輩們,你們是怎麼做的呢?

註:

User Story(使用者情節)是一段簡單的功能敘述,以客戶或使用者的觀點撰寫下有價值的功能(functionality/feature)。與其說它是規格文件(documentation),不如說它代表(represent)客戶需求,因為實做細節將延後至開發時才會確定。

幾個 User Stories 的範例如下:

  • 使用者可以在網站上張貼履歷
  • 使用者可以搜尋有哪些工作
  • 公司可以張貼新工作
  • 使用者可以限制誰可以看到他的履歷

參考資料:

實戰敏捷開發 Practices of an Agile Developer (6) 團隊開發篇

有效率的團隊互動是敏捷軟體開發的一個重要基石,能與人一起開發軟體是很棒的事情。

小小一本書,拖了這麼久終於整理完了,總計六篇:

Schedule Regular Face Time (進行每日的固定簡短開會)

開會是個溝通必要但又花時間討人厭的事情,在 Agile 方法論裡面提出了一種叫 Stand-up meeting 的每天開會方式,正如其名建議的:請站著開會,這樣才會提醒大家盡早開完。每個人至多兩分鐘報告三件事情 1.距離上次開會,我做了什麼? 2. 到下次開會,我打算做什麼 3. 我碰到的困難。如果有需要討論更細節特定的東西,請再約有關係的人在開完 stand-up 各自帶開即可。一般來說合理的 stand-up meeting 時間約10到15分鐘,至多絕不超過 30 分鐘。另外就是儘量不要等人,時間到了就開始吧。

Stand-up meeting 有幾個好處:1. 繼續專心每天的進度 2. 如果 developer 碰到任何問題,可以有機會發出聲音尋求幫助,加速事情解決 3. 幫助團隊成員了解其他人在做什麼事情。

Architects Must Write Code (架構設計師一定要寫程式)

軟體架構設計師已經變成有點被濫用的職稱了,一個只會畫好看的圖、講厲害的行話、用一堆 Patterns,但是真的要實做前就跑走的設計師是沒有用的。真的設計隨著實際開始實做、實際的 Feedback 之後才會逐漸演化。就像打戰一樣,一旦開打 Plan 就無用了,只能不斷地 Planning,策略決策也許千里遠可以做,但是戰術決定非得實際在戰場上了解才行。

The designer of a new kind of system must participate fully in the implementation. by Donald E. Knuth

作為一個架構設計師,要實際了解設計真的可行才算是負責。真正的 architect 是團隊中的導師,有能力解決較複雜的問題,但絕不放棄 coding。絕對不要用不 coding 的架構設計師,不實際 coding 了解系統是不可能設計好軟體的。 * Good Design evolves from active programmers. *

Practice Collective Ownership (練習集體擁有感)

“這個地方是 xxx 寫的,只有等他放假回來才能修的好”,這種事情很糟糕。請避免讓某一段 code 只能由某特定 developer 處理的情況,試著輪調 developer 去寫系統中不同的模組,分別由不同的人看過也可以讓程式更加被 checked、refactored 和 maintained 過,一旦碰到哪裡急需要修 Bugs 也不會發生等人的窘境。

當然,也許讓 developer 專屬某一領域的工作可能會開發一時會比較快速,不過放長遠一點看,透過多人共同開發的方式會獲得比較好的程式品質,也會比較好維護。整個團隊的 developers 的平均水準也可以拉高。對 developer 來說,知道寫出來的程式將來會被別人看,也會寫的更加用心。

這件事做起來當然是要注意過猶不及,例如太頻繁的輪調造成混亂或是系統中真的有某處需要特定人的特定專業知識。重點是 “你可以不需要了解系統中的每個細節,但是你也不應該害怕要你接手處理系統中的任何一塊”。

在 XP 方法論中,這件事情更是直接透過 Pair Programming 來達成,每一段 code 至少被兩個人擁有(處理) :p

Be a Mentor (當一個導師)

當你發現你比同事都懂的多的時候,請學著分享,正所謂教學相長,讓團隊一起提昇。當 Mentor 不代表要像當老師一樣用上課的方式,透過 Pair Programming 就是一種很好的學習成長方式。

Allow People to Figure It Out (給人魚不如給釣竿)

當 Mentor 不一定就是直接回答人家答案,回答一個方向讓人去思考也是個好方法,也許他還會回報你更多好答案。

Share Code Only When Ready (只 commit 準備好的程式)

千萬不要把(已知)會爛掉的程式 commit 進入版本控制系統,不然其他人更新最新版本之後不能正常執行,影響開發進度。請在做完一個任務或解完 Bug 之後,通過 unit test 之後再 commit 進去。當然,”It’s not ready” 不是一個久久不 commit 的藉口。

Review Code (程式碼複審)

找到程式問題的最佳時機就是剛寫好的時候:也就是在寫好程式及測試之後,輪流由另一外 developer 檢查程式(review code)。另一種採行 review code 的方式就是實行 XP 方法論的 pair programming:一人寫程式打字另一人是導航員。

Review code 要做什麼事情?

  • 你可以閱讀看得懂程式嗎? (如果看不懂表示程式沒寫的好懂哩)
  • 有沒有任何明顯的錯誤?
  • 這程式有沒有任何不期望的副作用?
  • 這程式有沒有跟系統其他地方有重複的程式碼?
  • 有沒有任何合理的改進或重構可以做?

Code Review 可以大大改進程式品質、降低錯誤率,而且非常實用讓團隊彼此進步。

Keep Others Informed (資訊公開)

不要在 Deadline 那天才報告壞消息。如果任務中途碰到技術困難,也要提出來讓大家想想看怎麼解決,甚至讓客戶知道也許他們願意用其他任務交換。盡量公開進度資訊,團隊跟客戶才會對進度感到放心、知道何時幫助你,讓你贏得信任。不要等著讓別人追問你進度如何。

實戰敏捷開發 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) 程式篇

物件導向程式的九個體操練習

最近在翻 The ThoughtWorks Anthology(知名軟體顧問公司 Thoughtworks 出的文集),裡面有篇 Object Calisthenics 蠻有意思的。

好的物件導向設計很難,我們都很同意何謂好的設計原則:高內聚力(cohesion)、低耦合(loose coupling)、不重複程式(Don’t Repeat Yourselp)、封裝(encapsulation)、可測試性、易閱讀性等等,但是實際寫的時候卻不容易化身為一行行的程式碼。這篇作者列了九條規則,並建議你練習寫個千行程式嚴格遵守看看,用以改善你的OO實作能力。

初次看到這九條時覺得有點誇張,但其實濃縮了不少OO想法在裡面,如果有閱讀過重構或物件導向設計原則等概念,應該能夠聯想到很多東西,挺有趣的。

1. 每個函式裡面只能有一層縮排,如果需要多一層,請多寫一個 method 去呼叫。

這個規則其實就是要求嚴格遵守 Compose Method:將邏輯操作轉換為細目等級相同的步驟,避免過深的邏輯而無法迅速了解,相信大家應該都有看(寫)過M型程式吧 :p

2. 不要使用到 else 這個關鍵字。

避免寫出複雜的 nested conditional 程式。不論是”重構“或是”重構-向範式前進“這兩本書,都有很多篇幅花在討論如何簡化條件邏輯,作法包括

a. 重構一書提到的 Replace Nested Conditional with Guard Clauses 方式,直接使用 return 返回,不要再 else 了。

b. 請愛用 Ternary Operator:也就是 boolean-expression ? expr1 : expr2。很多簡單的 if else 都可以用 Ternary Operator 簡化到一行一目了然。舉個 Ruby code 例子:


if ( is_something )
"foo"
else
"bar"
end

如果改成三重操作子就俐落多了:

( is_something )? "foo" : "bar"

另外初心者也常寫出根本不需要 if else 的情況:

def is_foobar
if ( a > 0 )
return true
else
return false
end
end

其實只需要這樣就可以了:

def is_foobar
( a > 0 )
end

c. 第三招要先念點書,請善用物件導向的多型(polymorphism)能力,請參考設計模式的 Strategy pattern 或重構的 Replace Conditional with Polymorphism

Continue reading 物件導向程式的九個體操練習

實戰敏捷開發 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, 你犯了一個大家都會犯的錯誤。即使聽起來很笨,也應該去試著聆聽。