l

2017年4月21日 星期五

例外處理壞味道:將例外當作控制流程

April 21 01:33~14:12

擷取

 

在〈例外處理壞味道(上)〉與〈例外處理壞味道(下)〉Teddy介紹了以下幾種例外處理壞味道(exception handling bad smells):

  • Return Code(回傳碼)
  • Ignored Checked Exception(忽略受檢例外)
  • Ignored Exception(忽略例外)
  • Unprotected Main Program(未被保護的主程式)
  • Dummy Handler(虛設的處理者)
  • Nested Try Statement(巢狀Try敘述)
  • Spare Handler(備胎)
  • Careless Cleanup(粗心的資源釋放)

昨天在北科上課review學生的作業,看到另一個很常見的例外處理壞味道:例外當作控制流程(using exceptions as control flow)

***

▼下圖是學生所寫的程式碼示意圖,getCompanyName函數首先判斷參數obj是否包含錯誤,如果是則丟出一個Exception並將其內容設為「找不到資料」。在第39行的catch clause中捕捉例外,然後傳回例外物件的getMessage字串。

屏幕截图 2017-04-21 01.48.25

 

問題一

有經驗的鄉民一眼就可以看出來上面這段程式出了什麼問題,首先在第29行丟出例外然後在第41行捕捉例外這種寫法就是典型的將例外作為控制流程,和寫go to是類似的效果。

▼程式可以改成當判斷obj物件包含錯誤的時候,直接傳回 “找不到資料”這個字串就可以了。

屏幕截图 2017-04-21 01.50.45

 

問題二

原本的程式寫法直接捕捉exception物件,這個物件在Java例外物件的階層中僅次於Throwable,捕捉Throwable或Exception這種寫法叫做blanket catch(請參考〈Java的try、catch、finally(1):Java SE 7之前〉)。在原本的程式碼中27~39行的try clause除了29行以外其他地方也可能會丟出例外,所以就算原本的程式碼不介意使用例外作為控制流程,41~43行的例外處理程式(exception handler)也可能因為其他程式發生例外而被觸發。也就是說,當呼叫getCompanyName函數的人收到"找不到資料"這個字串的時候,在原本的寫法當中有可能因為兩個原因所造成:

  • 真的找不到資料。
  • 因為發生例外被誤判為找不到資料。

很顯然地原本的程式邏輯是不正確的。

***

在絕大部分的情況下,例外應該單純用在程式異常狀況,而非因為丟出例外可以具有流程控制的特性而將例外使用在流程控制上。對於例外處理設計有興趣的鄉民們可以參考泰迪軟體的「例外處理設計與重構實作班」課程,或是Teddy所寫的《笑談軟體工程:例外處理設計的逆襲》 。

image

***

友藏內心獨白:程式不是看起來可以動就沒事了。

2017年4月10日 星期一

BDD(23)從規格、程式和測試思考BDD要解決什麼問題

March 31 12:30~13:24

屏幕截图 2017-03-31 12.38.07

 

前情提要

▲在〈三個圓圈(3):規格、程式和測試〉文章中Teddy介紹了上面這張圖的意義,並在文末留下一個問題:

有沒有什麼方法可以讓區域1變得越大越好?指派專人撰寫詳盡的規格書有幫助嗎?Design by Contract有幫助嗎?組cross-functional team有幫助嗎?BDD/TDD有幫助嗎?這些都是值得思考的問題。

今天從這張圖來思考BDD如何協助團隊把區域1變大。

***

瀑布式流程

在談論BDD之前先看看傳統瀑布式(Waterfall)開發方法,如下圖所示,在瀑布式流程中一開始是撰寫需求文件,也就是定義規格。雖然在定義規格的同時也應該一併把驗證方式準備好,但實務上很少有團隊可以做到,能把規格書在期限內生出來就已經很偷笑了(第一個圈圈)。

屏幕截图 2017-03-31 13.10.17

 

有了規格書之後,當然要請客戶畫押,發誓不再修改規格。之後再依據這份規格開始寫程式(第二個圈圈)。雖著時間演進程式越寫越多,但此時不知為何規格也跟著改變與膨脹(第三個圈圈)。好不容易等程式寫得差不多了,雖然沒有涵蓋全部的規格,但截止時間快到了,只好硬著頭皮先丟給QA部門測試(第四個圈圈),並且利用測試時間再繼續偷偷開發未完成的功能(第五~六個圈圈)。

以上所說都還算是負責任的情況,有時候趕著上市連最後的測試時間都沒有,直接丟給客戶請客戶幫忙測。在這種情況下,規格、程式和測試三者重疊的區域要大到哪裡去說真的不太容易。

