스파게티 코드 폭탄, 당신의 퇴근을 가로막고 있진 않나요?
어느 날 인수인계받은 수만 줄짜리 레거시 코드, 메인 함수(main.c)를 열어보는 순간 숨이 턱 막힌 경험 다들 있으실 겁니다.
전역 변수가 거미줄처럼 얽혀 있고, 하드웨어 제어 로직과 비즈니스 로직이 하나의 파일에 짬뽕되어 있는 소위 '유지보수 불가' 상태의 코드 말입니다.
특히 우리 같은 임베디드 소프트웨어 엔지니어들에게 이런 낡은 코드는 그야말로 언제 터질지 모르는 시한폭탄과 같습니다.
간단한 CAN 통신 기능 하나 추가하려는데 연관된 함수 수십 개를 뜯어고쳐야 합니다.
수정한 코드 때문에 엉뚱한 곳에서 uint16 비트 연산이나 바이트 오더링(Byte Ordering) 문제가 터져 밤을 새우게 되죠.
심지어 VectorCAST 2025 같은 자동화 툴로 단위 테스트를 돌려보면 빨간불이 수백 개씩 쏟아져 나와 개발 의욕마저 꺾어버립니다.
결국 본질적인 설계 개선 없이 땜질식 처방만 반복하다 보면, 개발 팀의 피로도는 극에 달하고 프로젝트 납기는 무한정 지연됩니다.
OCP와 패턴 관점으로 뜯어고치는 3단계 리팩토링 실무
더 이상 땜질은 통하지 않는다고 판단했을 때, 저는 과감하게 아키텍처를 뒤엎기로 결정했습니다.
핵심은 OCP(개방-폐쇄 원칙)와 디자인 패턴을 적용하여, '확장에는 열려있고 수정에는 닫혀있는' 견고한 구조를 설계하는 것입니다.
| 실행 단계 | 적용 기법 및 패턴 | 기대 효과 |
|---|---|---|
| 1단계: 하드웨어 종속성 분리 | 인터페이스(Wrapper) 패턴 적용 | 비즈니스 로직과 HW 드라이버 디커플링 |
| 2단계: OCP 기반 구조 설계 | 전략(Strategy) 패턴을 활용한 모듈화 | 기존 코드 수정 없이 새 기능(프로토콜 등) 추가 |
| 3단계: 단위 테스트 및 검증 | 의존성 주입(DI)을 통한 Mocking 테스트 | VectorCAST 커버리지 100% 달성 및 회귀 버그 차단 |
실제 어떻게 구현하고 설계했는지, 단계별 소스 코드 예시를 통해 구체적으로 살펴보겠습니다.
기존에는 특정 장비에 종속된 통신 코드가 비즈니스 로직 곳곳에 하드코딩되어 있었습니다.
💡 [STEP 1 & 2] PCAN 래퍼(Wrapper) 클래스와 OCP의 적용
아래 코드는 추상화된 인터페이스를 통해 하드웨어 의존성을 완벽하게 분리한 예시입니다.
// 1. 공통 통신 인터페이스 정의 (수정에 닫힘)
class ICANInterface {
public:
virtual ~ICANInterface() = default;
virtual bool SendMessage(uint16_t id, const uint8_t* data, uint8_t length) = 0;
};
// 2. PCAN용 기본 래퍼 클래스 구현 (확장에 열림)
class PCANWrapper : public ICANInterface {
public:
bool SendMessage(uint16_t id, const uint8_t* data, uint8_t length) override {
// PCAN-Basic API 호출 및 바이트 오더링 처리 등 하드웨어 종속 로직
return true;
}
};
// 3. 비즈니스 로직은 인터페이스에만 의존 (의존성 주입)
class DiagnosticService {
private:
ICANInterface* canInterface;
public:
DiagnosticService(ICANInterface* interface) : canInterface(interface) {}
void ExecuteService() {
uint8_t payload[8] = {0x02, 0x27, 0x01, 0, 0, 0, 0, 0};
canInterface->SendMessage(0x7E0, payload, 8);
}
};
이렇게 인터페이스를 구축하면, 향후 테스트 환경에서 `CANoe 11.0.96(SP4)` 가상 노드로 통신 채널을 스위칭할 때도 `DiagnosticService` 내부 코드는 단 한 줄도 수정할 필요가 없습니다. 이것이 바로 OCP가 가져다주는 강력한 유지보수성입니다.
레거시 코드, 더 늦기 전에 분리해야 합니다
이러한 패턴 기반의 리팩토링은 이제 단순한 선택이 아닌 엔지니어링 생존의 문제입니다.
향후 AUTOSAR 기반의 표준 플랫폼으로 아키텍처를 전환하거나 새로운 제어기 프로젝트를 수주했을 때, 지금의 낡은 스파게티 코드를 재사용할 수는 없기 때문입니다.
시스템의 전체적인 동작 흐름을 그나마 파악하고 있는 지금이 아키텍처를 바로잡을 수 있는 마지막 기회입니다.
관련하여 더 깊은 실무 지식이 필요하시다면 AUTOSAR 전환 대비 아키텍처 설계 가이드를 참고해 보시는 것을 적극 추천해 드립니다.
✅ 나의 레거시 코드 위험도 점검 퀴즈
현재 개발 중인 임베디드 프로젝트가 리팩토링이 시급한 상태인지 10초 만에 체크해 보십시오.
- ✅ 새로운 CAN 메시지 하나를 추가하기 위해 3개 이상의 C/C++ 소스 파일을 뒤져서 수정해야 한다.
- ✅ 단위 테스트를 돌리기 위해 타겟 보드(하드웨어)나 PCAN 같은 실제 장비가 반드시 연결되어야만 한다.
- ✅ 시스템의 특정 기능 동작 흐름을 완벽하게 이해하는 사람이 부서 내에 단 한 명뿐이다.
단순히 코드를 '타이핑'하는 것을 넘어, 아키텍처 '설계'를 고민하는 순간 엔지니어로서의 몸값과 가치는 비약적으로 상승합니다.
오늘 보여드린 OCP 관점의 인터페이스 분리와 래퍼 클래스 패턴을 여러분의 다음 릴리즈에 당장 적용해 보십시오.