본문으로 건너뛰기

© 2026 Molayo

Qiita헤드라인2026. 06. 23. 22:38

「작은 변경」은 거짓말이었다: 1줄 수정이 의존 그래프(Dependency Graph)를 통해 28개 파일로 파급된 사례

요약

코드 수정 시 발생하는 영향 범위를 의존 그래프(Dependency Graph)를 통해 실측하고 분석하는 방법론을 다룹니다. 인간의 인지 한계로 인해 발생하는 '작은 변경'의 오류를 방지하기 위한 구체적인 절차와 도구 활용의 중요성을 강조합니다.

핵심 포인트

  • 코드 수정 시 직관적인 영향 범위 예측은 실제와 다를 수 있음
  • 의존 그래프를 통해 변경 사항이 파급되는 파일 수를 실측해야 함
  • 인간의 인지 능력 한계인 '빙산의 예측' 오류를 경계해야 함
  • 검증 가능한 근거를 바탕으로 영향 범위를 파악하는 절차가 필요함

「이것은 작은 변경이라서 영향 범위가 좁습니다」

리뷰에서 이 문장을 쓴 적이 저에게도 몇 번 있습니다. 그리고 그중 몇 번은 거짓말이었습니다. 악의적인 거짓말이 아니라, 저 스스로도 진심으로 그렇게 믿었던 거짓말입니다. 1줄을 고쳤다고 생각했는데, 운영 환경에서 전혀 관계없을 터인 화면이 다운됩니다. 원인을 추적해 보니, 그 1줄이 생각지도 못한 곳까지 연결되어 있었습니다.

이 기사에서는 「작은 변경」이라는 예측이 왜 빗나가는지를, 코드베이스를 의존 그래프(Dependency Graph)화하여 실측한 결과로 보여드립니다. 제 수중에 있는 리포지토리(Repository)에서, 하나의 함수를 변경했을 때 파급되는 파일 수를 세어 보았습니다. 직관적으로는 5개 파일 정도일 것이라 예상했던 수정이, 실제로는 28개 파일로 이어져 있었습니다.

마지막에는 영향 범위를 직접 눈으로 확인하기 위한 구체적인 절차와 도구도 소개하겠습니다. 단순히 해설로 끝내지 않겠습니다.

먼저, 유사한 테마와의 차이점을 명시해 두겠습니다.

코드를 그래프(Graph)로 다루는 이야기는 몇 가지 문맥에서 이야기됩니다. 혼동하면 논점이 흐려지므로 축을 나누겠습니다.

목적이 기사와의 관계
GraphRAG대량의 문서 검색 정확도를 높임별개의 축 (검색 이야기)
AI 코드 리뷰 (AI Code Review)리뷰 시 토큰(Token) 소비를 줄임별개의 축 (AI 이야기)
영향 범위 예측인간이 변경 전에 파급처를 읽음본 기사

본 기사가 다루는 것은 가장 아래입니다. AI가 똑똑하게 리뷰하게 만드는 이야기도, 검색을 빠르게 만드는 이야기도 아닙니다. 저라는 인간이 「이 변경이 어디까지 울려 퍼지는가」를 다 읽어내기 위한 이야기입니다.

구체적인 사례부터 시작하겠습니다. 얼마 전, 저는 모노레포(Monorepo)의 리뷰를 맡게 되었습니다. 차분(Diff)은 500줄 정도였습니다. 담당자의 설명에는 「경미한 리팩터링(Refactoring)입니다」라고 적혀 있었습니다.

저는 순순히 믿었습니다. 그리고 운영 환경에 배포한 다음 날 아침, 전혀 관계없다고 생각했던 관리 화면의 CSV 익스포트(Export) 기능이 다운되었습니다. 원인 조사에 금요일 오후 3시간을 허비했습니다.

추적해 보니, 리팩터링으로 변경한 공통 함수가 익스포트 처리의 깊숙한 곳에서 호출되고 있었습니다. 차분 파일 목록에는 그 관리 화면이 단 하나도 나오지 않았습니다. grep으로 import 문을 쫓고, 수동으로 콜 스택(Call Stack)을 추적하며, 마지막에는 「아마 이걸로 전부겠지」라고 기도하며 Approve를 누릅니다. 그 기도는 대개 빗나갑니다.

