l

2010年4月30日 星期五

Problem Domain vs. Solution Domain

4/29 22:50 4/30 00:10

實驗室有個提早報到的新進準碩士班學弟被指派研究 CruiseControl,從最基本的安裝,學習如何撰寫 ANT scripts ,在建構活動中執行編譯與測試,最近進行到可以用 Cobertura 來跑 test coverage。某日,Teddy 在看過學弟的 demo 之後,問了他幾個問題:

  1. Cobertura 所產生報表,裡面有 line coverage, branch coverage, 與 average complexity。這三個數據各代表什麼意思?
  2. 你覺的使用 CruiseControl  (廣義的說,持續整合) 最困難的地方是什麼?
  3. 你接下來打算做什麼?
學弟的回答:

  1. 不清楚這些數據的意思 (PS:根據 Teddy 觀察,學弟認為只要把 Cobertura 的報表掛到 CruiseControl 中就代表完成了該項工作)。
  2. 在寫 Ant scripts  的時候,需要依據不同的工作設定不同的參數,很容易出錯。
  3. 預計把 StatSVN 的報表掛到 CruiseControl 中。

有帶過研究生經驗的鄉民們可以發現,這位學弟的回答是很典型的學生思考模式:只看到『Solution Domain』,而忽略了『Problem Domain』。

 ***

Problem Domain 就是問題發生的地方,也就是軟體開發所謂的『需求』。而 Solution Domain 就是問題的解決方案,也就是軟體開發所謂的『設計』或是『實做』。舉凡像是演算法,design patterns,refactoring,architecture 等等,都算是 Solution Domain 的範圍。

時時提醒自己哪些概念是屬於 Problem Domain 或是 Solution Domain  是一個很簡單,但卻很有用的思考與分析工具。以持續整合為例子,如果眼中只有 Solution Domain ,那麼這位學弟最後可能變成 ANT 大師,熟用數十種工具,但是卻不知道要如何利用這些 Solution Domain 的工具來幫助真實世界的軟體開發團隊來落實持續整合。為什麼,因為持續整合的 Problem Domain 是『軟體開發』,如果對於持續整合可以解決哪些軟體開發活動中所遭遇的問題未加以分析清楚,則光是會使用工具,並無法解決問題。這就像有一陣子很多公司花大錢買了 Rational Rose 的工具,以為這樣就增進軟體開發的速度並且改善軟體品質是一樣的。

光是知道問題,答案做不出來,也是白搭。答案寫出來了,但是答錯問題,更是白忙一場。所以,對於軟體從業人員而言,要能夠具備分析 Problem Domain 和『生出』 Solution Domain 都是同等重要的。以下列出幾個屬於持續整合 Problem Domain 的概念:

  • 持續整合的主要目的,就是要找出『整合』的問題。哪麼,那些算是『整合』的問題?
  • 針對不同性質的專案,持續整合的內含需包含哪些(例如,編譯,測試)?
  • 當一個軟體專案大到一定程度,為了分工以及組織 (模組化與管理相依性) 的目的,因此專案通常會被切割成若干個小專案,而最終的產品將由這些小專案組合而成。
    • 專案相依性對於持續整合有何影響?
    • 公用(共用)元件對於持續整合有何影響?
    • 持續整合的產出物有哪些?
  •  持續整合的執行速度使否會影響到軟體開發活動?
大致分析這些問題之後,當鄉民們需要評估 Solution 的時候,例如,選擇哪些持續整合系統,使用哪些工具,要如何應用,如何改變專案結構與開發流程來使得持續整合更順暢等等,才有個取捨的依據。


 ***


Teddy 不是一個聰明人,在處理事情的時候所使用的招式也都十分簡單。『區分Problem Domain 與 Solution Domain』這一招 Teddy 已經用了好幾年了,非常用有。下次有人請你幫他 review 東西的時候,可以拿出來試看看,也許可以輕鬆幫對方找出一些盲點。

友藏內心獨白:感冒什麼時候才會好啊...

2010年4月27日 星期二

精神不好的時候

4/27 21:07~22:04


這幾天 Teddy 感冒了,上禮拜五下午開始覺的精神不濟,但還不確定是得了感冒。上禮拜五 Teddy 利用 retrospective meeting 結束後的一點空閒時間整理放在 ezScrum 上面的 release plan 以及下一個 sprint 所需的 stories (備註:ezScrum 是一套 open source 的 Scrum 輔助軟體)。Teddy 在一個恍神之下,居然把下個 sprint (sprint n) 的資料從電腦中殺掉了(原本是要將某一個 story 從下個 sprint 中移除)。更慘的是,ezScrum 不能讓我重新建立這個編號 n 的 sprint,只能建立 sprint n+1,變成跳號啦。由於 sprint 編號對於 Teddy 而言是有意義的,因此如果不能重建 sprint n 將會很困擾。好里加在,ezScrum 是 Teddy 的學弟開發的,趕快 call out 尋求幫助。

