站在华为上汽面试官的角度去想问题-ATM状态机又撒了壹万亿
扫描二维码
随时随地手机看文章
引言
记得年轻的时候去面试一家大厂的嵌入式软件工程师的职位,面试官让很多候选人三人一组讨论去设计一款ATM机,当时我们那组讨论的热火朝天,什么把Linux操作系统内核移植过来,加入互锁机制确保取钱的安全性,什么多线程加快速度等等天马行空的方案。最终默默地在等待Offer,就一直默默而无下文了。现在站在大厂的角度想一想,可能是通过一些试题想考察你对一个项目的软件思维能力。啥叫软件思维能力。就是从软件的角度去解读需求。别囫囵吞枣,大而空地泛泛之谈。你能想得越细,设计的代码覆盖的case越多,设计的代码简单且RAM,ROM越节省,执行效率越高,这才是去做嵌入式软件工程师的正确解法。我们从状态机的角度去分析一下解题步骤:
-
全面详尽的弄清楚客户的需求,分解成相应的功能或多个子功能需求 -
弄清楚这些功能中的状态,并确定状态的转移条件(及状态的进入和退出条件或事件),设计出系统的状态骨架 -
测试每个转移的有效性 -
针对每个状态,结合功能需求细化状态中需要做哪些事情 -
集成进行系统的集成测试
ATM状态机
当您设计ATM 的功能时,设想一下你是小明。小明走到ATM机面前,一系列的事件(事件是指在某个时刻发生的事情 )发生了,插入卡片(Card_Insert_Event),输入密码(Pin_Enter_Even),选择办理事项(Option_Selection_Event:取钱/存钱/查询余额),输入数额(Amount_Enter_Event)并按确认,ATM吐出所需钱财(Amount_Dispatch_Event)。如下图为从ATM取钱的一个状态机模型, 描述了ATM状态及转移条件。
状态作为系统的一种特定阶段的状态,则会持续一段时间,直到特定的触发事件导致状态的转移的特定点为止。例如夜深人静的时候ATM大概率处于空闲状态。过节的时候(是蛮多和女生相关的且需要花钱的节日的),你走到ATM插入卡片后的那个时间点,ATM从空闲状态切换到了检测到卡片插入状态。
对事件发生所执行的事件处理是当前状态和输入事件的一个函数。此处的名字叫做EventHandle, 还可叫做Action(有三类:entry,during,exit)。其可存在转移的分支上,或者是位于状态的进入或退出动作或during标签中,如下图所示。
-
entry: 当进入一个状态的时候执行该标签下的action,该action在状态中其它任何action之前执行。 -
during: 当状态处于激活时执行during 标签下的action , during活动在进入活动之后执行,并且—直运行到它本身完成为止(同步执行的动作)。 -
exit: 当离开—个状态的时候触发执行该标签下的action,该活动在该状态结束之前并且所有其它action都完成后触发执行。
一般状态之间的转换不仅依赖于Event的发生,还需要[Condition Expression]这个门控条件成立,才能发生转移。如下图中的转移的方式:转移条件分别为Condition和Event。
状态机实现方式
如果你用C语言建模的话,一般推荐下面两种方式
-
实现方式1:switch-case结构。
实现方式简单,Case变多的时候不易维护,并且圈复杂度较高。
#include <stdio.h>
//Different state of ATM machine
typedef enum
{
Idle_State,
Card_Inserted_State,
Pin_Eentered_State,
Option_Selected_State,
Amount_Entered_State,
} eSystemState;
//Different type events
typedef enum
{
Card_Insert_Event,
Pin_Enter_Event,
Option_Selection_Event,
Amount_Enter_Event,
Amount_Dispatch_Event
} eSystemEvent;
//Prototype of eventhandlers
eSystemState AmountDispatchHandler(void)
{
return Idle_State;
}
eSystemState EnterAmountHandler(void)
{
return Amount_Entered_State;
}
eSystemState OptionSelectionHandler(void)
{
return Option_Selected_State;
}
eSystemState EnterPinHandler(void)
{
return Pin_Eentered_State;
}
eSystemState InsertCardHandler(void)
{
return Card_Inserted_State;
}
int main(int argc, char *argv[])
{
eSystemState eNextState = Idle_State;
eSystemEvent eNewEvent;
while(1)
{
//Read system Events
eSystemEvent eNewEvent = ReadEvent();
switch(eNextState)
{
case Idle_State:
{
if(Card_Insert_Event == eNewEvent)
{
eNextState = InsertCardHandler();
}
}
break;
case Card_Inserted_State:
{
if(Pin_Enter_Event == eNewEvent)
{
eNextState = EnterPinHandler();
}
}
break;
case Pin_Eentered_State:
{
if(Option_Selection_Event == eNewEvent)
{
eNextState = OptionSelectionHandler();
}
}
break;
case Option_Selected_State:
{
if(Amount_Enter_Event == eNewEvent)
{
eNextState = EnterAmountHandler();
}
}
break;
case Amount_Entered_State:
{
if(Amount_Dispatch_Event == eNewEvent)
{
eNextState = AmountDispatchHandler();
}
}
break;
default:
break;
}
}
return 0;
}
-
实现方式2:查表方式
易于维护,可以方便你增加新的状态或事件。减少代码的长度,通过函数指针和事件及状态进行绑定,非要说个缺点就是使用了指针,在汽车Misra C标准中不太推荐使用指针。
#include <stdio.h>
//Different state of ATM machine
typedef enum
{
Idle_State,
Card_Inserted_State,
Pin_Eentered_State,
Option_Selected_State,
Amount_Entered_State,
last_State
} eSystemState;
//Different type events
typedef enum
{
Card_Insert_Event,
Pin_Enter_Event,
Option_Selection_Event,
Amount_Enter_Event,
Amount_Dispatch_Event,
last_Event
} eSystemEvent;
//typedef of function pointer
typedef eSystemState (*pfEventHandler)(void);
//structure of state and event with event handler
typedef struct
{
eSystemState eStateMachine;
eSystemEvent eStateMachineEvent;
pfEventHandler pfStateMachineEvnentHandler;
} sStateMachine;
//function call to dispatch the amount and return the ideal state
eSystemState AmountDispatchHandler(void)
{
return Idle_State;
}
//function call to Enter amount and return amount entered state
eSystemState EnterAmountHandler(void)
{
return Amount_Entered_State;
}
//function call to option select and return the option selected state
eSystemState OptionSelectionHandler(void)
{
return Option_Selected_State;
}
//function call to enter the pin and return pin entered state
eSystemState EnterPinHandler(void)
{
return Pin_Eentered_State;
}
//function call to processing track data and return card inserted state
eSystemState InsertCardHandler(void)
{
return Card_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
int main(int argc, 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
}
}
return 0;
}
免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!