天天看點

《需求設計:建構使用者想要和需要的産品》——3.3 用例

本節書摘來自華章計算機《需求設計:建構使用者想要和需要的産品》一書中的第3章,第3.3節,作者: [英] 克裡斯·布裡頓(chris britton) 更多章節内容可以通路雲栖社群“華章計算機”公衆号檢視。

在前面幾節之中,筆者對靈活開發做了批評。除了靈活開發,目前還有一種比較流行的做法,那就是通過編寫用例來捕獲需求。這一節要讨論用例與情境設計之間的差別。

用例,本質上是用一系列帶有序号的步驟,來對場景進行簡單的文字描述,就此而言,我們可以在各種詳略不同的層面上撰寫用例。此外,還有一些用例圖可以展示每位參與者所使用的用例,以及用例之間的擴充關系,這裡所說的參與者(actor),與情境設計之中的使用者組有些相似。筆者稍後會讨論什麼叫做“擴充”。用例圖與筆者所說的情境圖看起來很像,隻是它沒有繪制資料表。在本書的第6章裡面,筆者要提出一種對任務進行文字描述的辦法,這種描述看上去與用例所做的描述很像。用例與任務描述的主要差別有兩點:第一,任務描述會專門指出該任務所要通路的資料;第二,筆者會從陳述(narrative)與規則(rule)這兩個方面來描述某項任務需要完成的事情,而用例則隻有陳述這一個方面。總之,用例與任務描述之間,必然有着相當多的共性,大家仔細想想,假如它們之間沒有這麼多的共性,那才是一件應該感到奇怪的事情。

筆者對用例有一些嚴重的擔憂:

用例缺乏正規的原子概念。

用例的設計層次不夠清晰。

用例比較模糊,甚至對于使用它們的人來說,也是如此。

以用例來描述的大型應用程式,是很難了解的。

用例不能為分析提供很好的支援,因而也無法很好地支撐工程化的設計。

接下來就分别讨論這些缺點。

3.3.1 原子性

用例可以用來描述任務,也就是說,可以用來描述某人在某時所做的某件事,但這并不意味着用例隻能描述一項任務。我們可以把相關的兩項任務合起來描述到同一個用例裡面。例如,我們經常會碰到那種需要填寫較長表單(如報稅表)的應用程式,這種應用程式會提供一個選項,使得使用者可以儲存目前的更新并于稍後繼續填表,填好表單之後,使用者可以送出并等待後續的處理。如果以用例來描述,那麼填寫表單這一操作,就會視為一個用例,但是筆者卻認為,這裡面其實有兩種任務:

1.開始填表,并且有可能填寫完畢。

2.繼續填表,并且有可能填寫完畢。

這兩項任務的絕大部分邏輯,自然是相同的,是以,筆者會把這些邏輯提取到一個共用的任務片段之中,這樣就無需重複撰寫這部分内容了。至于是否應該把二者合并到同一個用例裡面,則是個很難說明白的問題,因為據筆者所知,用例方面的書籍并沒有提到原子性這一概念,因而我們沒辦法說清楚這個問題。

那麼,筆者為什麼一定要把這兩項任務分開看待,而不贊成将其寫到同一個用例裡面呢?這是因為我們必須對兩者之間的缺口進行設計。即使面對這個非常簡單的例子,我們也依然要確定以下兩點:

使用者必須能夠看到目前還有哪些表單尚未填完。

有些表單可能永遠都不會填完。是以,設計者必須決定是把這些沒填完的表單一直放在那裡,還是将其删除,如果要删除,那麼應該隔多久再删除。

此外,我們還必須回答下面幾個問題:

如果一張表單是由某人開始填寫的,那麼是不是必須由這個人來填完這張表單?如果是,那怎麼保證這一點?如果不是,那又怎樣使其他使用者能夠看到沒有填完的表單?

如果應用程式出現故障,或使用者在填表的過程中離開,那麼,還沒有完全填好的表單是否應該自動儲存?

使用者在開始填寫新的表單之前,是不是必須把原來還沒填完的那張表單填好?

如果決定把這兩個任務合并到同一個用例裡面,那麼你就可以(注意,我隻是說可以,不是說必須)把剛才提出的那些問題暫時掩蓋起來,但是那些問題最終還是會出現,而且有可能是在已經編寫了一些實作代碼之後,才突然冒出來的。

3.3.2 設計層次不明确

