1293.102 Tick 메커니즘의 안티패턴
1. 안티패턴의 정의
안티패턴(anti-pattern)이란, 겉보기에는 합리적으로 보이지만 실제로는 성능 저하, 오류 유발, 유지보수 어려움 등의 부정적 결과를 초래하는 반복적 설계 또는 구현 패턴이다. 행동 트리의 Tick 메커니즘에서 흔히 발생하는 안티패턴을 식별하고 회피함으로써, 로봇 시스템의 신뢰성과 성능을 확보할 수 있다(Colledanchise & Ogren, 2018).
2. 블로킹 Tick
2.1 패턴 설명
노드의 tick() 함수 내에서 외부 서비스 호출, 파일 I/O, 네트워크 대기 등의 블로킹 연산을 수행하여 Tick 실행이 장시간 지연되는 패턴이다.
// 안티패턴: tick() 내 블로킹 서비스 호출
BT::NodeStatus tick() override {
auto response = service_client_->call(request_); // 수 ms ~ 수 s 블로킹
if (response.success) return BT::NodeStatus::SUCCESS;
return BT::NodeStatus::FAILURE;
}
2.2 문제점
- Tick 실행 시간이 비결정적으로 증가하여 데드라인 위반 발생
- 전체 트리의 응답성 저하
- 서비스 서버가 응답하지 않으면 Tick이 무한 대기
2.3 해결 방법
StatefulActionNode 패턴을 사용하여 비동기적으로 처리한다. onStart()에서 요청을 보내고, onRunning()에서 완료를 확인한다.
3. 과도한 Tick 빈도
3.1 패턴 설명
시스템의 실제 요구보다 훨씬 높은 빈도로 Tick을 실행하는 패턴이다. “빠를수록 좋다“는 잘못된 가정에 기인한다.
// 안티패턴: 서비스 로봇에 1000 Hz Tick
rclcpp::Rate rate(1000); // 불필요하게 높음
while (rclcpp::ok()) {
tree.tickOnce();
rate.sleep();
}
3.2 문제점
- 불필요한 CPU 자원 소비
- 전력 낭비 (배터리 구동 로봇에서 치명적)
- 동일한 센서 데이터의 중복 처리
- 다른 프로세스의 CPU 시간 박탈
3.3 해결 방법
로봇의 동작 특성과 센서 갱신 빈도에 맞는 적절한 Tick 주기를 설정한다.
4. 무분별한 ReactiveSequence 사용
4.1 패턴 설명
모든 Sequence를 ReactiveSequence로 구현하여, 매 Tick마다 모든 자식을 처음부터 재평가하는 패턴이다.
<!-- 안티패턴: 불필요한 전면 재평가 -->
<ReactiveSequence>
<Condition ID="IsMapLoaded"/> <!-- 변하지 않음 -->
<Condition ID="IsLocalized"/> <!-- 변하지 않음 -->
<Condition ID="IsBatteryAbove20"/> <!-- 드물게 변함 -->
<Action ID="NavigateToGoal"/> <!-- RUNNING 상태 -->
</ReactiveSequence>
4.2 문제점
- 변하지 않는 조건의 불필요한 매 Tick 재평가
- Tick 실행 시간 증가
- RUNNING 중인 액션이 매 Tick마다 불필요하게 Halt될 위험 (조건 실패 시)
4.3 해결 방법
실제로 매 Tick 재검사가 필요한 조건에만 ReactiveSequence를 적용하고, 정적 조건은 일반 Sequence(WithMemory)에 배치한다.
5. Halt 미구현
5.1 패턴 설명
비동기 액션 노드에서 onHalted() 함수를 구현하지 않거나, 형식적으로만 구현하여 자원 정리를 수행하지 않는 패턴이다.
// 안티패턴: Halt에서 자원 미정리
void onHalted() override {
// 아무것도 하지 않음 — goal_handle_ 누수
}
5.2 문제점
- 이전 ROS2 액션 목표가 취소되지 않아 액션 서버에 잔류
- 내부 상태가 초기화되지 않아 재시작 시 비정상 동작
- 자원 누수 (메모리, 연결, 핸들)
5.3 해결 방법
모든 비동기 노드에서 onHalted()를 구현하여, 진행 중인 작업을 취소하고 상태를 초기화한다.
6. 블랙보드 암묵적 의존
6.1 패턴 설명
노드가 포트 선언 없이 블랙보드에 직접 접근하여 데이터를 읽거나 쓰는 패턴이다.
// 안티패턴: 포트 선언 없이 직접 블랙보드 접근
BT::NodeStatus tick() override {
auto bb = config().blackboard;
auto goal = bb->get<geometry_msgs::msg::PoseStamped>("secret_goal");
// "secret_goal"이 포트에 선언되지 않음
}
6.2 문제점
- 데이터 의존성이 XML에서 보이지 않아 트리의 분석 및 디버깅이 어려움
- 키 이름 변경 시 영향 범위 파악 불가
- 서브트리 간 이름 충돌 위험
- Groot2에서 데이터 흐름이 시각화되지 않음
6.3 해결 방법
모든 입출력을 providedPorts()에 선언하고, getInput()/setOutput()을 통해 접근한다.
7. 조건 노드에서의 부수 효과
7.1 패턴 설명
조건 노드가 상태를 판단하는 것 외에 블랙보드 값을 변경하거나 외부 시스템에 명령을 보내는 등의 부수 효과(side effect)를 발생시키는 패턴이다.
// 안티패턴: 조건 노드에서 블랙보드 수정
BT::NodeStatus tick() override {
double battery = readBattery();
setOutput("battery_level", battery); // 부수 효과
setOutput("battery_status", "low"); // 부수 효과
return (battery > threshold_) ? NodeStatus::SUCCESS : NodeStatus::FAILURE;
}
7.2 문제점
- ReactiveSequence에서 매 Tick마다 부수 효과가 반복 실행
- 조건 평가 순서에 따라 동작이 달라지는 비결정적 행동
- 트리의 의미론적 명확성 훼손
7.3 해결 방법
조건 노드는 순수하게 상태를 판단만 하고, 데이터 변경은 별도의 액션 노드 또는 SetBlackboard 노드에서 수행한다.
8. 과도한 트리 깊이
8.1 패턴 설명
불필요한 중첩에 의해 트리의 깊이가 과도하게 깊어지는 패턴이다.
<!-- 안티패턴: 불필요한 중첩 -->
<Sequence>
<Sequence>
<Sequence>
<Condition ID="CondA"/>
<Action ID="ActA"/>
</Sequence>
</Sequence>
</Sequence>
8.2 문제점
- Tick 전파 경로 연장에 의한 실행 시간 증가
- 트리의 가독성 저하
- 디버깅 시 상태 추적의 복잡성 증가
8.3 해결 방법
논리적으로 동등한 단순한 구조로 리팩토링한다.
9. Tick 내 무한 루프
9.1 패턴 설명
노드의 tick() 함수 내에서 조건이 충족될 때까지 루프를 실행하여 Tick이 완료되지 않는 패턴이다.
// 안티패턴: tick() 내 대기 루프
BT::NodeStatus tick() override {
while (!isGoalReached()) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
// Tick이 영원히 반환되지 않음
}
return BT::NodeStatus::SUCCESS;
}
9.2 문제점
- Tick이 무한 대기하여 전체 트리가 정지
- 다른 노드의 Halt 처리 불가
- Tick 데드라인 위반
9.3 해결 방법
RUNNING을 반환하고 다음 Tick에서 재확인하는 StatefulActionNode 패턴을 사용한다.
10. 안티패턴 요약
| 안티패턴 | 핵심 문제 | 대안 |
|---|---|---|
| 블로킹 Tick | 데드라인 위반 | StatefulActionNode |
| 과도한 빈도 | 자원 낭비 | 적절한 주기 설정 |
| 무분별한 Reactive | 불필요한 재평가 | WithMemory 병행 |
| Halt 미구현 | 자원 누수 | onHalted() 완전 구현 |
| 암묵적 의존 | 투명성 결여 | 포트 시스템 사용 |
| 조건 부수 효과 | 비결정적 동작 | 순수 조건, 별도 액션 |
| 과도한 깊이 | 성능·가독성 저하 | 구조 평탄화 |
| Tick 내 루프 | 트리 정지 | RUNNING 반환 패턴 |
이러한 안티패턴의 회피는 코드 리뷰와 정적 분석을 통해 체계적으로 관리해야 한다.
참고 문헌
- Colledanchise, M., & Ogren, P. (2018). Behavior Trees in Robotics and AI: An Introduction. CRC Press.
- Faconti, D. (2022). BehaviorTree.CPP documentation and API reference. https://www.behaviortree.dev/