이때 뼈저리게 느낀 것은, 「경미하다」라는 말에 검증 가능한 근거가 없었다는 점입니다. 아무도 영향 범위를 세어보지 않았습니다. 세어 보았다면 익스포트가 파급처에 포함되어 있다는 것을 한눈에 알 수 있었을 것입니다.

애초에 왜 우리는 영향 범위를 잘못 읽는 것일까요?

이유는 간단합니다. 인간이 머릿속으로 쫓을 수 있는 의존(Dependency)의 깊이에는 한계가 있기 때문입니다. 당신이 고치는 함수를 호출하는 함수. 그것을 다시 호출하는 함수. 테스트. 미들웨어(Middleware). 타입 정의(Type Definition). 이 연쇄를 2단계, 3단계로 쫓다 보면 뇌의 스택(Stack)은 넘쳐버립니다.

저는 이것을 「빙산의 예측」이라고 부릅니다. 수면 위에 나와 있는 부분, 즉 직접적인 호출부(Caller)만 보고 「작다」고 판단합니다. 수면 아래에 몇 단계의 의존이 매달려 있는지는 보이지 않습니다.

구체적인 상황으로 생각해 봅시다. 어떤 유틸리티 함수의 반환값을 None을 반환할 수 있는 형태로 바꾼다고 가정해 봅시다. 직접적인 호출부는 2곳입니다. 여기까지는 누구나 볼 수 있습니다. 하지만 그 2곳을 호출하는 함수, 다시 그 결과를 사용하는 API 핸들러(Handler), 핸들러를 호출하는 테스트까지 쫓아가면 풍경이 달라집니다.

「직접적인 호출부가 적다」는 것과 「영향 범위가 좁다」는 것은 별개의 문제입니다. 직접적인 호출부가 2개뿐이라도, 그 너머에 깊은 의존의 연쇄가 있다면 파급은 단번에 확산됩니다.

영향 범위를 정확하게 읽으려면 코드를 「파일의 집합」이 아니라 「그래프(Graph)」로 볼 필요가 있습니다.

소프트웨어의 코드베이스는 본질적으로 그래프 구조를 가지고 있습니다.

  • 함수가 다른 함수를 호출함 (호출 그래프, Call Graph)
  • 클래스가 다른 클래스를 상속함 (상속 그래프, Inheritance Graph)
  • 모듈이 다른 모듈을 import함 (의존 그래프, Dependency Graph)

이 관계를 명시적으로 그래프화하면, 「이 함수를 바꾸면 영향을 받는 곳은 어디인가」라는 질문에 탐색(Search)으로 답할 수 있게 됩니다. grep으로 함수명을 검색하며 「아마 이쯤이 호출부겠지」라고 기도하는 작업에서 졸업할 수 있습니다.

그래프화의 기반 기술이 Tree-sitter입니다. 소스 코드를 추상 구문 트리(AST, Abstract Syntax Tree)로 파싱(Parsing)하는 라이브러리로, 19개 이상의 언어를 지원하며 LLM을 사용하지 않고 로컬에서 빠르게 동작합니다. 파일 내용을 외부로 보내지 않으므로, 기밀성이 높은 코드라도 안심하고 구조만 추출할 수 있습니다.

Tree-sitter는 함수, 클래스, import, 호출 관계를 노드(Node)와 엣지(Edge)로 변환합니다. 구체적으로 살펴보겠습니다. 다음과 같은 흔한 코드가 있다고 가정해 봅시다.

class UserService:
    def update_user(self, user: User) -> None:
        self.db.save(user)
        ...

Tree-sitter는 이를 기계적으로 노드와 엣지로 분해합니다.

노드:
(:Class {name: "UserService"})
(:Function {name: "update_user"})
...

update_userdb.savenotify_change를 호출하고 있으므로, 2개의 CALLS 엣지가 연결됩니다. 이러한 작은 축적이 결과적으로 코드베이스 전체의 지도가 됩니다.

주요 엣지의 종류를 정리해 두겠습니다.

엣지의미방향
CALLS함수 호출호출자 → 피호출자
...

영향 범위 탐색이란, 변경한 함수의 노드로부터 이러한 엣지를 역방향으로 따라가는 작업에 다름 아닙니다. "누가 나를 호출하고 있는가"를 추측 없이 그래프상의 사실로서 열거하는 것입니다.

여기서부터가 본론입니다. 제가 자신의 리포지토리에서 실제로 측정한 결과를 보여드리겠습니다.

