끝도 없는 switch-case의 늪, 당신의 MCU 코드는 안전한가요?
자동차 임베디드 소프트웨어 개발을 하다 보면, 제어 로직을 구현하기 위해 필연적으로 상태 머신(State Machine)을 작성하게 됩니다.
처음에는 간단한 `if-else`나 `switch-case` 문으로 시작하지만, 요구사항이 하나둘 추가되다 보면 코드는 순식간에 괴물이 되어버리죠.
"여기 조건 하나만 추가해 주세요"라는 기획자의 가벼운 요청에, 수백 줄짜리 switch 문을 뒤져야 했던 아찔한 경험, 다들 있으실 겁니다.
이런 하드코딩된 상태 머신을 방치하면 심각한 기술 부채로 돌아옵니다.
기능 하나를 수정했는데 전혀 엉뚱한 상태에서 오동작이 발생하고, 사이클로매틱 복잡도(Cyclomatic Complexity)가 치솟아 실무에서 활용하는 VectorCAST 2025와 같은 검증 툴에서 분기 커버리지(Branch Coverage) 100%를 달성하는 것이 불가능에 가까워집니다.
결국 유지보수에 매일 밤을 새우고, 퇴근은 점점 멀어지는 악순환에 빠지게 됩니다.
✅ 하나의 `switch` 문 길이가 100줄을 훌쩍 넘긴다.
✅ 새로운 상태(State)나 이벤트(Event)를 추가할 때마다 기존 코드를 뜯어고쳐야 한다.
✅ 상태 전환 로직과 특정 하드웨어 제어 코드가 한 함수에 뒤섞여 있다.
OCP를 만족하는 상태 머신 설계 3단계 실전 예제
이 지옥에서 탈출하는 가장 확실한 방법은, 소프트웨어 설계 패턴을 도입하여 개방-폐쇄 원칙(OCP)을 준수하는 구조로 개편하는 것입니다.
기존 코드는 수정하지 않으면서(Closed), 새로운 상태 확장은 쉽게(Open) 만드는 단계별 방법론을 소개합니다.
| 비교 항목 | 기존 (Switch-Case) 방식 | 개선 (함수 포인터 테이블) 방식 |
|---|---|---|
| 유지보수성 | 매우 낮음 (수정 시 에러 유발) | 매우 높음 (모듈화 완료) |
| OCP 준수 여부 | 불만족 (기존 코드 수정 필수) | 만족 (테이블 행만 추가하면 됨) |
| 실행 속도 | 조건이 많을수록 느려짐 | O(1) 시간 복잡도로 일정함 |
1단계: 상태와 이벤트를 명확히 분리하기
열거형(Enum)을 사용하여 상태와 이벤트를 정의합니다.
2단계: 전이(Transition) 테이블 설계하기
상태 머신을 함수 포인터 배열이나 구조체 테이블로 매핑하는 것이 핵심입니다. 아래의 C언어 소스 코드 샘플을 확인해 보세요.
// 1. 상태 및 이벤트 정의
typedef enum { STATE_INIT, STATE_RUN, STATE_FAULT } State_t;
typedef enum { EV_START, EV_ERROR, EV_RESET } Event_t;
// 2. 상태 처리 함수 시그니처 정의
typedef State_t (*ActionFunc)(void);
// 3. 상태별 동작 함수 구현 (OCP 만족: 새 상태가 필요하면 새 함수만 작성)
State_t Do_Init_Action(void) { /* 하드웨어 초기화 */ return STATE_RUN; }
State_t Do_Fault_Action(void) { /* 안전 모드 진입 */ return STATE_INIT; }
// 4. 상태 테이블 매핑 (함수 포인터 활용)
ActionFunc StateMachineTable[3] = {
Do_Init_Action, // STATE_INIT 일 때 실행
NULL, // STATE_RUN (예시 생략)
Do_Fault_Action // STATE_FAULT 일 때 실행
};
// 5. 메인 루프 실행부
void Run_StateMachine(State_t currentState) {
if (StateMachineTable[currentState] != NULL) {
currentState = StateMachineTable[currentState](); // O(1) 실행
}
}
3단계: 검증 환경 최적화
이렇게 구조화된 코드는 각 상태 함수가 독립적이기 때문에 단위 테스트가 매우 쉽습니다.
나아가 현재 랩실에 세팅된 CANoe 11.0.96 같은 시뮬레이션 환경에서 특정 통신 이벤트를 강제로 발생시켰을 때, 상태가 정확히 전이되는지 깔끔하게 모니터링할 수 있습니다.
설계의 차이가 곧 개발자의 가치입니다
자동차 임베디드 시스템은 SDV 시대로 진입하며 그 복잡성이 상상을 초월하고 있습니다.
단순히 당장의 버그를 막기 위해 땜질식 처방을 하는 개발자와, 전체 아키텍처 패턴을 이해하고 OCP 기반의 유연한 코드를 작성하는 전문가의 몸값은 천지 차이입니다.
지금 당장 진행 중인 프로젝트의 가장 복잡한 모듈 하나를 골라, 오늘 소개한 테이블 기반 상태 머신 패턴을 적용해 보세요.