조건 노드 설계의 안티패턴 (Anti-Patterns in Condition Node Design)

조건 노드 설계의 안티패턴 (Anti-Patterns in Condition Node Design)

1. 개요

조건 노드 설계의 안티패턴(anti-pattern)은 조건 노드를 구현할 때 흔히 발생하는 잘못된 설계 관행을 식별하고 그 문제점과 개선 방법을 제시하는 것이다. 안티패턴은 표면적으로는 동작하는 것처럼 보이나, 안전성, 성능, 유지보수성 측면에서 심각한 문제를 내포한다.

2. 안티패턴 목록

2.1 RUNNING을 반환하는 조건 노드

문제: 조건 노드에서 RUNNING을 반환하면, ReactiveSequenceReactiveFallback의 반응적 동작이 파괴된다.

// 안티패턴
BT::NodeStatus tick() override
{
    if (!data_ready_)
        return BT::NodeStatus::RUNNING;  // 절대 금지
    // ...
}

개선: 데이터가 준비되지 않았으면 FAILURE를 반환한다.

2.2 부작용을 포함하는 조건 노드

문제: 조건 노드에서 토픽 발행, 서비스 호출, 블랙보드 쓰기 등의 부작용이 있으면, ReactiveSequence에 의한 반복 평가에서 부작용이 다중 실행된다.

// 안티패턴
BT::NodeStatus tick() override
{
    cmd_vel_pub_->publish(stop_cmd_);  // 부작용!
    return checkCondition() ? SUCCESS : FAILURE;
}

개선: 부작용은 액션 노드에서만 수행한다.

2.3 차단 호출을 포함하는 조건 노드

문제: 동기 서비스 호출, 긴 대기 루프 등이 tick 주기를 지연시킨다.

// 안티패턴
BT::NodeStatus tick() override
{
    auto result = client_->send_request_and_wait(request);  // 차단!
    return evaluate(result);
}

개선: 비동기 호출과 캐싱을 사용하거나, 별도의 액션 노드로 분리한다.

2.4 과도하게 복잡한 조건 노드

문제: 단일 조건 노드에서 복수의 관련 없는 조건을 평가하면, 재사용성이 저하되고 테스트가 어려워진다.

// 안티패턴
BT::NodeStatus tick() override
{
    if (battery_ok_ && gps_ok_ && wind_ok_ && temp_ok_)
        return SUCCESS;
    return FAILURE;
}

개선: 개별 조건을 별도의 노드로 분리하고, Sequence로 결합한다.

2.5 하드코딩된 임계값

문제: 임계값이 코드에 직접 기입되어 있으면, 변경 시 재컴파일이 필요하다.

// 안티패턴
if (battery_level > 0.2)  // 매직 넘버

개선: 입력 포트를 통해 임계값을 매개변수화한다.

2.6 무효 데이터에 대한 미처리

문제: NaN, Inf, 빈 배열 등의 무효 데이터를 검증하지 않으면, 잘못된 조건 평가가 발생한다.

// 안티패턴
BT::NodeStatus tick() override
{
    double value = msg->data;  // NaN 검사 없음
    return (value > threshold) ? SUCCESS : FAILURE;
    // NaN > threshold → false, 그러나 이것이 의도된 동작인가?
}

개선: std::isfinite()로 검증 후 평가한다.

2.7 메시지 미수신 시 SUCCESS 반환

문제: 센서 데이터가 수신되지 않은 상태에서 SUCCESS를 반환하면, 안전 조건이 충족되지 않았음에도 행동이 실행된다.

// 안티패턴
BT::NodeStatus tick() override
{
    if (!msg)
        return BT::NodeStatus::SUCCESS;  // 위험!
    // ...
}

개선: 데이터 부재 시 FAILURE를 반환한다(실패 안전 원칙).

2.8 스레드 안전성 미보장

문제: 콜백 스레드와 tick 스레드 사이에서 공유 데이터에 대한 동기화 없이 접근하면 데이터 경합이 발생한다.

// 안티패턴
void callback(const MsgPtr& msg) { last_msg_ = msg; }
BT::NodeStatus tick() { return evaluate(last_msg_); }
// 동시 접근에 대한 뮤텍스 없음!

개선: std::mutex로 공유 데이터를 보호한다.

2.9 과도한 로깅

문제: 매 tick마다 상세한 로그를 출력하면 I/O 오버헤드가 발생하고, 로그 파일이 급격히 증가한다.

개선: 상태 전환 시에만 로깅하거나, DEBUG 수준으로 설정하여 필요시에만 활성화한다.

2.10 조건 노드에서 상태 머신 구현

문제: 조건 노드 내부에 복잡한 상태 머신을 구현하면, 노드의 동작이 이전 tick의 이력에 의존하여 예측이 어려워진다.

개선: 상태 추적이 필요한 경우 블랙보드를 활용하거나, 행동 트리의 구조적 메커니즘(데코레이터, 제어 노드)을 사용한다. 히스테리시스와 같이 필수적인 내부 상태는 최소한으로 유지한다.

3. 안티패턴 점검 체크리스트

번호점검 항목준수 여부
1RUNNING을 반환하지 않는가?
2외부 상태를 변경하지 않는가?
3차단 호출이 포함되지 않았는가?
4단일 조건만을 평가하는가?
5임계값이 매개변수화되어 있는가?
6무효 데이터를 검증하는가?
7데이터 부재 시 FAILURE를 반환하는가?
8스레드 안전성이 보장되는가?
9로깅이 적절한 수준인가?
10내부 상태가 최소한으로 유지되는가?

4. 참고 문헌

  • Colledanchise, M., & Ogren, P. (2018). Behavior Trees in Robotics and AI: An Introduction. CRC Press.
  • BehaviorTree.CPP 공식 문서. https://www.behaviortree.dev/

버전날짜변경 사항
v0.12026-04-04초안 작성