초당 수만 건의 체결 데이터와 주문 내역이 쏟아지는 증권사 HTS(Home Trading System) 및 MTS 서버 환경에서 C++은 압도적인 성능과 메모리 통제권을 제공하는 핵심 언어입니다.
하지만 성능에만 과도하게 치중하다 보면, 코드의 가독성과 유지보수성을 놓치기 쉽습니다. 과거의 C++(C++03 이하)이 "기계가 이해하기 좋은 방식"으로 메모리와 반복자를 수동 통제했다면, C++11 이후의 모던 C++(Modern C++)은 "개발자의 '의도(Intent)'를 코드에 명확히 드러내어 휴먼 에러를 방지하는 것"에 초점을 맞춥니다.
오늘은 HTS 실무에서 자주 발생하는 '특정 종목의 복수 주문 내역(`std::multimap`) 조회 및 순회 시나리오'를 통해 레거시 C++ 코드를 모던 C++ 스타일로 리팩토링하는 과정을 소개합니다.
1. 실무 시나리오: 특정 종목의 모든 주문 조회
- 종목코드(예:
"005930"- 삼성전자)를 키(Key)로 하고, 주문 정보(Order객체)를 값(Value)으로 갖는std::multimap데이터 구조가 존재함. - 특정 종목코드로 들어온 모든 주문을 검색하여 체결 처리 및 화면에 출력해야 함.
2. 기존 레거시 C++03 스타일 코드의 한계
과거 C++03 방식에서는 lower_bound와 upper_bound를 조합하거나, 길고 복잡한 반복자(Iterator) 타입을 직접 명시하곤 했습니다.
// [C++03 기존 스타일 레거시 코드]
#include <iostream>
#include <map>
#include <string>
struct Order {
long long orderId;
int price;
int qty;
};
typedef std::multimap<std::string, Order> OrderMap;
void ProcessOrders(const OrderMap& mapOrders, const std::string& stockCode) {
// 1. 복잡하고 긴 반복자 타입 명시
OrderMap::const_iterator itLower = mapOrders.lower_bound(stockCode);
OrderMap::const_iterator itUpper = mapOrders.upper_bound(stockCode);
// 2. 범위를 직접 비교하며 순회
for (OrderMap::const_iterator it = itLower; it != itUpper; ++it) {
std::cout << "종목: " << it->first
<< " | 주문번호: " << it->second.orderId
<< " | 가격: " << it->second.price << "\n";
}
}
- 시각적 노이즈(Type Noise):
OrderMap::const_iterator와 같은 장황한 반복자 선언이 반복되어 정작 핵심 로직인 주문 처리 알고리즘이 눈에 들어오지 않습니다. - 의도의 불명확함:
lower_bound와upper_bound를 각각 따로 호출하는 것은 "해당 키의 전체 구간(Range)을 얻고 싶다"는 개발자의 의도 대신, 구간을 구하기 위한 세부 단계(How)만 나열되어 있습니다. - 휴먼 에러 가능성: 두 번의 검색 함수 호출 과정에서 키값을 잘못 전달하거나, 루프 조건식 비교에서
itUpper대신 다른 범위를 대입하는 실수를 저지를 수 있습니다.
3. Modern C++ 단계별 리팩토링
Step 1. auto 키워드로 타임 노이즈 제거 (C++11)
C++11의 auto를 도입하면 컴파일러가 타입을 추론하므로, 복잡한 const_iterator 구문을 제거해 코드가 훨씬 간결해집니다.
// [Step 1: auto 도입]
void ProcessOrders_Step1(const OrderMap& mapOrders, const std::string& stockCode) {
auto itLower = mapOrders.lower_bound(stockCode);
auto itUpper = mapOrders.upper_bound(stockCode);
for (auto it = itLower; it != itUpper; ++it) {
std::cout << "주문번호: " << it->second.orderId << "\n";
}
}
Step 2. std::equal_range 사용으로 개발자의 '의도' 명확화 (C++11)
lower_bound와 upper_bound를 각각 호출하는 대신, std::equal_range (또는 multimap::equal_range)를 사용하면 특정 키에 해당하는 시작과 끝 반복자 쌍(std::pair)을 단 한 번의 호출로 반환받습니다.
"이 키값에 해당하는 모든 원소의 범위를 한 번에 추출하겠다"는 비즈니스 의도를 한 단어로 명확히 전달합니다.
// [Step 2: equal_range 활용]
void ProcessOrders_Step2(const OrderMap& mapOrders, const std::string& stockCode) {
// [itBegin, itEnd] 쌍을 한 번에 반환
auto range = mapOrders.equal_range(stockCode);
for (auto it = range.first; it != range.second; ++it) {
std::cout << "주문번호: " << it->second.orderId << "\n";
}
}
Step 3. C++17 구조화된 바인딩 (Structured Binding)으로 완성
C++17의 구조화된 바인딩(Structured Binding)을 활용하면 range.first, range.second 같은 불투명한 멤버 이름 대신, [rangeBegin, rangeEnd]라는 직관적인 이름을 부여할 수 있습니다.
// [Step 3: C++17 Structured Binding 적용 - 최종 완성형]
void ProcessOrders_Final(const OrderMap& mapOrders, const std::string& stockCode) {
// range.first -> rangeBegin, range.second -> rangeEnd로 직관적 분해
auto [rangeBegin, rangeEnd] = mapOrders.equal_range(stockCode);
for (auto it = rangeBegin; it != rangeEnd; ++it) {
const auto& [code, order] = *it; // 요소 역시 key, value로 분해 가능
std::cout << "종목: " << code
<< " | 주문번호: " << order.orderId
<< " | 수량: " << order.qty << "\n";
}
}
4. 리팩토링 전/후 비교 및 가치 분석
- 가독성 향상:
OrderMap::const_iterator스타일의 불필요한 보일러플레이트 코드 대폭 감소. - 의도 명확화:
equal_range함수 사용으로 "이 범위 전체를 가져온다"는 의미가 코드에 직접 드러남. - 안전성 향상: 반복자 경계 조건 착오나 키 입력 오타 등 자잘한 휴먼 에러의 여지를 차단.
5. 마무리
좋은 코드는 "컴파일러가 잘 해석하는 코드"에 그치지 않고, "함께 일하는 동료 개발자와 미래의 나 자신이 빠르게 읽고 의도를 파악할 수 있는 코드"입니다.
기존 프로젝트의 C++ 레거시 코드를 다루고 계신다면, 불필요한 반복자 타입 나열을 auto와 equal_range, 그리고 Structured Binding으로 전환해 보세요. 소소한 리팩토링만으로도 버그는 줄어들고 코드의 품격은 크게 향상될 것입니다.