초당 수천 건의 시세 데이터와 체결 통보가 오가는 증권사 HTS/MTS 핵심 트레이딩 엔진 모듈은 무엇보다 성능(Performance)과 안전성(Stability)이 최우선입니다.
하지만 오랜 기간 유지보수되어 온 C++ 레거시 코드를 다루다 보면, 최적화라는 명목하에 복잡하고 장황한 수동 포인터/반복자 제어가 난무하여 정작 "이 로직이 수행하고자 하는 비즈니스 의도가 무엇인가?"를 한눈에 파악하기 힘든 경우가 많습니다.
오늘은 HTS/MTS 실무에서 가장 흔하게 접하는 '주문 번호 및 종목별 복수 주문 세션(`std::multimap`) 조회 및 순회 로직'을 바탕으로, 모던 C++(C++11 ~ C++17) 문법을 도입해 코드의 의도를 선명하게 드러내는 리팩토링 실무 노하우를 정리합니다.
1. HTS 실무 문제 상황: 종목별 복수 주문 처리
- 종목코드(예:
"005930") 하나에 대해 복수의 주문(Order) 데이터가std::multimap구조로 저장되어 있음. - 실시간 체결 통보 수신 시 해당 종목의 모든 주문 내역을 순회하며 일치하는 주문을 찾거나 일괄 처리해야 함.
2. 기존 레거시 C++03 스타일 코드의 문제점
// [C++03 레거시 스타일]
typedef std::multimap<std::string, OrderInfo> OrderContainer;
void CancelOrdersByStock(const OrderContainer& orders, const std::string& stockCode) {
// 1. 장황하고 눈을 피로하게 만드는 반복자 타입 명시
OrderContainer::const_iterator itLower = orders.lower_bound(stockCode);
OrderContainer::const_iterator itUpper = orders.upper_bound(stockCode);
// 2. 범위를 수동으로 비교하며 순회
for (OrderContainer::const_iterator it = itLower; it != itUpper; ++it) {
if (it->second.status == ORDER_PENDING) {
ProcessCancel(it->first, it->second);
}
}
}
- 시각적 노이즈(Type Noise):
OrderContainer::const_iterator라는 장황한 타입명이 코드 공간의 대부분을 차지하여 로직 본문이 한눈에 들어오지 않습니다. - 의도의 파편화:
lower_bound와upper_bound를 각각 따로 호출하는 방식은 "해당 키의 전체 구간(Range)을 얻는다"는 단일 목적을 두 단계의 세부 기술적 절차(How)로 분해하여 의도를 흐립니다. - 실수 유발 가능성: 두 검색 함수에 서로 다른 변수를 넣거나 루프 조건에서
itUpper대신 잘못된 범위를 대입하는 실수가 발생하기 쉽습니다.
3. Modern C++ 단계별 리팩토링
Step 1. auto 키워드로 시각적 노이즈 제거 (C++11)
auto를 적용하면 컴파일러가 반복자 타입을 자동으로 추론해주므로, 불필요하게 길었던 보일러플레이트 코드가 제거됩니다.
// [Step 1: auto 도입]
void CancelOrdersByStock_Step1(const OrderContainer& orders, const std::string& stockCode) {
auto itLower = orders.lower_bound(stockCode);
auto itUpper = orders.upper_bound(stockCode);
for (auto it = itLower; it != itUpper; ++it) {
if (it->second.status == ORDER_PENDING) {
ProcessCancel(it->first, it->second);
}
}
}
Step 2. std::equal_range로 비즈니스 의도 표현 (C++11)
lower_bound와 upper_bound를 따로 부르던 것을 equal_range 단 한 번의 호출로 통합합니다.
equal_range라는 함수명 자체가 "이 키값과 일치하는 모든 원소의 구간(Pair)을 가져오라"는 명확한 개발자의 의도를 표현합니다.
// [Step 2: equal_range 적용]
void CancelOrdersByStock_Step2(const OrderContainer& orders, const std::string& stockCode) {
// range.first(시작 반복자)와 range.second(끝 반복자)를 담은 std::pair 반환
auto range = orders.equal_range(stockCode);
for (auto it = range.first; it != range.second; ++it) {
if (it->second.status == ORDER_PENDING) {
ProcessCancel(it->first, it->second);
}
}
}
Step 3. C++17 구조화된 바인딩 (Structured Binding)으로 완성
C++17의 Structured Binding을 적용하면 range.first, range.second, it->first, it->second 같은 불투명한 멤버 접근 대신 [beginIt, endIt] 및 [code, order]라는 비즈니스 명칭을 직접 선언할 수 있습니다.
// [Step 3: C++17 Structured Binding - 최종 완성형]
void CancelOrdersByStock_Final(const OrderContainer& orders, const std::string& stockCode) {
// 1. 범위의 시작과 끝 반복자를 직관적인 변수명으로 해체
auto [beginIt, endIt] = orders.equal_range(stockCode);
// 2. 루프 내 원소(*it) 역시 Key와 Value로 직관적 바인딩
for (auto it = beginIt; it != endIt; ++it) {
const auto& [code, order] = *it;
if (order.status == ORDER_PENDING) {
ProcessCancel(code, order);
}
}
}
4. 결론: 리팩토링이 가져다주는 가치
- 가독성 및 신속한 코드 파악: 후임 개발자나 미래의 내가 코드를 볼 때 복잡한 반복자 타입을 해석할 필요 없이 비즈니스 로직에만 집중할 수 있습니다.
- 실패 가능성 차단: 검색 범위를 잘못 지정하거나 반복자 비교 조건을 틀리는 휴먼 에러를 구조적으로 예방합니다.
- 성능 저하 제로: 모던 C++ 문법 요소(
auto,equal_range, Structured Binding)는 모두 컴파일 타임 추론 및 인라이닝이 이루어지므로 실행 성능 손실이 전혀 없습니다.
HTS/MTS처럼 높은 성능과 안정성이 요구되는 C++ 시스템일수록, **"어떻게 실행되는가(How)"**에 얽매이기보다 **"무엇을 하고자 하는가(What)"**를 명확히 보여주는 유연하고 정돈된 코드가 장기적으로 최고의 자산이 됩니다.