在學弟的幫助之下,經過約半小時之後,Teddy 終於重新建立了 sprint n。可是沒想到更倒楣的事還在後頭,Teddy 在不知不覺中居然把這個 sprint 已經完成的三個 stories 給刪除了(這 ezScrum 也有點問題,既然這些 stories 的狀態都已經是 done 了,怎麼沒有防呆,還讓 Teddy 把他們刪除了!)。怎麼辦?再度 call out..又搞了快  20 分鐘,才把資料復原。

結果原本在 30 分鐘內就可以處理的完畢的事情,Teddy 那一天大概花了 1.5 個小時。

***

不曉得鄉民們有沒有那種熬夜寫程式,隔天睡醒之後發現昨天熬夜所寫的程式錯誤連篇,以至於要整個拔掉重寫的經驗?抑或是,花了好幾個小時解一個 bug 卻是怎麼都無法搞定,反而把程式越改越亂,越改越亂越改越亂。最後終於受不了了,跑出去散個步,喝杯咖啡,大個便,洗個澡,睡個覺... 說也奇怪,靈感突然來了,重新回到座位上,問題迎刃而解。


這也是 Teddy 不贊成加班的另外一個原因。平心而論,一天只要能夠有 5-6 小時很認真寫程式的時間,就已經夠了不起,夠累人的了(難道是 Teddy 年紀大了,不耐操... XD)。加班寫程式,也不是說不會有生產力,短期來講會有不錯的效果,但是如果變成常態,人也麻痺了,專注力可能會變差,不但降低正常上班時間的工作效率,甚至於『長工時』所增加的生產力很有可能變成負的。因為很有可能做出更多的 bug 等著自己明天來 fix,導致於『明天的 bug,今天就給你傳便便』的現象。

結論就是,ㄏ一 (hee)... 要休息。

友藏內心獨白:Teddy 沒有打 H1N1 疫苗啦....不要傳染感冒給他的啦...

2010年4月22日 星期四

要抄就要抄最好的:架構師篇

4/22 21:17~20:30

史提芬周:『只要有心,人人都可以成為食神』。
Teddy:『只要有心,人人都可以成為 architect』(啊,沒有押韻...)。

在『食神』電影中,有人問『史提芬周』要如何才能做出好吃的菜。史提芬周回答:『一字謂之心』。在白日夢裡,有人問 Teddy 要如何才能成為 architect,Teddy 回答:『一字謂抄』。沒錯,就是『抄』,英文叫做 『copy』,內地人叫做『山寨』,老台灣人叫做『海盜』。俗話說:『有樣不抄,對不起父母。抄光學光,為國爭光』。不只要抄,而且還要挑最好的,最高檔次的來抄。

***
對軟體開發人員來講,有朝一日能夠成為 architect 應該是職涯中相當重要的一個階段,通常代表自己歷經風霜,飽嘗客戶與老闆的荼毒,算是見過大世面的高級技術打工仔。這樣的人才,相當於武俠小說中的『高手』。雖然在武俠小說中高手要練得絕世武功動不動都要耗費個幾十上百年的,但是也有所謂的『少俠』,十分好狗運,吃了什麼『千年靈芝』,『何首烏』,『大還丹』之類的補品,又不小心撿到『九陰真經』,『九陽神功』之類的的秘笈。其結果就好比娶了有錢人的獨生女,少奮鬥了幾十年。

軟體界有那麼好康的事嗎?有滴!Teddy 是不知道有沒有什麼東西吃了可以讓程式設計的比較好一點,不過秘笈倒是有滴。只要將這本秘笈中的招式學個 3-4 成,就可以略有小成。要是學個 7-8 成的話,不得了,躺在家裡都不怕 選不上 系統做不出來。什麼書那麼強... 各位觀眾 (奏樂...):

Contributing to Eclipse: Principles, Patterns, and Plug-ins, by Erich Gamma and Kent Beck

鄉民甲:Teddy 你又在話唬爛,整本書名沒有一個字在講 architecture,甚至連個 a 開頭的字都沒有。

