← back to archive
dev

AI 코드는 개똥입니다.

구현은 AI에게, 계약은 사람에게.

  • #ai
  • #claude-code

1. AI 코드는 개똥같습니다

네. 정말로요.

AI가 만들어내는 코드를 한번 진득하게 들여다본 적 있으신가요? 얼렁뚱땅 기능 위주의 프롬프트 — "이거 만들어줘", "저거 고쳐줘" — 로 뽑아낸 결과물 말입니다. 첫인상은 좋습니다. 동작합니다. 테스트도 통과합니다. "오, 됐네?" 싶습니다.

그런데 가만히 보고 있으면 슬슬 등골이 서늘해집니다.

사이드이펙트가 여기저기 흩뿌려져 있습니다. 한 함수가 다섯 가지 일을 동시에 하고 있고, 그중 세 가지는 호출자 입장에서 전혀 예상할 수 없는 일입니다. 작은 단위의 로직은 재사용성을 고려하지 않은 채로 그 자리에 박혀 있습니다. 같은 일을 하는 코드 조각이 세 군데에 복붙되어 있는 건 양반입니다. 비슷한데 미묘하게 다른 코드 조각이 다섯 군데에 흩어져 있는 건 흔한 일입니다.

전체 구조 내에서 이 코드가 어느 레이어에 속하는지에 대한 고민은 보이지 않습니다. UI 코드가 DB를 직접 호출하고, 도메인 로직이 HTTP 응답 형식을 알고 있으며, 어느 모듈이 어느 모듈을 의존하는지는 그날의 운에 맡겨져 있습니다. 참조가 뒤엉킬 가능성? 가능성이 아닙니다. 이미 뒤엉켜 있습니다.

요컨대, 그럴듯하게 동작하는 코드일 뿐입니다. 동작하는 것과 예측 가능한 것은 다릅니다. AI는 그럴듯한 — 일단 동작하는 — 코드를 뽑는 데에는 천재지만, 예측 가능한 코드를 만드는 데에는 아직 갈 길이 멉니다.

2. 너도 AI로 코딩하는 주제에!

그래서 AI 안쓸거냐고요? 에이 어떻게 안 쓸 수 있겠습니까. 이렇게 편리한 것을! AI가 가져다주는 코드 생산성의 폭발은 무시하기엔 너무 큽니다. 한나절 걸릴 보일러플레이트를 5분에 끝내주고, 낯선 라이브러리의 사용법도 알아서 찾아 적용해줍니다. 이걸 거부하고 모든 코드를 손으로 짜겠다는 건, 마치 엑셀 두고 주판으로 회계 보겠다는 소리에 가깝습니다.

그러니 우리는 어쩔 수 없이 양다리를 걸쳐야 합니다. AI는 써야 하고, 그런데 AI가 만든 코드는 개똥같고. 이 모순을 어떻게 풀 것이냐, 가 우리가 마주한 진짜 질문입니다.

3. 중요한 건 인터페이스와 계약사항입니다

저는 여전히 숙련된 개발자가 좋은 코드를 만들 수 있다고 믿습니다. 모두가 AI로 코딩하는 이 시점에서도 말입니다. 그리고 좋은 코드란 결국 예측 가능한 코드입니다.

예측대로 동작하는 코드라야 의도대로 동작하는 제품이 됩니다. 그리고 의도대로 동작하는 제품이라야 좋은 품질의 제품입니다. 너무 당연한 이야기 아니냐구요? 하지만 예측 가능성이라는 단어의 무게는 생각보다 더 무겁습니다.

예측 가능성은 보통 코드 계약사항이 얼마나 명시적으로 드러나있는가에 의해 결정됩니다.

계약? 무슨 계약을 말하는걸까요?

난 이 조건을 줘야만 실행될 수 있어. 실행되면 이런 결과를 줄게!

대부분의 코드는 이런 계약사항이 있습니다. 코드에는 실행되기 전에 필요한 조건과 환경이 있습니다. 또한 실행되고 나서 우리가 기대하는 결과도 있지요. 이러한 계약사항을 명시적으로 코드에 드러나게 하는 일은 예측 가능성을 향상시키고 좋은 품질의 제품을 만드는 데에 있어서 정말 중요한 필요조건입니다.

어디 그것 뿐일까요? 실행 조건과 실행 결과를 명시적으로 정의한다는 말은, 그 코드가 무엇에 의존하고 무엇에 의존하지 않는지를 명시한다는 뜻입니다. 그 말은 곧 코드 주변과의 결합과 분리를 명시적으로 다룰 수 있다는 뜻입니다. 결국 코드를 예측가능하게 한다는 말은 크게보면 설계에 관한 이야기이기도 합니다.

4. 낯설지 않은 개념입니다

여기서 잠깐. 지금 이거, 새로운 이야기 아닙니다.

왜 사이드이펙트는 분리해야 할까요? 왜 순수함수는 순수함수인 채로 남겨두어야 할까요? 함수형 프로그래밍만의 이야기가 아닙니다. 왜 객체지향에서는 구현체가 아닌 인터페이스에 의존해야 한다고 그토록 강조했을까요? 다 같은 맥락의 이야기입니다.

