前幾天和某某同窗吃飯席間,他聊到每當要修改老項目中本身寫的代碼時就痛苦不堪,問我是否是也有同感。我以爲這應該是很多程序猿的心聲,之因此會這樣,大體有兩個主因:git
程序猿飯桌上總少不了對產品經理的吐槽:「產品經理又對業務流程進行了瘋狂調整,我以爲這會致使狀態機沒法支持了。」他的這個槽點讓我一時有些語塞,倒不是懷疑產品經理的腦洞還能大到把狀態機開到失控,只是詫異難道咱們還有比狀態機更適合應對業務流程變動的武器嗎?github
事實上狀態機對於軟件工程師來講應該是個很基礎的知識點,它原理簡單卻擁有強大的適應力並被普遍應用(譬如:遊戲開發、工做流、編譯器、正則表達式等解析器),掌握好它的原理和應用,能幫助咱們從容應對不少棘手問題,它於程序猿應對複雜流程性問題,就比如醫生使用抗生素應對細菌感染同樣的最佳武器。同時,它仍是防止代碼失控的一劑良藥。正則表達式
狀態機通常泛指「有限狀態機(Finite State Machine)」,《離散數學》中有關於它的專門章節,如下謹爲我對相關概念的形式上的非精準釋義,若有出入請以教科書或相關學術資料爲準。併發
有了上面的基本概念,咱們來看一個最簡單的狀態圖:優化
你可能會奇怪這個圖怎麼跟網上那些狀態機圖不同,連狀態轉換條件都沒有呢?這是由於,我以爲在瞭解狀態機以前,最好先確立如下兩種概念:spa
因此,在常見的狀態機圖中標註的那些狀態轉換條件只是「遷移斷定」的一種具體表現形式,它便可以由狀態機內置,也能夠是獨立的斷定器來處理,又或者由狀態圖預先定義好,如此等等。設計
創建「狀態驅動」和「遷移斷定」這兩個被抽象化的概念,有助於咱們深刻理解狀態機的機理,而且對咱們設計一個魯棒性和擴展性更好到狀態機有實際指導意義。code
如下是表示一個‘簡陋’的 Email 地址格式的解析器狀態圖,狀態遷移條件採用正則表達式來表達,其中圖二又稱爲「狀態遷移圖」。對象
圖一:節點式blog
圖二:表格式(紅色格表示拒絕或異常;灰色格表示忽略或無心義;其餘格表示遷移條件)
有了上面的狀態圖,就像建築工人拿到了詳細的建築設計圖紙;如今咱們只須要對着狀態機圖,把它映射成代碼便可完成一個基本狀態機。狀態機圖越詳細,實現起來就越容易,同時代碼的可維護性也越好。
public class Email { public string Identifier { get; private set;} public string Host { get; private set; } public string Domain { get; private set; } private Email() {} public static Email Parse(string text) { if(string.IsNullOrEmpty(text)) return null; var state = State.None; /* The State-Driven */ for(int i=0; i<text.Length; i++) { var chr = text[i]; switch(state) { case State.None: //do state transition decision break; case State.Identifier: //do state transition decision break; case State.Delimiter: //do state transition decision break; case State.Host: //do state transition decision break; case State.Dot: //do state transition decision break; case State.Domain: //do state transition decision break; } } return new Email(...); } private enum State { None, Identifier, Delimiter, Host, Dot, Domain, } }
上面的代碼雖然看起來沒什麼技術含量,但它已經具有了一個狀態機最基本的三大要素了(狀態、狀態驅動、遷移斷定),針對具體業務場景咱們只需完善和優化它的程序結構,底層原理的基本要義其實就是這麼簡單。
人腦是一個很神奇的存在,它很擅長處理抽象思惟,對於邏輯推理也有很好的應對能力,但卻有個不擅長處理併發任務的Bug。好比當面臨不少個邏輯分支,各分支的斷定條件彼此關聯,大腦很快就會陷入繁雜的狀態中沒法自拔。
表如今解決複雜流程相關的任務時就是,寫着寫着你會發覺腦子好像不夠用了,而程序中的 Bug 卻像打地鼠遊戲中的老鼠同樣層出不窮。不難想象,即便腦力過人的你在勉強寫完後的某天,產品經理帶着他的腦洞又來找你了,在他的威逼利誘下你打開了一個月前的代碼,突然,以爲仍是抱着產品經理玉石俱焚算了……
這大概是某某同窗,面對本身曾經的代碼時痛苦的根源所在,由於普通人面對複雜流程問題時,終歸受人腦算力所限。本質上這是人腦算力有限的一個困境,人類解決這個困境的一個行之有效的辦法就是「分而治之」,即將一個大問題或複雜問題不斷進行分解分化,直至達到人腦能相對輕鬆理解和處理的程度。
爲何說狀態機是解決此類問題的一劑良藥?
經過狀態機圖能夠很容易的看到它天生具備「分解、分化」的特徵,一個複雜的流程由多個流程節點組成,這些節點能夠理解爲對流程的分解,流程節點之間的轉移條件(遷移斷定)能夠當作是被分化後的邏輯分支,若是大腦直接處理整個流程很容易陷入紛擾的流程分支和各類細節中,可是,當咱們把眼光聚焦在某個流程節點和它的轉移條件上的時候,大腦須要處理的信息量就變得很是少了。
因此,當咱們直面一個繁雜的流程圖的時候,第一感受就是複雜、腦闊痛,這實際上是大腦的正常反應,當你把眼光聚焦到「Start」節點上,並順着它往下推,每一個節點的信息量必定是大腦能輕鬆處理的量級,這種順藤摸瓜的方式反過來也正是流程設計的套路。我有時會被本身剛畫完的狀態機圖給驚訝到,怎麼這麼複雜?由於當我一點點把細節補充上去後,總體性天然會變得複雜了,可是局部依然是簡單的,而簡單就是可靠、魯棒、可維護性的同義詞。
代碼只是狀態機圖的相關元素的一種表現形式,它與「節點式」或「表格式」的狀態機圖並沒有本質不一樣。
另外,狀態機圖相對代碼而言,它更專一於流程自己,而代碼畢竟是具體實現層面的東西,除了流程自己還包括程序結構、業務代碼等與流程無關的代碼,這些額外的東西對咱們解讀流程形成了干擾,於是相對純粹的狀態機圖就比如是代碼實現的「地圖」。
通過一段時間後,咱們可能已經不記得實現細節了,這時看着狀態機圖來進行代碼解讀和修改將會大大提升效率和準確度,這就是提高代碼可維護性的有力手段。
如上,狀態機是防止代碼失控的一劑良藥,製備完善的狀態機圖就是防止代碼失控的一種有效手段。
試着脫離狀態機圖擼一個「成員訪問表達式」的解析器去體驗下失控的感覺。下次,咱們將一塊兒來實現這個東西。
成員訪問表達式:訪問對象方法、屬性、字段、索引器(包括字典、列表)這些成員的表達式,其中方法和索引器(包括字典、列表)的參數支持常量和成員表達式(即表達式遞歸)。詳細的文法請參考C#語言手冊。譬如:
PropertyA .ListProperty[100] .MethodA(PropertyB, 'StringConstant for Arg2', 200, ['key']) .Children['arg1', 'arg2']
若是你以爲此次的文章對你有所幫助,又或者你以爲咱們的開源項目作的還不錯,請爲咱們點贊並關注咱們的公衆號。
本文可能會更新,請閱讀原文: https://zongsoft.github.io/blog/zh-cn/zongsoft/coding-outcontrol-statemachine-1,以免因內容陳舊而致使的謬誤,同時亦有更好的閱讀體驗。
本做品採用 知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議 進行許可。歡迎轉載、使用、從新發布,但必須保留本文的署名 鍾峯(包含連接:http://zongsoft.github.io),不得用於商業目的,基於本文修改後的做品務必以相同的許可發佈。若有任何疑問或受權方面的協商,請致信給我 (zongsoft@qq.com)。