沒錯,這是一本介紹 Eclipse 設計的書,雖然整本書名沒有提到 architecture 或是 architect,但是這本書的內容呼應了本篇的主旨:『要抄就要抄最好的』。Eclipse 的強大不需要 Teddy 多費唇舌說明了,多麼棒的設計啊。有人寫書介紹這些架構的設計理念(先研究不傷身體,再講求藥效 !@#%$),只要花 USD 39.99 就可以帶回家,實在是太划算了啦。不買遺憾終身,買了不看終身遺憾。

究竟這本書內容在談些什麼呢?還是那句老話,自己看 (鄉民乙:挺不負責任的部落客...)。請原諒 Teddy,因為寫到這邊已經很累了 (今天找了 10 幾個 bugs...原本已經放下的某根指頭又蠢蠢欲動....) ,再加上秘笈內容博大精深,網路上高手又多,萬一 Teddy 一不小心講錯,豈不是自己討打。

友藏內心獨白:其實這一篇是硬擠出來的,湊湊數。

軟體庫存

4/21 23:07~ 4/22 00:33

2009 年下半年金融海嘯發生的時候,國內某家以產品品質『以卵擊石 堅若磐石』為賣點的電腦公司發生了創業以來第一次的虧損,據報導主要的原因在於該公司對於景氣過於樂觀,製造了一大堆的庫存。沒想到金融海嘯席捲全球,這些庫存賣不出去,造成不少庫存跌價損失。

公司的(成品或半成品)庫存水位很高,其實是一種警訊,它會掩蓋許多流程上的問題。例如,公司的行銷或是業務部門沒有做好市調或是市場研究,因此生產了一堆沒人要買的產品堆在倉庫。而公司為了打消庫存,可能會想一些『手段』想辦法把客戶不需要的產品『塞』給他們。對公司與客戶來講,都是一種浪費。因此『精實生產』(Lean Production)的一項重要改善活動就是要降低庫存,期望達到『訂購生產』(客戶下了訂單才開始生產,但是要想辦法在很短的時間交貨給客戶)的目標。

***

那麼,軟體呢?如果軟體庫存太多會怎樣?

鄉民甲:騙肖ㄟ,軟體看不到也摸不著,那裡會有庫存的問題?

以下 Teddy 列幾項軟體庫存的例子給鄉民們參考,參考:

  • 部份完工的功能(partially done work):這種類型的庫存量在開發中的軟體系統中佔了非常大的量,例如,沒有測試的程式碼,沒有整合的模組,沒有重整過的程式,沒有說明文件的功能,無法安裝的系統,沒有解決的 bugs。這一類的軟體只能算是『半成品庫存』,看起來好像系統開發的進度很不錯,但是實際上沒有一項功能可以真正被使用。
  • 額外功能(extra features):這種『沒有列在需求中的額外功能』通常是開發人員『預留伏筆,以求自保』的招數。『既然都做到這裡了,以後 users 應該也會需要這個和那個,所以就順便一起做一做好了』。這種『要五毛給一塊』的情節也不算少見。
  • 過度設計(over design):做軟體的人都知道 users 和需求都是善變的,因此傳統的軟體工程教育我們要『為未來做好打算』(搞得這些做軟體的人好像都要變成算命師)。因此,很多人習慣在軟體開發之初便要設計一個可以『應付未來』的架構。所以,這邊增加一個 XML 設定,那邊套用一個 pattern,上面加個 MVC,底下補個 OR-Mapping,中間再應用 SOA + Cloud + Plug-ins + 龜派氣功 + 無敵風火輪 + 黯然消魂掌 + 芋頭牛奶 ... (有怪獸,有怪獸)。最後開發出一套不到 1000 行的留言板系統,寫程式花了三天,設計架構可能花了三個月。這些過度設計所造成的庫存當然也沒推銷出去,最後放到保存期限過期。
鄉民們如果仔細端詳一下,許多 agile methods 的精神與作法都與『消除軟體庫存』有關,而改善軟體流程的手段,也可以從『如何消除軟體庫存』來思考。例如,如果鄉民們採用 Scrum,發現經常性的在每個 sprint 中都有幾個 stories 是在解決之前 sprint 發生的 bugs,這就有可能是軟體開發團隊在之前的 sprint產生太多『半成品庫存』的現象。解決方法可能是要研究產生這些 bugs 的根本原因(root causes),針對這些原因提出改善方案。或是檢視每一個 story 是否有清楚定義出『DONE』的條件,這些條件是否需要修正?開發人員是否清楚這些條件且確實遵循?

軟體庫存也會隱藏流程上的問題,例如,因為流程規劃不當或是工作分派不均,造成團隊中有些 programmers 已經閒閒沒事做了。這些『櫻櫻美代子』的 programmers 只好沒事找事做(記得侏儸紀公園的至理名言:生命會自己找到出路),一不小心就製造了許多軟體庫存。乍看之下這些 programmers 的確有很努力的在工作啊,搞不好還加班到 11,12 點。但是如果這些產出只是增加不必要的軟體庫存,那麼很明顯地是開發流程出了問題,需要改善。Teddy 再強調一次,庫存會隱藏流程上的問題,需要密切控管。

***

庫存可能會漲價,也可能會跌價。對於軟體庫存而言,跌價的機會似乎比較高一點。此外,製造出這些軟體庫存也是要花成本的,這些成本都算是一種時間與金錢的浪費。鄉民們,抽空盤點一下你的系統,想辦法消除不必要的庫存。

友藏內心獨白:做軟體的人可真是會 抄襲 學習,建築的也抄,生產管理的也抄,算是現代版的吸星大法。

2010年4月20日 星期二

販賣對不起

04/20 20:55~21:10

幾年前公益彩券換台彩公司接手,剛開始的時候電腦系統非常不穩,經常當機,導致彩券經銷商必須要不停的跟顧客賠不是。當時有一位彩券經銷商接受記者訪問時說:『原本我們是在販賣希望,結果現在變成在販賣對不起(因為要不停的跟客人說對不起)』

Teddy 學習了 Scrum 之後,養成了一個壞習慣,就是沒事看到別人做事『不順』的地方,就會不自覺地思考要如何改善。 久而久之,Teddy 發現台灣的服務業,除了主要服務項目之外,兼賣『對不起』的比率也非常高。例如,上禮拜天 Teddy 和 Kay 到捷運中山站 2 號出口的某家販賣日式餐點的餐廳吃飯,Kay 的餐點都快吃完了,Teddy 點的鮮魚餐連個影子都沒看到,連其他比 Teddy 晚到的客人餐點也都到齊了。於是,Teddy 忍不住問了一下服務生:

Teddy:請問我的餐點何時會好?
服務生:(看了一下單子) 你點的鮮魚是『現做的』,所以會比較慢。
Teddy:就算是『現釣的』應該也做好了吧 (這句沒講出口...) 如果需要做這麼久,那點餐的時候為什麼不先行告知呢?
服務生:對不起....

Teddy 需要的當然不是一聲對不起,而是希望上菜能夠『平準化』,就是說兩個人一起用餐,總沒有人希望一個人吃飽了,另一個人的餐點還沒到吧。如果客人點的餐點需要比較久的時間,一般來講服務生都會提醒『這個餐點要比較久喔』然後看客人是否願意等待,一個小動作就可以免除雙方的不愉快。

接下來不久後發生了一件更誇張的事,Teddy 旁邊那一桌客人,剛好坐在一台分離式冷氣機的下頭,用餐到一半冷氣機滴下了大量的水,把客人的背部與褲子都弄濕了。至於服務生的回應,除了請顧客換到另外一桌以外,就只是不停的說對不起,對不起。還是顧客主動要求服務生才拿乾毛巾給顧客擦拭身體。坐在距離 50 公分不到的 Teddy 與 Kay 用餐心情當然也受到影響,但是服務人員完全沒有對我們有任何表示,把我們看作是隱形人。


這麼爛的服務,居然還要收 10% 服務費。這算不算詐欺還是公然搶劫?

***

認識 Teddy 的人可能會覺的 Teddy 是一個很喜歡批評的人(這一點是事實)。那到底有沒有什麼是 Teddy 覺的服務還不錯的店家?有滴,這邊講一個 Teddy 親身在 Starbucks 遇到的故事。話說幾個月前的某個週六,Starbucks 正在舉辦買一送一的活動。當天 Teddy 和 Kay 抱持貪小便宜的心態到重慶南路天瓏書局對面的 Starbucks 喝咖啡順便看書。由於當天人很多,Kay 先到二樓去找位置,Teddy 在一樓點餐。排在 Teddy 前面的是一位看似高中生的小女生,拿了兩杯類似星冰樂的飲料正要上樓的時候(其中有一杯是綠色的,很顯眼),Teddy 忽然聽到很大一聲『啪啦』的聲響,回頭一看,那個小女生整個人跌倒之後趴在樓梯轉角處,手上拿的兩杯影料以『天女散花』的形式灑落在牆面上,而這個跌倒的小女生全身也濕透了(怎麼 Teddy 淨是遇到這種和水有關的意外)。

此時 Teddy 第一個念頭就是替那個小女生感到難過:『啊,我買的 Starbucks 超貴飲料一口都沒喝到就這樣沒了... 救命啊....』。過了不到三秒鐘,Starbucks 的人十分迅速的來的 案發 事故現場,詢問小女生是否有受傷,並且馬上通報櫃台人員有人在一樓樓梯『倒杯』了(真的,原來他們用的術語叫做倒杯)。Starbucks 的人除了主動拿毛巾給對方,還立即派人把事故現場的地面與牆面整理乾淨,最令人感動的事,Starbucks 還免費重作了兩杯一樣的飲料給她,Teddy 看了真是覺的『足感心』,從此對 Starbucks 印象改觀。之前 Teddy 一直覺的 Starbucks 咖啡雖然好喝,但是賣得太貴了,經過這件事之後,覺的人家貴的有道理,在台灣這樣的服務算是不錯的(PS:Teddy 也曾經在 Starbucks 遇到很鳥的事,不過大體來講 Starbucks 的服務算是很 OK 的)。

在這邊先穿插一個題外話,話說當時 Kay 在二樓也聽到有人跌倒的聲音,馬上從二樓拿了衛生紙到一樓給那個跌倒的人。因為她的第一個反應就是:『會不會是 Teddy 跌倒』... 這... 原來 Teddy 在 Kay 的心目中就是屬於『笨手笨腳』類型的人 ...

***


又是被 Scrum, Lean,和 TPS (Toyota Production System) 害的,Teddy 現在只要看到『事先不思考如何提高品質,事後再來販賣對不起』的心態與作法,越來愈不能接受。相同的觀點,拿到軟體開發來看也是一樣的。為什麼大部分台灣的軟體開發人員(也包含 Teddy 啦)總是覺的程式有 bugs 是很正常,很自然的一件事?也鮮少很認真,嚴肅的思考是否有可能儘量第一次就把程式寫對。至少當別人向你回報 bugs 的時候,應該要感謝對方並且檢討為什麼會發生這樣的 bugs,以及如何避免類似的問題重複發生。有些態度更不好的開發人員,當你跟他回報 bugs,還會反過來發脾氣 ... 此時你的心中只有一句話:『看...什麼看』。

結論就是,任何從業人員如果要配的上『專業』兩個字,就要認真看待自己製作的產品或是提供服務的品質,朝向『第一次就做好』的目標努力。

友藏內心獨白:我們受到差不多先生,小姐的影響太大了,短期很難改過來的說。

2010年4月18日 星期日

你的軟體架構有多軟

04/17 22:49 ~ 04/18 00:42

『軟體』這個名詞有時候真是害苦了從事這個行業的人。『軟體』---->『軟的物體』,顧名思義就是一種像是『黏土』一樣可以捏來捏去,沒有固定形體的東西。原本客戶一開始要的是小花貓,你作到一半他忽然要改成頭上有個『王』字的大老虎,你也要想辦法生給他。

客戶:這個很簡單的啦,不是改幾行程式就好了。
你: 靠... 左邊走...

根據 Teddy 的經驗,大部分的軟體系統其實都很『硬』,隨便改了一行程式都可能會額外『製造』出意想不到的 bugs。雖然實務上軟體是很硬的,但是你的客戶或是老闆卻不斷的催眠你:『軟體是軟的,軟體是軟的』。久而久之,涉世未深的 programmers 居然也開始萌生『軟體應該是軟的喔』這樣的想法。為了讓自己手邊硬到不行的軟體變軟,programmers 們便開始嚐試各種作法,其中一個常見的作法是:設計一個 包山包海 具有可擴充性的軟體架構。

鄉民們如果查一下軟體架構的內含,應該知道軟體架構主要是用來滿足availability, modifiability, performance, security, testability, usability 以及其他族繁不及備載的 nonfunctional requirements (或稱為 quality attributes)。傳統上(應該說主流的想法,因為到現在大部分的人也都還這麼想)大家都認為要在系統開工之後才來考慮 nonfunctional requirements 通常會造成系統大改,成本太高。這有點像是,當你蓋好了『一品苑』之後才發現它長得太高了,可能會讓有心人士可以直接把總統官邸當成靶場,違反了『security』這個 nonfunctional requirements。房子都蓋好了不然是要怎樣?(套句遙遙的廣告台詞:這是你的 security,不是我的security ...XD)不管是要總統搬家(誰想出這個乞丐趕廟公的餿主意?)或是停發使用執照,成本都很高。

所以,雖然 agile methods 告訴我們不要做 big up-front design,但是軟體架構身份這麼特殊,是否應該享有特權,等軟體架構設計好了再開工?

***

問題來了,怎麼才算是『軟體架構設計好了』?仔細想一想,這是一個『先有雞,還是先有蛋』的問題。如果鄉民們相信 agile methods 所倡導的 iterative and incremental development,那麼應該知道:

  • 三心二意的客戶以及劇烈變化的市場隨時都可能改變需求。
  • 客戶的需求通常在他看到或用到軟體之後會越來越明確。
  • 因此,軟體專案不必也沒辦法等所有需求都確定才可以開始(因為需求永遠都在變啊,要等所有需求都確定才開始那不就等於永遠都不會開始)。

在需求會變動的情況下,怎麼設計一個軟體架構來滿足可能會變動的需求?另外,如果軟體架構可以或應該在軟體開發之前就完全決定,那是否表示需求就要固定?

以上兩句繞口令可直接忽略,重點是,如果依據傳統的作法,在 coding 之前就要把軟體架構設計好,會引誘開發團隊進入 big up-front design 的陷阱。更慘的是,就好像在 waterfall 流程中通常會要求客戶對需求畫押保證不再變動一樣,結果就是.... 不管你事前花了多少時間,設計好的需求與軟體架構,在 coding 之後幾乎不可能不改變(請不要拿美國 NASA 或是國防部的案例來嗆聲... 台灣大部分的開發人員在有生之年應該都沒有這個榮幸做到這種案子)。

講了這麼多,那到底要怎麼做?首先就是心態要調整一下,要先相信軟體架構是有可能採用『逐步成長』的模式來慢慢成型。這一點當然是『說得比做的容易』,因為『選對軟體架構可以讓開發團隊上天堂,選錯軟體架構就只能住套房』。看到其他人用好的軟體架構或是 framework 過的自由自在的日子,而自己卻在住在套房中被綁手綁腳的,這種感覺會讓開發團隊覺的自己很遜,最後可能變成『砍掉重練』(傳說中的設計魔人是否就是這樣誕生滴?)。

由於這個問題太難了,因此 Teddy 就不要不懂裝懂,留待各位鄉民們自行發揮。不過以下有一點資料可以參考一下。最近從 Teddy 在讀 Implementing Lean Software Development: From Concept to Cash  這本書,裡面提到:

The objective of a good software architecture is to keep such irreversible decisions (nonfunctional requirements such as security, performance, extensibility, etc) to a minimum and provide a framework that supports iterative development.

It is time to abandon the myth that architecture is something that must be complete before any development takes place.

要如何 “keep irreversible decisions to a minimum and provide a framework that supports iterative development” 說真的就要靠硬功夫了,以下是幾招常見的標準招式:

  • Plug-in Architecture
  • Layered Architecture
  • Model-View-Controller
  • Service-Oriented Architecture
  • Component-Based Development

前面三招是目前 Teddy 的最愛...


友藏內心獨白:歡迎喜歡吃『軟飯』的人加入軟體開發行列,讓軟體變得更軟 !@#$!#%&

2010年4月15日 星期四

放下心中舉起的中指

04/14 22:48~  04/15 00:20

對於 PM 或是 project leader 而言,讓人最沮喪的事情,莫過於團隊成員告訴你程式都寫好了,但是你稍微使用一下卻發有問題。遇到這樣的情況,典型的心情的轉折為:『沮喪』--> 『生氣』-->『想罵三字經』。

無論最後那句 XXX 有沒有說出口,此時的心情是十分低落的。心中不自覺的響起許多怪東怪西的聲音:
  • 這些該死的刁民 (programmers),程式寫好都不測試的啊!
  • 這麼簡單的問題也要我來告訴你 (programmers) ?你不是『應該』要自己想到的嗎?
  • 真是太欠罵了,這個問題都發生過幾次了...
  • 這就是自己帶領的團隊嗎?是不是自己的領導太失敗了...

***

假設鄉民們的團隊採用 Scrum 方法,但是團隊中並沒有專門的人來負責驗收測試。為了確保每個 sprint 結束時所完成的系統品質不至於太差,可以採用在每個 story 裡面都加了一個 testing task 的方法 (這個 testing task 是做 functional testing,unit testing 已經包含在 programmers 開發功能之中),以確保 story (需求) 從『未完成』變成『完成』之前,會被團隊成員測試過。通常這樣的 testing task 會找非開發該 task 的團隊成員來領取,以避免測試自己測試自己所開發的功能(因為『自己寫,自己測』通常更找不到問題)。很多時候團隊成員在認領這些功能測試工作時,經常會發現,許多 有一些宣稱已經做完的 tasks,其實還是存在著 bugs。看到這邊,也許鄉民們或說:『程式有 bugs 是很正常的啊』。的確,之前 Teddy 也是這樣想,但最近看了 Lean 與 Toyota Production System (TPS) 的書之後,提高了 Teddy 對於『品質』以及『減少浪費』的意識 (測試找出 bugs,修改 bugs,再次測試,這些都是額外的浪費),因此越發覺的應該要重視這個問題。

Bugs 可以簡單的分成兩大類:

  • 情有可原的 bugs:雖然 programmers 有撰寫單元測試程式,但是 client 卻用某種『預料之外』的方式來呼叫自己的程式。這種情況勉強算是『情有可原』。
  • 不可原諒的 bugs:此類 bugs 又可細分為兩種
    • 偷懶:有時候 programmers 偷懶,只寫最簡單的單元測試。在正常的操作之下,只要步驟或是輸入資料稍微複雜一點點,就會出錯。
    • 自作主張:Programmers 在寫程式的時候,對需求不是很清楚(尤其是使用者介面的設計與操作方法),但又不肯問就坐在他旁邊的 product owner,而依照自己的想法把功能做完(通常都是過度簡化)。雖然完成的功能本身可能沒什麼錯誤,但是這樣的功能並不完全符合 product owner 所需要的需求。
當遇到『不可原諒的 bugs』時,相信每個負責測試的人員在心中會很想罵 XXX。但是,生氣,除了讓自己的血壓上升以外,並不能解決問題。此外,要求開發人員寫出零錯誤的程式實務上的確是非常不容易的(Teddy 自己也寫不出零錯誤的程式啊),因此也沒立場要求其他人不准犯錯。在 Scrum 中,可以選擇在 retrospective meeting 時把個問題提出來討論,在看看要如何改善。假設『不可原諒的 bugs』發生的原因為:

  • 資料庫問題:因為有一些和資料庫有關的程式,在測試的時候需要在資料庫中設定測試資料,而準備這些資料和執行這些測試程式都很花時間。因此,有時候就偷懶沒測。(這樣的理由聽起來雖然有點牽強,不過總是反映出和資料庫有關的測試案例不容易撰寫這個問題)
  • 開發人員自作主張(通常發生在使用者介面)的問題:開發人員經常是用最容易施工的方式來設計,沒有特別思考到是否容易使用或是一致性的問題,也沒有將設計好的雛型先請 product owner 看一下。

針對這兩個原因,可能會有以下的改善方案。
  1. 在下個 sprint 規劃一個 technical story,設計一組產生資料庫測試資料的公用程式,以簡化測試案例的撰寫。
  2. 著手整理 UI checklist 與 UI patterns,降低使用者介面不一致與不方便使用的問題。
  3. 對於全新開發的功能,一律增加一個撰寫 acceptance test cases 的 task,以減少由於對於需求細節不清楚造成自作主張的問題。
  4. 對於所有發現的 bug,一律要先撰寫一個自動化的 unit test 來重新產生該 bug。
當然還可以列出更多的作法,但是由於每個 sprint 的週期通常很短(2-4 週),因此列太多也不太可能一次改善完畢。逐步實施這四點改善方案,觀察一陣子之後再依據實際實施與改善情況來決定後續的改善方法。

***
QA 時間

鄉民甲:UI checklist 與 UI patterns 不是早就應該整理了,為什麼要等到問題發生了才整理?

Teddy:在系統開發之初,通常團隊並不確定這個系統會長成什麼樣子,因此可能無法對於 UI 做很多的 up-front design(事前設計)。隨著系統功能越來越多,product owner 在操作過系統之後才慢慢有一些具體的建議。因此,依照 agile 精神此時整理 UI checklist & patterns 應該算是合理的作法。


鄉民乙:『對於所有發現的 bug,一律撰寫一個自動化的 unit test 來重新產生該 bug』這個作法不是 agile methods 的基本要求嗎?為什麼不在專案一開始就實施呢?

Teddy:專案一開始的時候團隊可能還不熟悉自動化測試,為了讓團隊成員先熟悉 Scrum 框架,所以 Scrum Master 可能會把導入自動化測試這件事排在稍晚的 sprint 中在開始實施,等團隊熟悉 Scrum 框架之後在來考慮自動化測試,以免一次導入太多新的東西團隊成員無法吸收。

***
友藏內心獨白:放下中指,立地改善。

2010年4月3日 星期六

敏捷式例外處理設計 (8):這是你的問題,不是我的問題

04/02 23:21~ 04/03 01:28

最近 Teddy 讀了『別為我解釋印度』這本書,其中有一章關於印度人如何 推卸 釐清責任的敘述,十分有趣,以下節錄幾段:

『Madam, 請聽我說,這是你的問題,不是我的問題』... 我(作者)第一項領教到的便是印度人事事分的清清楚楚的習性,像這樣的話語不斷地出現在我們耳邊...


旅館經理:『Madam, 請聽我說,飛機誤點太久或停飛不是我的問題,那並不是我造成的,如果飛機誤點,我能做的只是請司機再帶你們回來而已,其實那是你們自己的問題!』

作者:『什麼叫飛機停飛是我們的問題?我請你幫我們確認航空公司當日起飛的時間是否有更改,你也告訴我航空公司說一切都和原訂時刻相同,現在你又告訴我如果飛機停飛是我的問題?』


旅館經理:『Madam, 請聽我說,全世界沒有人會知道飛機會不會出問題,會不會誤點或停飛,你要我怎麼向你保證航空公司說當天的飛機沒有問題,到時候飛機真的就一點問題都沒有?』

作者:『所以我應該要花錢坐特別貴的計程車到機場,發現飛機停飛,然後就乖乖等在機場,等到三天後飛機再來?這就是我的問題?』

旅館經理:『Madam, 是的!這就是你會遇上的問題,但是你這個問題我很樂意幫你解決,那就是請計程車司機等在機場外,萬一你們去到機場發現班機時間突然改了,你們就可以再坐計程車回來,您同意嗎?』 

作者:『我開始有點佩服這個經理了...』

***

當程式執行時發生了例外,首要之務就是要找到例外發生的根本原因(root cause),其次,就是要釐清『誰該負責』來處理這個例外狀況。今天要談的是誰該負責的問題。所謂『冤有頭,債有主』,如果找到不該負責的人來處理例外,那麼程式很可能會變得不易理解也不容易維護,弄不好也可能會導致更多的錯誤發生。

舉個例子,假設你要設計一個讓使用者透過網頁輸入個人資料然後將資料儲存到資料庫的功能,其中有一個欄位是輸入『年齡』。我們都知道,一般正常的人類應該是不可能超過 200 歲(『彭祖』和偷吃了長生不老藥的『嫦娥』除外),所以你在資料庫中將年齡這個欄位的長度設為 unsigned short (0-255)。除非科學家發明了長生不老藥,否則 255 應該很夠用了。

你為這個功能設計了四個元件:

  • UI:顯示網頁的程式碼。
  • UserBean:用來將使用者輸入的資料由 Web UI 傳送到資料庫。
  • Servlet:呼叫 UserDAO 將由 UI 端收到的 UserBean 存到資料庫中 。
  • UserDAO (Data Access Object):負責透過 JDBC 或是 OR-Mapping 工具將 UserBean 存到資料庫中 。如果儲存資料發生錯誤,將丟出 exception。

程式開發完畢,很幸運的你找來『彭祖』幫你作測試,『彭祖』在『年齡』這個欄位輸入 888,按下確定送出之後,畫面上看到由資料庫所發出的 exception,內容類似『integer out of range ...』 這一纇的訊息。好了,這個例外,是誰的問題?

  • UI:Madam, 請聽我說,這不是我的問題。誰叫你資料庫欄位設計的太小,改成 unsigned int (0-65535) 不就好了。
  • UserBean:Madam, 請聽我說,很明顯的這不是我的問題,因為我的 setAge() 和 getAge() 接受和傳回的都是 Int,範圍大於 888。
  • Servlet:Madam, 請聽我說,這絕對不是我的問,我只是負責把收到的 UserBean 傳給 UserDAO。
  • UserDAO :Madam, 請聽我說,這肯定不是我的問題。依照規格,年齡欄位最大值就是 255。有人要輸入超過這個數值的資料,當然一定會發生例外,如果不丟出例外才是我的問題。


和這個例子類似的狀況屢見不鮮,只要有寫過資料庫程式的鄉民們應該都會遇到。一般來說 UI 應該依據資料庫可接受值的範圍來做檢查,以避免將不正確或無法接受的資料存到資料庫。問題是,第一個丟出例外的人是 UserDAO,所以,如果你是設計 UserDAO 的人,你如何決定要自行處理這個例外(例如,用一個預設的數值來代替),或是往上丟交給別人處理?個時候就是要學學印度人釐清責任的功力,休息一下,接著看下去。
 
 ***

設計軟體元件(methods, functions, classes, or components)的目的就是要提供某種服務,因此當程式在執行的時候,軟體元件之間的互動關係,便可簡單區分為以下兩者:

  • Client:呼叫其他元件以便獲得某種服務。
  • Supplier:提供服務的元件。

有了 client 和 supplier 的概念之後,要幫軟體系統釐清責任的方法就變得很簡單了,只要幫元件介面撰寫『合約』(contract) 就可以了。合約有兩種 (其實有三種,為了避免 Teddy 打字打到手痛,這邊先看兩種就好),分別是:

  • Precondition:在執行某個軟體元件之前,必須要滿足的條件。當 client 要呼叫 supplier 之前,client 必須要保證定義在該 supplier 的 preconditions 有被滿足,才可以呼叫 supplier (也就是說 client 必須提供執行 supplier 所需的環境)。
  • Postcondition:在執行某個軟體元件之後,必須要滿足的條件。當 client 要呼叫 supplier 之後,supplier 必須要保證定義在該 supplier 的 postconditions 有被滿足 (也就是說 supplier 提供了他所宣稱要提供的服務)。
當程式執行時,如果合約被違反了,系統將會丟出 exceptions。Precondition violation exceptions 表示 client 有 bugs,而 postcondition violation exceptions 則表示 supplier 有 bugs。

故事講到這邊,如果鄉民們還看的懂那麼 Teddy 先給你拍拍手...再回到前面儲存使用者資料的例子,有了合約的概念,UserDAO 的合約大概長成這樣子:

public void saveUser (UserBean aBean)

Precondition: 
   require aBean.getAge() >= 0 && aBean.getAge() <= 255

Postcondition:
   ensure DBUtil.getUser(aBean.getName()).equals(aBean) // 表示資料成功存到資料庫

有了這個合約,如果使用者在 UI 的年齡欄位輸入 888 而 UI 又沒有做檢查的情況下,當 Servlet 呼叫 UserDAO.saveUser() 時便會出現 precondition violation exception。有了合約之後,這是誰的問題就很清楚了。由於 UserDAO 已經明白表示『如果要呼叫我, age 的值一定要介於 0-255』,所以根本也不用考慮是否需要修改資料庫欄位來接受大於 255 的數值(因為違反 preconditions 是 client 的問題,不是 supplier 的問題)。

以此類推,如果 Servlet 和 UserBean 都有類似的合約,那麼當使用者在畫面上的年齡欄位輸入 888,就可以立刻知道這是 UI 的問題,不是別人的問題了。那麼,UI 的合約要怎麼寫?

Precondition:
   require true

Postcondition:
   ensure  A valid UserBean is created and saved in the session

UI 畫面是要讓使用者輸入資料的,所以只要使用者執行這個功能就可以用,因此 precondition 永遠成立(當然你也可以寫成 require a user is logged on 之類的)。既然 UI 的目的是要收集使用者資料,所以在畫面結束之後(假設使用者按下確定送出),要保證能夠產生一個合法的 UserBean 物件並將它存在 session。因此,為了滿足這個 postcondition,很顯然的 UI 的實做就必須要檢查使用者所打的欄位是否正確。也就是說,使用者輸入資料的 validation 要做在 UI 端。

友藏內心獨白:這就是 Design by Contract 的觀念啦!