
Dead Code Finder: 예상보다 어려웠던 GitLab Orbit 기반 정적 분석
요약
GitLab Duo Agent Platform을 활용하여 실제 호출되지 않는 데드 코드를 찾는 정적 분석 과정을 다룹니다. 에이전트의 도구 접근 권한 문제와 스킬 주입의 불확실성을 해결하기 위한 폴백(fallback) 메커니즘 구축 경험을 공유합니다.
핵심 포인트
- GitLab Orbit 기반 에이전트의 도구 누락 문제 확인
- 시스템 프롬프트 인라인 포함 및 파일 기반 폴백 모드 구축
- 정적 분석 시 매직 메서드(__init__ 등)의 에지 카운트 오류 주의
저는 해커톤을 위해 GitLab Duo Agent Platform 플로우를 구축했습니다. 목표는 간단했습니다. 실제로 아무것도 호출하지 않는 코드를 찾는 것이었습니다.
"이것을 삭제하면 무엇이 망가지는가"가 아닙니다. 그 질문에는 이미 수십 개의 도구가 있습니다. 저는 더 좁은 범위의 질문을 원했습니다:
query_graph와 get_graph_schema가 플로우(flow)의 YAML 설정에는 선언되어 있었지만, 플로우가 실행될 때 실제 도구 세트(toolset)에는 포함되어 있지 않았습니다. 권한 문제는 아니었습니다. 그래프 자체는 완전히 쿼리 가능한 상태였습니다. 이후 glab orbit remote query CLI를 통해 실제 SKILL.md 절차를 수동으로 실행하여 실제 CALLS/IMPORTS 결과를 받아냄으로써 이 사실을 확인했습니다. 문제는 이 환경의 커스텀 플로우(custom flows)가 특정 기초 에이전트(foundational agents)들처럼 해당 도구들에 접근할 수 있는 문서화된 경로를 가지고 있지 않다는 점이었습니다.
이와 별개로, 스킬 주입(skill injection) 또한 신뢰할 수 없었습니다. 때때로 플로우는 실제 절차 본문(procedure body) 대신 SKILL.md에 대한 매니페스트 항목(manifest entry), 즉 이름과 설명만을 전달받았습니다.
그래서 저는 두 가지 조치를 취했습니다. 첫째, 확실한 폴백(fallback) 수단으로서 전체 절차를 시스템 프롬프트(system prompt)에 직접 인라인(inline)으로 포함시켰고, 둘째, 그래프 도구가 누락되었을 때를 대비한 파일 기반 폴백 모드(file-based fallback mode)를 구축했습니다. 폴백 모드에서 플로우는 list_repository_tree, get_repository_file, find_files, blob_search를 사용하여 실제 저장소(repo) 파일을 읽고, 동일한 휴리스틱(heuristics)을 적용하며, 모든 발견 사항에 [INFERRED] 라벨을 붙입니다. 그리고 어떤 도구가 누락되었는지 명시적인 배너와 함께 보고서를 엽니다.
그래프 근거가 없을 때는 있는 척하지 않습니다.
2. 정적 분석은 보기보다 복잡합니다
단순한 에지 카운트(edge-count) 체크가 잘못 판단하는 몇 가지 사례는 다음과 같습니다:
__init__, __enter__, __exit__, __del__
이들은 단순한 체크 방식에서는 거의 항상 데드 코드(dead code)처럼 보입니다. 클래스를 인스턴스화(instantiate)할 때, Orbit은 __init__이 아니라 해당 클래스에 CALLS 에지(edge)를 등록합니다. 따라서 __init__은 클래스가 수십 번 인스턴스화되더라도 들어오는 에지(incoming edges)가 0개로 표시됩니다. 저는 전체 유닛 테스트(unittest) 스위트를 갖춘 실제 약 500라인 규모의 SDK 클래스(EphemeralAgentExecutor)를 통해 이를 확인했습니다. 체크 방식을 감싸고 있는 클래스의 들어오는 에지를 확인하도록 수정함으로써 오탐(false positives)을 해결했습니다.
데코레이터 기반 디스패치 (Decorator-based dispatch)
데코레이터(decorator)를 통해 딕셔너리(dict)에 등록되고 나중에 문자열 키(string-key) 조회로 호출되는 함수는 정적 호출 그래프(static call graph) 상에서 구조적으로 데드 코드(dead code)와 구별할 수 없습니다. 소스 코드 내에 명시적인 호출(literal call)이 존재하지 않기 때문입니다. 이러한 사례들은 실제 이유와 함께 '건너뜀(Skipped)' 항목으로 분류됩니다.
테스트 프레임워크 리플렉션 (Test framework reflection)
unittest.TestCase 메서드들은 어디에서도 명시적인 호출을 통해 발견되는 것이 아니라, 클래스 속성에 대한 리플렉션(reflection)을 통해 발견됩니다. 테스트 러너(test runner)는 정적 그래프가 볼 수 없는 방식으로 런타임(runtime)에 이들을 찾아냅니다. 앞선 사례와 동일한 분류군이며, 동일한 이유로 처리됩니다.
상속 및 MRO (Inheritance and MRO)
메서드를 오버라이드(override)하지 않는 서브클래스(subclass)를 통해서만 도달 가능한 메서드는 베이스 클래스(base class) 메서드 자체에 직접적인 들어오는 에지(incoming edges)가 없는 것으로 나타날 수 있습니다. 이러한 사례들은 데드 코드로 플래그(flag)를 지정하는 대신 '불확실함(Uncertain)'으로 분류됩니다.
검증 (Validation)
저는 까다로운 사례들을 집중적으로 다루기 위해 다음과 같은 테스트 픽스처(test fixtures)를 구축했습니다: 일반적인 미사용 함수, 파일 간 임포트(import) 해결, 상속/MRO, 데코레이터 디스패치, 생성자/던더(constructor/dunder) 처리, 그리고 unittest 리플렉션입니다.
폴백 모드(fallback mode)에서의 두 차례 실전 실행 결과, 심어진 모든 데드 코드 픽스처를 파일 및 라인 증거와 함께 정확히 식별해냈습니다. 또한 데코레이터 및 테스트 발견 사례들을 각각의 실제 이유와 함께 정확히 제외했으며, 모호한 상속 및 스크립트 진입점(entry point) 사례들을 '불확실함(Uncertain)'으로 정확히 격하시켰습니다.
그 후, 폴백 모드의 파일 기반 추측이 실제 그래프 데이터와 일치하는지 확인하기 위해 CLI를 통해 실제 그래프를 대상으로 실제 Orbit 순회(traversal) 절차를 수동으로 실행했습니다.
결과:
totally_unused_helper— 유입되는 CALLS/IMPORTS 엣지(edge)가 없음. 실제로 데드 코드(dead code)임.cross_file_dead_helper— 유입되는 엣지가 없음. 실제로 데드 코드임.undecorated_dead_function— 유입되는 엣지가 없음. 실제로 데드 코드임.Base.greet—Child.run으로부터 실제 유입 엣지가 존재함. 상속을 통해 도달 가능(reachable)함. 폴백 모드(fallback mode)가 이를 데드 코드로 표시하는 대신 '불확실(Uncertain)'로 올바르게 분류함.summarize_text— 스크립트 엔트리 포인트(entry point)를 통해 살아있음(alive). 올바르게 '불확실(Uncertain)'로 분류됨.__init__,__exit__,__del__— 직접적인 유입 엣지는 없으나, 감싸고 있는 클래스에서 실제로 사용됨. 생성자 수정(constructor correction)이 검증됨.
실제 그래프는 폴백 모드에서 확인할 수 없었던 사항도 드러냈습니다: add_temp_file과 issue_credential은 유입되는 CALLS 엣지가 0개로 나타납니다. 파일 읽기만으로는 결론을 내릴 수 없었던, 잠재적인 실제 데드 코드일 가능성이 있습니다.
모든 폴백 결과가 실제 그래프와 일치함을 확인했습니다.
다르게 시도했을 점
절차 로직은 결과적으로 신뢰할 수 있는 부분이었습니다. 플랫폼 통합이 신뢰할 수 없는 부분이었는데, 저는 이를 사전에 충분히 고려하지 못했습니다.
로직을 작성하기 전에 런타임(runtime)에서 실제로 어떤 도구들을 사용할 수 있는지 확인하는 것부터 시작했을 것입니다. 폴백 모드를 구축하는 것이 어렵지는 않았지만, 처음부터 두 경로 모두를 고려하여 설계했다면 더 깔끔했을 것입니다.
아직 남아있는 과제
- 실제 Orbit 도구들을 워크플로우(flow)에 연결 (아직 커스텀 워크플로우를 위한 문서화된 경로가 없음)
- Python 이외의 언어로 검증 범위 확장
- 전이적 데드 코드(transitive dead code) 탐지 (다른 데드 코드에 의해서만 참조되는 코드)
- 코드 이외의 파일에서의 참조: YAML, CI 템플릿, cron 작업
실제로 배운 점
진정으로 알 수 없는 경우에
그 결과는 플랫폼에도 동일하게 적용되었습니다. Orbit 도구들을 사용할 수 없다는 것을 확인했을 때, 올바른 결정은 그것을 실제 결과물처럼 보이게 만드는 대체 수단(fallback)으로 가리는 것이 아니라, 보고서에 명확하게 밝히는 것이었습니다.
세 가지 버킷(three-bucket)으로 나누어 출력하는 방식 또한 동일한 직관에서 비롯되었습니다. 대부분의 데드 코드(dead-code) 도구들은 평면적인 리스트(flat list)를 제공합니다. 하지만 이러한 평면적인 리스트는 결과가 몇 번 틀리기 시작하면 사용자가 이를 무시하도록 길들입니다. 불확실성을 명시적으로 라벨링(labeling)하는 것이야말로, 확신을 가지고 찾아낸 결과물들이 실제로 행동에 옮길 가치가 있게 만드는 핵심입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기