우리에게는 코드와의 계약사항이 있습니다. 코드가 예측대로 동작하려면, 실행을 위해 필요한 조건이 있고 실행 후 보장하는 결과가 있습니다. 순수함수에서는 인자와 반환값이 그 역할을 합니다. 객체지향에서는 인터페이스라는 호출 조건만 충족시켜주면, 은닉된 구현체가 아무튼 결과를 보장해줍니다. (적어도 그렇게 되도록 설계하려고 합니다.)

이것은 낯선 개념이 아닙니다. 코드 설계에 있어 원래 중요했던 부분이고, 여전히 중요한 부분입니다. AI가 등장했다고 해서 갑자기 새로 발명된 개념이 아닙니다. 오히려 AI 시대에 중요해졌을 뿐입니다.

5. 무엇을 위임하고, 무엇을 주도해야 할까요?

한가지 더 짚고 넘어가고 싶은 게 있습니다.

AI는 이제 꽤 어려운 알고리즘도 순식간에 풀어냅니다. 그럼에도 불구하고 명백한 한계가 있습니다. 콘텍스트의 문제입니다.

모방하거나 참조할 주변 코드베이스가 엉망진창이라면, AI는 그 엉망진창을 학습해 더 큰 엉망진창을 만듭니다. 코드베이스가 너무 커서 탐색의 한계에 부딪히면, AI는 보이지 않는 곳에 이미 존재하는 함수를 또 만들어냅니다. AI가 보지 못하는 영역의 중복을 막고, 모듈 간 의존 방향을 정돈하는 일 — 이건 결국 코드베이스 전체를 조망할 수 있는 사람의 몫입니다. AI를 잘 쓰려면, 역설적으로 설계에 더 신경 써야 합니다.

구현은 AI에게 맡기세요. 구현은 말입니다.

하지만 코드의 계약사항만큼은 사람이 관리해야 합니다. 스펙을 기준으로 구현만 찍어내는 코드를 생산하다 보면, AI는 당장의 스펙에 맞추기 위해 책임이 얕고 잘게 쪼개진 모듈을 양산할 뿐입니다. 그러고는 그 수많은 조각들 사이에서 길을 잃겠죠. 우리도 그 옆에서 같이 길을 잃을 거고요.

그래서, 구체적으로는?

추상적인 이야기로 들리지 않게, 제가 의식적으로 지키려는 것들을 적어둡니다.

(1) 타입과 시그니처는 사람이 먼저 못 박는다. "유저 프로필 가져오는 함수 만들어줘"라고 던지면, AI는 throw도 하고 null도 반환하고 콘솔에도 찍는 만능 함수를 만들어옵니다. 입력 타입, 출력 타입, 실패 케이스를 사람이 먼저 정의하고 구현부만 AI에게 넘기면, 같은 모델·같은 프롬프트라도 결과 코드의 결이 완전히 달라집니다.

// 시그니처는 사람이, 구현은 AI가.
type UserProfileError =
  | { kind: "not-found" }
  | { kind: "unauthorized" }
  | { kind: "network"; cause: unknown };
 
interface UserProfileRepository {
  findById(
    id: string,
  ): Promise<
    { ok: true; profile: UserProfile } | { ok: false; error: UserProfileError }
  >;
}

계약을 이렇게 못 박아 두면, AI는 이 계약 안에서만 움직입니다. 실패 케이스를 누락하거나 throw로 도망갈 자리가 없습니다.

(2) 모듈 경계와 의존 방향은 사람이 가드한다. "UI가 DB를 직접 호출하지 않는다", "도메인은 HTTP 응답 형식을 모른다" 같은 규칙은 AI에게 매번 프롬프트로 설명할 게 아니라, 폴더 구조 / lint 규칙 / 리뷰 체크리스트로 강제하는 게 맞습니다. AI는 코드베이스 전체를 조망하지 못하므로, 경계는 사람이 미리 깔아둔 가드레일에 의해서만 지켜집니다.

(3) 응집도는 사람이 주기적으로 들여다본다. AI는 매 요청을 "지금 스펙"에만 맞춥니다. 그래서 비슷한 함수가 세 군데에 흩어져 있어도 알아채지 못합니다. PR 단위 리뷰에 더해, 가끔은 한 발 물러서서 "이 모듈, 책임이 너무 얕아지지 않았나?"를 사람이 직접 봐야 합니다.

요약하면 — AI가 구현의 단위를 처리하는 동안, 사람은 단위와 단위 사이를 책임집니다.

6. 저도 사실 AI 코딩 좋아합니다.

이렇게 이야기하는 저도 사실 AI코딩 좋아합니다. 코딩이라는 행위도 좋아하지만 코딩이 사람에게 주는 가치와 이로움에 더 관심이 많습니다. 적은 비용과 시간으로 순식간에 자신의 아이디어와 제품을 만들고 시험해볼 수 있는 세상이 와서 하루하루 재밌게 AI와 코딩 하고 있습니다.

다만 이 즐거움이 오래 가려면, AI가 더 잘 일할 수 있는 자리를 사람이 만들어줘야 합니다. 구현은 AI에게, 계약은 사람에게. 결국 더 나은 코드와 제품은 그 경계를 잘 그어주는 사람의 몫입니다.