***

測試後行的敏捷開發

敏捷開發團隊不須把所有規格都定義好之後才可以開工,以Scrum為例子product backlog有足夠接下來1~2 sprints的user story就可以了(第一個圈不需要很大)。下圖中的黑色圈表示規格,紅色圈表示程式,綠色圈表示測試。對於沒有導入BDD/TDD的敏捷開發團隊,開發順序先寫production code再寫test code,所以先有紅色圈再有綠色圈。基本上程式的範圍由user story的驗收條件所規範,而測試案例也可以參考這個驗收條件來撰寫。加上user story的粒度相較於傳統規格書的一個功能要小很多,因此比較容易讓規格、程式、測試三者重疊。

屏幕截图 2017-03-31 12.54.04

 

但是,有不少敏捷團隊雖然有事後「補寫」測試案例,但也常常因為sprint時間的限制只把程式寫完而來不及讓綠色圈與紅色圈重疊(測試不完整)。長期累積下來規格、程式、測試三者重疊的區域就大幅減低。

***

測試先行的敏捷開發

測試先行的作法利用測試案例來劃定規格的範圍,接著再撰寫程式。只要程式通過測試案例,既代表滿足規格所需。這樣的順序「理論上」可以大大地增加規格、程式、測試重疊的範圍。這裡有一個很重要的前提,使用測試案例所規範的需求要具備代表性,也就是《Specification By Example》書中所說的「關鍵範例(key example)」,否則一開始測試和規格重疊的部分就很小的話,後續依據測試所寫出來的程式也不可能涵蓋原本的規格。

屏幕截图 2017-03-31 13.03.32

***

結論

許多軟體開發方法都嘗試著增加規格、程式、與測試的重疊範圍。BDD透過兩個關鍵的做法來達到這個目的:

  • 透過協作來探索規格的範例。
  • 透過測試案例作為規格的代表,自動驗證規格與程式是否一致。

***

友藏內心獨白:BDD從流程上避免三者發散。

2017年4月6日 星期四

BDD(22)狀態與行為測試

March 30 18:15~19:23

屏幕截图 2017-03-30 18.25.00

 

測試術語

採用BDD開發軟體,首先你先寫一個失敗的驗收測試規範系統的行為與範圍,接著開發production code讓驗收測試通過。在寫production code的時候如果驗收測試無法清楚表達實作程式的行為,則可以進一步撰寫一個失敗的單元測試,然後再用最簡單、最直接、最無腦的方式來撰寫production code。等待單元測試通過之後,再拿出Refactoring(重構)這一把「小刀」,將剛剛快速撰寫的production code修剪一番,朝向clean code目標邁進;後者就是傳統上大家所理解的TDD。

無論是先寫一個失敗的驗收測試,或是失敗的單元測試,這個「待測物」(System Under Test;SUT)通常需要借助其他物件的幫忙來達成任務,例如資料存取物件(OAD)相依於資料庫,網路應用程式需要Socket物件來建立網路連線。這些相依物件稱為Depended-on component(DOC)Collaborator

一個SUT的所有DOC集合稱為這個SUT的Context。為了提高SUT的重複使用性與可測性,在設計上希望SUT不要自己管理Context,而是由外部將它所需要的Context傳入,這也就是Dependency Injection(DI)的作法。

以上名詞解釋完畢,可以回到BDD身上。開發人員撰寫這個「失敗的測試案例」,可以從兩個角度切入,分別是驗證狀態驗證行為

  • State-based testing:測試程式提供SUT所需的Context,然後呼叫SUT,最後驗證SUT的狀態是否正確。例如,傳入5給判斷質數的函數,傳回結果為true,如果傳入8則結果應為false。又例如push一筆資料到一個stack物件,則stack的容量會加一。以上做法都是藉由驗證SUT的狀態來判斷SUT的實作是否正確,有種黑箱測試的味道。
  • Behavior-based testing:測試程式根本不管SUT的執行結果與狀態是否正確,只關心SUT與DOC的互動是否有如預期。聽起來有點玄,但在某些時候卻必須做這種方式的驗證。例如在Observer設計模式中,當subject狀態改變會自動呼叫observer的update函數。如果想驗證的重點是Observer設計模式是否實作正確,則測試者根本不管也不關心subject的business logic到底是在做什麼東東,只要確定當它狀態改變的時候真的會呼叫一次observer的update函數就可以了。這種測試白箱的味道很重,因為測試者必須知道SUT實作的邏輯才有可能知道要如何驗證SUT如何與DOC互動。

***

測試替身

