우리는 제3자 코드를 신뢰한다, 이제 AI 생성 코드를 신뢰할 때다
요약
AI 생성 코드를 라이브러리와 같은 제3자 코드로 취급하여 관리해야 한다는 관점을 제시합니다. 저자성, 버전 관리, 자동화된 검증 등 오픈 소스 신뢰 스택의 원칙을 AI 생성 코드에도 적용할 것을 제안합니다.
핵심 포인트
- AI 생성 코드를 제3자 코드와 동일한 사고 모델로 관리해야 함
- 오픈 소스 신뢰 스택(저자성, 버전 관리, 커밋 관습 등)의 적용 필요
- 코드의 의도와 행동을 정의하는 계약(Contract) 중심의 접근
- 자동화된 검증 시스템을 통한 AI 코드의 신뢰성 확보
AI 생성 코드 (AI-generated code)는 제3자 코드 (third-party code)로 취급되어야 합니다. 우리가 이미 라이브러리 (libraries)와 의존성 (dependencies)에 대해 사용하는 것과 동일한 사고 모델 (mental model)을 적용해야 합니다. 우리는 lodash, fastapi, 또는 chi의 모든 코드를 한 줄 한 줄 검토하지 않습니다. AI 생성 코드의 모든 줄을 검토해야 한다고 기대해서도 안 됩니다.
저는 이전 포스트에서 이 점을 주장했습니다. 자연스럽게 뒤따르는 질문은 이것입니다:
오픈 소스 신뢰 스택 (open-source trust stack)을 도구가 아닌 그 위에 구축된 근본적인 계약 (contracts), 즉 기본 요소 (primitives)로 분해해 보면 다음과 같은 결과가 나옵니다.
저자성 (Authorship). 모든 변경 사항에는 저자, 타임스탬프 (timestamp), 그리고 이상적으로는 이유가 있습니다. Git 히스토리는 단순한 로그가 아니라 감사 추적 (audit trail)입니다. 코드 한 줄을 작성된 순간과 작성한 사람까지 거슬러 올라가 추적할 수 있습니다.
계약으로서의 버전 관리 (Versioning as a contract). 유의적 버전 관리 (Semantic versioning)는 단순한 번호 매기기 체계가 아닙니다. 그것은 약속입니다. 패치 (patch) 버전 업데이트는 "무언가를 수정했지만, 아무것도 깨지지 않습니다"라고 말합니다. 메이저 (major) 버전 업데이트는 "계약을 변경했으니, 적응이 필요합니다"라고 말합니다. 이는 유지 관리자 (maintainers)와 사용자 (consumers) 사이의 통신 기본 요소 (communication primitive)입니다.
의도 표현으로서의 관습적 커밋 (Conventional commits as intent). fix:, feat:, chore:와 같은 커밋 메시지는 변경의 의도를 인코딩합니다. 이는 변경 로그 (changelog)가 생성되는 토대입니다. 변경 로그는 모든 차이점 (diff)을 읽지 않고도 무슨 일이 일어났는지 이해하는 방법입니다.
행동 계약 (Behavioral contracts). 타입 시그니처 (Type signatures), 문서화된 API, 인터페이스 정의 (interface definitions). 이것들은 코드가 무엇을 수행하기로 약속하는지를 정의합니다. 이는 테스트가 작성되는 명세 (spec)이자 사용자가 의존하는 경계 (boundary)입니다.
자동화된 검증 (Automated verification). 린터 (Linters), 타입 체커 (type checkers), 보안 스캐너 (security scanners), 커버리지 게이트 (coverage gates). 이는 일회성 확인이 아니라, 모든 변경 사항에 대해 실행되는 강제되고 반복 가능한 게이트입니다. 신뢰는 단일 테스트에 있는 것이 아니라, 검증하는 습관에 있습니다.
격리 (Isolation). 코드는 패키지 경계 (package boundaries) 뒤에 존재합니다. 코드를 고정 (pin)하거나, 교체하거나, 대체할 수 있습니다. 경계가 실재하기 때문에 영향 범위 (blast radius)가 제한됩니다.
이 중 어느 것도 도구가 아닙니다. 이것들은 합의 (agreements)입니다. 우리가 읽어보지 않은 제3자 코드를 배포할 수 있는 이유는, 이 합의의 스택이 매번, 조용히, 배경에서 우리를 대신해 일을 수행하고 있기 때문입니다.
제가 AI 생성 코드에 적용하고자 하는 관점도 바로 이것입니다. 이 각각의 요소에 대해, 에이전트 (agent)가 우리 자신의 리포지토리 (repo) 내부에서 생성한 코드에 해당하는 것은 무엇일까요? 그것이 이미 존재하는 곳은 어디인가요? 구축이 필요한 곳은 어디인가요?
AI 생성 코드에 필요한 기본 요소 (The Primitives AI-Generated Code Needs)
1. 추적 가능성 (Traceability)
오픈 소스에서의 대응물: 저작권 (Authorship). 작성자, 커밋 타임스탬프 (commit timestamp), git blame.
AI 생성 코드에 필요한 것: 세 가지가 필요합니다. 이 코드가 AI에 의해 생성되었다는 명확한 표시 (marker). 이를 승인한 사람 (human). 그리고 시작점이 된 요청 (originating request)에 대한 링크. 시작점이 된 요청이란 귀하의 팀이 작업 단위(unit of work)를 설명하기 위해 사용하는 티켓 (ticket), 이슈 (issue), RFC, 명세서 (spec) 등을 의미합니다.
이 세 가지가 없다면, 스택의 그 어떤 것도 연결될 지점이 없습니다. 식별할 수 없는 대상은 버전 관리 (versioning), 감사 (audit), 격리 (isolate), 또는 사후 분석 (post-mortem)을 할 수 없습니다.
오픈 소스 (open-source) 세계는 Conventional Commits (관습적 커밋)와 변경 로그 (changelogs)를 통해 이 문제를 해결했습니다. 불완전하고 일관성이 없을지라도, 최소한 존재는 합니다. 원시적인 형태라도 존재하는 것입니다. 하지만 AI 생성 코드 (AI-generated code)의 경우, 우리는 그것이 필요하다는 사실조차 합의하지 못했습니다.
3. 행동 계약 (Behavioral Contracts)
오픈 소스의 대응물: 타입 시그니처 (Type signatures), 문서화된 API (documented APIs), 인터페이스 정의 (interface definitions).
AI 생성 코드에 필요한 것: 동일한 것이 필요하지만, 코드로부터 추론되는 것이 아니라 코드 작성 전에 작성되어야 합니다.
방향성이 중요합니다. 오픈 소스 방식에서는 인터페이스를 먼저 정의하고 코드가 이를 구현합니다. 반면 AI 생성 방식에서는 코드는 작동하지만, 그에 맞춰 인터페이스를 역공학 (reverse-engineered) 하는 상황이 발생하기 쉽습니다. 이는 거꾸로 된 방식입니다. 계약 (contract)은 약속이어야 합니다. 만약 구현 (implementation)이 약속을 정의한다면, 계약은 아무런 역할을 하지 못하는 것입니다.
올바른 접근 방식은 계약 우선 생성 (contract-first generation)입니다. 즉, 행동 계약을 먼저 정의하면 AI가 이를 충족하는 코드를 생성하는 것입니다. 이렇게 하면 계약은 사양 (spec), 테스트 앵커 (test anchor), 그리고 문서 (documentation)의 역할을 동시에 수행하게 됩니다. 또한 이는 인간의 검토 (human review) 단계를 한 줄씩 읽는 방식에서 계약 수준으로 전환해 줍니다. 당신은 구현을 검토하는 것이 아닙니다. 계약을 검토하고, 검증 레이어 (verification layer)가 이를 강제할 것이라고 신뢰하는 것입니다. 이것이 바로 우리가 이미 제3자 라이브러리 (third-party libraries)에 대해 수행하고 있는 방식입니다. 우리는 lodash를 읽지 않습니다. fastapi를 읽지 않습니다. chi를 읽지 않습니다. 우리는 계약을 신뢰합니다.
4. 검증 (Verification)
오픈 소스의 대응물: 린터 (Linters), 타입 체커 (type checkers), 보안 스캐너 (security scanners), 공개 API에 대해 사용자가 작성한 테스트 (tests).
AI 생성 코드에 필요한 것: 세 가지 레이어를 통한 검증 (Verification with three layers).
결정론적 (Deterministic). 린터 (Linters), 타입 체커 (Type checkers), 정적 분석 (Static analysis), 보안 스캐너 (Security scanners), 스키마 검증기 (Schema validators). 검증 스택 중에서 컨디션에 영향을 받지 않는 부분들입니다. 이들은 실행되며, 통과하거나 실패할 뿐, 주관적인 판단을 내리지 않습니다. AI 생성 코드의 경우, 이 점은 사람이 작성한 코드보다 덜 중요한 것이 아니라 오히려 더 중요합니다. 최소한 사람이 작성했을 때는 린터가 알지 못하는 문맥 (Context)이라도 있었습니다. AI 작성자 역시 그 문맥을 가지고 있지 않았습니다. 결정론적 바닥 (Deterministic floor)은 작성자나 검토자를 신뢰하지 않고도 의지할 수 있는 검증의 영역입니다.
계약 기반 (Contract-anchored). 진실의 근거 (Source of truth)는 구현 (Implementation)이 아니라 계약 (Contract)입니다. 계약이 요구하는 사항 대신 코드가 무엇을 하는지를 설명하는 테스트는 진정한 테스트가 아니라, 자기 초상화에 불과합니다. 이러한 분리는 제3자 라이브러리를 사용할 때 거저 얻는 이점입니다. 테스트 작성자는 문서와 공개 API (Public API)를 가지고 있으며, 계약은 테스트가 의지할 수 있는 유일한 대상입니다. AI 생성 코드의 경우, 구현이 테스트의 검증 항목을 주도하기 시작하는 순간 우리는 이 이점을 잃게 됩니다.
조직 인지 (Org-aware). 컨벤션 (Conventions), 내부 도구 (Internal tooling), 명명 패턴 (Naming patterns), 서비스 간의 관계, 그리고 계약이 결코 포착하지 못한 비즈니스 불변량 (Business invariants). 이는 오늘날 인간 검토자들이 생각 없이도 확인하는 것들입니다. 구현은 자기 자신과 비교되는 것이 아니라, 조직에 축적된 표준에 따라 검증됩니다. 이 레이어가 없다면, "우리는 모든 줄을 검토하지 않을 것이다"라는 말은 "우리는 현재 인간이 잡아내는 것들을 잡아내지 못할 것이다"라는 말로 변질됩니다.
이 세 가지 레이어를 종합한 목적은 인간이 모든 줄을 검토할 필요가 없게 만드는 것입니다. 검증 스택은 오늘날 검토자들이 잡아내는 것들을 잡아내며, 가능한 경우 결정론적으로, 계약이 명세 (Spec)인 곳에서는 계약에 따라, 그 외의 모든 곳에서는 조직의 표준에 따라 검증합니다.
5. 격리 및 폭발 반경 (Isolation and Blast Radius)
오픈 소스의 대응물: 패키지 경계 (Package boundaries), 의존성 주입 (Dependency injection), 그리고 코드베이스를 다시 작성하지 않고도 라이브러리를 교체할 수 있어야 한다는 원칙.
AI 생성 코드에 필요한 것: 의도적으로 적용된 동일한 규율.
패키지 경계 (Package boundary)는 단순히 조직적인 편의를 위한 것이 아닙니다. 그것은 폭발 반경 (blast-radius) 제어 메커니즘입니다. 패키지가 깨지더라도 시스템의 나머지 부분은 계속 작동합니다. 패키지를 교체하고 싶을 때, 그것과 접촉했던 모든 것을 다시 작성할 필요가 없습니다. 경계가 없는 AI 생성 코드는 그 반대입니다. 그것은 침투합니다. 결합되어서는 안 될 것들과 결합 (couple)됩니다. 코드의 일부가 잘못되었다는 것을 깨달았을 때는, 이미 코드베이스의 절반을 휘감고 있을 것입니다.
이것은 사실 새로운 것이 아닙니다. 기존의 규율을 내부로 적용하는 것입니다. 명확한 인터페이스 (interface) 뒤에 격리되어 있고, 교체 가능하며 (swappable), 대체 가능하고 (replaceable), 삭제 가능한 (deletable) 제3자 코드에 우리가 이미 적용하고 있는 것과 동일한 주의를 AI 생성 모듈에도 확장해야 합니다. 경계가 마련되면 운영상의 시나리오도 자연스럽게 따라옵니다. 피처 플래그 (Feature flags), 단계적 배포 (Staged rollouts), 검증된 경로로의 폴백 (Falling back to a known-good path). 경계가 기본 요소 (primitive)이며, 나머지는 실행 (practice)의 문제입니다.
정보에 기반한 소유권 (Informed Ownership)
당신은 가져오는(import) 모든 라이브러리의 모든 줄을 읽지는 않습니다. 하지만 하나가 고장 나면, 당신은 그것을 고칩니다. 문서를 읽습니다. 그 동작에 맞춰 테스트를 작성합니다. 변경 로그 (changelog)를 확인합니다. 이해의 수준은 "소스 코드를 다 읽었다"가 아닙니다. "이것이 무엇을 하는지, 무엇을 약속하는지, 그리고 제대로 작동하지 않을 때 무엇을 해야 하는지 안다"는 수준입니다.
그것이 우리가 AI 생성 코드에 대해 목표로 삼아야 할 수준입니다. 당신이 직접 작성하지 않았을 수도 있습니다. 모든 줄을 검토하지 않았을 수도 있습니다. 하지만 그것은 당신의 코드베이스에 있습니다. 그것은 당신의 것입니다. 신뢰 스택 (trust stack)이 바로 그 소유권을 실질적으로 만드는 것입니다.
다음 단계
이것은 시작점이지 완성된 프레임워크가 아닙니다. 우리가 AI 생성 코드를 이미 제3자 코드처럼 다룰 수 있기 전에 존재해야 한다고 생각하는 다섯 가지 기본 요소 (primitives)입니다.
다음에 작업할 가치가 있다고 생각하는 질문들은 다음과 같습니다:
- AI가 생성한 코드를 표시하고, 이를 특정 요청(request) 및 승인자(approver)와 연결하는 컨벤션(convention)은 누가 정의해야 할까요? 이는 각 도구 벤더가 개별적으로 만들어낼 것이 아니라, 팀 전체에 걸쳐 표준화되어야 할 문제로 느껴집니다.
- 실제 환경에서 의사결정 로그(decision log)는 어떤 모습일까요? 구조화된 주석 블록(comment block)일까요? 연결된 명세서(spec)일까요? 프롬프트 세션에서 생성된 요약본일까요? 아니면 별도의 메타데이터 파일에 기록된 항목일까요?
- AI 생성의 입력값으로서도 유용하고, 검증(verification)을 위한 앵커(anchor)로서도 유용한 행동 계약(behavioral contracts)을 어떻게 작성할 수 있을까요? 기존의 타입 시스템(type systems)만으로 충분할까요? 아니면 새로운 무언가가 필요할까요?
- 조직 인지적(org-aware) 검증 계층은 실제로 어떤 모습일까요? CI(지속적 통합) 내에 존재할까요? 에이전트의 컨텍스트(context) 내에 존재할까요? 아니면 둘 다일까요? 오늘날의 검증 스택(verification stack)은 결정론적 체크(deterministic checks)와 계약 테스트(contract tests) 단계에서 멈춰 있으며, 비즈니스 불변량(business invariants)과 조직의 컨벤션(org conventions)을 잡아내는 계층은 아직 실존하지 않습니다.
- 적절한 격리(isolation) 수준은 무엇일까요? 모듈 수준(Module-level)? 서비스 수준(Service-level)? 함수 수준(Function-level)? 어느 시점부터 과잉(overkill)이 될까요?
이 중 어떤 것에 대해서라도 의견이 있거나, 제가 작성한 목록이 틀렸다고 생각하신다면 기꺼이 듣고 싶습니다. @sag1v로 찾아와 주세요.
오픈 소스(Open-source)는 우리가 매일 의식하지 않고 사용하는 신뢰 프레임워크(trust framework)를 우리에게 주었습니다. AI가 생성한 코드도 그와 동일한 대우를 받을 자격이 있습니다.
원문은 debuggr.io에 게시되었습니다.
저는 소프트웨어 엔지니어링, AI, 그리고 그 과정에서 흥미를 느끼는 것들에 대해 글을 씁니다. 이 글이 공감이 되셨다면, 더 많은 글을 위해 debuggr.io를 방문해 주세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기