我們再舉一個例子,這個例子會引發更加複雜的問題。假設要撰寫這樣的用例:它的參與者是送貨司機,用例本身用來描述揀選包裹、投遞包裹并且使客戶簽收包裹這一過程。那麼,這其中還是會有兩項任務,一項是把包裹配置設定給送貨司機,另一項是客戶簽收該包裹。用例的撰寫者可能會把這兩項任務寫到同一個用例裡面。現在假設送貨的車輛出現了故障。此時,該包裹應該會指派給另外一位送貨司機去派送,更倒黴的情況是,該包裹會取消派送。然而問題在于,車輛出現故障這一狀況,應該由哪個任務來記錄呢?可能應該有一個專門用來記錄車輛停運的任務。而且還應該再安排一項任務,用來擷取包裹,并決定如何處置該包裹。這樣就越來越複雜了。而且還有可能會有一項業務流程用來顯示包裹的派送情況,另外一套流程用來對這些派送車輛進行管理。當某輛車發生故障時,這兩項流程就要進行互動。理論上我們可以把這些事項全都寫到一個用例裡面,但筆者不建議這樣做,因為這會令用例變得特别複雜(然而有人告訴我,一個用例通常要用好幾頁紙才能寫完,是以我懷疑實際工作中可能真的有人把這些全都寫到一個用例裡面)。由于本例還涉及兩條業務流程,是以,我們可能需要用兩個用例來記錄這個場景,每個用例都對應于其中的一條流程,這兩個用例之間可能會以微妙的方式溝通。要想在冗長而複雜的用例之中對溝通情況進行分析,是相當困難的。實際上,為了進行分析,我們首先必須把用例拆分成多個單元,使得每個單元都用來表示某人于某時所做的某件事。換句話說,要想了解複雜的場景,我們首先還是得确定其中的各項任務。

通過這個例子,大家可以看出,用例的層次感是比較模糊的,我們不清楚它究竟是在流程層面進行描述,還是在任務層面進行描述。在剛才那兩個例子裡面,我們都必須對流程進行一些描述,才能将兩項或多項任務聯系起來。

實際上,用例不僅會把流程層面與任務層面混為一談,而且還會令任務層面與邏輯使用者界面的設計之間,無法形成清晰的界限。

公允地說,某些用例專家明确表示,他們并不會在用例之中指定使用者界面。筆者對這種說法的了解是,他們不僅不會在用例中指定實際的螢幕布局,而且也不會在其中包含邏輯使用者界面。我強烈懷疑有沒有人會遵守這條規則,因為業界總是鼓勵設計者以場景的角度來思考問題,于是他們自然就會想象有一位使用者正坐在計算機前面。而且,就連這些專家自己所舉的例子,通常也和他們提倡的原則有所出入。例如,筆者曾經好幾次看到有人把登入也包含在用例的各個步驟之中。如果改用情境驅動開發,那麼登入根本就不應該包含在使用者界面的設計裡面,也更不會包含在情境設計之中,它隻是技術設計裡面的一個部分。

之是以要在任務設計與使用者界面設計之間畫出清晰的界限,是因為我們在設計的過程中,總是應該由此來厘清自己所要達成的目标與達成該目标所用的方法。如果忽視“自己所要達成的目标”而單單去談論方法,那就容易錯過某些重點,反之,若忽視“達成該目标所用的方法”而單單去談論目标,則容易錯過一些更好的設計方案。我們尤其不應該跳過對使用者界面所做的設計,而直接根據用例去編寫程式,因為這樣做會錯過很多的機會,使我們無法發現更好的設計:

把使用者界面設計視為一個整體,可以幫助應用程式變得更加易于使用。使用者界面設計可以對任務設計做出重大的轉變,本書第8章将會讨論這個問題。

對使用者界面設計進行審閱,就相當于同時對情境設計進行檢查。

使用者界面設計可以提升程式員的程式設計速度。如果沒有使用者界面設計,那麼程式員就需要在缺乏相關資訊的情況下,花很多時間在腦中構想使用者界面,而且在發現自己做錯了之後,又要花很多時間去重做,有了使用者界面設計之後,這些時間就可以縮短,而且項目經理也更容易找到合适的程式員來解決那些更為困難的程式設計問題。

使用者界面設計之中還包含一種資訊,那就是詳細的資料字段,它們沒有出現在情境設計之中,因為情境設計更加簡潔。假如以用例來表達這些字段,那就必須将其寫入用例文檔,這會令文檔變得冗長而啰嗦。

我們一定要清楚自己目前正在哪個層面工作,而且一定要防止這些層面彼此混同。每一層都有它自身的完整度限制條件,并且都有一套各自的恢複技術。在資料庫事務層面,其恢複工作是由資料庫系統來處理的,而在任務層面,我們則會以各種手段進行恢複,使其看起來與資料庫事務一樣,具備原子性。

3.3.3 用例本身比較模糊

對用例的解釋,已經成了一項專門的學問。很多書和很多專家都在談這個問題,而且用例也有很多種不同的風格。筆者懷疑,在業界剛開始撰寫用例的那段時間裡,專家們或許就已經發現,這些用例寫得相當糟糕,于是,他們覺得應該制定一些規則與指針。其實用例本質上是很簡單的,它就是一份包含各種動作的有序清單而已,然而如此簡單的用例卻需要用這麼多的專業技巧才能寫好,這是相當奇怪的。

有一些情況令人感到困惑。最為突出的一種情形,是把用例運用在戰略層面,這種用例稱為戰略用例。某網站有一條評論[16]說道:“我都寫這麼多年用例,但還是不太能了解系統範圍(system scope)與戰略範圍(strategic scope)之間有什麼微妙的差別!”筆者自己也很難想象,如何才能把戰略用一系列步驟定義出來。用例的表達形式,重在描述“怎樣做”(how),而戰略思想則重在描述“做什麼”(what)。是以,筆者并不推薦以用例來确定戰略方面的内容。

