Codebase-as-Harness
AI 에이전트 시대의 브라운필드 엔지니어링
AI 코딩 도구는 빠르게 발전하고 있다. 과거의 도구가 몇 줄의 코드를 자동완성하는 데 그쳤다면, 지금의 에이전트는 작업 지시를 이해하고 저장소를 탐색하며 관련 파일을 찾아 코드를 고친다. 테스트를 실행하고 실패 로그를 읽은 뒤 다시 수정하는 일도 가능하다. 겉으로 보면 소프트웨어 개발의 상당 부분이 곧 자동화될 것처럼 보인다. 그러나 오래되고 복잡한 시스템을 실제로 운영해본 사람이라면 에이전트가 코드를 만들 수 있는가보다, 에이전트가 만든 변경을 대규모 브라운필드 시스템에 안전하게 통합할 수 있는가를 더 따져보게 된다.
브라운필드(레거시)에서 어려운 일은 코드를 입력하는 것이 아니다. 정책이 실제로 어디에 있는지 찾고, 여러 구현 가운데 어느 것이 권위 있는지 판단하며, 문서에 없는 규칙을 추론하고, 하나의 변경이 다른 서비스와 운영 환경에 어떤 영향을 미칠지 계산하는 일이 더 어렵다. 사람도 이런 환경에서 자주 실수한다. 에이전트는 저장소의 역사와 조직의 암묵지를 모르는 상태에서 검색 결과와 파일 내용, 테스트 출력만을 근거로 판단해야 하므로 더 쉽게 잘못된 결론에 도달할 수 있다.
이런 상황에서 코드 생성 속도만 높이면 병목은 다른 곳으로 이동한다. 작성 시간은 줄어들지만 리뷰 부담은 커지고, 기능 추가보다 회귀 방지가 중요해지며, 테스트 통과 이후에도 운영 안정성을 사람이 다시 확인해야 한다. 코드 생산량은 늘었는데 안전하게 병합할 수 있는 변경량은 오히려 줄어드는 역설이 생길 수 있다. 그래서 AI 에이전트 시대의 소프트웨어 엔지니어링은 단순한 코드 생성 자동화로 설명하기 어렵다. 더 중요한 과제는 일반적으로 거론되는 하네스(에이전트를 자유롭게 풀어놓는 대신 필요한 맥락과 제약, 검증 장치, 멈춤 조건을 제공하는 작업 환경)를 넘어서 코드베이스 자체를 에이전트가 안전하게 일할 수 있는 하네스로 바꾸는 것이다.
나는 이 관점을 Codebase-as-Harness라고 부른다. 좋은 코드베이스는 에이전트에게 무엇을 고쳐야 하는지 알려주고, 어디까지 바꿔도 되는지 제한하며, 변경이 올바른지 스스로 증명할 수 있게 한다. 결국 중요한 것은 에이전트의 자율성을 무작정 늘리는 것이 아니라, 그 자율성이 안전하게 작동할 수 있는 경계를 설계하는 일이다.
브라운필드의 본질은 코드가 아니라 맥락이다
브라운필드는 단순히 오래된 코드가 아니다. 그 안에는 장애 이력, 고객별 예외, 운영 관행, 데이터 마이그레이션, 조직의 소유권, 임시 해결책, 과거의 의사결정이 겹겹이 쌓여 있다. 예를 들어 “구독 갱신 정책을 바꿔달라”는 요청은 겉보기에는 간단하지만, 실제로는 구독 상태와 고객 상태, 청구서 생성, 결제 사업자 매핑, 재시도 정책, 웹훅 처리, 할인과 세금, 관리자 화면, 모바일 API, 알림, 분석 파이프라인까지 연결될 수 있다.
이때 문제는 관련 파일이 많다는 사실만이 아니다. 어떤 구현이 현재 정책이고, 어떤 코드는 과거의 흔적이며, 어떤 문서는 더 이상 신뢰하면 안 되는지 구분하기 어렵다는 데 있다. 같은 정책이 세 군데에 중복되어 있고 테스트는 그중 한 곳만 검증하며 문서는 예전 내용을 설명하고 있다면, 에이전트는 잘못된 진실을 바탕으로 매우 그럴듯한 패치를 만들 수 있다. 모델이 아무리 좋아져도 저장소 내부의 진실이 모호하면 결과는 불안정할 수밖에 없다.
그래서 브라운필드 문제는 단순히 더 많은 정보를 모델에 넣는 것으로 해결되지 않는다. 긴 컨텍스트는 더 많은 파일과 문서를 한 번에 읽게 해주지만, 어떤 정보가 최신이고 권위 있는지는 알려주지 않는다. 오히려 오래된 문서와 중복 구현, 폐기된 코드와 임시 처리를 모두 함께 넣으면 판단이 더 어려워질 수도 있다. 필요한 것은 컨텍스트 확장이 아니라 맥락 관리다. 맥락의 양을 늘리는 것보다, 맥락의 우선순위와 신뢰도를 정리하는 일이 먼저다.
에이전트의 실제 성능은 모델 하나로 결정되지 않는다. 검색 품질과 도구, 코드베이스의 구조, 테스트의 신뢰도, 피드백의 명확성, 사람의 운영 방식이 함께 작용한다. 같은 모델을 쓰더라도 어떤 저장소에서는 작은 버그를 안정적으로 고치고, 다른 저장소에서는 관련 없는 파일까지 건드리며 중복 코드를 만들 수 있다. 차이는 에이전트가 얼마나 똑똑한가보다, 코드베이스가 얼마나 명확한 작업 환경을 제공하는가에서 나온다.
에이전트가 어려워하는 것은 수정이 아니라 탐색과 판단이다
현대의 코딩 에이전트는 대체로 요청을 해석하고, 관련 파일을 찾고, 코드를 읽고, 수정 계획을 세운 뒤 패치를 적용한다. 그다음 테스트와 정적 검사를 실행하고, 실패하면 로그를 읽어 다시 수정한다. 이 흐름만 보면 상당히 합리적이지만, 실제 실패는 대부분 수정 이전과 이후에서 발생한다.
검색어가 잘못되면 핵심 파일을 놓치고, 심볼명이 모호하면 비슷한 구현을 잘못 선택한다. 정책이 여러 계층에 흩어져 있으면 권위 있는 위치를 판단하기 어렵고, 기존 추상화를 찾지 못하면 새 helper를 만들어 중복을 늘린다. 테스트가 약하면 잘못된 변경도 통과하고, 실패 메시지가 모호하면 엉뚱한 곳을 고친다. 에이전트가 코드를 잘 쓰는 능력보다 필요한 정보를 정확히 찾고, 변경의 성공 조건을 이해하며, 위험한 시점에 멈추는 능력이 더 중요해지는 이유다.
이 관점에서 유지보수성을 다시 생각해볼 수 있다. 어떤 변경을 안전하게 수행하기 위해 에이전트가 얼마나 많은 정보를 찾아보고 이해해야 하는가. 나는 이 범위를 작업 맥락의 크기(Context Surface Area), 즉 작업 맥락의 크기라고 부른다. 관련 파일과 심볼뿐 아니라 도메인 개념, 서비스 경계, DB 테이블, API 계약, 테스트, 로그, 운영 문서, 암묵적인 정책까지 모두 포함된다.
작은 버그 하나를 고치기 위해 수십 개 파일과 여러 서비스를 이해해야 한다면 작업의 작업 맥락의 크기(Context Surface Area)가 크다. 반대로 정책과 테스트가 한 모듈 안에 모여 있고 외부 의존성이 명확하게 분리되어 있다면 작다. 기존 소프트웨어 공학이 강조해온 고응집, 저결합, 명확한 타입, 도메인 경계, 테스트 가능성 같은 원칙은 모두 이 작업 맥락을 줄이는 장치로 해석할 수 있다.
좋은 설계는 복잡성을 없애지 않는다. 대규모 시스템에는 본질적인 복잡성이 존재한다. 대신 변경에 필요한 복잡성을 작은 경계 안에 가둔다. 구독 갱신 정책을 바꿀 때 도메인 정책과 관련 테스트만 보면 되고, 결제 사업자 매핑을 바꿀 때는 변환 계층만 보면 되며, API 계약을 바꿀 때는 계약 테스트가 명확하게 따라오는 구조가 필요하다. 에이전트에게 유리한 코드베이스는 단순한 코드베이스가 아니라, 작업마다 필요한 맥락이 잘 국소화된 코드베이스다.
이름은 가독성을 넘어 검색을 위한 주소가 된다
에이전트는 무엇인가를 고치기 전에 먼저 찾아야 한다. 따라서 이름은 단순한 스타일의 문제가 아니라 검색을 위한 주소다. 같은 개념이 user, member, customer, account, client로 섞여 있다면 사람은 문맥을 보고 대충 같은 의미라고 짐작할 수 있지만, 에이전트는 검색 과정에서 일부 구현을 놓치거나 서로 다른 개념을 하나로 오해할 수 있다.
이런 상태를 만들려면 같은 개념이 어디서나 같은 이름으로 검색되어야 한다. 도메인 개념과 정책, 상태, 이벤트, 실패 원인이 일관된 이름을 통해 코드와 테스트, 문서, 로그에서 연결되는 정도를 뜻한다. 결제 도메인에서 실제 결제 주체를 Customer라고 정했다면 CustomerId, CustomerStatus, CustomerRepository, CustomerSuspendedEvent처럼 관련 개념도 같은 언어를 사용해야 한다. 외부 결제 사업자가 client_id라는 용어를 쓰더라도 내부까지 그 이름을 끌고 들어오기보다 경계에서 명시적으로 변환하는 편이 낫다.
function mapProviderClientToCustomer(input: ProviderClient): CustomerId {
return CustomerId.from(input.client_id);
}
이름의 일관성은 클래스와 함수에만 적용되지 않는다. DB 컬럼, API 필드, 이벤트, 로그, metric, 테스트명, ADR 제목, runbook, alert까지 가능한 한 같은 언어로 이어져야 한다. 운영 로그에는 user_id가 나오고 코드에는 customerId, DB에는 member_no가 있다면 장애에서 코드까지 추적하는 일이 어려워진다. 사람에게도 불편하지만, 검색에 의존하는 에이전트에게는 더 치명적이다.
물론 대규모 브라운필드에서 모든 이름을 한 번에 바꾸는 것은 현실적이지 않다. 그래서 먼저 권위 있는 용어 지도를 만드는 편이 낫다. 예를 들어 도메인 용어 사전에 preferred name과 legacy alias, canonical file, 로그 필드, 사용 금지 영역을 기록할 수 있다.
terms:
Customer:
preferred: Customer
aliases:
- User
- Member
- Account
- Client
canonical_files:
- services/billing/domain/customer.ts
log_fields:
- customer_id
notes: "Billing context의 결제 주체"
중요한 것은 이 문서를 따로 보관하는 데서 끝나지 않는다는 점이다. AGENTS.md, 정적 검사 rule, 검색 도구, 로그 쿼리, PR 체크리스트와 연결되어야 한다. 완벽한 이름 통일보다 먼저 필요한 것은 어떤 이름과 위치가 권위 있는지 알려주는 구조다.
테스트 통과보다 먼저 변경의 성공 조건을 정의해야 한다
에이전트는 테스트를 통과시키는 데 점점 능숙해지고 있다. 그러나 테스트 통과는 정답과 같지 않다. 테스트가 없을 수도 있고, 잘못된 동작을 고정하고 있을 수도 있으며, 구현 세부사항만 확인할 수도 있다. 운영 데이터의 분포, 보안, 개인정보, 하위 호환성, 성능, 장애 대응 조건은 테스트에 포함되지 않을 수 있다.
따라서 변경 전에 무엇이 절대 깨지면 안 되는지 먼저 정의해야 한다. 나는 이를 변경 계약이라고 부른다. 변경 계약는 특정 변경이 보존해야 하는 도메인 불변조건과 API·데이터 호환성, 보안 조건, 운영 조건을 검증 가능한 형태로 정리한 것이다.
구독 갱신 정책을 바꾼다고 해보자. “expired subscription은 갱신하지 않는다”라는 단위 테스트 하나로는 충분하지 않다. 청구서가 생성되지 않아야 하고, 건너뛴 이유가 로그에 남아야 하며, 재시도해도 중복 청구가 생기지 않아야 한다. 기존 정상 구독은 계속 갱신되어야 하고, 웹훅과 이벤트 스키마는 이전 소비자와 호환되어야 한다. 이런 조건을 하나의 변경 계약으로 묶어야 한다.
change_contracts:
subscription_renewal_policy:
invariants:
- 만료된 구독은 갱신되지 않는다
- 정지된 고객에게는 과금하지 않는다
- 갱신은 idempotent해야 한다
- 정상 결제 중인 구독은 계속 갱신된다
compatibility:
- 웹훅 페이로드 스키마는 하위 호환을 유지한다
- 인보이스 이벤트 스키마는 하위 호환을 유지한다
observability:
- 갱신이 건너뛰어지면 skipped_reason을 로그로 남긴다
- 중복 갱신 방지 시 메트릭을 기록한다
required_checks:
- pnpm test services/billing/subscription
- pnpm test services/billing/invoice
- pnpm test:contract billing
- pnpm typecheck
forbidden_changes:
- 결제 대행사 상태 매핑
- 기존 마이그레이션 파일 수정
테스트는 이 계약을 실행 가능한 형태로 만드는 수단이다. 에이전트에게 “테스트를 통과시켜라”라고 지시하는 것보다 “이 계약을 만족시켜라”라고 지시하는 편이 훨씬 안전하다. 전자는 현재 존재하는 테스트에만 맞추게 하지만, 후자는 변경의 성공 조건과 보존해야 할 현실의 동작을 함께 제시하기 때문이다.
설계는 문서가 아니라 실행 가능한 제약이 되어야 한다
아키텍처 문서에 “domain layer는 infra를 import하면 안 된다”고 적는 것만으로는 부족하다. 사람도 문서를 놓치는데 에이전트라고 항상 따를 수는 없다. 중요한 규칙은 검색 가능하고 기계적으로 검증 가능해야 한다.
에이전트가 읽을 수 있는 아키텍처 규칙은 아키텍처 경계와 의존 방향, 소유권, 금지된 import, 테스트 명령, 위험 파일, 승인 조건을 에이전트와 CI가 읽을 수 있는 형식으로 표현하는 것이다. 예를 들어 billing 도메인 경계의 root, 허용 의존성, 금지 의존성, 필수 검증 명령, 위험 경로를 설정 파일로 관리할 수 있다.
bounded_contexts:
billing:
owner: payments-platform
root: services/billing
allowed_dependencies:
- shared/kernel
- services/customer/contracts
forbidden_dependencies:
- services/customer/internal
- services/admin/internal
verification:
unit: pnpm test services/billing
contract: pnpm test:contract billing
risky_paths:
- services/billing/migrations
- services/billing/provider-mapping
- services/billing/domain/money.ts
이 설정 파일는 에이전트가 작업 계획을 세울 때도 사용되고, CI가 변경을 검증할 때도 사용되며, 리뷰어가 위험을 판단할 때도 사용된다. AGENTS.md는 사람이 읽기 좋은 시작점이 될 수 있지만, 실제 기준이 되는 정보는 자동으로 집행되는 규칙이어야 한다. 프롬프트는 지침에 가깝지만 CI는 규칙을 실제로 적용하는 장치다. CI 단계에서 아키텍처 규칙을 검증할 수 있게 도와주는 라이브러리들도 이미 존재하므로 이러한 것들을 함께 활용하는 것이 중요하다.
이 관점에서 보면 우리가 이미 좋은 설계라고 불러온 원칙들의 의미도 더 분명해진다. 도메인에서 같은 말을 같은 뜻으로 쓰면 에이전트가 필요한 코드를 찾기 쉬워지고, 책임이 잘 나뉜 구조는 한 작업을 위해 읽어야 할 범위를 줄여준다. 중복을 줄이는 목적도 단순히 코드 줄 수를 줄이는 데 있지 않다. 권한 정책이나 상태 전이, 금액 계산, 재시도 규칙처럼 중요한 동작의 기준을 한곳에 두는 것이 핵심이다.
테스트도 마찬가지다. 단순히 테스트가 존재하는 것만으로는 부족하다. 테스트 이름이 어떤 규칙을 보장하는지 설명해야 하고, 에이전트가 도메인 용어로 검색했을 때 관련 정책과 함께 발견되어야 한다. it('works') 같은 이름은 무엇을 지키는지 알려주지 못한다. 반면 it('does not charge suspended customers')라는 이름은 테스트가 확인하는 비즈니스 규칙을 그대로 드러낸다.
운영 로그도 같은 역할을 해야 한다. 좋은 로그는 단순한 디버깅 메시지가 아니라, 운영에서 발생한 문제를 코드의 어느 부분과 연결해야 하는지 알려주는 단서가 되어야 한다.
{
"event": "SubscriptionRenewalSkipped",
"subscription_id": "sub_123",
"customer_id": "cus_456",
"reason": "CUSTOMER_SUSPENDED"
}
이런 로그는 이벤트 이름과 상태, 테스트, 코드 심볼을 연결한다. 반면 process failed나 invalid status 같은 메시지는 무엇을 찾아야 하는지 알려주지 않는다.
에이전트에게는 자율성보다 변경 범위와 멈춤 조건이 필요하다
에이전트는 요청을 해결하려다 수정 범위를 넓히는 경향이 있다. 작은 버그를 고치면서 helper를 새로 만들고, 타입을 바꾸고, 외부에 공개된 API를 손보며, 관련 없는 파일까지 정리할 수 있다. 브라운필드에서는 diff가 커질수록 리뷰 비용과 회귀 위험이 비선형적으로 증가한다.
그래서 작업 유형별로 어디까지 바꿀 수 있는지를 미리 제한할 필요가 있다. 이를 변경 범위 예산이라고 부른다. 예를 들어 bugfix는 수정 파일 다섯 개 이하, 외부에 공개된 API 변경 금지, migration 금지, 의존성 변경 금지로 정할 수 있다. 이 범위를 넘어가면 에이전트는 계속 진행하는 대신 사람에게 판단을 넘겨야 한다.
task_type: bugfix
max_files_changed: 5
public_api_change: false
db_migration: false
dependency_change: false
requires_test: true
requires_human_escalation_if:
- auth policy touched
- payment calculation touched
- more than 15 files changed
중요한 것은 에이전트가 더 오래 자율적으로 일하게 만드는 것이 아니다. 위험한 시점에 멈추게 만드는 것이다. 예상보다 많은 파일을 수정해야 하거나, 외부에 공개된 API나 DB migration이 필요하거나, 권한과 결제, 개인정보, 삭제 로직을 건드리는 경우에는 사람의 승인이 필요하다. 테스트를 약화해야만 통과하거나, 정책의 권위 있는 위치가 불분명하거나, 같은 규칙의 중복 구현이 발견된 경우에도 멈춰야 한다. 브라운필드에서 멈춤은 실패가 아니라 안전 기능이다.
SCOPE: 수정하기 전에 찾고, 수정한 뒤 증명한다
이런 원칙을 실제 작업 흐름으로 옮기면 SCOPE라는 루프로 정리할 수 있다. Search, Contract, Operate, Prove, Explain의 순서다.
Search 단계에서는 바로 코드를 고치지 않는다. 관련 파일과 정책 위치, 중복 가능성, 테스트, 문서, 예상 변경 범위, 불명확한 점을 먼저 찾는다. 이 과정이 충분하지 않으면 이후의 수정은 빠르더라도 잘못된 방향으로 갈 가능성이 크다.
Contract 단계에서는 보존해야 할 동작과 새로 만족해야 할 동작, 호환성, 보안, 데이터, 관측성, 필수 검증, 멈춤 조건을 정리한다. Operate 단계에서는 그 계약을 만족하는 최소 diff를 만든다. 새 helper를 만들기 전에 기존 구현을 검색하고, 계약에 없는 외부에 공개된 API 변경이나 migration은 하지 않는다.
Prove 단계에서는 관련 단위 테스트와 통합 테스트, 계약 테스트, 타입 검사, 정적 검사, 스키마 호환성, 마이그레이션 검증을 실행한다. 테스트를 약화하거나 삭제해 통과시키는 것은 허용하지 않는다. 마지막 Explain 단계에서는 무엇을 바꿨는지뿐 아니라 왜 그 위치를 선택했는지, 어떤 계약을 만족했는지, 어떤 검증을 했는지, 어떤 위험이 남았는지를 기록한다.
이 기록은 단순한 작업 요약이 아니다. 리뷰어가 에이전트의 판단 과정을 확인할 수 있는 근거가 된다. 어떤 파일을 읽었고, 어떤 정책을 기준으로 삼았으며, 어떤 테스트를 실행했고, 변경 범위가 사전에 정한 선을 넘지 않았는지 보여줘야 한다. 에이전트가 만든 PR은 코드만 볼 것이 아니라 그 변경이 어떤 근거 위에서 만들어졌는지도 함께 봐야 한다.
생산성 지표도 달라져야 한다. LOC/hour는 에이전트 시대에 거의 의미가 없다. 에이전트는 코드 줄 수를 쉽게 늘릴 수 있다. 더 중요한 지표는 사람이 리뷰한 시간 대비 얼마나 많은 검증된 변경을 안전하게 병합했는가다. 코드 생산량이 아니라 사람의 리뷰 시간당 검증된 변경량를 봐야 한다.
에이전트 친화도가 모델보다 오래 남는 경쟁력이 된다
조직은 AI 도구를 얼마나 많이 쓰는지보다, 코드베이스가 에이전트에게 얼마나 친화적인지를 살펴야 한다. 같은 개념이 같은 이름으로 불리는지, 핵심 정책의 기준 위치가 분명한지, 작은 변경이 실제로 작은 범위 안에서 끝나는지, 테스트와 로그가 같은 도메인 언어를 쓰는지, 중요한 설계 규칙이 자동으로 검사되는지를 확인해야 한다.
이런 준비가 부족한 시스템에 최신 에이전트를 도입하면 코드 생산량은 늘 수 있다. 그러나 리뷰 병목과 회귀 버그, 중복 구현, 기술부채도 함께 늘어날 가능성이 크다. 반대로 구조와 규칙이 잘 정리된 시스템에서는 모델 간 성능 차이가 줄어든다. 평범한 에이전트도 작은 변경을 안정적으로 수행할 수 있고, 더 강한 에이전트는 더 넓은 유지보수 작업을 맡을 수 있다.
모든 작업을 같은 수준으로 위임해서도 안 된다. 문서 업데이트와 정적 검사 수정, 테스트명 개선처럼 위험이 낮은 작업은 적극적으로 자동화할 수 있다. 단일 모듈의 버그 수정이나 변환 계층 mapping 변경은 변경 계약와 관련 테스트가 있다면 위임할 수 있다. 여러 파일에 걸친 정책 이동과 API schema 확장은 에이전트가 탐색과 초안을 맡고 사람이 경계와 계약을 승인해야 한다. 서비스 간 계약, DB migration, 권한, 결제, 개인정보, 데이터 삭제처럼 위험한 변경은 에이전트를 영향 분석과 테스트 생성에 활용하되 핵심 결정은 사람이 내려야 한다.
조직의 도입 순서도 중요하다. AI 도구를 먼저 구매하고 대규모 리팩터링에 투입하는 것은 위험하다. 우선 현재의 작업 소요 시간, 리뷰 시간, CI 실패율, rollback 비율, 불안정한 테스트 비율, 병합 이후 결함를 측정해야 한다. 그다음 위험이 낮은 반복 작업부터 자동화하면서 작업 절차와 운영 규칙를 안정화하고, 주요 도메인 용어와 AGENTS.md, 도메인 용어 사전를 정리해야 한다. 이후 자주 바뀌고 위험한 도메인부터 변경 계약를 만들고, 변경 범위 예산과 사람에게 판단을 넘기는 기준을 CI에서 집행해야 한다. 이런 기반이 갖춰진 뒤에야 프레임워크 전환이나 API 전환, 대규모 정책 추출 같은 유지보수에서 에이전트의 능력이 큰 효과를 낼 수 있다.
개발자는 코더에서 맥락 관리자로 이동한다
에이전트 시대에도 개발자는 사라지지 않는다. 다만 핵심 역할이 달라진다. 개발자는 도메인 경계를 정하고, 어떤 정책이 권위 있는지 결정하며, 변경 계약과 테스트를 설계하고, 아키텍처 제약과 멈춤 조건을 집행하게 된다. 어떤 작업을 에이전트에게 맡길지 판단하고, 운영 신호와 코드의 의미를 연결하며, 반복되는 실패를 도구와 구조에 반영해야 한다.
이 역할을 맥락 관리자라고 부를 수 있다. 이는 덜 기술적인 역할이 아니다. 오히려 코드와 도메인, 테스트, 운영, 보안, 조직 구조를 함께 이해해야 한다는 점에서 더 기술적이다. 에이전트가 코드 작성을 맡을수록 사람은 어떤 맥락을 제공하고 어떤 제약을 강제하며 어떤 증거를 요구할지 결정해야 한다.
결론
AI 에이전트 시대의 소프트웨어 엔지니어링은 AI에게 코드를 많이 쓰게 하는 문제가 아니다. 진짜 문제는 브라운필드 시스템을 에이전트가 안전하게 작업할 수 있는 대상으로 바꾸는 것이다.
이를 위해 기존 소프트웨어 공학의 원칙들을 에이전트가 실제로 사용할 수 있는 형태로 더 발전시키고 구체화해야 한다. DDD는 검색 가능한 도메인 언어가 되고, Clean Architecture는 작업 맥락을 줄이는 경계가 된다. DRY는 정책의 단일 출처를 만들고, TDD는 실행 가능한 변경 계약이 된다. CI/CD는 에이전트 작업의 관문이 되고, Observability는 운영 신호를 코드의 의미와 연결한다. Code Review는 코드 스타일 검토를 넘어 근거 중심의 변경 관리가 된다.
앞으로 조직의 생산성 차이는 어떤 모델을 쓰느냐만으로 결정되지 않을 것이다. 같은 모델을 사용하더라도 어떤 조직은 빠르고 안전하게 변경을 병합하고, 어떤 조직은 더 많은 코드를 만들면서 더 많은 부채를 쌓게 된다. 그 차이는 코드베이스가 에이전트에게 얼마나 명확한 작업 환경을 제공하느냐에서 나온다.
좋은 코드베이스는 사람이 읽기 쉬운 코드베이스를 넘어선다. 에이전트가 필요한 맥락을 찾을 수 있고, 권위 있는 정책 위치를 식별할 수 있으며, 작은 diff를 만들고, 변경 계약로 결과를 검증하고, 위험한 변경에서는 멈출 수 있어야 한다.
이것은 일반적으로 거론되는 하네스를 넘어서, 코드베이스 자체를 하네스로 설계하는 방법이다. 결국 기존의 좋은 엔지니어링 조직에서 행하던 많은 Practice 를 잘 따르고 있다면 하네스는 저절로 따라온다.
앞으로 원래부터 좋았던 조직은 더 크게 장점이 증폭될 것이고, 안좋았던 조직은 단점이 빠르게 커질 것이다.
그런데 생각해보면 현실에서 훌륭한 조직은 별로 없다. 기존에도 잘 못하던/안하던 조직이 AI 시대라고 갑자기 잘하게 될리는 없다고 본다. AI 시대에도 특별한 비법은 없다. 결국 마주한 문제들의 본질을 잘 분석하고, 하나씩 팀워크를 통해 해결해나가다보면 좋은 조직이 될 것이고, 그런 조직은 자연스럽게 AI 라는 팀원도 쉽고 안전하게 일을 할 수 있는 환경을 갖추어 생산성도 크게 올라갈 것이다. 무엇이 먼저인지 생각하자.