데코레이터 노드 설계의 안티패턴 (Anti-Patterns in Decorator Node Design)
1. 개요
데코레이터 노드 설계의 안티패턴은 데코레이터를 구현할 때 발생하는 잘못된 설계 관행을 식별하고 문제점과 개선 방법을 제시한다.
2. 안티패턴 목록
2.1 RUNNING 상태의 변환
문제: 자식의 RUNNING을 SUCCESS나 FAILURE로 변환하면 비동기 실행 추적과 halt 메커니즘이 파괴된다.
개선: RUNNING은 항상 그대로 전달한다.
2.2 halt()에서 내부 상태 초기화 누락
문제: 카운터, 타이머 등이 halt 시 리셋되지 않으면 다음 실행에 잔여 상태가 영향을 미친다.
개선: halt()에서 모든 내부 상태를 초기화하고 기반 클래스의 halt()를 호출한다.
2.3 tick() 직접 호출
문제: child_node_->tick() 대신 executeTick()을 사용하지 않으면 노드의 상태 관리 로직이 누락된다.
개선: 항상 child_node_->executeTick()을 사용한다.
2.4 과도한 데코레이터 중첩
문제: 4단계 이상의 데코레이터 중첩은 가독성을 심각하게 저하시킨다.
개선: 3단계를 초과하면 서브트리로 분리한다.
2.5 ForceSuccess로 안전 행동 감싸기
문제: 안전에 중요한 행동의 실패를 ForceSuccess로 은폐하면 안전 보호가 무력화된다.
개선: 안전 행동에 ForceSuccess를 적용하지 않는다.
2.6 Timeout 없는 무한 반복
문제: Repeat(-1) 또는 KeepRunningUntilFailure를 Timeout 없이 사용하면 영원히 종료되지 않을 수 있다.
개선: 반드시 Timeout 또는 외부 조건에 의한 종료 메커니즘과 결합한다.
2.7 이중 Inverter
문제: Inverter(Inverter(C))는 항등 변환으로 무의미하다.
개선: 이중 Inverter를 제거한다.
2.8 Retry + ForceSuccess 내부
문제: Retry(ForceSuccess(Action))에서 ForceSuccess가 FAILURE를 차단하여 Retry가 항상 즉시 성공한다.
개선: ForceSuccess를 Retry 외부에 배치하거나 제거한다.
2.9 자식 미실행 시 halt 누락
문제: 전처리에서 자식을 skip할 때, 이전에 RUNNING이었던 자식의 halt를 호출하지 않으면 리소스 누수가 발생한다.
개선: 자식을 skip하기 전에 haltChild()를 호출한다.
2.10 부작용을 포함하는 데코레이터
문제: 데코레이터가 토픽 발행, 서비스 호출 등의 부작용을 포함하면 예측 불가능한 동작이 발생할 수 있다.
개선: 부작용은 액션 노드에서만 수행한다.
3. 점검 체크리스트
| 번호 | 점검 항목 |
|---|---|
| 1 | RUNNING을 변환하지 않는가? |
| 2 | halt()에서 내부 상태를 초기화하는가? |
| 3 | executeTick()을 사용하는가? |
| 4 | 중첩 깊이가 3단계 이내인가? |
| 5 | 안전 행동에 ForceSuccess를 적용하지 않았는가? |
| 6 | 무한 반복에 종료 메커니즘이 있는가? |
| 7 | 이중 Inverter가 없는가? |
| 8 | 자식 skip 시 haltChild()를 호출하는가? |
4. 참고 문헌
- Colledanchise, M., & Ogren, P. (2018). Behavior Trees in Robotics and AI: An Introduction. CRC Press.
- BehaviorTree.CPP 공식 문서. https://www.behaviortree.dev/
| 버전 | 날짜 | 변경 사항 |
|---|---|---|
| v0.1 | 2026-04-05 | 초안 작성 |