用例的另外一項特性也容易令人誤解,那就是它的“擴充”或“包含”能力。這兩種能力都用來捕獲共用的邏輯,使得這些邏輯隻需要寫一次就好。“包含”(include)指的是某用例納入了另外一個用例之中的某個片段,如果兩個或多個用例有某些步驟是相同的,那麼這樣做就比較合理。“擴充”(extend)說的是某用例與另外一個用例相似,然而在某種程度上還有着微妙的差別。“包含”是一種局限性較大的用法,因為你必須把那個片段全部包括進來,而擴充則允許我們在很多地方進行修改。另外,要對用例進行擴充,就必須針對整個用例,而不能僅僅針對其中的片段。實際工作中,我們必須謹慎地考慮自己到底是應該“包含”,還是應該“擴充”。我們經常會急着擴充某個用例,而沒有考慮這樣做所帶來的各種後果。

(很多參考資料裡面所舉的例子,也不能夠較好地消除這種困惑。例如,筆者于2015年1月在維基百科上面搜尋“use case”詞條,然後看到一張圖(當時是如此,現在可能有所變化),它說“吃食物”(eat food)與“喝酒”(drink wine)這兩個用例之間是擴充的關系,筆者覺得這樣舉例并沒有太大幫助,而且還顯得有些奇怪。)

用例之是以令人困惑,其主要原因還在于3.3.2節所說的那個因素,也就是它會把各種層面的設計混同起來。

筆者現在要說一句會惹很多人讨厭的話:我覺得用例有一個根本問題,那就是缺乏一套良好的理論。這意味着我們很難精确地定義出自己必須要做或者絕對不能做的事情,而且它也缺乏一套使我們能夠判斷出正誤的概念。

3.3.4 大型的用例文檔難以了解

以用例來描述大型的應用程式,會産生出相當枯燥的文檔。撰寫用例的人,在制定并記錄這些用例(如有100~1000個用例)的過程中,自然會對應用程式有一個很好的了解,然而需求文檔還應該有一項功能,那就是要把即将制作的這款應用程式解釋給利益相關者聽,使其能夠驗證這套設計方案是否正确,并給我們提供良好的回報。但是,龐大而枯燥的文檔,并不能夠較好地實作這項功能。

3.3.5 用例對工程化的設計起不到幫助作用

之是以說用例無法給工程化的設計提供支援,其根本原因就在于它們很難加以分析。

我們先停下來想想,一份易于分析的設計方案,應該具備哪些特征:

它必須能夠劃分成一些不太複雜的塊。這不僅可以把設計方案分解到多個功能領域之中,而且還有助于我們據此建構設計體系。用例,是不具備這個特征的,所有的用例,都位于同一層面。

它必須把所有的依賴關系都記錄下來。把設計方案劃分成元件的時候,我們必須完全確定元件之間的所有關系都得到了記錄。任務之間的很多依賴關系,都位于他們所共享的資料之中,而用例并沒有把這一點明确記錄下來。我們必須閱讀相關的文字,才能将其找出來。

它一定要有原子性這一概念。我們可以把元件說成黑盒,因為我們隻知道每個元件所做的事情,而不知道它是如何完成這件事的。對于任何一種設計來說,我們都必須假設其中的元件所輸出的結果,其數量是固定且可以預知的。我們隻能對自己所見的内容進行分析。如果元件有着某種隐藏的輸出結果,那麼任何一種分析都沒有辦法揭示它。

它必須能夠在解決方案與需求之間輕松地建立關聯,換句話說,也就是能夠回答“需求x是怎樣實作的?”這一問題。在設計方案的最頂層,我們必須要能夠把它與基本的業務需求聯系起來。如果為業務需求提供支撐的用例有100~1000個,那麼筆者覺得這一點是很難保證的,即便我們假設基本的業務需求确實寫在了其中的某個地方,也依然不容易做到這一點。

它應該要有一套易于分析的規則,而不是一些步驟或松散的描述。我們可以利用數學技巧來分析規則。就算不做數學分析,通常也能夠看出互相之間明顯沖突的那些規則。然而步驟分析起來就沒有規則那麼容易了,因為我們總是要面對一個問題,那就是:“這些步驟之間的順序到底是至關重要的,還是随意安排的?”

簡單地說,用例是用來幫助我們收集需求的,而不是用來幫助我們分析需求的。

3.3.6 小結

總之,用例會使業務設計變得困難。

在寫這本書的時候,有些人告訴我說他們很享受撰寫用例的過程,并且認為用例挺好的。筆者對此的看法是:這些撰寫用例的人之是以認為用例不錯,是因為他們為了撰寫用例,必須要思考應用程式所處的場景,而這種思考,無疑會幫助他們更好地了解應用程式所應完成的事情。然而對于那些必須把用例從頭到尾讀一遍的人來說,這種用例恐怕就不會讨人喜歡了吧。

此外,用例還有一個相當微妙的問題,那就是:它并不能為項目的估算成本工作打下堅實的基礎。這個問題3.4節再談。

繼續閱讀