站在華為上汽面試官的角度去想問題-ATM狀態(tài)機又撒了壹萬億

引言

記得年輕的時候去面試一家大廠的嵌入式軟件工程師的職位,面試官讓很多候選人三人一組討論去設計一款ATM機,當時我們那組討論的熱火朝天,什么把Linux操作系統(tǒng)內(nèi)核移植過來,加入互鎖機制確保取錢的安全性,什么多線程加快速度等等天馬行空的方案。最終默默地在等待Offer,就一直默默而無下文了?,F(xiàn)在站在大廠的角度想一想,可能是通過一些試題想考察你對一個項目的軟件思維能力。啥叫軟件思維能力。就是從軟件的角度去解讀需求。別囫圇吞棗,大而空地泛泛之談。你能想得越細,設計的代碼覆蓋的case越多,設計的代碼簡單且RAM,ROM越節(jié)省,執(zhí)行效率越高,這才是去做嵌入式軟件工程師的正確解法。

我們從狀態(tài)機的角度去分析一下解題步驟:

全面詳盡的弄清楚客戶的需求,分解成相應的功能或多個子功能需求

弄清楚這些功能中的狀態(tài),并確定狀態(tài)的轉移條件(及狀態(tài)的進入和退出條件或事件),設計出系統(tǒng)的狀態(tài)骨架

測試每個轉移的有效性

針對每個狀態(tài),結合功能需求細化狀態(tài)中需要做哪些事情

集成進行系統(tǒng)的集成測試

ATM狀態(tài)機

當您設計ATM 的功能時,設想一下你是小明。

小明走到ATM機面前,一系列的事件(事件是指在某個時刻發(fā)生的事情

)發(fā)生了,插入卡片(Card_Insert_Event),輸入密碼(Pin_Enter_Even),選擇辦理事項(Option_Selection_Event:取錢/存錢/查詢余額),輸入數(shù)額(Amount_Enter_Event)并按確認,ATM吐出所需錢財(Amount_Dispatch_Event)。如下圖為從ATM取錢的一個狀態(tài)機模型,

描述了ATM狀態(tài)及轉移條件。

狀態(tài)作為系統(tǒng)的一種特定階段的狀態(tài),則會持續(xù)一段時間,直到特定的觸發(fā)事件導致狀態(tài)的轉移的特定點為止。例如夜深人靜的時候ATM大概率處于空閑狀態(tài)。過節(jié)的時候(是蠻多和女生相關的且需要花錢的節(jié)日的),你走到ATM插入卡片后的那個時間點,ATM從空閑狀態(tài)切換到了檢測到卡片插入狀態(tài)。

對事件發(fā)生所執(zhí)行的事件處理是當前狀態(tài)和輸入事件的一個函數(shù)。此處的名字叫做EventHandle, 還可叫做Action(有三類:entry,during,exit)。其可存在轉移的分支上,或者是位于狀態(tài)的進入或退出動作或during標簽中,如下圖所示。

entry: 當進入一個狀態(tài)的時候執(zhí)行該標簽下的action,該action在狀態(tài)中其它任何action之前執(zhí)行。

during: 當狀態(tài)處于激活時執(zhí)行during 標簽下的action , during活動在進入活動之后執(zhí)行,并且—直運行到它本身完成為止(同步執(zhí)行的動作)。

exit: 當離開—個狀態(tài)的時候觸發(fā)執(zhí)行該標簽下的action,該活動在該狀態(tài)結束之前并且所有其它action都完成后觸發(fā)執(zhí)行。

一般狀態(tài)之間的轉換不僅依賴于Event的發(fā)生,還需要[Condition Expression]這個門控條件成立,才能發(fā)生轉移。

如下圖中的轉移的方式:轉移條件分別為Condition和Event。

狀態(tài)機實現(xiàn)方式

如果你用C語言建模的話,一般推薦下面兩種方式

實現(xiàn)方式1:switch-case結構。

實現(xiàn)方式簡單,Case變多的時候不易維護,并且圈復雜度較高。

#include<stdio.h>

//Different?state?of?ATM?machine

typedefenum

{

Idle_State,

Card_Inserted_State,

Pin_Eentered_State,

Option_Selected_State,

Amount_Entered_State,

}?eSystemState;

//Different?type?events

typedefenum

{

Card_Insert_Event,

Pin_Enter_Event,

Option_Selection_Event,

Amount_Enter_Event,

Amount_Dispatch_Event

}?eSystemEvent;

//Prototype?of?eventhandlers

eSystemStateAmountDispatchHandler(void)

{

returnIdle_State;

}

