AI 시대에 코드 품질이 여전히 중요한 기준: 일회용 소프트웨어와 지속형 소프트웨어
Max Kanat-Alexander의 AI Engineer 강연을 바탕으로 코드 품질의 본질인 시스템 이해와 안전한 변경, 4대 내구성 지표에 따른 일회용·지속형 소프트웨어 구분법, 그리고 엄격성 비용을 낮추는 4가지 엔지니어링 전략을 살펴봅니다.
무료 공개 · 최근 수정
AI가 몇 초 만에 코드를 짜는데 코드 품질이 여전히 필요할까
AI 코딩 에이전트에게 자연어로 지시하면 수백 줄의 화면과 스크립트가 몇 초 만에 만들어집니다. 코드를 새로 쓰는 비용이 급격히 낮아지면서 개발 현장에서는 "언제든 다시 생성하면 되는데 굳이 시간을 들여 구조를 다듬고 엄격한 코드 리뷰를 해야 하는가"라는 질문이 끊이지 않습니다. 시니어 엔지니어들은 흔히 "상황에 따라 다르다(It depends)"고 답하지만, 정작 그 기준이 무엇인지 명확히 정의하지 않으면 팀 전체가 불필요한 검증에 시간을 쏟거나 반대로 핵심 시스템에 기술 부채를 쌓게 됩니다.
구글의 코드 헬스(Code Health) 테크 리드를 거쳐 캐피털 원(Capital One)의 총괄 수석 엔지니어(Executive Distinguished Engineer)로 재직 중인 맥스 카낫 알렉산더(Max Kanat-Alexander, 『Code Simplicity』 저자)는 2026년 10월 AI Engineer 강연과 에세이에서 이 질문에 대한 구체적인 판단 틀을 제시했습니다. 결론부터 말하면 모든 코드에 똑같은 품질 잣대를 댈 필요는 없으며, 소프트웨어가 가진 위험도와 수명에 따라 완전히 다른 두 가지 전략을 취해야 합니다.
이 글에서는 애초에 소프트웨어 공학에서 코드 품질이 왜 존재했는지 근본 이유부터 짚어 봅니다. 이어서 내가 만드는 코드가 품질 관리가 필요 없는 일회용인지, 아니면 엄격한 규율이 필수적인 지속형 시스템인지 판별하는 4가지 기준과 검증 비용을 낮추는 실무 기법을 차례대로 살펴봅니다.
코드 품질이 존재하는 진짜 이유는 시스템을 이해하고 안전하게 고치기 위해서다
코드 품질(Code Quality)과 관련된 모든 도구와 규칙은 단지 개발자의 기분을 좋게 하거나 미학적인 만족을 얻기 위해 만들어진 것이 아닙니다. 모델을 정렬하든, 서버 성능을 높여 사용자를 모으든, 소프트웨어로 달성하려는 모든 목표는 오직 '이해(Understanding)'와 '이해에 기반한 실행(Executing on that understanding)'이라는 두 가지 요소에 달려 있습니다.
우리가 사용하는 디버거, 관측 가능성(Observability) 플랫폼, 모델 평가(Evals), 코딩 스타일 가이드, 심지어 자동화 테스트조차도 궁극적으로는 시스템에 대한 이해도를 높이기 위해 존재합니다. 소프트웨어 엔지니어가 시스템에 대해 반드시 확보해야 하는 핵심 이해는 다음 두 가지입니다.
- 시스템의 실제 동작 파악: 기획 문서나 주석에 적힌 희망 사항이 아니라, 현재 코드가 실제로 어떻게 행동하는지 정확히 아는 것입니다.
- 동작을 안전하게 변경하는 방법 파악: 의도한 기능을 추가하거나 바꿀 때 다른 곳을 망가뜨리지 않고 정확히 수정하는 방법을 아는 것입니다.
아주 작은 프로그램은 전체 동작을 한 문장으로 설명할 수 있습니다. 화면에 "Hello World"를 출력하는 프로그램을 "Hi World"로 바꾸는 일은 코드 구조가 어떻든 누구나 즉시 해낼 수 있습니다. 하지만 크로미움(Chromium) 웹 브라우저나 C 컴파일러처럼 현실의 대규모 시스템은 입력과 상태의 조합이 사실상 무한대에 가깝습니다. 사람이나 AI 모델이 이처럼 무한한 동작 가능성을 가진 시스템을 이해하는 유일한 방법은 전체를 역할이 명확한 부품(Component parts) 단위로 쪼개어 파악하는 것입니다.
결국 우리가 고급 프로그래밍 언어로 코드를 작성하고 아키텍처를 설계하는 본질적인 이유는 기계에 명령을 내리기 위해서가 아니라 이해 가능한 구조(Understandable Structure)를 만들기 위해서입니다. 예를 들어 리눅스에서 "Hello World"를 출력하는 파이썬 코드 한 줄(print("Hello World"))과 수십 줄의 x86 어셈블리 시스템 콜 명령어는 기계 입장에서 똑같이 동작합니다. 그러나 파이썬 코드는 무엇을 하는 부품인지 정체성이 한눈에 드러나는 반면, 수억 줄의 어셈블리 명령어 나열은 인간과 AI 모두에게 구조적 단서를 주지 못합니다.
이해하기 어려운 코드는 변경할 때마다 복잡도가 복리로 불어난다
이해 가능한 구조가 가장 결정적인 역할을 하는 순간은 시간이 흐르며 소프트웨어를 반복해서 수정할 때입니다. 코드베이스에 가하는 모든 변경에는 항상 두 가지 확률적 위험이 뒤따릅니다.
- 현재 변경이 문제를 일으킬 확률: 버그가 발생하거나, 예상보다 일정이 몇 배로 지연되거나, 예산 초과로 프로젝트가 실패하는 위험입니다. 이는 '지금 시스템을 이해하기가 얼마나 어려운가'에 비례합니다.
- 이번 변경이 미래의 변경을 더 어렵게 만들 확률: 당장 급한 기능을 넣기 위해 구조를 우회하거나 임시방편을 추가하여 다음번 수정 시 사고가 날 확률을 높이는 위험입니다. 이는 '이번 수정이 시스템을 얼마나 더 이해하기 어렵게 만들었는가'에 해당합니다.
가장 치명적인 문제는 두 번째 결과가 첫 번째 원인으로 되먹임(Feedback)되면서 악순환을 만든다는 점입니다. 현재 시스템을 이해하기 어려울수록 개발자나 AI 에이전트는 전체 맥락을 파악하지 못한 채 국소적인 땜질 코드를 덧붙이게 되고, 그 결과 시스템은 다음번에 더욱 이해하기 어려워집니다. 즉 소프트웨어의 구조적 복잡도는 시간에 따라 선형(Linear)으로 증가하는 것이 아니라 복리(Compounding)로 증폭됩니다.
복잡도가 복리로 쌓이다 보면 어느 순간 인간 개발자도, 최신 AI 코딩 모델도 합리적인 시간 안에 부작용 없이 코드를 고칠 수 없는 한계점에 도달합니다. 수억 줄 규모의 노후 메인프레임 시스템에서 사소한 버튼 위치 하나를 바꾸는 데 수개월이 걸리고 매번 장애가 터지는 이유가 바로 이 구조적 복잡도의 한계를 넘었기 때문입니다.
[참고] 장애 사후 분석(Postmortem)의 공통 원인
카낫 알렉산더는 자신이 수년간 검토한 모든 프로덕션 장애 보고서(Postmortem)의 근본 원인을 '이해의 실패'로 요약합니다. 운영 설정 파일이 너무 복잡해 이해하지 못했거나, 누군가 이해할 수 없는 방식으로 코드를 고쳤거나, 컴포넌트 간 결합으로 예측 불가능한 창발적 동작(Emergent properties)이 나타난 경우가 대부분입니다.
AI 코딩 에이전트가 폭발시킨 일회용 소프트웨어와 4대 내구성 지표
구조적 복잡도의 복리 누적을 막기 위해 그동안 소프트웨어 공학은 정적 분석, 코드 리뷰, 테스트 커버리지 같은 높은 엄격성(Rigor)을 요구해 왔습니다. 그런데 AI 코딩 에이전트의 등장은 소프트웨어 생태계에 완전히 새로운 전환점을 만들었습니다. 과거에도 간헐적으로 존재했지만 이제 전체 소프트웨어 수량의 대부분을 차지하게 될 새로운 범주, 바로 일회용 소프트웨어(Disposable Software)의 부상입니다.
만약 어떤 소프트웨어가 다음 네 가지 조건을 모두 만족한다고 가정해 봅니다.
- 실패의 파급력(Consequences of failure)이 거의 없다.
- 이 소프트웨어에 의존하는 사람이나 외부 시스템(Dependencies)이 거의 없다.
- 처음부터 완전히 새로 작성하는 교체 비용(Cost of replacement)이 0에 가깝다.
- 유지보수 비용(Cost of maintenance)이 들지 않는다.
이 네 가지가 모두 참인 세상에서는 코드 품질이 전혀 중요하지 않습니다. 잘못된 변경으로 인한 위험도 없고, 시간이 흐르며 복리로 쌓이는 복잡도도 없기 때문입니다. 운영체제 기본 계산기 화면이 마음에 들지 않아 5초 만에 나만의 계산기를 생성하거나, 이번 주말 아이 생일 파티 한 번을 위해 맞춤형 일정 관리 앱을 몇 분 만에 만들고 행사가 끝난 뒤 버리는 경우가 대표적입니다. 기능을 바꿔야 하면 기존 코드를 읽고 고치는 대신 버리고 새로 생성하면 됩니다.
반면 수년간 운영되며 수백만 명이 의존하고, 장애 발생 시 기업에 치명적인 손실을 입히는 지속형 소프트웨어(Durable Software)는 절대 사라지지 않습니다. 오히려 AI 도구의 도움으로 인류가 구축하는 핵심 지속형 시스템의 절대량 또한 크게 늘어날 것입니다. 중요한 것은 일회용과 지속형을 흑백논리로 나누는 대신, 아래 4대 내구성 지표(Degree of Durability) 스펙트럼 위에서 현재 프로젝트의 위치를 정확히 판별해 엔지니어링 엄격성의 강도를 조절하는 일입니다.
| 평가 지표 | 일회용 소프트웨어 (Disposable) | 지속형 소프트웨어 (Durable) |
|---|---|---|
| 실패 파급력 (Consequences) | 낮음 (일시적 불편, 개인 작업 지연 수준) | 높음 (금전적 손실, 개인정보 유출, 서비스 전면 중단) |
| 외부 의존도 (Dependencies) | 단일 사용자 또는 독립된 일회성 작업 | 수많은 사용자, 타 팀의 마이크로서비스 및 외부 API가 의존 |
| 재작성 비용 (Cost of Replacement) | 수분~수시간 내에 AI 에이전트로 전체 재생성 가능 | 수개월~수년의 도메인 지식과 데이터 마이그레이션 필요 |
| 유지보수 비용 (Cost of Maintenance) | 0에 수렴 (수정하는 대신 폐기 후 재작성) | 지속적인 보안 패치, 규정 준수, 스키마 및 하위 호환성 유지 |
| 요구되는 코드 품질 및 엄격성 | 불필요 (작동 여부만 확인) | 과거 어느 때보다 높은 수준의 구조적 품질과 규율 필수 |
실무에서 이 네 가지 지표를 평가할 때는 항상 예상보다 지표가 높을 것(더 지속형에 가까울 것)이라고 보수적으로 가정해야 합니다. 임시로 만든 사내 스크립트가 어느새 핵심 배치 파이프라인에 연결되어 지속형 시스템으로 굳어지는 일이 빈번하기 때문입니다.
시스템을 더 일회용에 가깝게 설계하고 엄격성 비용을 낮추는 아키텍처
이 스펙트럼을 이해하면 현대 소프트웨어 엔지니어링에는 두 가지 새로운 전략적 방향이 열립니다. 첫째는 아키텍처에 안전장치를 둘러 시스템의 일부를 의도적으로 더 일회용에 가깝게 만드는 것이고, 둘째는 지속형 소프트웨어에 필요한 엄격성(Rigor)의 실행 비용 자체를 극단적으로 낮추는 것입니다.
다음 그림은 시스템 경계 격리를 통해 위험을 낮춘 뒤, 4대 내구성 지표에 따라 일회용과 지속형 경로를 나누고 자동화 도구로 검증 비용을 줄이는 전체 구조를 보여 줍니다.
그림: 4대 내구성 지표에 따른 일회용·지속형 분기와 엄격성 비용 절감 흐름
1. 경계 격리를 통해 소프트웨어를 더 일회용으로 만들기
모든 코드를 무거운 지속형 시스템으로 만들 필요는 없습니다. 시스템 주변에 아키텍처 가드레일을 설치해 실패 파급력과 외부 의존도를 강제로 낮추면, 구성원들이 코드 품질 걱정 없이 AI로 빠르게 실험하고 버릴 수 있는 안전한 일회용 영역이 넓어집니다.
- 격리된 실행 환경(Isolated Environments): 고객의 개인정보(PII)나 프로덕션 데이터베이스, 사내 핵심망에 접근할 수 없고 오직 실행자 본인의 데이터만 다룰 수 있는 샌드박스 배포 환경을 제공합니다.
- 의존성 강제 차단(Enforcing Independence): 해당 환경에 배포된 서비스로는 다른 프로덕션 시스템의 인바운드 API 호출을 시스템적으로 금지합니다. 아무도 이 코드에 의존할 수 없게 만들면 언제든 안전하게 폐기할 수 있습니다.
- 내구성 백업과 바이브 코딩 분리(Redundancy): 뒤단에 튼튼하고 검증된 지속형 백엔드(또는 투박하지만 확실히 동작하는 구형 백업 시스템)를 유지한 채, 앞단 화면은 각 실무자가 자신의 업무 효율에 맞게 직접 바이브 코딩(Vibe coding)으로 만들어 쓰게 합니다. 만약 AI로 급조한 프론트엔드가 고장 나더라도 백업 시스템을 쓰거나 즉시 재생성하면 되므로 장애 파급력이 사라집니다.
- 범위 제한(Limiting Scope): 범용 플랫폼을 지향하는 대신 '특정 부서의 이번 분기 데이터 변환'처럼 단일 목적과 수명을 명확히 한정합니다.
2. 지속형 소프트웨어의 엄격성 비용(Cost of Rigor) 낮추기
반대로 4대 지표가 높은 지속형 시스템이거나, 처음에는 일회용인 줄 알았으나 나중에 핵심 서비스로 전환해야 하는 경우에는 높은 엔지니어링 엄격성이 필수입니다. 이때 엄격성을 지키는 비용이 비싸면 개발 속도가 느려지고 구성원들이 규칙을 우회하게 됩니다. 카낫 알렉산더는 엄격성 자체를 저렴하고 마찰 없이 만드는 4가지 핵심 프랙티스를 강조합니다.
- 기술 표준화(Standardization): 표준화의 진짜 목적은 통제가 아니라 '고민해야 할 경우의 수를 제거하는 것'입니다. 조직 내에 백엔드 언어와 프레임워크, RPC 프로토콜이 수십 가지로 파편화되어 있으면, AI 에이전트가 도구를 만들어 준다 해도 매번 각 언어용 린터와 보안 취약점 패치 방식을 새로 구성해야 합니다. 기술 스택을 표준화하면 한 번 구축한 검증 파이프라인을 모든 프로젝트에 무료로 재사용할 수 있습니다.
- 워크플로 내장 도구(In-workflow Tooling): 개발자나 에이전트가 어떤 검증 도구를 실행할지 선택하거나 실행 방법을 배울 필요 없이, 코드를 저장하거나 커밋하는 흐름 안에서 자동으로 즉시 실행되는 도구입니다. 오탐(False positive)이 적고 실행 속도가 빨라 사실상 공짜처럼 느껴진다면 일회용 소프트웨어에조차 부담 없이 적용할 수 있습니다(구체적인 컴파일러 가드레일 사례는 AI 시대에 Rust가 주목받는 이유와 Python 연동 구조를 참고합니다).
- 쉽고 결정론적인 테스트 인프라(Making Testing Easier): 개발자가 테스트 전문가가 아니더라도 모든 테스트가 네트워크 지연이나 외부 장애 없이 밀리초(ms) 단위로 빠르고 일관되게(Deterministic) 돌아가야 합니다. 이를 위해 각 서비스 팀이 실제 외부 연동 대신 메모리 안에서 즉시 동작하는 인메모리 가짜 객체(In-memory fake)를 함께 제공하는 것이 좋습니다. 과거에는 이를 일일이 짜기 번거로웠지만, 이제는 코딩 에이전트에게 인터페이스에 맞는 인메모리 대역 생성을 맡겨 전사에 배포할 수 있습니다.
- 초기 최대 엄격성(Maximum Strictness): 프로젝트 첫날부터 린터와 정적 분석 규칙을 최고 강도로 켜고 타협하지 않는 전략입니다. 100만 줄이 쌓인 뒤에 린터를 켜면 10만 줄의 수정 PR이 발생해 아무도 손대지 못하지만, 처음부터 최대 강도로 고정하면 어떤 규칙을 쓸지 논쟁하는 시간과 미래의 잠재적 결함을 동시에 없앨 수 있습니다.
작은 예제로 확인하는 인메모리 테스트 대역과 정적 검증 가드레일
엄격성 비용을 낮추는 핵심 기법인 '인메모리 가짜 객체(In-memory fake)'와 '최대 강도 정적 검사'가 어떻게 코드 이해와 안전한 변경을 돕는지 작은 파이썬 예제로 확인해 봅니다.
지속형 커머스 시스템에서 주문 결제 로직을 수정할 때 실제 외부 결제망을 호출하면 테스트가 느려지고 네트워크 상태에 따라 간헐적으로 실패합니다. 대신 다음과 같이 인터페이스 동작을 메모리 사전(dict)으로 똑같이 구현한 InMemoryPaymentFake를 두면 외부 의존성 없이 결제 성공과 잔액 부족 분기를 즉시 검증할 수 있습니다.
class InMemoryPaymentFake:
"""네트워크 호출 없이 결제 승인과 잔액 부족 상태를 즉시 검증하는 인메모리 대역"""
def __init__(self) -> None:
self.balances: dict[str, int] = {"user_1": 10000}
self.charged_history: list[tuple[str, int]] = []
def charge(self, user_id: str, amount: int) -> bool:
if amount <= 0:
raise ValueError("결제 금액은 0보다 커야 합니다.")
if self.balances.get(user_id, 0) < amount:
return False
self.balances[user_id] -= amount
self.charged_history.append((user_id, amount))
return True
def checkout_order(gateway: InMemoryPaymentFake, user_id: str, price: int) -> str:
if gateway.charge(user_id, price):
return f"ORDER_CONFIRMED:{price}"
return "PAYMENT_DECLINED"
fake_gateway = InMemoryPaymentFake()
print("1차 결제(4,000원):", checkout_order(fake_gateway, "user_1", 4000))
print("2차 결제(8,000원):", checkout_order(fake_gateway, "user_1", 8000))
print("남은 잔액:", fake_gateway.balances["user_1"])
print("결제 기록:", fake_gateway.charged_history)Python 3.12 환경에서 위 코드를 실행하면 외부 서버 연결 없이도 0.01초 이내에 정확한 상태 변화와 이력이 출력됩니다.
1차 결제(4,000원): ORDER_CONFIRMED:4000
2차 결제(8,000원): PAYMENT_DECLINED
남은 잔액: 6000
결제 기록: [('user_1', 4000)]이처럼 명시적인 타입 어노테이션과 결정론적인 인메모리 테스트를 갖추어 두면, 사람이나 AI 에이전트 루프가 checkout_order 함수를 수정할 때 시스템의 실제 동작을 오해해 생기는 사고를 즉각 차단할 수 있습니다.
[참고] 일회용 프로토타입이 지속형 시스템으로 전환될 때의 신호
처음에는 개인 업무용으로 만든 일회용 스크립트라도 ① 다른 팀원이 실행 결과를 정기적으로 받아 보기 시작하거나 ② 사내 데이터베이스 쓰기 권한을 요청하거나 ③ "기존 코드에 기능 하나만 더 붙여 달라"는 유지보수 요청이 3회 이상 반복된다면 지속형 소프트웨어로 이동하고 있다는 신호입니다. 이때는 임시 코드 위에 기능을 덧붙이지 말고, 표준화된 프레임워크와 엄격한 린트·테스트가 설정된 저장소에서 에이전트를 활용해 구조화된 코드로 재작성해야 복잡도 누적을 막을 수 있습니다.
참고 문서
- AI Engineer — Does Code Quality Still Matter? It Depends on This (Max Kanat-Alexander 강연 영상, 2026-10-21)
- Code Simplicity — Does code quality even matter anymore? (Max Kanat-Alexander 원문 에세이, 2026-10-13)
- Code Simplicity — The Philosophy of Testing (테스트와 시스템 이해의 관계)
- O'Reilly Media — Code Simplicity: The Fundamentals of Software (Max Kanat-Alexander 저)
설명이 어렵거나 잘못된 부분을 발견하셨나요?
문서 수정 의견 보내기