대상은 Python으로 작성된 중규모 API 서버(약 480개 파일)입니다. 여기서 인증 관련 유틸리티 함수인 get_current_user()의 반환 타입(Return Type)을 한 곳 변경한다는 상황을 가정했습니다. 코드베이스를 의존 그래프(Dependency Graph)로 만들고, 이 함수를 기점으로 홉(Hop) 수에 따른 파급 범위를 계산했습니다.

홉 거리의미파급 파일 수
Hop 0변경한 함수 자체1
...합계28

직관적으로 예상했을 때는 "호출자가 몇 군데뿐이니 테스트를 포함해도 5개 파일 정도겠지"라고 생각했습니다. 하지만 실측 결과는 28개였습니다. 약 5.6배입니다.

차이를 만든 것은 Hop 2 이후의 존재입니다. 직접적인 호출자 4개 파일은 예상대로였습니다. 하지만 그 4개 파일을 사용하는 미들웨어, 미들웨어를 경유하는 엔드포인트(Endpoint), 그리고 그들의 테스트까지 포함되었습니다. 그래프를 2단계, 3단계로 따라가자 보이지 않았던 20개의 파일이 드러났습니다.

왜 파급 효과가 이토록 넓게 퍼졌을까요? 이유는 결합도(Coupling)에 있습니다. get_current_user()는 인증의 뿌리에 있는 함수로, 많은 엔드포인트가 직접적 또는 간접적으로 의존하고 있었습니다. 이런 "뿌리"를 건드리면 파급 효과는 순식간에 나무 전체로 퍼집니다. 반대로 잎 끝에 있는 독립적인 함수라면 파급 효과는 거의 일어나지 않습니다.

여기서 알 수 있는 점은, 똑같은 "1줄의 변경"이라도 코드베이스의 어느 부분을 건드리느냐에 따라 영향 범위가 천차만별로 달라진다는 것입니다. 따라서 "변경 행수"는 영향 범위의 지표가 될 수 없습니다. 지표가 되어야 하는 것은 "파급 파일 수"입니다. 1줄을 수정하더라도 뿌리를 건드리면 28개 파일, 잎을 건드리면 1개 파일. 이 차이를 변경 전에 미리 알고 싶은 것입니다.

파급 파일 수는 코드베이스의 결합도에 따라 크게 달라집니다. 느슨한 결합(Loose Coupling) 설계라면 파급이 작고, 강한 결합(Tight Coupling)이라면 커집니다. 효과적인 방법은 "자신의 코드베이스에서 실제로 계산해 보는 것"입니다. 숫자는 설계의 건강 검진 결과가 됩니다.

저는 '신장의 야망'이라는 게임을 15년 정도 해왔는데, 미리 포석을 깔아두면 이후 전개가 편해진다는 감각이 있습니다. 영향 범위의 가시화는 이와 같은 포석입니다. 변경하기 전에 판 전체가 보인다면, 어디에 병력을 배치할지(=어떤 테스트를 강화할지)를 미리 결정할 수 있습니다.

다음으로 궁금한 점은, "이거 AI에게 물어보면 되지 않을까?"라는 부분입니다. 저도 시도해 보았습니다.

동일한 변경에 대해 코드베이스를 읽힌 상태에서 AI에게 "get_current_user를 변경했을 때의 영향 범위를 열거해줘"라고 요청했습니다. 그 결과를 의존 그래프의 실측값과 대조해 보았습니다.

항목의존 그래프 (실측)AI에 대한 단순 질문
검출 파일 수2816
...

AI는 직접적인 호출자는 거의 완벽하게 찾아냈습니다. 하지만 홉(Hop)이 깊어질수록 놓치는 부분이 늘어납니다. 코드베이스 전체를 컨텍스트(Context)에 다 담아내지 못하면, AI도 인간과 마찬가지로 "수면 위"만 볼 수밖에 없게 됩니다.

놓친 12개 파일의 내역을 살펴보면, 대부분이 테스트와 미들웨어(Middleware)였습니다. 본체 로직에서 두 단계, 세 단계 떨어져 있으면서도, 만약 망가지면 운영 환경(Production)이 멈춰버리는 곳들입니다. 서두에서 제가 3시간을 허비했던 CSV 내보내기(Export) 기능도 바로 이 '두 단계 앞'에 있었습니다. 아이러니하게도, AI에게 물어보고 안심했던 제가 놓치는 곳은 언제나 가장 치명적인 지점입니다.