講了這麼多還是沒提到和BDD有什麼關係。當開發人員要讓失敗的測試案例通過的時候,這時候SUT與DOC很可能都還沒產生(因為production code還沒寫啊),如果一口氣要把SUT與DOC全部寫好讓測試通過,可能需要花很長的時間。因此為了讓SUT可以在比較短的時間通過測試,開發人員可以採用Test Double(測試替身)的技巧「欺騙騙」SUT,先專注於SUT的開發而暫緩DOC的實質內容,又可以讓測試案例通過。

因為測試方式有狀態測試與行為測試這兩種,所以測試替身也可以分成這兩大類。Dummy、Stub、Fake屬於狀態測試,而Spy和Mock則屬於行為測試。

Teddy習慣用Dummy、Stub或Fake來做狀態測試,較少用Spy或Mock,總覺得Mock Object Library,例如mockito這種工具用「設定期望值」的方式來做行為測試,程式碼不是那麼容易閱讀,過一陣子沒看還要想一下才知道在驗證什麼。另一方面因為mock object library具有「純屬虛構」的能力,有可能真正的production code行為已經改變但使用mock object library所撰寫的測試案例還是通過,這也是有點傷腦筋的地方。

▼用mockito測試Command pattern

屏幕截图 2017-03-31 10.22.05

 

但是mock object library也有它的好處,尤其在做BDD/TDD的時候它可以讓你比較快速的採用piece by piece的方式完成SUT而不用先把DOC用「老師傅手工打造」的方式手刻一個假的版本出來。對於行為有一定穩定度的程式,例如最常見的就是Observer設計模式,用mock object library會比手刻DOC簡單很多。

***

無論是使用哪種測試替身,程式碼終究還是寫給人看的。工具本身並無對錯,在合適的時機下使用合適的工具需要由人來判斷,很多時候如何判斷時機才是最難的點。

***

友藏內心獨白:Mock到最後連開發人員都被騙了。

 

延伸閱讀

2017年4月4日 星期二

【敏捷開發懶人包:物件導向技能】五月份平日班

March 31 09:00~09:35

擷取

 

敏捷開發懶人包:物件導向技能」已經開了三梯次,課程設定目標是希望鄉民們在找工作面試的時候,如果面試官問到物件導向技術方面的問題,能夠具備輕鬆回答的能力。上課方式第一次規劃以講解為主,但沒想到幾次下來增加了越來越多的活動、討論、與練習,排定的四小時上課時間不夠用。這次把課程時間調整成一日班,可以安排更完整的練習與討論活動。

課程內容也依據學員的回饋每次都稍加調整,對於下列人士特別有效:

  • 寫了多年的C程式,突然被叫去學Java、C#或是開發App,不知道如何著手用物件導向觀念作設計。
  • 寫了多年物件導向程式,但不確定自己是否用正確的方法使用它。
  • 想知道有沒有比較好的方式可以設計物件介面與分配責任。
  • 軟體設計如何有彈性地應付改變?
  • 物件導向分析與設計到底在講什麼東東?
  • 我沒有時間與耐心慢慢學會以上這幾個問題。

 

▼課程照片

屏幕截图 2017-03-31 09.54.10

 

Teddy挑選以下幾個最常使用的重要主題加以介紹,快速幫學員打底,補充身體所缺乏的「物件導向養分」:

  • 物件導向基礎觀念
    • 封裝、多型繼承
    • 耦合、內聚
    • 介面、委託、聚合
    • 物件導向與程序導向之優缺點比較
  • 依合約設計(Design By Contract)
    • 為什麼防衛式程式設計不好?
    • 前置條件、後置條件、類別不變量
    • 違反合約:例外處理機制
    • 合約與繼承
    • 依合約設計(DBC)與測試驅動開發(TDD)比較
  • 物件導向設計原則這樣聽就懂了
    • S.O.L.I.D.五大原則
  • 物件導向分析與設計
    • 問題敘述
    • 環境圖
    • 分析模型
    • 設計模型
  • 實例討論

上課日5月16日(週二) 09:30-14:30,共六小時。

image

***

友藏內心獨白:可快補充不足的物件導向能量。

2017年4月3日 星期一

說故事的技巧:Yes, And

March 31 08:23~08:54

屏幕截图 2017-03-31 08.51.48

▲畫面節錄自Google搜尋結果

 

前幾天和Erica聊天,他說在一門即興表演的課程中學到一種講故事的技巧:YesAnd

Yes就是把別人說過的話用不同的方式再講一次,例如:「小紅帽被大野狼吃掉了;大野狼吃了小紅帽」,而And則是在原本的情節中加入新的內容,例如:「小紅帽被大野狼吃掉了,大野狼穿了小紅帽的衣服,假裝成小紅帽打算連老奶奶一起吃掉。

