예전에는 이 생각을 일부러 자극적으로 표현했습니다. “Codex에 가장 좋은 언어는 TypeScript다.” 지금은 조금 더 정확하게 말하고 싶습니다. 제가 실제로 하는 프로덕션 프론트엔드·풀스택 개발에서는 TypeScript가 Codex를 가장 쉽게 제어하고, 검증하고, 합리적으로 신뢰할 수 있게 해 주는 환경입니다.
중요한 단어는 “TypeScript” 자체가 아니라 통제 가능성입니다. AI coding agent는 그럴듯한 코드를 만드는 데 매우 능숙합니다. 하지만 프로덕션 엔지니어링은 더 엄격합니다. 기존 계약을 지키고, 동작을 보존하며, 리팩터링을 견디고, 잘못된 가정이 있을 때 빠르게 실패해야 합니다. TypeScript는 순수 JavaScript보다 훨씬 많은 제약을 기계가 읽고 검증할 수 있는 형태로 만듭니다.
Codex가 코드베이스에서 실제로 필요로 하는 것
좋은 프롬프트는 도움이 되지만 컨텍스트의 한 부분일 뿐입니다. 코드베이스 자체가 작업을 이해하기 쉽게 만들 수도 있고, 모델이 추측하게 만들 수도 있습니다. 실무에서는 중요한 가정이 코드에 드러나고 자동 검증되는 환경이 가장 강합니다.
- 함수 시그니처는 허용되는 입력과 출력을 정의합니다.
- interface와 type은 도메인 객체의 형태를 명시합니다.
- union과 enum은 유효한 상태의 범위를 좁힙니다.
- 컴파일러는 많은 잘못된 가정을 수정 직후 구체적인 오류로 바꿉니다.
계약을 위반했을 때 코드베이스가 즉시 반응해 준다면 AI agent가 애플리케이션 전체를 완벽히 이해할 필요는 없습니다. 피드백 루프가 짧을수록 자신감 있는 추측이 들어갈 여지는 줄어듭니다.
타입은 단순한 문서가 아니라 실행 가능한 컨텍스트다
좋은 TypeScript는 사람에게 설계 의도를 설명합니다. 자동화된 workflow에서는 그 이상입니다. 그 문서 자체를 검증할 수 있기 때문입니다. 예를 들어:
type PaymentResult =
| { ok: true; receiptId: string }
| { ok: false; code: "declined" | "timeout" };
function getReceiptId(result: PaymentResult) {
if (result.ok) return result.receiptId;
return result.receiptId;
// TypeScript: Property 'receiptId' does not exist
// on the failure branch.
}JavaScript에서는 같은 실수가 해당 branch가 runtime에 실행될 때까지 자연스럽게 보일 수 있습니다. TypeScript는 merge 전에 그 잘못된 가정을 거부할 수 있습니다. Codex 입장에서 compiler error는 에디터의 빨간 줄이 아니라 “어떤 전제를 잘못 이해했는지” 알려 주는 구체적인 신호입니다.
그래서 저는 Record<string, any> 같은 넓은 타입보다 명확한 도메인 타입을 선호합니다. 타입이 현실을 정확히 표현할수록 코드베이스는 개발자와 AI 모두에게 더 좋은 컨텍스트가 됩니다.
컴파일러가 실수를 피드백 루프로 바꾼다
저는 “Codex에게 코드를 요청하고 첫 답을 그대로 채택”하는 방식은 신뢰하지 않습니다. 프로세스는 반복적이어야 합니다.
task → inspect → edit → typecheck → lint → test → review diff지루한 순서지만 그래서 강합니다. 의미 있는 변경 뒤에는 환경이 응답합니다. Type error는 깨진 계약을 찾고, lint는 다른 종류의 문제를 찾습니다. 테스트는 타입으로 표현할 수 없는 동작을 검증합니다. 최종 diff는 여전히 사람이 review합니다.
좋은 프롬프트는 첫 시도를 개선할 수 있습니다. 강한 검증 루프는 모든 시도를 개선합니다. 프로덕션에서는 후자가 훨씬 중요합니다.
대규모 리팩터링에서 TypeScript의 가치가 더 커진다
작고 독립적인 수정은 대부분의 coding assistant가 잘합니다. 어려운 경우는 변경이 수십 개 파일로 퍼지는 상황입니다. 도메인 필드 이름 변경, API response 변경, component props 강화, union 분리, 오래된 abstraction 교체 같은 작업입니다.
TypeScript에서는 breaking change가 영향받는 가정의 지도를 만듭니다. 컴파일러는 기존 계약이 아직 사용되는 위치를 보여 줍니다. Codex는 그 목록을 따라 수정하고 매 단계 다시 검증할 수 있습니다. 이것이 대규모 리팩터링을 자동으로 올바르게 만들지는 않지만 blast radius를 보이게 합니다.
타입이 느슨한 코드베이스에서는 놓친 dependency가 개발 중 아무도 열어 보지 않은 화면의 버그로 처음 발견될 수 있습니다.
TypeScript가 해결해 주지 못하는 것
TypeScript는 guardrail이지 정확성의 증명이 아닙니다.
- 잘못된 비즈니스 로직: 완벽히 typed된 코드도 잘못된 값을 계산할 수 있습니다.
- Runtime 입력: API, DB, form, third-party service는 compile-time 가정과 다른 데이터를 반환할 수 있습니다.
- 부족한 제품 컨텍스트: 컴파일러는 왜 특정 flow가 존재하는지, 어떤 edge case가 사용자에게 중요한지 모릅니다.
- 보안과 아키텍처: 유효한 타입이 올바른 authorization, caching, DB 경계, infrastructure를 보장하지 않습니다.
그래서 runtime validation, tests, logging, observability, review는 계속 필요합니다. 깨끗한 typecheck를 “feature가 정확하다”와 혼동하면 안 됩니다.
제가 AI-assisted 변경에서 사용하는 workflow
- 목표를 좁게 정의합니다. 무엇을 바꾸고, 무엇은 바꾸지 않으며, acceptance criteria가 무엇인지 정합니다.
- 수정 전에 Codex가 조사하게 합니다. 기존 type, call site, test, 인접 module은 긴 prompt보다 더 좋은 컨텍스트가 될 때가 많습니다.
- 가장 작은 일관된 변경을 선호합니다. 집중된 diff가 관련 없는 코드를 “도와주며” 다시 쓰는 것보다 검증하기 쉽습니다.
- 즉시 typecheck와 lint를 실행합니다. 구조적 오류는 변경이 작을 때 드러나야 합니다.
- 관련 테스트를 실행하고 behavior가 바뀌면 테스트를 추가합니다. 타입은 계약을, 테스트는 동작을 검증합니다.
- 최종 diff를 직접 review합니다. naming, architecture, 중복, edge case, 그리고 원래 문제를 실제로 해결했는지 확인합니다.
JavaScript로 충분한 경우도 있다
JavaScript가 나쁘다는 이야기가 아닙니다. 작은 script, 버리는 prototype, 간단한 automation, shared state가 거의 없는 코드라면 TypeScript의 추가 구조가 큰 이득을 주지 않을 수 있습니다. 테스트와 convention이 매우 좋은 JavaScript 프로젝트도 AI에게 훌륭한 환경이 될 수 있습니다.
제 주장은 더 제한적입니다. 코드베이스가 더 크고 오래 유지되며 더 많은 사람이 함께 수정할수록 기계적으로 검증 가능한 제약의 가치가 커집니다. 그리고 바로 그 시점에 AI의 실수 비용도 커집니다.
제가 TypeScript와 Codex 조합을 선호하는 진짜 이유
TypeScript가 Codex 자체를 더 똑똑하게 만드는 것은 아닙니다. 대신 나쁜 추측을 환경이 더 쉽게 거부하게 만듭니다.
프로덕션에서는 단순히 창의적인 agent가 아니라, 틀릴 때마다 계속 피드백을 받는 시스템 안의 agent가 필요합니다. 타입, compiler error, lint, tests, 사람의 review가 함께 그 시스템을 만듭니다.
그래서 TypeScript가 Codex와 매우 잘 맞는다고 말할 때, 언어 팬덤을 이야기하는 것이 아닙니다. 핵심은 빠르고 기계적으로 검증 가능한 피드백입니다. 모호함을 줄이고, blast radius를 줄이고, 리팩터링을 안전하게 만들며, 검증을 진지하게 수행할 때 엔지니어링 속도를 훨씬 더 신뢰할 수 있게 합니다.