초당 수만 건의 체결 데이터와 주문 내역이 쏟아지는 증권사 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";
    }
}
🚨 레거시 코드의 문제점:
  1. 시각적 노이즈(Type Noise): OrderMap::const_iterator와 같은 장황한 반복자 선언이 반복되어 정작 핵심 로직인 주문 처리 알고리즘이 눈에 들어오지 않습니다.
  2. 의도의 불명확함: lower_bound와 upper_bound를 각각 따로 호출하는 것은 "해당 키의 전체 구간(Range)을 얻고 싶다"는 개발자의 의도 대신, 구간을 구하기 위한 세부 단계(How)만 나열되어 있습니다.
  3. 휴먼 에러 가능성: 두 번의 검색 함수 호출 과정에서 키값을 잘못 전달하거나, 루프 조건식 비교에서 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)을 단 한 번의 호출로 반환받습니다.

💡 equal_range의 의도 표현:
"이 키값에 해당하는 모든 원소의 범위를 한 번에 추출하겠다"는 비즈니스 의도를 한 단어로 명확히 전달합니다.
// [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으로 전환해 보세요. 소소한 리팩토링만으로도 버그는 줄어들고 코드의 품격은 크게 향상될 것입니다.