後來聊到在臉書上許多朋友分享的資訊,也可以分成Yes、And這兩種。Yes派的擅長不斷地轉述別人講過的話、轉貼網路上的文章,以分享之名行刷存在感之實,如果能意外造成「自己也好棒棒」的形象那就更好了。And派的也會轉述與轉貼,在刷存在感的同時還會上加幾句自己的看法,算是盡到一點點「資訊加工」的責任。

***

Teddy覺得Yes,And這種看法很有趣,也可以應用於學習之上。有些人在上完課之後會不斷重複上課中所聽到的「洗腦口號 名言佳句」,但腦袋中的知識只是上課教材的子集合而已,並沒有舉一反三的能力也無後續的自學行為,很顯然是採用Yes的學習方法。也有人上完課之後引發後續的一連串學習行為,所學範圍已大大超出原本上課的內容,這是And的表現。

不少人為了證照、口號、安全感而學習,Yes + Yes + Yes …,不管多少個Yes,如果沒有後續的And,就好像鸚鵡一樣,只是學人講話的音調,就算再多學幾句話,鸚鵡還是鸚鵡,不可能變成人。

你會想跟鸚鵡說話嗎?一開始覺得有趣,看透了之後就乏味了。

***

友藏內心獨白:要平衡Yes與And。

2017年3月31日 星期五

5月【例外處理設計與重構】平日班

March 31 07:40~08:16

螢幕快照 2013-09-13 下午9.07.53

▲看到這樣的網路銀行畫面用起來心裡會不會毛毛地?

 

有一句閩南語俗話是這樣說的:「生吃都不夠,哪還有多的可以曬乾?」這句話很確切的點出例外處理的處境—「開發正常功能的時間都不夠,哪還有多餘的時間去處理例外狀況?

沒有多餘的食物拿來曬乾不打緊,但軟體系統沒有好好地處理例外狀況卻是很嚴重的問題。不穩定的系統輕則無法提供正常服務給使用者,重則造成使用者時間、金錢甚至是生命安全的危害。強調使用者體驗的今日,系統穩定度不良的問題,絕對不是請使用者重開機或是不斷地重新載入(reload)網頁就可以輕鬆帶過

螢幕快照 2014-01-02 下午11.48.05

▲Facebook錯誤畫面,大拇指受傷比讚就不好看了啊XD

***

身為專業軟體開發人員的各位鄉民,從小到大一定學過各式各樣的軟體設計技術與方法。從最基礎的程式語言、資料結構與演算法,到物件導向分析與設計、設計模式、軟體架構,以及各種敏捷開發實務做法,包含自動化測試、測試驅動開發、行為驅動開發、持續整合、敏捷設計原則等。以上,所有的大師費盡一生心力,都在告訴各位一件事:「如何設計軟體的光明面,也就是『正常行為』(normal behavior)」。而Teddy要告訴鄉民們,如何對付軟體的黑暗面,也就是『異常行為』(abnormal behavior)。唯有正常與異常的行為「陰陽調和」,軟體才可以提供正常且穩定的服務給客戶。

***

例外處理設計與重構實作班】專治例外處理疑難雜症,適合以下人士:

  • 經常因為系統當機,而被主管、老闆與客戶痛罵者…Orz
  • 對設計高強健度軟體系統有興趣者。
  • 想深入並徹底了解例外處理設計者。
  • 因為例外處理不良導致系統一直當機者。
  • 想知道如何在敏捷開發中,以逐步成長的方式來增進系統強健度者
  • 想開發出穩定的軟體,借此賺大錢者。

***

課程介紹與報名網址在此【看板方法與精實開發實作班】,上課日期2017年5月2、3日(二、三)。

image

***

友藏內心獨白:想成為絕地武士須先克服黑暗力量。

2017年3月29日 星期三

BDD(21)從測試金字塔看BDD的自動化驗收測試

March 28 23:40~24:00; March 29 00:00~00:58

屏幕截图 2017-03-29 00.57.19

▲畫面節錄自Google搜尋結果

 

前輩的測試金字塔

在〈BDD(18)這是一個End-To-End的Scenario嗎?〉、〈BDD(19)幫開發票功能加上使用者介面的三種方法〉和〈BDD(20)使用者介面細節放哪裡〉Teddy花了三集討論了使用者介面和BDD的關係,故事還沒結束,今天從「測試金字塔」(Test Pyramid)的角度來思考這個問題。測試金字塔的概念由Mike Cohn描述在《Succeeding with Agile》書中,多年來已經有各方人馬提供各自版本的測試金字塔。

