ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 모듈이 바깥에 한 약속 — 공개 인터페이스를 코드에서 뽑아 표로 만들기
    IT 2026. 7. 7. 21:00
    모듈이 바깥에 한 약속 — 공개 인터페이스를 코드에서 뽑아 표로 만들기

    들어가며 — 식당 메뉴판이라는 약속

    식당에 가면 메뉴판이 있다. "아메리카노, 4,500원, 따뜻함/차가움 선택"이라고 적혀 있으면 손님은 주방 사정을 전혀 몰라도 그대로 주문하면 된다. 원두를 어디서 떼오는지, 그라인더가 어떤 모델인지, 바리스타가 몇 명인지는 손님이 알 바 아니다. 메뉴판은 식당이 손님에게 한 약속이고, 손님은 이 약속만 믿고 주문하고 식당은 이 약속을 지킨다.

    소프트웨어 모듈도 정확히 이 메뉴판과 같은 걸 내놓는다. 어떤 모듈이든 바깥세상에 "이런 걸 이렇게 부르면, 이런 결과를 준다"는 표면(surface)을 내건다. 이 공개 표면을 인터페이스(interface)라고 부른다. 이 글은 그 표면을 코드에서 어떻게 결정적으로 뽑아내 표로 만들고, 그 표를 위키가 빠짐없이 설명했는지를 어떻게 강제하는지를 정리한 개인 학습 노트다.

    참고로 공개 인터페이스란, 모듈이 내부 구현은 숨긴 채 "바깥에서 무엇을 기대할 수 있는지"만 약속한 표면을 말한다. 공개 함수 시그니처와 HTTP 엔드포인트가 모두 여기에 속한다.

    배경 — 공개 표면에는 무엇이 담기나

    모듈이 바깥에 내거는 것들을 풀어보면 대략 이렇다.

    함수 시그니처(function signature)는 함수의 이름, 파라미터, 그리고 반환을 말한다. 메뉴판으로 치면 "메뉴 이름 + 옵션 + 받게 될 음료"에 해당한다. 모듈이 공개적으로 호출하라고 내건 함수가 여기 속한다.

    HTTP 엔드포인트(endpoint)는 어떤 주소로 어떤 방식(GET/POST 등)의 요청을 보내면 응답을 돌려준다는 약속이다. 웹 서비스끼리 주고받는 메뉴판인 셈이다.

    메뉴판이 주방 사정을 숨기듯, 공개 표면은 "안쪽이 어떻게 돌아가는지"는 가리고 "바깥에서 무엇을 기대할 수 있는지"만 드러낸다. 이 가림막이 있어서 모듈을 만든 쪽은 표면만 지키면 내부를 자유롭게 바꿀 수 있고, 쓰는 쪽은 표면만 믿고 마음 놓고 호출할 수 있다.

    문제 — 의존 그래프는 "무엇을 약속하는지"를 모른다

    코드를 분석하는 흔한 방법 중 하나가 의존 그래프(dependency graph)다. "모듈 A가 모듈 B를 가져다 쓴다(import)"를 화살표로 그린 지도다. 이 지도는 분명 유용하지만, 막상 써보면 결정적인 한 가지를 빼먹고 있다. B가 바깥에 무엇을 약속하는지를 담지 못한다는 점이다.

    diagram

    다이어그램 설명. 위 지도는 주문하는 쪽이 결제하는 쪽에 의존한다는 사실까지만 말해준다. 그런데 결제하는 쪽이 정작 바깥에 어떤 함수와 어떤 엔드포인트를 내걸고 있는지는 이 화살표 어디에도 없다. 화살표는 "연결"만 알지 "그 끝의 모듈이 무엇을 약속하는지"는 모르기 때문이다. 함정은, 이 그래프가 멀쩡해 보여서 정작 사람이 알고 싶은 정보가 빠졌다는 걸 눈치채기 어렵게 만든다는 데 있다.

    diagram

    다이어그램 설명. 같은 결제 모듈이지만 이번에는 그것이 바깥에 내건 공개 표면을 펼쳐 보여준다. 어떤 공개 함수를 호출할 수 있고, 어떤 주소로 어떤 방식의 요청을 보낼 수 있는지가 항목으로 드러난다. 단순 의존 그래프에서는 결코 보이지 않던, "이 모듈에 기대려면 무엇을 알아야 하는가"가 비로소 손에 잡힌다. 다만 이 표는 "무엇을 약속하는가"는 보여주되 "그 약속이 바뀌면 누가 깨지는가"까지 자동으로 추적해주지는 않는다 — 그건 뒤에서 솔직하게 다룬다.

    해결 — 공개 표면을 코드에서 결정적으로 뽑아낸다

    직관 먼저

    핵심 발상은 단순하다. 모듈의 공개 표면은 코드 안에 이미 다 적혀 있으니, 그걸 기계가 읽어 표로 뽑아내자는 것이다. 사람이 따로 명세 문서를 손으로 적어 유지하는 게 아니라, 코드 자체를 진실의 원천(source of truth)으로 삼아 거기서 표면을 추출한다.

    내가 만들고 있는 코드 위키 자동생성 시스템은 각 저장소마다 이 표면을 모아 api.md라는 페이지로 발행한다. 추출은 전적으로 결정적(deterministic)이다 — 코드를 실행하지도, LLM에게 추측을 맡기지도 않고, 코드의 구문 구조(추상 구문 트리, AST — 소스 코드를 문법 구조에 따라 나무 형태로 표현한 것)에서 직접 뽑는다. 같은 코드면 항상 같은 표가 나온다.

    무엇을 표면으로 보는가

    내 시스템이 한 저장소의 공개 표면으로 모으는 항목은 이렇다.

    표면 항목 코드에서 무엇을 보는가
    HTTP 라우트 웹 서버에 등록된 GET/POST 같은 경로 핸들러
    CLI 서브커맨드 명령줄 인자 파서가 정의한 플래그와 위치 인자
    서비스 유닛 백그라운드로 도는 서비스의 설명과 실행 명령
    공개 함수 모듈이 공개로 내건 최상위 함수의 시그니처

    표 설명. 네 종류 모두 "바깥에서 이 저장소에 어떻게 말을 거는가"를 나타낸다. 웹으로 부르는 길(라우트), 명령줄로 부르는 길(서브커맨드), 백그라운드 서비스로서의 모습(유닛), 그리고 코드로 직접 부르는 길(공개 함수)이다. 공통점은 넷 다 사람이 따로 적은 명세가 아니라 코드의 구조에서 기계적으로 뽑힌다는 점이다.

    이 결정적 추출 위에, 사람이 읽기 좋게 한 줄 설명을 LLM이 덧붙이는 보강 단계가 선택적으로 얹힌다. 다만 표의 데이터 자체 — 어떤 라우트가 있고 어떤 함수가 공개인지 — 는 결정적으로 고정되고, LLM은 그 위에 설명만 더한다. 결정적 사실과 가변적 서술이 섞이지 않게 분리하는 것이 원칙이다.

    발행만으로 끝이 아니다 — 커버리지 게이트로 강제한다

    표를 뽑아 발행하는 것만으로는 절반이다. 진짜 핵심은 "위키가 이 공개 표면을 빠짐없이 설명했는가"를 기계적으로 강제하는 데 있다.

    여기서 빈자리에는 두 종류가 있다는 걸 운영하며 깨달았다. 하나는 공개 표면의 빈자리 — 바깥 호출자가 실제로 보는 함수나 엔드포인트가 위키에 빠진 경우다. 이건 외부 호출자에게 "그건 존재하지 않는다"고 거짓을 가르치는 셈이라, 자동 생성 문서가 잘못된 정보를 학습시키기 가장 좋은 자리다. 다른 하나는 내부 구현의 빈자리 — 모듈 안에서만 쓰는 보조 함수가 위키에 빠진 경우다. 바깥에서 볼 일이 없으니 빠져도 거짓을 가르치진 않고, 일상적인 코드 변경에서 잠깐 비는 게 정상이다.

    그래서 내 시스템은 이 둘을 다르게 다룬다.

    diagram

    다이어그램 설명. 같은 추출 결과를 두 층으로 나눠 다르게 강제하는 흐름이다. 공개 표면 항목은 단 하나라도 위키에서 빠지면 야간 자동 검증을 멈춰 세운다 — 바깥에 거짓을 가르치기 전에 막는 것이다. 반면 내부 구현 항목은 빠져도 차단하지 않고 채워진 비율만 추세로 기록한다. 함정은, 두 빈자리에 같은 잣대를 들이대면 한쪽이 무너진다는 점이다. 둘 다 엄격하게 막으면 일상적인 내부 함수 추가마다 검증이 깨져 알림이 마비되고, 둘 다 느슨하게 두면 정작 바깥에 거짓을 가르치는 공개 표면 누락을 못 잡는다.

    공개 표면을 엄격하게 강제할 수 있는 이유는 분모가 통제되기 때문이다. 새 공개 함수나 새 엔드포인트를 추가하는 일은 의도적인 결정이고, 자주 일어나지 않으며, 보통 하나의 변경 묶음으로 함께 온다. 그러니 "그 변경이 위키 설명도 같이 들고 오게" 강제하는 게 합리적이다. 반면 내부 보조 함수는 리팩터링 중에도 수십 개씩 생기므로, 같은 엄격함을 걸면 알림 신뢰가 무너진다.

    한계 — 정직하게 짚어둘 경계

    여기서 과장하지 않고 분명히 해둘 게 있다. 내 시스템은 공개 표면을 추출해 표로 만들고 그 설명 누락을 막는 데까지만 한다. 다음 것들은 지금 구현하지 않았다.

    • 깨는 변경 자동 탐지: "이번 변경이 기존 호출자를 망가뜨리는가"를 자동으로 판정하지 않는다. 표는 현재 표면을 보여줄 뿐, 두 버전을 비교해 호환성을 판정하는 기능은 없다.
    • 제공자-소비자를 잇는 계약 그래프: "이 약속을 누가 믿고 쓰는가"를 양방향 엣지로 잇는 그래프는 만들지 않았다. 그래서 "이 함수를 바꾸면 누가 깨지는가"를 그래프로 역추적하는 일은 아직 사람의 몫이다.
    • 동적 경계의 정밀 추출 한계: 파이썬처럼 동적인 언어에서, 실행 중에 경로가 조립되거나 응답 모양이 바뀌는 코드는 정적 추출이 정밀하게 잡지 못한다. 핸들러 시그니처와 패턴으로 근사하는 수밖에 없다.

    이 한계를 인정하고 시작하는 게 오히려 건강하다 — 도구가 100% 정밀한 척하면 오히려 위험하다. 내가 실제로 채택한 가치는 "공개 표면을 코드에서 정직하게 뽑아 표로 굳히고, 그 표면 설명이 빠지지 않게 막는다"는 데 분명하게 한정된다.

    내 코드 위키 자동생성 시스템과의 연결

    코드를 읽어 설명 문서를 자동으로 짓는 시스템에서, "A가 B에 의존한다"까지만 적힌 지도는 반쪽짜리다. 정작 사람이 알고 싶은 건 "이 모듈이 바깥에 무엇을 약속하는가"인데, 그 답은 공개 표면을 명시적으로 다뤄야만 나온다.

    그래서 내 시스템은 각 저장소의 공개 함수 시그니처와 HTTP 엔드포인트를 코드에서 결정적으로 추출해 api.md 표로 발행하고, 그 표면이 위키에서 빠짐없이 설명됐는지를 커버리지 게이트로 강제한다. 같은 추출기가 만든 같은 표면 정의를 발행과 검증이 함께 공유하므로, 둘 사이에 드리프트가 생길 수 없다. 메뉴판을 그냥 "이 식당은 저 식당이랑 거래한다"로 적는 것과, "이 식당은 이런 메뉴들을 내건다, 그리고 그 메뉴 설명이 빠지면 발행을 멈춘다"로 강제하는 것의 차이 — 그 차이가 자동 생성 문서를 신뢰할 수 있게 만든다는 걸, 이번 학습으로 분명히 정리했다.


    이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.

Designed by Tistory.