eSystemStateEnterAmountHandler(void)

{

returnAmount_Entered_State;

}

eSystemStateOptionSelectionHandler(void)

{

returnOption_Selected_State;

}

eSystemStateEnterPinHandler(void)

{

returnPin_Eentered_State;

}

eSystemStateInsertCardHandler(void)

{

returnCard_Inserted_State;

}

intmain(intargc,char*argv[])

{

eSystemState?eNextState?=?Idle_State;

eSystemEvent?eNewEvent;

while(1)

{

//Read?system?Events

eSystemEvent?eNewEvent?=?ReadEvent();

switch(eNextState)

{

caseIdle_State:

{

if(Card_Insert_Event?==?eNewEvent)

{

eNextState?=?InsertCardHandler();

}

}

break;

caseCard_Inserted_State:

{

if(Pin_Enter_Event?==?eNewEvent)

{

eNextState?=?EnterPinHandler();

}

}

break;

casePin_Eentered_State:

{

if(Option_Selection_Event?==?eNewEvent)

{

eNextState?=?OptionSelectionHandler();

}

}

break;

caseOption_Selected_State:

{

if(Amount_Enter_Event?==?eNewEvent)

{

eNextState?=?EnterAmountHandler();

}

}

break;

caseAmount_Entered_State:

{

if(Amount_Dispatch_Event?==?eNewEvent)

{

eNextState?=?AmountDispatchHandler();

}

}

break;

default:

break;

}

}

return0;

}

實現(xiàn)方式2:查表方式

易于維護,可以方便你增加新的狀態(tài)或事件。減少代碼的長度,通過函數(shù)指針和事件及狀態(tài)進行綁定,非要說個缺點就是使用了指針,在汽車Misra C標準中不太推薦使用指針。

#include<stdio.h>

//Different?state?of?ATM?machine

typedefenum

{

Idle_State,

Card_Inserted_State,

Pin_Eentered_State,

Option_Selected_State,

Amount_Entered_State,

last_State

}?eSystemState;

//Different?type?events

typedefenum

{

Card_Insert_Event,

Pin_Enter_Event,

Option_Selection_Event,

Amount_Enter_Event,

Amount_Dispatch_Event,

last_Event

}?eSystemEvent;

//typedef?of?function?pointer

typedefeSystemState(*pfEventHandler)(void);

//structure?of?state?and?event?with?event?handler

typedefstruct

{

eSystemState?eStateMachine;

eSystemEvent?eStateMachineEvent;

pfEventHandler?pfStateMachineEvnentHandler;

}?sStateMachine;

//function?call?to?dispatch?the?amount?and?return?the?ideal?state

eSystemStateAmountDispatchHandler(void)

{

returnIdle_State;

}

//function?call?to?Enter?amount?and?return?amount?entered?state

eSystemStateEnterAmountHandler(void)

{

returnAmount_Entered_State;

}

//function?call?to?option?select?and?return?the?option?selected?state

eSystemStateOptionSelectionHandler(void)

{

returnOption_Selected_State;

}

//function?call?to?enter?the?pin?and?return?pin?entered?state

eSystemStateEnterPinHandler(void)

{

returnPin_Eentered_State;

}

//function?call?to?processing?track?data?and?return?card?inserted?state

eSystemStateInsertCardHandler(void)

{

returnCard_Inserted_State;

}

//Initialize?array?of?structure?with?states?and?event?with?proper?handler

sStateMachine?asStateMachine?[]?=

{

{Idle_State,Card_Insert_Event,InsertCardHandler},

{Card_Inserted_State,Pin_Enter_Event,EnterPinHandler},

{Pin_Eentered_State,Option_Selection_Event,OptionSelectionHandler},

{Option_Selected_State,Amount_Enter_Event,EnterAmountHandler},

{Amount_Entered_State,Amount_Dispatch_Event,AmountDispatchHandler}

};

//main?function

intmain(intargc,char*argv[])

{

eSystemState?eNextState?=?Idle_State;

while(1)

{

//Api?read?the?event

eSystemEvent?eNewEvent?=?read_event();

if((eNextState?<?last_State)?&&?(eNewEvent?<?last_Event)&&?(asStateMachine[eNextState].eStateMachineEvent?==?eNewEvent)?&&?(asStateMachine[eNextState].pfStateMachineEvnentHandler?!=NULL))

{

//?function?call?as?per?the?state?and?event?and?return?the?next?state?of?the?finite?state?machine

eNextState?=?(*asStateMachine[eNextState].pfStateMachineEvnentHandler)();

}

else

{

//Invalid

}

}

return0;

}

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容