▼Mike Cohn原始版本。

屏幕截图 2017-03-28 23.52.27

 

▼下圖節錄自Martin Fowler的版本,左邊用烏龜和兔子用來代表不同類型測試執行速度快慢,右邊用錢來代表對使用者的價值,UI測試的價值最高,單元測試的價值最低(因為使用這看不到)。

屏幕截图 2017-03-28 23.54.50

 

▼下圖取自agilefaqs網站的〈Inverting the Testing Pyramid〉文章,左手邊為傳統專案中的測試金字塔,大部分為人工手動測試,其次為自動化GUI測試,再來則是整合測試,單元測試所佔比例最少。但這是不健康的現象,因為占比最多的人工測試與自動化GUI測試的執行很耗時,且維護成本高,找到問題之後debug的時間也很長。執行速度最快,且最容易避免系統缺陷的單元測試反而最少,所以應該把左手邊的測試金字塔反轉過來,變成右手邊這樣。

image08

 

上圖的測試種類分得十分詳細,而且還包含每種測試所佔的比例。基本上從Biz Logic Acceptance Tests到End to End Flow Tests之間的測試都可以視為傳統軟體測試所說的整合測試(Integration Tests),或對應到Mike Cohn版本的Service測試。

***

Teddy的測試金字塔

▼綜合上述說明,加上網路上找到的資料以及這一陣子讀的書,Teddy把測試金字塔畫成下圖。
屏幕截图 2017-03-29 00.27.55

 

首先看到中間的金字塔本體,一樣是三大層,Teddy把中間層叫做驗收測試(Acceptance Test)原本Teddy是採用傳統軟體測試中整合測試這個名稱,但後來想一想,從「目標導向」的角度思考,這中間層的整合測試,其目的應該是要「驗收」一群軟體物件或元件之間的協作是否如預期,所以改成驗收測試覺得目標比較明確。

圖的右方把驗收測試展開之後,是沿用〈Inverting the Testing Pyramid〉的分類,驗收測試細分之後包含商業邏輯驗收測試、傳統的整合測試、工作流程的驗收測試、以及在GUI下一層的End-To-End流程驗收測試。用這種方法分類,和BDD的流程與工具就可以配合得很好。為什麼,請看以下說明。

圖的左邊是從工具觀點來看金字塔的三層,最底層是單元測試,可以用xUnit系列工具或Spock與RSpec。往上一層驗收測試層,這一層的最頂層「End-To-End流程驗收測試」,可以用BDD工具像是Cucumber或Fitness來開發,其他下層的測試則可以沿用單元測試工具。 這樣的對應關係恰好是Teddy在〈BDD(18)這是一個End-To-End的Scenario嗎?〉文章中所討論內容,一個位於GUI下一層的End-To-End流程測試,這是一種驗收測試,用來驗收Scenario(或 User Story)的測試。

至於最頂層的GUI測試,通常是從使用者的角度所執行的測試。因為自動化GUI測試建置、維護與執行的成本高,為了減少這類測試案例的數量,在設計上應該考量使用者最關注的系統重要功能開始撰寫。自動化的工具如果是網頁則可以採用Selenium,桌面程式與手機APP也有相對應的GUI自動化測試工具。

在這裡要提醒一點,上圖中的驗收測試(Acceptance Test)與使用者驗收測試(User Acceptance Test;UAT)不同,UAT需透過GUI來執行,而這裡的驗收測試則是採用「目標導向」的觀點對於傳統整合測試的另一種稱呼。

***

結論

如果採用Teddy所介紹的測試三角形觀點,則BDD的驗收測試就可以「名正言順」的和GUI脫鉤,而在需要掛鉤的時候參考〈BDD(19)幫開發票功能加上使用者介面的三種方法〉文章中介紹的方法。在此Teddy要強調這只是Teddy自己的觀點,並不是說BDD一定採取這種做法。實際上在《Growing Object-Oriented Software, Guided by Tests》(GOOS)書中就主張驗收測試一定要跨越應用程式的每一層,也就是需要從GUI開始進入系統,如此方可達到兩種不同的End-To-End效果:

  • User Story
  • Process

但Teddy認為就算是採用本文建議的作法,也還是可以吸納GOOS書中的好建議,讓BDD的Scenario串起End-To-End Process,只不過Scenario的入口點採用「可插入」(Pluggable)的方式,不一定是哪一種特定的UI,可以是Web UI、Console UI、手機UI。因此,稍微延緩一下定義UI的時間點對於End-To-End Process的建立所造成的負面影響應該可以想辦法降到最低。

***

友藏內心獨白:感覺快要可以寫一篇論文了…Orz。