여기에 중요한 시사점이 있습니다. AI가 영향 범위(Impact Scope)를 정확하게 답변하게 하고 싶다면, 코드베이스를 먼저 의존 그래프(Dependency Graph)화하고 그 구조를 AI에게 전달하는 것이 효과적입니다. 가공되지 않은 코드(Raw Code)를 통째로 읽게 하여 추측하게 하는 것보다, 그래프라는 지도를 먼저 쥐여주는 것입니다. 이것이 AI 코드 리뷰 계열 도구들이 Tree-sitter를 기반으로 삼고 있는 이유이기도 합니다.

참고로 덧붙이자면, 이것은 AI가 무능하다는 이야기가 아닙니다. 컨텍스트(Context)에 담길 수 있는 범위 내라면 AI의 열거는 상당히 정확합니다. 문제는 수백 개의 파일로 구성된 코드베이스 전체를 한 번에 다 읽을 수 없다는 물리적인 제약에 있습니다. 이는 인간이 뇌의 스택(Stack)을 넘치게 만드는 것과 구조적으로 동일합니다. 그렇기에 인간과 AI 모두에게 '미리 만들어진 지도'를 전달하는 가치가 생겨납니다.

이론은 여기까지 하고, 실제로 시도해 볼 수 있는 방법을 소개합니다.

의존 그래프화 도구는 2024년 이후 급증했습니다. 용도별로 정리해 드립니다.

먼저 시도해보고 싶다면 → GitNexus. npx gitnexus analyze 명령으로 실행할 수 있으며, 브라우저 내에서 그래프가 구축됩니다. 인프라가 필요 없습니다. -
에디터 내에서 완결하고 싶다면 → CodeLayers. VS Code 확장 프로그램으로, 파일 저장 시 영향 범위를 색상으로 구분하여 표시합니다. -
Neo4j로 실무 운영을 하고 싶다면 → CodeGraphContext. 그래프 데이터베이스(Graph DB)로 KuzuDB/FalkorDB/Neo4j 중 선택할 수 있습니다. -
Python의 의존성만 빠르게 보고 싶다면 → pydeps. 모듈 간 임포트(Import)를 시각화하는 경량 도구입니다.

가장 작은 첫걸음은 평소 사용하는 리포지토리(Repository)에서 npx gitnexus analyze를 실행해 보는 것입니다. 500개가 넘는 파일로 구성된 코드베이스라면, 그래프화의 혜택을 즉시 실감할 수 있습니다.

운영 측면에서 효과적인 방법은 영향 범위 체크를 CI(지속적 통합)에 포함하는 것입니다. 풀 리퀘스트(Pull Request)의 차이점(Diff)으로부터 파급 파일 수를 자동으로 산출하고, 임계값을 초과하면 라벨을 붙입니다. "small change"라고 적힌 PR이 실제로는 28개 파일과 연결되어 있다면 리뷰어에게 경고가 전달됩니다. 이것만으로도 서두의 저와 같은 실수는 줄어듭니다.

영향 범위의 시각화는 "변경을 두려워하기 위해서"가 아니라 "안전하고 대담한 변경을 하기 위해서"의 도구입니다. 판세가 보인다면 오히려 과감한 리팩터링(Refactoring)에 나설 수 있습니다. 시각화를 위축되는 이유로 삼지 마세요.

  • "작은 변경"에 대한 예측은 인간이 추적할 수 있는 의존 깊이의 한계에서 어긋난다
  • 코드베이스를 Tree-sitter로 의존 그래프화하면, 파급 범위를 홉(Hop) 수로 실측할 수 있다
  • 제 리포지토리에서는 직관적으로 5개 파일의 변경이 실측 결과 28개 파일로 파급되었다
  • AI에 대한 단순한 질문은 직접적인 호출부에는 강하지만, 깊은 홉(Hop)을 놓치기 쉽다. 그래프를 지도로서 전달하면 정확도가 올라간다
  • 우선 자신의 리포지토리에서 npx gitnexus analyze를 실행하는 것부터 시작하라

영향 범위를 오판하는 공포는 지도가 없다는 데서 옵니다. 지도만 있다면 리팩터링은 훨씬 더 즐거워질 것입니다.

의존 그래프나 영향 범위 분석, 코드의 지식 그래프(Knowledge Graph)화에 대해 더 깊이 알고 싶으신 분은 이곳에 정리해 두었습니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0