기술 부채의 재정의
AI 시대에 무엇이 더 비싸고 무엇이 더 싸졌는가
기술 부채는 오래된 개념이지만, AI 코딩 도구의 등장으로 비용 구조 자체가 바뀌고 있다. 과거에 비싸던 것이 싸졌고, 신경 쓰지 않아도 됐던 것이 새로운 비용 요인이 되었다. 이 변화를 코드 사례와 함께 살펴본다.
전통적 기술 부채 모델
마틴 파울러(Martin Fowler)의 기술 부채 사분면은 여전히 유효하다. 하지만 각 사분면의 비용 가중치가 AI 시대에 크게 달라졌다는 점이 흥미롭다.
전통적 기술 부채의 공식은 단순했다.
| 요소 | 전통적 해석 | AI 시대 변화 |
|---|---|---|
| 원인 | 시간 압박, 경험 부족 | 동일 + AI 과신이 추가 |
| 증상 | 나쁜 코드, 중복, 미흡한 테스트 | AI가 이해 못하는 코드가 추가 |
| 비용 | 미래의 수정 비용 증가 | AI 협업 비용으로 전환 |
| 해결 | 리팩터링, 코드 리뷰 강화 | AI 친화적 구조 전환 |
이 모델에서 핵심 가정은 **"코드를 작성하는 비용은 고정적"**이라는 것이었다. 개발자 시간이 곧 비용이었고, 한번 나쁘게 작성된 코드는 나중에 더 큰 비용으로 돌아왔다.
비용 구조의 역전
AI 코딩 도구가 가져온 가장 큰 변화는 비용 구조의 역전이다. 이 역전을 이해하면 기술 부채에 대한 시각이 근본적으로 달라진다.
첫 번째 막대가 전통적 비용, 두 번째 막대가 AI 시대 비용이다. CRUD, 타입 정의, 테스트, 문서화는 비용이 급감했지만, 커스텀 추상화와 메타프로그래밍의 유지보수 비용은 오히려 급증했다.
비용이 크게 줄어든 영역
| 작업 | 과거 비용 | 현재 비용 | 변화 요인 |
|---|---|---|---|
| 보일러플레이트 작성 | 높음 | 거의 0 | AI가 즉시 생성 |
| CRUD 엔드포인트 | 중간 | 낮음 | 패턴화된 코드는 AI가 정확 |
| 단위 테스트 작성 | 중간 | 낮음 | AI가 엣지 케이스까지 커버 |
| 타입 정의 / 인터페이스 | 중간 | 낮음 | 스키마에서 자동 생성 |
| 문서화 | 높음 | 낮음 | AI가 코드에서 추출 |
| 코드 포맷팅 | 낮음 | 거의 0 | 린터 + AI 자동 교정 |
비용이 오히려 높아진 영역
| 작업 | 과거 비용 | 현재 비용 | 이유 |
|---|---|---|---|
| 과도한 추상화 유지 | 낮음 | 높음 | AI가 추상화 의도를 이해 못함 |
| 암묵적 규칙 | 낮음 | 높음 | AI에게 매번 설명 필요 |
| 마법 같은 메타프로그래밍 | 중간 | 매우 높음 | AI가 런타임 동작 추론 불가 |
| 프레임워크 고유 패턴 | 낮음 | 중간 | 학습 데이터에 없으면 비용 급증 |
| 비명시적 의존성 | 낮음 | 높음 | AI가 의존성 그래프 파악 어려움 |
| 깊은 상속 계층 | 낮음 | 높음 | AI가 부모 클래스 전체를 이해해야 |
새로운 부채의 원천
과거에는 "나쁜 코드"가 기술 부채였다. AI 시대에는 **"AI가 이해하기 어려운 코드"**가 새로운 기술 부채가 되는 경향이 있다. 아무리 우아한 추상화라도 AI가 이해하지 못하면 유지보수 비용이 급증한다.
코드로 보는 가치의 변화
사례 1: 과도한 제네릭 추상화
// 전통적으로 "좋은 코드"로 여겨지던 극도로 DRY한 제네릭 유틸
type DeepPartial<T> = T extends object
? { [P in keyof T]?: DeepPartial<T[P]> }
: T;
type PathKeys<T> = T extends object
? { [K in keyof T]: K extends string
? T[K] extends object
? K | `${K}.${PathKeys<T[K]>}`
: K
: never
}[keyof T]
: never;
function deepUpdate<T extends object>(
obj: T,
path: PathKeys<T>,
value: unknown
): T {
// 12줄의 재귀 로직
const keys = (path as string).split(".");
// ...복잡한 구현
}이 코드는 사람이 봐도 이해하기 어렵고, AI는 더 어려워한다. PathKeys 같은 재귀 타입은 AI가 정확히 추론하기 매우 어렵다.
// 대안 - 명시적이고 구체적인 함수
function updateUserProfile(
user: User,
field: "name" | "email" | "phone",
value: string
): User {
return { ...user, [field]: value };
}
function updateOrderStatus(
order: Order,
status: "pending" | "confirmed" | "shipped"
): Order {
return { ...order, status, updatedAt: new Date() };
}| 비교 항목 | 제네릭 추상화 | 구체적 함수 |
|---|---|---|
| 코드 줄 수 | 약 20줄 (1개) | 약 15줄 (2개) |
| AI 이해도 | 낮음 | 높음 |
| 타입 안전성 | 런타임에서만 확인 | 컴파일 시점 확인 |
| 수정 비용 | 전체 제네릭 영향 분석 필요 | 해당 함수만 수정 |
| 새 필드 추가 | 자동이지만 위험 | 유니온에 추가, AI가 즉시 처리 |
제네릭 추상화가 나쁜 것은 아니다. 다만, 그 추상화가 AI와의 협업에서 얼마나 비용을 발생시키는지를 함께 고려할 필요가 생겼다는 것이다.
사례 2: 데코레이터 마법
// 데코레이터 체이닝 방식
@Controller("/users")
@UseGuards(AuthGuard)
@UseInterceptors(LoggingInterceptor, CacheInterceptor)
@ApiTags("Users")
class UserController {
@Get("/:id")
@Cacheable(300)
@RateLimit(100)
@Validate(GetUserSchema)
async getUser(@Param("id") id: string) {
return this.userService.findById(id);
}
}AI는 각 데코레이터가 어떤 동작을 하는지, 실행 순서가 어떤지 추론하기 어렵다. 특히 커스텀 데코레이터는 AI의 학습 데이터에 없으므로 추측에 의존하게 된다.
// 명시적 미들웨어 체이닝 방식
export async function GET(
request: Request,
context: RouteContext
) {
// 1. 인증 확인
const session = await getServerSession();
if (!session) return unauthorized();
// 2. Rate limit 확인
const allowed = await checkRateLimit(session.userId, 100);
if (!allowed) return tooManyRequests();
// 3. 입력 검증
const { id } = validateParams(context.params, GetUserSchema);
// 4. 캐시 확인
const cached = await cache.get(`user:${id}`);
if (cached) return NextResponse.json(cached);
// 5. 비즈니스 로직
const user = await userService.findById(id);
// 6. 캐시 저장
await cache.set(`user:${id}`, user, 300);
return NextResponse.json(user);
}AI는 이 코드의 모든 단계를 즉시 이해하고, 어떤 부분이든 정확하게 수정할 수 있다. 두 방식 모두 같은 기능을 구현하지만, AI와의 협업 효율에서 상당한 차이가 발생한다.
DRY 원칙에 대한 재고
"Don't Repeat Yourself"는 수십 년간 소프트웨어 공학의 금과옥조였다. 하지만 코드 생성 비용이 0에 수렴하는 시대에, 이 원칙을 맹목적으로 따르면 오히려 비용이 증가할 수 있다는 점은 생각해볼 만하다.
반복이 추상화보다 나은 경우
// 전통적 DRY: 공통 헬퍼로 추출
// utils/formatDate.ts에 12가지 날짜 포맷 함수
export function formatDate(date: Date, format: string): string {
// 200줄의 범용 포맷 함수
}
export function formatRelative(date: Date): string {
return formatDate(date, "relative");
}
export function formatISO(date: Date): string {
return formatDate(date, "iso");
}
// ... 9개 더// 각 컴포넌트에서 인라인으로 작성한 경우
// OrderList.tsx
function formatOrderDate(date: Date): string {
return dayjs(date).tz("Asia/Seoul").format("YYYY.MM.DD");
}
// UserProfile.tsx
function formatJoinDate(date: Date): string {
return dayjs(date).tz("Asia/Seoul").format("YYYY년 M월 D일");
}
// 각 함수는 독립적 - AI가 컨텍스트 없이 바로 수정 가능
// 한 곳의 변경이 12곳에 전파되지 않음판단의 기준
| 상황 | 전통적 DRY | AI 시대의 경향 | 판단 근거 |
|---|---|---|---|
| 비즈니스 로직 | 추출 (한 곳에서 관리) | 추출 (여전히 유효) | 정합성이 핵심 |
| UI 유틸리티 | 추출 | 인라인 허용 | AI 생성 비용이 0 |
| 설정값 | 상수 파일로 추출 | 상수 파일 (여전히 유효) | 변경 시 일괄 적용 필요 |
| 보일러플레이트 | 반드시 추출 | 반복 허용 | AI가 즉시 생성 |
| 타입 변환 | 유틸 함수 | 인라인 허용 | 명시적이 추상적보다 나음 |
| 검증 로직 | 공통 스키마 | 도메인별 분리 | 도메인 독립성 확보 |
WET 원칙의 재조명
"Write Everything Twice" - 정확히 두 번 반복될 때까지는 추상화하지 않는다는 원칙이 AI 시대에 더 합리적인 전략이 되고 있다. AI가 반복 코드를 순식간에 생성할 수 있으므로, 섣부른 추상화의 비용이 반복의 비용을 초과하게 되는 경우가 늘고 있다. 단, 비즈니스 규칙의 정합성이 중요한 영역에서는 DRY가 여전히 우선한다.
AI 친화적 코드의 특성
AI가 이해하기 쉬운 코드에는 몇 가지 공통된 특성이 관찰된다.
명시적 타입 선언
AI는 타입 추론보다 명시적 타입을 더 정확하게 이해하는 경향이 있다.
// 복잡한 타입 추론 체인
const result = data
.filter(Boolean)
.map(transform)
.reduce(accumulate, initial);
// result의 타입을 AI가 추론하려면 3개 함수를 모두 분석해야 함
// 명시적 타입 명시
const activeUsers: ActiveUser[] = data
.filter((user): user is ActiveUser => user !== null)
.map((user): UserSummary => ({
id: user.id,
name: user.name,
}));파일 단위 독립성
// 파일 1개를 이해하려면 5개 파일이 필요한 구조
// OrderService.ts
import { BaseService } from "./base"; // 추상 클래스
import { mixinCacheable } from "./mixins"; // 믹스인
import { OrderDecorators } from "./decorators"; // 데코레이터
import { ORDER_CONFIG } from "./config"; // 설정
import { OrderTypes } from "./types"; // 타입
// 파일 하나로 이해 가능한 구조
// order-service.ts
interface OrderInput { ... }
interface OrderResult { ... }
const CACHE_TTL = 300;
export async function createOrder(input: OrderInput): Promise<OrderResult> {
// 모든 로직이 이 파일 안에서 완결
}표준 패턴 활용
| 카테고리 | AI가 어려워하는 패턴 | AI가 잘 이해하는 패턴 |
|---|---|---|
| 상태 관리 | 커스텀 옵저버 패턴 | React useState/useReducer |
| DI | 서비스 로케이터 | 함수 파라미터 주입 |
| 에러 처리 | 커스텀 Either 모나드 | try-catch + Result 타입 |
| 설정 | 동적 프록시 기반 config | 정적 타입 config 객체 |
| 데이터 접근 | 커스텀 쿼리 빌더 | Prisma / Drizzle 표준 ORM |
의도를 전달하는 함수 이름
// AI가 맥락 없이 이해하기 어려운 이름
const result = process(data);
const output = handle(input);
const value = compute(items);
// AI가 이름만으로 동작을 추론할 수 있는 이름
const validatedOrder = validateAndNormalizeOrder(rawOrderData);
const monthlyRevenue = calculateMonthlyRevenue(transactions);
const eligibleUsers = filterEligibleUsersForPromotion(allUsers);이 네 가지 특성에서 공통적으로 드러나는 것은, AI 친화적 코드가 결국 사람에게도 읽기 좋은 코드와 상당 부분 겹친다는 점이다. 다만, 사람은 암묵적 맥락을 공유할 수 있지만 AI는 그렇지 못하기 때문에, "명시성"의 가치가 더 높아졌다고 볼 수 있다.
기술 부채의 비용이 어떻게 재편되고 있는가
기존 코드베이스의 기술 부채를 평가할 때, AI 시대에는 평가 기준 자체가 달라지고 있다. 과거에는 중복 코드와 스타일 비일관성이 높은 우선순위였지만, 지금은 테스트 부재와 과도한 추상화가 더 큰 비용을 발생시키는 경향이 있다.
부채 유형별 위험도 변화
| 부채 유형 | AI 시대 위험도 | 위험도 변화 방향 | 이유 |
|---|---|---|---|
| 테스트 부재 | 매우 높음 | 상승 | AI 생성 코드 검증 불가 |
| 복잡한 메타프로그래밍 | 매우 높음 | 급상승 | AI가 완전히 무력화됨 |
| 암묵적 의존성 | 높음 | 상승 | AI가 사이드이펙트 예측 불가 |
| 과도한 추상화 레이어 | 높음 | 상승 | AI 컨텍스트 비용 증가 |
| 비표준 패턴 | 중간 | 상승 | AI 학습 데이터에 없음 |
| 문서 부재 | 중간 | 하락 | AI가 코드에서 문서 생성 가능 |
| 중복 코드 | 낮음 | 하락 | AI가 쉽게 수정 가능 |
| 스타일 비일관성 | 낮음 | 하락 | AI + 린터가 자동 교정 |
가장 위험한 조합: 테스트 부재 + 복잡한 추상화
이 두 가지가 동시에 존재하면 AI 활용이 사실상 불가능해진다. AI가 코드를 생성해도 검증할 방법이 없고, 기존 코드를 이해하는 데도 과도한 컨텍스트가 필요하다. 이 조합이 존재하는 코드베이스에서는 AI 도구의 효과를 거의 체감하기 어렵다.
CodeScene: 코드 건강도 9.5+ 필수 (2026)
CodeScene의 2026년 연구는 AI 에이전트 도입 전에 **코드 건강도(Code Health)**가 충분히 높아야 AI의 효과가 극대화된다는 것을 데이터로 보여주었다.
| 코드 건강도 | AI 에이전트 성능 | 비용 영향 |
|---|---|---|
| 9.5 이상 | 높은 정확도, 빠른 생성 | AI 투자 대비 효과 극대화 |
| 7.0 ~ 9.5 | 불안정, 잦은 수정 필요 | AI와 사람의 수정 비용이 병존 |
| 7.0 미만 | AI가 오히려 부채 가속 | 에이전트 도입보다 정리가 먼저 |
핵심 인사이트는 **"속도는 좋은 설계와 나쁜 결정을 모두 증폭한다"**는 것이다. 코드 건강도가 낮은 상태에서 AI 에이전트를 투입하면, AI가 나쁜 패턴을 더 빠르게 확산시킨다. 반대로 건강한 코드베이스에서는 AI가 좋은 패턴을 더 빠르게 적용한다.
에이전트 배포 전 코드 건강도 점검
AI 에이전트를 팀에 도입하기 전에, 코드베이스의 건강도를 먼저 측정하고 개선하는 것이 순서다. 테스트 커버리지, 복잡도, 결합도, 문서화 수준 — 이 기본 지표를 올려놓은 뒤에 에이전트를 도입해야 "속도 × 품질"의 복리 효과를 얻을 수 있다.
AGENTS.md를 인프라로 취급하기
CodeScene은 AGENTS.md를 문서가 아니라 인프라로 다뤄야 한다고 강조한다. AGENTS.md는 에이전트가 코드베이스를 이해하는 방식을 결정하며, 그 품질이 에이전트의 출력 품질에 직접적으로 영향을 미친다.
실질적으로 이것은 기술 부채의 새로운 차원을 만든다. 과거에는 "나쁜 코드"가 부채였고, AI 시대에는 "AI가 이해하기 어려운 코드"가 부채였다면, 이제는 **"AI가 코드를 이해하는 맥락 자체의 부재"**도 부채에 포함된다. AGENTS.md가 없거나 부정확한 프로젝트에서 AI 에이전트를 사용하는 것은, 신입 개발자에게 온보딩 문서 없이 코드를 맡기는 것과 같다.
리팩터링에 대한 전략적 사고
AI 시대의 리팩터링을 관찰하면, 효과적인 팀들은 대체로 비슷한 순서로 접근하는 경향이 있다. 모든 부채를 동시에 갚으려 하지 않고, AI 협업 효과가 가장 큰 영역부터 시작한다.
1단계: 안전망 구축
AI와 함께 리팩터링하기 전에 안전망부터 구축하는 것이 자연스러운 순서다.
- 핵심 비즈니스 로직에 통합 테스트 추가: AI에게 "이 함수의 현재 동작을 검증하는 테스트를 작성해줘"라고 요청하면, 기존 동작을 보존하는 테스트를 빠르게 만들 수 있다.
- CI에서 타입 체크 + 테스트 자동 실행: 리팩터링 중 회귀를 즉시 감지할 수 있는 환경.
- any 타입 제거: AI가 타입 정보 없이 정확한 코드를 생성하기 어려우므로, 타입 강화는 AI 협업의 전제 조건에 가깝다.
2단계: AI 친화적 구조화
가장 핵심적인 단계다. AI가 효과적으로 작업할 수 있는 구조를 만드는 과정이다.
- 깊은 상속 체인 풀기: 3단계 이상의 상속은 컴포지션으로 전환하면 AI의 이해도가 크게 올라간다.
- 동적 의존성 제거: 서비스 로케이터, 동적 의존성을 명시적 주입으로 전환.
- CLAUDE.md에 아키텍처 결정 기록: AI가 매번 추측하지 않도록 배경을 명시.
3단계: AI 가속
안전망과 구조가 갖춰지면 AI가 생산성을 크게 높일 수 있는 단계에 진입한다.
- 반복적인 CRUD, 폼 처리, 데이터 변환을 AI에게 위임
- "이 패턴과 같은 스타일로 나머지 엔드포인트도 만들어줘"
- 테스트 커버리지를 AI가 빠르게 확장
리팩터링 사례: 상속에서 합성으로
Before: 3단계 상속 + 믹스인
// 전통적 "좋은 설계" - 하지만 AI가 이해하기 어려움
abstract class BaseRepository<T> {
abstract tableName: string;
async findById(id: string): Promise<T | null> { /* ... */ }
async findAll(): Promise<T[]> { /* ... */ }
async create(data: Partial<T>): Promise<T> { /* ... */ }
}
class CacheableRepository<T> extends BaseRepository<T> {
// BaseRepository를 읽어야 이해 가능
async findById(id: string): Promise<T | null> {
const cached = await this.cache.get(id);
if (cached) return cached;
return super.findById(id);
}
}
class UserRepository extends CacheableRepository<User> {
// BaseRepository + CacheableRepository를 읽어야 이해 가능
tableName = "users";
}After: 함수 기반, 파일 독립적
// 이 파일만 읽으면 모든 동작을 이해할 수 있는 구조
interface UserRepository {
findById(id: string): Promise<User | null>;
findAll(): Promise<User[]>;
create(data: CreateUserInput): Promise<User>;
}
export function createUserRepository(
db: PrismaClient,
cache: CacheClient
): UserRepository {
return {
async findById(id) {
const cached = await cache.get<User>(`user:${id}`);
if (cached) return cached;
const user = await db.user.findUnique({ where: { id } });
if (user) await cache.set(`user:${id}`, user, 300);
return user;
},
async findAll() {
return db.user.findMany({ orderBy: { createdAt: "desc" } });
},
async create(data) {
return db.user.create({ data });
},
};
}| 비교 | 상속 기반 | 함수 기반 |
|---|---|---|
| AI가 이해하려면 | 3개 파일 필요 | 1개 파일로 충분 |
| 새 메서드 추가 | 어느 레이어에 넣을지 고민 | 객체에 함수 추가 |
| 캐시 동작 변경 | 부모 클래스 영향 분석 | 해당 함수만 수정 |
| 테스트 | 모킹 복잡 | 직관적 의존성 주입 |
달라진 관점의 요약
| 전통적 관점 | AI 시대에 부상하는 관점 |
|---|---|
| 중복 코드 = 부채 | 중복 코드 = 허용 가능 (AI 생성 비용 약 0) |
| 추상화 = 자산 | 과도한 추상화 = 부채 (AI 컨텍스트 비용 증가) |
| 코드량 = 비용 | 컨텍스트 복잡도 = 비용 |
| DRY = 항상 옳다 | WET이 더 나을 때가 있다 |
| 사람이 읽기 좋은 코드 | 사람 + AI가 읽기 좋은 코드 |
이 변화가 시사하는 바는, 기술 부채의 평가 기준이 **"코드 품질"에서 "AI 협업 가능성"**으로 확장되고 있다는 것이다. 중복 코드는 더 이상 큰 부채가 아닌 반면, 테스트 부재, 과도한 추상화, 암묵적 규칙이 가장 비싼 부채로 부상하고 있다. 이 비용 구조의 변화를 인식하는 것만으로도, 리팩터링의 우선순위를 잡는 데 상당한 도움이 될 수 있다.
다음 장 미리보기
기술 부채의 기준이 바뀌면, 다음 질문은 자연스럽게 팀 운영으로 이어집니다. 다음 장에서는 AI가 팀원처럼 참여할 때 워크플로우와 리뷰 기준이 어떻게 달라지는지 다룹니다.
참고 자료
참고 자료 안내
이 장의 관점과 프레임워크를 뒷받침하는 참고 자료입니다. 본문의 모든 주장이 아래 자료에서 직접 인용된 것은 아니며, 실무 경험과 커뮤니티 사례를 종합한 해석이 포함되어 있습니다.
- Fowler, M. (2019). "Technical Debt." https://martinfowler.com/bliki/TechnicalDebt.html
- CodeScene (2026). "Code Health and AI Agent Performance." https://codescene.com/blog/code-health-ai-agents