軟體設計方法論:狀態機之生老病死
又一篇副標題難想的系列文章,更
「前言」
有鑒於新人越來越多,每次都要重講一次公司內的設計方法,其實還挺麻煩的,而且也不是每個新人的吸收消化能力都很夠,在描述抽象概念的東西時,或多或少有人會掉包。為此,整理出一下筆記,至少之後的人們在討論或教學時,或多或少有個依據可以不斷消化。
「先定義好狀態機」
狀態機是用來建模一套完整系統的過程,裡面存在著系統中可以描述的事件(event)與狀態(state)。當我們描述事件或狀態時,會有 99% 的內容會落在所描述的狀態機當中。更具體來說,狀態機主要是描述生命週期內所經歷的狀態過程,以及如何反應事件。
「狀態、事件傻傻分不清楚」
本段落中,我們將區分出狀態、事件的重要性,以及如何定義出好的狀態、事件。
先說重點,中文口語上在講人的一生中的生、老、病、死,為四個事件,不是四個狀態。回憶一下,我們會說誰又老了一歲,誰在哪一年被生下來。邏輯上知道說我不可能生了之後,又生出來。不可能從子宮滑出來之後,又再度滑出來,這是不可能的,同理,我們也知道說死了不可以再死,這時候你可能想,可是我可以老了又老呀,但是這還不夠,你看,我們會說又老了一歲,更進一步來說,老是個發生的過程,生病也是一樣,你可以得了感冒之後,又得了癌症。所以我們會說生老病死是四個事件,而非四個狀態。
結論來講,我們可以確定出說事件最好是動詞,由於中文的抽象含義較高,筆者強烈建議使用英文做思索,同時,我們知道狀態本身最好是名詞。
「設計流程」
這段落中,我們將介紹設計一套設計出狀態機的標準作業流程。
先說結論,設計狀態機時,要先確立命題,接著找出在這個命題下所存在的事件,為了方便進行抽象化思考,必須得把事件進行編號化,藉此找出可能未發現的情境故事,最後也是最重要的一個步驟,每從一個狀態出發時,每一次都需要把所有的事件標列上去,不能偷懶。
以下筆者將使用圖表配合和文字說明方式進行講解:
步驟一、命題
這個步驟時,先確定要解決的問題範圍,像我們現在進行的話題好了,我們正在討論人的ㄧ生,因此先把命題寫在最上方,永遠不讓自己忘記目前現在當下正在處理哪個範圍的問題,像是深淵當中的繩索一般,牢牢抓緊著,不會迷失在思考的旋窩當中。
步驟二、找出事件
目前命題為人的一生,在這個命題之下,我們曉得最為抽象的四個事件為生、老、病、死,一個人從出生到死亡會經歷的四個過程。於是在上頭寫上事件:生、老、病、死。
更進一步來說,為了讓我們可以隨時保持抽象思維在處理事件與狀態之間的轉換,必須將事件進行編號化,於是變成1(生)、2(老)、3(病)、4(死)。
步驟三、找出狀態
一開始先從起始點(init)出發,同時列出所有事件,一筆一畫把箭頭化出去,並且填上事件編號 1,2,3,4,在事件末端各自填上一個狀態,先暫時用狀態 A,狀態 B,狀態 C,狀態 D。
接著開始思索一下,接受這個事件之後,這個狀態本身有沒有含義,一錠都要從頭開始想起,絕對不要亂跳,或是省略。人的ㄧ生,事件 1 ,生是不是有意義的,有,我們保留。事件2,老是不是有意義的,還沒有生,不能老,所以無意義。事件3,病是不是有意義,還沒有生,所以無意義。事件4,死是不是有意義,都還沒生出來怎麼死啦,所以無意義。
於是乎,將事件1還有狀態A保留,事件2,3,4 丟進去無意義當中。就狀態機角度而言,這意味著,當起始點接收到事件1時,會進入到狀態A,接受到事件2,3,4 時,會是無意義的。
接著我們繼續往下推演,從狀態 A 出發,列出所有事件,把事件安排妥當,事件1,2,3,4,在事件另一端寫上狀態B、狀態C、狀態D、狀態E。也許你會想偷懶,也許你會覺得無聊,但這就是一套標準作業流程,讓你即使在極度難以思考的情況下也能進行抽象推演的過程,只要跳了,之後做更加複雜的命題時,就容易手忙腳亂的。
思索一下,人的一生,狀態 A ,接受事件 1 時,是有意義的嗎?人可以生了又生嗎?不行,無意義。事件 2,老是有意義的嗎?人出生後就開始老,有意義。事件3,病是有意義的嗎?嚴格說起來是沒有,但我們先假設有,故保留著。事件4,死是有意義的嗎?沒有,人要經過老才能死亡。
狀態C 開始推演,一樣先把事件列出來,這邊我會放慢步驟,因此圖片會比較多張,希望你可以閱讀玩文字之後,再去瀏覽圖片,看看跟腦中或是自己紙筆上所描繪的是否相同。人的ㄧ生,狀態 C,接受事件 1 時,有意義嗎?人活到50 歲之後,又出生?無意義,因此丟到無意義中
狀態C ,接受事件 2 時,有意義嗎?人可以老了又老,我是不斷接收老這個事件的,反思個問題,現在的你幾歲?假設永遠青春的18 好了,那你閱讀到這邊的時候還是 18 歲嗎?準確來說是 18 歲又 4 秒吧,所以其實你一直都在老。由於如此,我們在狀態 C 接受事件2時,是返回到自己身上,於是把箭頭指回到自己身上。
狀態C ,接受事件 3 時,有意義嗎?人可以老了接著生病?當然可以,這是毫無疑問的,活到 12歲時,突然生一場重病是吧,由於生病的狀態已經描述好了,我們可以把箭頭指向過去到狀態 D
狀態C ,接受事件 4 時,有意義嗎?人老了接著死亡,合理,所以保留狀態 H 。
能夠跟到這裡的讀者,希望你不會感覺到無聊,因為這套狀態幾的旅程快走完了,你可能會感覺到煩躁,但這就是耐著性子把流程推演完的過程。好了的話,繼續望下走,從狀態 D 開始推演,一樣將事件列出來,事件1、事件2、事件3、事件4 陳列出來,並且寫上對應的狀態 E、狀態 F、狀態 G、狀態 I,狀態中的代號只是方便指認而已,沒有其他順序上的含義。
狀態 D,接受事件 1 時,有意義嗎?人活到50 歲之後,又出生?無意義,因此丟到無意義中。
狀態 D,接受事件 2 時,有意義嗎?我生了場重病,接著痊癒了,這時候我是回到持續生長的過程,所以我發現了在這個命題之下,人的一生,之前沒有發現到的事件,叫做康復,於是乎給上編號 5(康復),並且是回到狀態 C 裡頭。這就是逐步仔細檢視事件與狀態的好處,有機會找到一開始沒找到的事件還有狀態。
狀態 D,接受事件 3 時,有意義嗎?我得了重感冒,之後又得了咽喉癌,病了又病,顯然地,會回到狀態 D 本身,於是把事件只回到自己身上。
狀態 D,接受事件 4 時,有意義嗎?得了場無法痊癒的大病之後死亡,合理,於是銜接到狀態 H 中。
呼,終於來到最後一個狀態 H 的推演了,如果沒有發現其他狀態的話就真的是最後一個了。繼續來推演吧。列出所有事件,把事件安排妥當,事件1,2,3,4,在事件另一端寫上狀態 E、狀態 F、狀態 G、狀態 I。
人的ㄧ生,狀態 H ,接受事件 1 時,有意義嗎?人進到棺材還可以從子宮滑出來?不合理,無意義的。狀態 H ,接受事件 2 時,有意義嗎?進到棺材,還可以持續增長歲數?不合理,無意義的。狀態 H ,接受事件 3 時,有意義嗎?都進棺材了,人可以生病嗎?不行,無意義的。狀態 H ,接受事件 4 時,有意義嗎?人可以進到棺材之後再進到棺材?沒有吧,所以無意義。
這時候你注意到所以接受到的事件都會掉入到無意義當中,這個狀態 H 顯然是終點了,因此我們稱這個狀態為 end point,我們用一個圓圈抱著另一個圓圈做表達。
最後整理一下圖表,把之前所推演過的狀態寫上名詞,起碼要讓其他人去地懂,這個狀態的含義是什麼,交給設計師看,交給展場規劃師看,交給電子元件設計師看,交給軟體工程師看時,至少能夠讓對方明白,再者,事件雖有編號,但名稱有保留,紙筆上只留編號是沒差的。
「實作範例介紹」
本章節當中,我們將使用實際程式情境來介紹方才介紹過的狀態機設計方法,本次所用採用的案例為簡易計數器(Simple Counter)。
這邊我們採用當下時尚時尚最時尚的 Reactive programming 做說明好了,同時也是每個軟體工程師在接觸程式必須經過的流程,計數器。閱讀至此的讀者們,希望可以自己先嘗試做過一次,再繼續往下閱讀,這邊我先給出命題叫做「計數器的ㄧ生」,大概15 分鐘以內就可以推完吧,好了之後再往下滾吧。
下面是結果圖,這是我依照連結中題目所規劃出來的狀態機結果,具體斯老流程跟細節,礙於篇幅問題,我將這個段落移動到「軟體設計方法論:狀態機實作篇」,這樣會方便閱讀許多。
「結語」
筆者採用生老病死來描述是因為,凡是有開始的地方必然會有結束,且常見的名詞本身,都具備這個特性,所以讀者能夠熟練零活得使用這套方法論的話,相信在設計大型專案、電子元件、展場流程規劃等都勢必用得上,希望至此,你已經了解使用狀態機進行軟體設計的方法。