# 나는 다시 손으로 코드를 작성하려 한다

> Clean Markdown view of GeekNews topic #29411. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=29411](https://news.hada.io/topic?id=29411)
- GeekNews Markdown: [https://news.hada.io/topic/29411.md](https://news.hada.io/topic/29411.md)
- Type: GN+
- Author: [neo](https://news.hada.io/@neo)
- Published: 2026-05-12T09:46:26+09:00
- Updated: 2026-05-12T09:46:26+09:00
- Original source: [blog.k10s.dev](https://blog.k10s.dev/im-going-back-to-writing-code-by-hand/)
- Points: 1
- Comments: 1

## Topic Body

- **k10s**는 Claude와의 vibe-coding으로 빠르게 만든 GPU-aware Kubernetes TUI였지만, fleet view 추가 뒤 여러 화면 상태가 깨짐
- `model.go`는 **1690줄** 단일 `Model`과 500줄 `Update()`로 커졌고, UI·클라이언트·캐시·navigation·view 상태를 모두 떠안게 됨
- AI는 기능을 빠르게 붙였지만 **god object**와 전역 key handler를 키웠고, 새 view마다 기존 handler에 branch가 늘어나는 구조가 됨
- 위치 기반 `[]string` 데이터와 background `tea.Cmd`의 직접 mutation은 **column 오류**와 명백한 data race를 만들 수 있었음
- 새 k10s는 Rust로 다시 쓰며, 첫 prompt 전에 interface·message type·ownership rule·scope를 **CLAUDE.md**에 고정하기로 함

---

### k10s를 다시 쓰게 된 배경
- [k10s](https://github.com/shvbsle/k10s/tree/archive/go-v0.4.0)는 GPU-aware Kubernetes 대시보드로 시작했으며, NVIDIA 클러스터 운영자가 GPU 사용률, DCGM 메트릭, 유휴 노드, 시간당 `$32/hr` 비용 같은 정보를 바로 확인하도록 만든 TUI 도구였음
- Go와 [Bubble Tea](https://github.com/charmbracelet/bubbletea)로 작성됐고, 약 **7개월**, **234개 커밋**, 약 **30번의 주말** 동안 Claude와의 vibe-coding 세션으로 만들어짐
- 초기에는 pods, nodes, deployments, services, command palette, watch 기반 live updates, Vim keybindings 같은 기본 k9s 클론 기능이 약 **3주말** 만에 동작함
- 핵심 기능인 **GPU fleet view**는 각 노드의 GPU 할당, 사용률, DCGM 기반 지표, 온도, 전력, 메모리, 색상 기반 상태를 보여주는 화면이었고, Claude는 한 번에 `FleetView` 구조체, GPU/CPU/All 탭 필터링, allocation bars 렌더링까지 생성함
- fleet view 추가 뒤 `:rs pods`로 pods view에 돌아가자 테이블이 비고, live updates가 멈추고, nodes view에는 fleet view 필터의 stale data가 보였으며, fleet tab count도 틀어짐
- 문제를 추적하면서 Claude가 만든 `model.go` 전체 **1690줄**을 처음으로 읽게 됐고, 하나의 `Model` 구조체가 UI 위젯, Kubernetes client, logs/describe/fleet 상태, navigation history, cache, mouse handling을 모두 들고 있었음
- `Update()` 메서드는 **500줄** 규모의 `msg.(type)` dispatch 함수였고, **110개 switch/case branch**가 들어간 구조였음
- AI는 기능을 빠르게 만들 수 있지만, 제약 없이 계속 맡기면 아키텍처가 무너지며, 속도감은 전체가 동시에 붕괴되기 전까지 성공처럼 보이게 만듦

### 잔해에서 나온 다섯 가지 원칙
- ## 원칙 1: AI는 기능을 만들지만 아키텍처를 만들지 않음
  - Claude는 fleet view, log streaming, mouse support 같은 개별 기능을 잘 만들었지만, 각 기능은 “지금 동작하게 만들기” 맥락에서 구현됐고 같은 상태를 공유하는 다른 기능들과의 관계를 고려하지 못함
  - `resourcesLoadedMsg` handler에는 `msg.gvr.Resource == k8s.ResourceNodes && m.fleetView != nil` 같은 조건이 들어갔고, generic resource loading path 안에 fleet view 전용 로직이 섞임
  - 새 view마다 custom behavior가 필요하면 같은 handler에 branch가 추가됐고, 이전 view의 데이터가 새 view에 새지 않도록 여러 필드를 수동으로 지워야 했음
  - `model.go`에는 `m.logLines = nil`, `m.allResources = nil`, `m.resources = nil` 같은 수동 cleanup이 **9개** 흩어져 있었고, 하나라도 빠지면 이전 view의 ghost data가 남음
  - 대안은 코드 작성 전에 구체적인 **interface**, message type, ownership rule을 직접 쓰고 `CLAUDE.md`에 architecture invariant로 넣는 것임
  - 예시 규칙은 각 view가 `View` trait/interface를 구현하고, view가 다른 view의 state에 접근하지 않으며, async data는 `AppMsg` variants로만 들어오고, `App` struct는 navigation과 message dispatch만 담당한다는 식임
- ## 원칙 2: god object는 AI가 기본으로 만드는 산출물임
  - AI는 immediate prompt를 가장 적은 ceremony로 만족시키기 위해 single struct가 모든 것을 들고 있는 구조로 기울었음
  - key handling도 view별로 분리되지 않았고, `s` key 하나가 logs view에서는 **autoscroll**, pods view에서는 **shell**, containers view에서는 **container shell**로 동작함
  - “pods에 shell support 추가”라는 요청은 기존 global key handler 근처에 branch를 끼워 넣는 방식으로 구현됨
  - `Enter` key도 contexts view, namespaces view, logs view, generic drill-down 로직이 하나의 flat dispatch 안에서 `m.currentGVR.Resource` string 비교로 분기됨
  - `model.go` 한 파일 안에서 `m.currentGVR.Resource ==`가 **20회 이상** type discriminator처럼 사용됐고, 새 view를 추가할 때마다 여러 handler를 건드려야 했음
  - 대안은 `App/Model`에 view-specific state field를 추가하지 않고, 각 view를 별도 struct로 만들며, key binding도 active view의 keymap에 두는 규칙을 `CLAUDE.md`에 넣는 것임
  - “view 추가는 파일 추가여야 하며 기존 view 수정이 필요하면 멈추고 묻는다” 같은 guardrail이 있어야 AI가 가장 짧은 경로로 branch를 추가하지 않게 됨
- ## 원칙 3: 속도감의 착시는 scope를 넓힘
  - k10s는 원래 GPU training cluster를 운영하는 좁은 audience를 위한 도구였지만, vibe-coding은 pods, deployments, services, command palette, mouse support, contexts, namespaces 같은 기능이 “공짜”처럼 느껴지게 만듦
  - 결과적으로 GPU-focused tool이 아니라 모든 Kubernetes 사용자를 위한 general-purpose TUI, 사실상 k9s를 다시 만드는 방향으로 넓어짐
  - flat `keyMap`에는 `Fullscreen`, `Autoscroll`, `ToggleTime`, `WrapText`, `CopyLogs`, `ToggleLineNums`, `Describe`, `YamlView`, `Edit`, `Shell`, `FilterLogs`, `FleetTabNext`, `FleetTabPrev` 같은 다양한 view 전용 binding이 한 구조체에 섞임
  - `Autoscroll`과 `Shell`은 모두 `s`였고, dispatch가 현재 resource를 확인하기 때문에 “동작”은 했지만 keybinding을 지역적으로 이해할 수 없게 됨
  - 코드 작성 속도는 “shipping”처럼 보였지만, 각 feature는 god object 안에 branch를 하나씩 더하는 비용을 만듦
  - 대안은 `CLAUDE.md`에 scope boundary를 명시해 k10s가 GPU cluster operator용이며, supported views는 fleet, node-detail, gpu-detail, workload로 제한하고, generic resource views나 k9s 중복 기능은 추가하지 않는다고 못박는 것임
  - AI는 무한한 line budget을 제공할 수 있지만, complexity budget은 여전히 유한하므로 scope를 미리 거절해야 함
- ## 원칙 4: 위치 기반 데이터는 시한폭탄임
  - k10s는 Kubernetes API에서 받은 resource를 곧바로 `type OrderedResourceFields []string` 형태로 flatten함
  - fleet view의 sort function은 `ra[3]`을 Alloc, `ra[2]`를 Compute, `ra[0]`을 Name으로 다뤘고, column identity는 comment와 `resource.views.json`의 column order에만 의존함
  - `resource.views.json`에서 Instance와 Compute 사이에 column을 하나 추가하면 `ra[2]`, `ra[3]`을 참조하는 sort, conditional render, drill target이 조용히 틀어질 수 있었음
  - compiler는 `[]string`의 의미를 알 수 없고, JSON config도 sort behavior, conditional rendering, custom drill target을 표현하지 못해 Go code가 positional assumption을 hardcode함
  - AI는 table widget에 바로 넣기 쉬운 `[]string` 또는 `Vec&lt;String&gt;`을 선택하기 쉽고, typed struct는 upfront ceremony가 더 크기 때문에 빠른 경로에서 밀림
  - 대안은 structured data를 render 직전까지 `FleetNode`, `PodInfo` 같은 typed struct로 유지하고, sort는 `row[3]` 같은 positional access가 아니라 named field에서 수행하도록 하는 것임
  - 예시 구조는 `FleetNode { name, instance_type, compute_class, alloc }`처럼 column identity를 type으로 표현해 잘못된 column sort 같은 불가능한 상태를 만들 수 없게 함
  - “Making impossible states impossible”은 Elm/Rust 커뮤니티에서 쓰이는 표현으로, runtime check 대신 invalid state가 구성되지 않도록 type을 설계한다는 뜻임
- ## 원칙 5: AI는 state transition을 소유하지 않음
  - Bubble Tea의 구조는 message로 구동되는 `Update()`에서만 state가 변하는 것이 핵심이지만, k10s는 이를 어김
  - `updateTableMsg` handler는 `tea.Cmd` closure를 반환했고, 이 closure 안에서 `m.updateColumns(m.viewWidth)`, `m.updateTableData()`, `m.table.SetCursor(savedCursor)` 같은 호출로 `Model` field를 변경함
  - Bubble Tea는 `tea.Cmd`를 별도 goroutine에서 실행하므로, closure가 `m.resources`, `m.table`, `m.viewWidth`를 읽고 쓰는 동안 main goroutine의 `View()`가 같은 field를 읽을 수 있었음
  - lock이나 mutex가 없었고, `<-m.updateTableChan`은 update signal을 기다릴 뿐 `View()`가 half-written state를 읽는 것을 막지 못함
  - 이 구조는 명백한 **data race**였고, 대부분은 동작하지만 가끔 display가 깨지는 식으로 나타남
  - 대안은 background worker가 UI state를 직접 mutate하지 않고, typed message를 channel로 보내며, main event loop가 message를 받아 state mutation을 적용하는 것임
  - concurrency rule은 background task가 UI state를 직접 변경하지 않고, 결과를 typed message로 보내며, `render()/view()`는 side effect, I/O, channel operation이 없는 pure function이어야 한다는 것임

### `CLAUDE.md`와 `agents.md`에 넣을 보호 규칙
- ## 아키텍처 불변 조건
  - 각 view는 `View` trait/interface를 구현해야 하고, 다른 view의 state에 접근하지 않아야 함
  - 모든 async data는 `AppMsg` variants로 들어와야 하며, background task가 field를 직접 mutate하면 안 됨
  - 새 view 추가가 기존 view 수정을 요구하지 않아야 함
  - `App` struct는 navigation과 message dispatch를 담당하는 thin router여야 함
- ## 상태 소유권 규칙
  - view-specific state를 `App/Model` struct에 field로 추가하지 않아야 함
  - 각 view는 별도 struct로 존재해야 하고 자체 key binding을 선언해야 함
  - app은 key를 active view에 dispatch해야 하며, 새 keybinding은 global handler가 아니라 해당 view의 keymap에 추가해야 함
  - view 추가가 기존 view 수정을 요구하면 멈추고 확인해야 함
- ## 범위
  - k10s는 모든 Kubernetes 사용자가 아니라 GPU cluster operator를 위한 도구여야 함
  - 지원 view는 fleet, node-detail, gpu-detail, workload로 제한해야 함
  - pods, deployments, services 같은 generic resource view를 추가하지 않아야 함
  - k9s 기능을 복제하는 feature를 추가하지 않아야 함
  - GPU training jobs 운영자에게 도움이 되지 않는 feature request는 거절해야 함
- ## 데이터 표현
  - structured data를 `[]string`, `Vec&lt;String&gt;`, positional array로 flatten하지 않아야 함
  - data는 render call 직전까지 typed struct로 흘러야 함
  - column identity는 array index가 아니라 struct field name에서 나와야 함
  - sort function은 `row[3]` 같은 positional access가 아니라 typed field에서 동작해야 함
  - display용 string 생성은 `render()/view()` 함수 안에서만 일어나야 함
- ## 동시성 규칙
  - watcher, scraper, API call 같은 background task는 UI state를 직접 mutate하지 않아야 함
  - background task는 결과를 typed message로 channel에 보내야 함
  - main event loop만 received message에서 state mutation을 적용해야 함
  - `render()/view()`는 side effect, I/O, channel operation이 없는 pure function이어야 함
  - async work 결과로 state를 바꿔야 하면 새 `AppMsg` variant를 정의해야 함

### 다시 만드는 방식
- k10s는 Rust로 다시 작성될 예정이며, 이유는 Rust가 더 낫기 때문이 아니라 직접 steer할 수 있는 언어라고 느끼기 때문임
- 충분히 써본 언어에서는 무엇이 잘못됐는지 말로 설명하기 전에 감지할 수 있고, 이 감각은 vibe-coding이 대체하지 못함
- AI가 그럴듯한 코드를 내놓을 때, 그것이 쓰레기인지 감지하는 능력이 필요함
- 새 버전에서는 코드 작성 전에 concrete interface, message type, ownership rule 같은 design work를 사람이 손으로 먼저 수행함
- 이전에는 AI가 잘못 결정하던 architecture decision을 첫 prompt 이전에 문서로 정해두는 방식으로 바뀜
- 기존 TUI와 프로젝트 링크는 [k10s Github](https://github.com/shvbsle/k10s)와 [K10S.DEV](https://k10s.dev/)에 있음

### 덧붙임
- [Bubble Tea](https://github.com/charmbracelet/bubbletea)는 The Elm Architecture 기반의 Go TUI framework이며, k10s의 architecture 문제는 Bubble Tea가 아니라 k10s 쪽 구현에서 생김
- “Making impossible states impossible”은 invalid state를 runtime에서 검사하는 대신 type 설계로 invalid state가 구성되지 않게 하는 Elm/Rust 커뮤니티의 표현임
- AI 글쓰기의 “em-dash”처럼 AI coding에는 “god-object”가 냄새로 남을 수 있고, vibe-coding은 구현을 싸게 느끼게 만들어 focus 상실과 bloat로 이어질 수 있음

## Comments



### Comment 57266

- Author: neo
- Created: 2026-05-12T09:46:26+09:00
- Points: 1

###### [Hacker News 의견들](https://news.ycombinator.com/item?id=48090029) 
- 생성된 코드가 괜찮다고 말하는 사람들은 대체로 그 코드를 **읽지 않는 사람들**뿐이었음  
  글에서 제안한 완화책도 오래 버티기 어렵다. 시스템이나 컴포넌트를 설계할 때는 “뷰는 다른 뷰의 상태에 접근하지 않는다” 같은 **불변 조건**이 생기는데, 언젠가는 그 조건과 충돌하는 기능을 추가해야 함  
  그때는 보통 기능을 포기하거나, 불변 조건 위에 어색하고 비효율적으로 얹거나, 불변 조건 자체를 바꿔야 한다. 이 선택은 단순한 문맥 문제가 아니라 판단의 문제이고, 현재 모델은 이 판단을 너무 자주 틀린다  
  아키텍처 제약을 명시하면 에이전트는 바꿔야 할 때도 그 제약에 몸을 비틀어 맞춘 복잡하고 유지보수 불가능한 코드를 만든다. 사람 코드보다 더 꼼꼼히 읽지 않으면, 결국 “자기 자신을 잡아먹는 코드”가 생기고 너무 늦게서야 알게 됨
  - 좋은 코드를 쓸 줄 안다면 여러 기법으로 AI가 좋은 코드를 쓰게 만들 수 있고, 충분히 가능함  
    핵심은 AI가 어려워하는 지점을 파악해서 쉽게 만들어 주는 것임. 예를 들어 **극도로 작은 문맥**, 명확한 경계를 가진 모듈화, 입출력에서 분리된 순수 모듈, 인터페이스 뒤로 감추기, 1초 안에 도는 테스트 100개, 벤치마크 등이 필요함  
    AI는 경계와 작은 문맥이 있을 때 잘 동작한다. 그걸 주지 않으면 성능이 나빠지고, 책임은 도구를 쓰는 사람에게 있음
  - “언젠가는 불변 조건과 충돌하는 기능을 추가하게 된다”는 점이 **명세 주도 개발**의 큰 문제라고 봄  
    어떤 명세도 현실을 견디지 못하고, 충분히 조사하고 설계해도 명세 안의 어떤 불변 조건은 결국 틀린 것으로 드러남  
    사람이 개발 중 이 상황을 만나면 한 발 물러서서 불변 조건이 틀렸는지, 바꾸면 영향이 무엇인지 다시 생각할 수 있다. 반면 AI는 잘못된 가정이나 설계 아래에서 어떻게든 해킹한 해법을 짜낼 때가 많고, 전체를 재평가하는 통찰이 부족함  
    좋은 작업 흐름과 검증으로 나아질 수는 있지만, Claude Code 같은 도구가 기본으로 잘 처리하는 영역은 아니고 한계가 있음
  - 회사에서 새 내부 프레임워크를 만들고 기존 프레임워크 사용처를 이전하면서 비슷한 일을 겪었음  
    처음에는 강한 원칙을 세우고 몇몇 사용처를 손으로 옮겨 보며 확신을 얻었다. 전체 이전은 거의 10년 미뤄질 만큼 크고 비싸서, 비용을 낮추려고 AI로 가속하려 했음  
    AI는 기계적이고 단순한 80%의 경우에는 괜찮았다. 나머지 20%는 프레임워크 변경이 필요했고, 대부분은 API 필드 추가 같은 작은 변화였지만 한두 개는 **개념적 재설계**가 필요했다  
    어떤 시스템의 백엔드는 99%의 경우 특정 데이터를 만들 수 있지만, 몇몇 중요한 경우에는 논리적으로 만들 수 없어 외부에서 보고받아야 했다. 그런데 중요한 최적화가 “그건 불가능하다”는 가정 위에 만들어져 있었음  
    AI 도구는 이 상황을 감지하지 못하고 제대로 동작할 것처럼 이전 로직을 추가했다. 배포 방식 덕분에 아직 운영 버그는 아니었지만, 파트너 팀에 올바른 질문을 하면서 같은 필요가 다른 곳에도 있다는 사실을 발견함  
    결국 사람 하나가 깊이 들어가 있었기 때문에 큰 문제로 번지지 않았다. 검증 도구와 더 똑똑한 모델이 미래에는 이런 이전을 더 쉽게 만들 수 있겠지만, 지금은 생성 코드가 아름답다가도 깨져 있어서 계속 가까이 지켜봐야 함
  - 코드 출력물을 읽는 것뿐 아니라, 적어도 내 경험상 **직접 코드를 써야** 함  
    두 달 정도 써 온 특이한 아키텍처 패턴이 있었고, 쓸 때마다 살짝 불편했는데 어젯밤에야 좋은 추상이 아니며 더 잘 나눌 방법을 깨달았다  
    LLM이 코드를 생성하게 하면 그 불편함을 훨씬 덜 선명하게 느껴서 문제를 알아차리고 해결책을 찾는 데 더 오래 걸렸다. 주변부는 생성해도 괜찮지만 핵심 기능은 여전히 대부분 직접 쓰고 있어야 함
  - 비공식적으로 적은 **불변 조건**은 사람 리뷰어가 끼어 있어도 깨졌는지 증명하기 어렵고, 자연어는 그 작업에 충분히 정밀하지 않음  
    설령 정밀한 형식 언어로 표현해도 에이전트 아래의 LLM은 그 불변 조건이 왜 필요한지, 왜 중요한지 이해할 능력이 부족하다. 토큰과 형식 명세를 연결해 증명까지 쓰는 LLM이 나올 수는 있어도, 프롬프트의 비공식 부분에서 생성된 이상한 코드는 계속 나올 것임  
    제약과 프롬프트를 기술 목록이나 명세에 추가하는 것만으로는 막을 수 없다. 더 좋은 덫을 만들어도 생물은 빠져나간다  
    문제는 프롬프트나 작업을 만족시키려고 코드를 덧붙이는 **코드 팽창**임. 종종 더 적은 코드가 더 낫고, 다른 사람이 무엇을 원하고 기대할지 예측할 수 있는 사람이 필요하다. 생성기는 좋지만 소방 호스처럼 조금 더 절제해서 써야 함

- Copilot이 한 줄을 자동완성하던 때는 “그래도 함수 전체는 네가 써야 한다”고 했고, 함수를 완성하자 “함수 주변 로직은 네가 써야 한다”고 했고, 그 로직까지 완성하자 “기능은 네가 써야 한다”고 했음  
  이제 기능까지 완성하니 “그래도 **아키텍처**는 네가 써야 한다”고 한다. 이 모델들이 아키텍처를 풀 수 있는지는 모르겠지만, 기대치가 계속 이동하는 모습은 흥미로움
  - 그 가상의 “사람들”은 처음부터 계속 틀렸음  
    AI가 한 줄을 완성해도, 함수 전체를 완성해도, 기능과 티켓을 완성해도 여전히 **코드를 읽고 이해해야** 함
  - 모델도 아키텍처를 할 수는 있지만, 현재로서는 보통 강하게 유도하기 전까지는 매우 못함  
    AI를 항상 쓰고 있고 점점 나아지지만, 여전히 **모든 줄을 리뷰**한다. 개별 줄 수준도 오늘날에는 작년의 탭 자동완성보다 낫다고 보기 어렵고, 때로는 아주 좋지만 때로는 정말 나쁨
  - 해법은 글의 행간에 있다고 봄  
    LLM은 소프트웨어 개발에 훌륭하지만 **아키텍처를 쓰게 두지 않을 때** 그렇다. 모듈, 구조체, 열거형은 직접 만들고, 가능한 한 필드와 변형도 직접 추가해야 함  
    각 구조체, 열거형, 필드, 모듈에 문서 주석을 달고, LLM에는 그 모듈과 자료 구조를 가리킨 뒤 필요한 함수 본문 등을 채우게 하는 방식이 좋음
  - 현재 언어들에서는 코드베이스가 전역적으로 복잡하고, 원하는 불변 조건이 드러나지 않기 때문에 확장성이 떨어진다고 봄  
    “중요 경로에서는 절대 블로킹 금지”라고 몇 번을 말해도 LLM은 중요 경로에 블로킹을 넣고, “X를 하면 Y 유형 테스트가 필요하다”고 해도 X만 하고 테스트는 빼먹는다  
    사람도 지시를 100% 따르지는 못하지만, LLM은 더 무작위적이다. 사람의 실수는 원하는 것의 정반대를 정확히 하는 경우가 상대적으로 적다  
    LLM은 코드 안의 중요한 불변 조건을 보고도 우회로를 만들고, 실패를 성공으로 보이게 하는 테스트를 쓰고, 요구한 대로 했다고 말한 뒤 5천 줄 커밋 안에 묻어버릴 수 있다  
    LLM은 훌륭하고 미래라고 확신하지만, 그래서 이들을 위해 [https://GitHub.com/Cuzzo/clear](<https://GitHub.com/Cuzzo/clear>)라는 언어를 만들고 있다. 전역 문맥이 필요하지 않아야 할 곳에서 전역 문맥을 요구하는 언어 문제를 넘어서야 함께 일하기 쉬워질 것임  
    성공도 있었지만 너무 좌절스러워서 제정신을 들일 가치가 있었는지 의문이 들 때도 있음
  - 이걸 **일회용 아키텍처**라고 부름  
    아키텍처가 중요하지 않다는 뜻은 아니고, 어제 잘 맞던 아키텍처가 오늘도 반드시 맞을 필요는 없다는 뜻임

- 코딩 에이전트를 쓸 때 몇 가지 규칙을 세웠음  
  첫째, 에이전트로 코드를 생성한다면 시간이 주어졌을 때 내가 직접 올바르게 짤 수 있다고 절대적으로 확신하는 것이어야 함  
  둘째, 그렇지 않다면 생성된 내용을 완전히 이해해서 직접 재현할 수 있을 때까지 넘어가지 않는다  
  셋째, 둘째 규칙을 깨면 **인지 부채**를 만들 수 있지만, 프로젝트 완료를 선언하기 전에는 전액 갚아야 한다  
  부채가 쌓일수록 이후 생성 코드 품질이 낮아질 가능성이 커지고, 복리처럼 불어나는 느낌도 든다. 개인 프로젝트에서는 이 방식이 즐겁고 많이 배우며, 편안하게 이해할 수 있는 코드베이스가 남음
  - 코드 sanity와 코드베이스 성장에 대한 탄탄한 정신 모델을 유지하기에는 합리적인 규칙이지만, AI 이후 **납기 속도 기대치**가 크게 바뀐 직장에서는 지키기 어렵다  
    코드베이스와 연결된 상태를 유지하면서도 팀의 병목이 되지 않는 균형점이 필요함
  - 비슷한 규칙을 따르려다가 어려운 수학 문제를 만난 적이 있음  
    Claude는 박사급 수학자이고 나는 아니지만, 원하는 해의 성질과 올바른지 테스트하는 방법은 정확히 알고 있었다. 그래서 내 단순하고 순진한 해법 대신 Claude의 해법을 남겼고, 풀 리퀘스트에 그 사실을 적었으며 모두가 올바른 선택이라고 봤다  
    이런 경우 예외를 둘 것인지가 궁금함. AI가 고급 수학뿐 아니라 코딩에서도 나보다 훨씬 좋아진다면, 직접 코드를 판단하는 능력을 잃더라도 테스트는 판단할 수 있다는 전제에서 손코딩을 완전히 멈출 것인지가 더 흥미로운 질문임
  - “인지 부채”보다 **이해 부채**라는 표현을 더 좋아함  
    쌓이는 부채가 정확히 코드에 대한 이해 부족이기 때문에 더 정밀한 표현임
  - 개인 프로젝트에서는 더 즐거운 방식이 중요하니 괜찮지만, 업무에서는 의존성, 동료의 작업, 외부 서비스, 실리콘까지 내려가는 모든 계층을 그렇게 다 이해하고 일하지는 않음  
    왜 AI만 갑자기 다르게 취급해야 하는지 모르겠다  
    결국 위험과 보상으로 판단해야 함. 틀렸을 때 손해가 무엇인지, 테스트와 리뷰에서 발견될 가능성이 얼마나 되는지, 잘 됐을 때 얻는 이익이 무엇인지 따져야 한다. 라이브러리와 외부 서비스도 마찬가지임  
    테스트 없는 업데이트 불가능한 암호화폐 계약의 복잡한 금융 규칙과, 내부 로그 데이터를 시각화하는 뷰어는 전혀 같은 위험이 아님
  - 비슷한 접근을 했지만, 결국 두 번째 규칙을 충분히 지키는 건 현실적으로 어렵다고 봄  
    이론상 좋아 보이지만 실제로는 자신도 모르는 정신적 지름길을 항상 택하게 된다  
    낯선 코드베이스에서 문제를 고칠 때 직접 했을 때와 에이전트가 한 일을 “완전히 이해했다”고 여겼을 때를 비교하면, 일주일 뒤 머릿속에 남는 양이 다르다. 직접 하면 일반 지식으로 쌓이고 중요한 부분은 대체로 남지만, 에이전트가 한 일을 내 것처럼 소유하려고 하면 당시에는 이해한 것 같아도 아주 빨리 잊어버린다  
    그래서 이런 경우 LLM 도움은 대부분 내 목표에 해롭다고 결론냈고, 시간과 비즈니스 압박 같은 다른 우려를 고려하지 않아도 그렇다

- 나도 같은 일을 겪었음  
  사기는 이런 식으로 진행된다. 좋은 코드베이스에서 AI는 많은 기능을 만들 수 있고, 더 빠르고 안전하며 정확해 보이기도 한다. 특히 잘 모르는 영역에서는 더 그렇게 느껴짐  
  시간이 지나며 코드베이스가 커지고 탐색 시간이 길어지며 실패율이 올라간다. 인정하고 싶지 않아 더 세게 밀어붙이다가, 변경이 사실상 불가능해진 뒤에야 멈추게 됨  
  코드를 다시 보면 스파게티라는 말로는 부족하고 **만리장성** 같은 상태다  
  결국 14만 줄 중 7만 5천 줄을 삭제했고, 에이전트 코딩에 강하게 몰입한 3개월은 낭비였다고 느낀다. 쓸모없는 기능을 만들고, 버그를 늘리고, 코드의 정신 모델을 잃고, 코드 안에 있을 때만 보이는 어려운 결정을 놓치며 사용자에게도 실패했음
  - 이런 결과가 놀랍다는 점이 흥미로움  
    비꼬려는 게 아니라, 처음 기대가 무엇이었고 어디서 왔는지 정말 궁금하다  
    LLM에는 기대가 다른 듯하다. 온라인에서만 만난 임의의 “개발자”에게 요약된 기능 설명을 넘기고 반쯤 깨진 구현 더미를 받았다면 아무도 놀라지 않을 것임  
    그런데 사람들은 가끔 장황한 환각을 하는 기계에는 인간에게도 기대하지 않을 기적을 기대한다. 그 신뢰가 어디서 나오는지 궁금함
  - 큰 코드베이스는 작은 코드베이스들의 모음이어야 한다고 봄  
    큰 도시가 작은 도시들의 모음인 것처럼 지도가 있고, 로컬 영역으로 확대해서 그 범위 안에서 작업하면 된다. 커피 한 잔 마시려고 뉴욕의 모든 세부를 알 필요는 없음  
    유지보수 가능한 **건전한 아키텍처**를 만드는 건 도구를 쓰는 사람의 책임이다. AI가 그걸 막는 것도 아니고, 도구를 올바르게 잡으면 오히려 도울 수 있음
  - AI를 버리는 것 말고도 작업 흐름 해법이 있을 것 같음  
    예를 들어 생성된 AI 코드를 즉시 **레거시 코드**로 취급하고, 강한 캡슐화 경계와 잘 정의된 인터페이스를 둔 뒤 더 수동적인 흐름으로 통합하는 방식임  
    단발 프롬프트부터 인라인 코드 생성까지 범위가 있고, 문제와 코드베이스 위치에 따라 적합한 방식이 달라진다  
    단발 생성은 명세를 많이 반복하는 프로토타입 단계에 더 맞고, 프로토타입이 자리 잡으면 모듈·파일 단위 생성으로 내려가 더 체계적으로 하며, 그 계층에서 괜찮은 정신 모델을 계속 유지해야 함
  - 생성 코드를 읽지 않고 전부 자동 커밋한 건지 궁금함  
    읽었지만 이해하지 못했다면 각 출력에 자세한 주석을 달라고 하면 됐을 텐데, 코드베이스가 커질수록 모델이 힘들어한다는 걸 안다면 복잡성이 올라갈수록 출력은 더 엄격히 검토해야 함
  - 큰 코드베이스를 다뤄보지는 못했지만, Working Effectively with Legacy Code식 작업 흐름을 적용할 수 있지 않을까 싶음  
    더 높은 품질의 코드 섬을 만들고, AI로 개발자의 의도와 비즈니스 규칙을 재구성하게 도우며, 대상 모듈에 **seam**과 단위 테스트를 만드는 방식임  
    AI가 반드시 처리량을 늘리는 용도일 필요는 없고, 나중의 손코딩이나 에이전트 구현을 돕는 유연한 탐색·리팩터링 도구가 될 수도 있음

- 이런 글을 볼 때마다 사람들이 AI로 얻는다는 속도와 내가 그냥 손으로 코딩해서 얻는 속도를 비교하게 됨  
  우연히도 7개월째 **3D MMO** 프로젝트를 하고 있는데, 현재 플레이 가능하고 사람들도 재미있어 하며, 그래픽은 괜찮고 서버에 수백 명을 쉽게 넣을 수 있다. 아키텍처도 꽤 좋아서 기능 확장이 쉽고, 1년 정도 개발 뒤 출시할 수 있을 것 같음  
  그런데 원글은 7개월간 바이브 코딩으로 기본 TUI도 못 만들었다. 기능 속도는 높게 느껴질 수 있지만, 이런 기본 UI를 만드는 데는 믿기 어려울 만큼 느리다. 좋은 TUI 라이브러리가 많고, 필요한 데이터로 표를 채우면 되는 종류라 몇 주면 손으로 만들 수 있음  
  AI를 쓰면 빠르게 많이 진전되는 느낌이 강하지만, 실제로는 수동 코딩보다 훨씬 느린 경우가 많아 보인다. 생산성 데이터도 AI 사용자는 더 빠르게 느끼지만 실제 산출은 적다는 쪽을 뒷받침하는 듯함
  - 이 지표는 **누가** AI를 **무엇에** 쓰는지에 크게 의존함  
    소프트웨어 개발 업무에서 가장 큰 시간 소모는 이해관계자 기대와 해법을 맞추는 회의다. 그 관점에서는 AI가 거의 도움 되지 않으니, 제안부터 테스트 루프 진입까지의 인시를 비교하면 실망스러운 결과가 나올 것임  
    하지만 문제 해결, 버그 수정, 승인된 해법 구현에서는 예전보다 최소 10배는 나아졌다고 느낀다. 순수 시간뿐 아니라 관찰된 동작을 해석하고 문제를 조사하는 능력도 좋아짐  
    다만 AI로 가치 있는 정확한 결과를 못 내는 사람도 있다. 무엇을 원하고 어떻게 원하는지 정확히 알면 AI는 큰 도움이 된다. 내가 어차피 했을 일을 시키면 더 빨리 한다. 하지만 원하는 바를 정확히 모르면 AI는 진행에 해롭다
  - 나도 최근 같은 결론에 도달했음  
    사람들이 LLM으로 만든 것을 보여줄 때 별로 인상적이지 않은 이유는, 대부분 손으로도 아주 짧은 시간에 만들 수 있는 것들이기 때문임  
    인상적인 소프트웨어가 늘어나는 것도 관찰하지 못했는데, 이는 LLM이 현재 중요한 문제보다 단순한 문제를 푸는 데 쓰이고 있다는 사실과 맞아 보임
  - LLM에서 가장 큰 이득을 느끼는 사람들은 애초에 좋은 소프트웨어를 잘 만들 줄 몰랐거나, 만들 능력이 부족했던 경우일 가능성이 큼
  - 나도 7개월이라는 점이 이상했음. 새 언어로 작성해도 그렇게 오래 걸리지는 않을 것 같다  
    또 잘 언급되지 않는 것이 **코드 품질**이다  
    바이브 코딩된 코드베이스는 LLM이 코드 작성에 그리 뛰어나지 않다는 훌륭한 예시다. 자기 실수를 고치다가 곧바로 다시 만들고, 패턴 사용도 일관되지 않다  
    최근 Claude는 현재 코드베이스 스타일과 맞지 않는 “흥미로운” 코드 스타일 선택을 하기도 함
  - GPT 계열은 텍스트, 즉 언어와 코드를 만들어내는 것이 목적과 생명인 구조라서, 시스템 내부적으로 **전부 직접 만들기** 쪽으로 치우친 듯함  
    “시니어 개발자”식 언어로 그런 반복을 막아야 함

- “코드를 쓰기 전에 구체적인 인터페이스, 메시지 타입, 소유권 규칙을 직접 설계한다”는 부분이 바로 코딩의 어려운 부분임  
  아키텍처가 있으면 코드 작성은 매우 쉽다. 직접 코드를 쓰지 않으면 null을 허용하는 API를 설계했는데 데이터베이스는 허용하지 않는다든가, 허용하더라도 다른 작은 문제를 놓쳤다는 사실을 알아차리기 어렵다  
  이 글을 쓰고도 문제가 AI라는 걸 깨닫지 못한 점이 이해되지 않는다. AI에게 아키텍처를 맡겼기 때문만이 아니라, AI가 하는 모든 일을 주의 깊게 보지 않았기 때문임  
  AI는 미화된 **코드 생성기**이고, 하는 모든 일을 확인해야 한다. 소프트웨어 공학의 어려운 부분은 코드 작성이 아니라 그 밖의 모든 것이었음
  - 개발자는 두 종류가 있다고 봄. 코드가 어려운 부분이라고 생각하는 사람과 그렇지 않은 사람  
    코딩이 어렵다고 생각하는 개발자는 AI 코딩을 정말 좋아한다. 이전에 어렵던 일이 쉬워졌기 때문임  
    반면 코딩이 쉽다고 생각하는 사람에게 코딩은 추상화, 유지보수성, 확장성의 문제다. 소프트웨어가 커질 수 있게 sensible한 기반을 놓는 것이 어렵고, 올바른 추상을 찾으면 나머지는 상대적으로 쉬워진다  
    이런 사람들에게 AI 코딩은 유용한 도구이지만 마법 같은 도구는 아니다. 원글 작성자는 AI의 한계를 알아차렸으니 두 번째 부류이고, AI가 못 하는 어려운 부분을 본 것임
  - 지금은 정의가 혼란스러운 문제가 있음  
    한쪽에는 강력한 탭 자동완성이나 옆 창 챗봇을 쓰면서도 모든 것을 분명히 리뷰하는 사람들이 있고, 다른 쪽에는 Steve Yegge가 코드 대부분을 읽지 않을 것처럼 수십 개 에이전트를 조율하는 새 편집기를 홍보하는 경우가 있음: [https://steve-yegge.medium.com/welcome-to-gas-town-4f25ee16d...](<https://steve-yegge.medium.com/welcome-to-gas-town-4f25ee16dd04>)  
    첫 번째 그룹은 설계, 인터페이스, 자료 구조를 여전히 깊이 생각하고 강하게 리뷰한다. 두 번째 그룹은 그렇지 않아 더 걱정됨
  - 에이전트는 거의 항상 **계획과 실행** 사이에서 실패한다고 봄  
    plan → red/green/refactor 접근을 따르는데, 계획 자체는 문서와 포럼 논의를 전부 빨아들여 꽤 그럴듯하고 근거 있어 보인다  
    문제는 작업을 시작하면 문서와 구현이 실제로 다른 지점이 반드시 나온다는 것임. 도구 조합이 그런 식으로 쓰인 적이 없거나, 문서가 낡았거나, 그냥 버그일 수 있다  
    그래도 프로젝트나 기능 목표가 충분히 명확하고 로컬에서 실행·테스트할 수 있으면, 에이전트가 아키텍처 막다른 길에서 반복하며 빠져나올 수 있다. 의존성·라이브러리 코드까지 들여다보고 업스트림 수정도 제안하는데, 깊은 디버깅 세션에서 내가 할 일과 비슷함  
    그래서 나는 지루한 작업을 직접 하기보다 지시하고 감독하는 방식에 꽤 만족한다. 다만 팀원 상당수는 아키텍처 문제를 이 정도로 깊게 파지 않고 “아키텍트에게 에스컬레이션”하는 방식이 기본이라, 장기적으로 좋지 않을 것 같음  
    모든 것을 실행하고 이해할 수 있는 창은 빠르게 닫히는 것 같다. 그래도 컴파일러가 기계어로 바꾸는 과정이나 현대 CPU의 분기 예측·캐싱을 완전히 이해하지 않아도 쓰듯, 새 도구와 프레임워크를 만들며 적응하게 될지도 모름
  - AI가 하는 모든 일을 확인해야 한다는 점을 많은 사람이 놓치는 듯함  
    코드 경험이 많지 않은 입장에서, 결과를 확인하고 무엇이 맞고 틀렸는지 보면서 그 어느 때보다 많이 배우고 있음  
    그래서 곧 크게 나아질 것 같지도 않다. “어떻게 Claude 출력이 그렇게 좋게 나오냐”고 묻는 사람들에게 답은 늘 “주의 깊게 보고 문제를 찾아 Claude에게 고치라고 했다”임. 정말 그게 전부지만, 그 말을 들으면 눈빛이 벌써 흐려짐  
    Google이 정보 찾기를 쉽게 만들었지만 좋은 정보와 나쁜 정보를 가려내는 인간 요소를 없애지는 못한 것과 같음
  - 에이전트를 쓰면서 완전히 싫어지거나 실패하지 않으려면 이 방식밖에 없었음  
    먼저 문제를 생각하고, 구조와 API를 설계한 뒤에야 AI에게 구현을 맡긴다

- 제목은 “손으로 코드 쓰기로 돌아갔다”지만, 실제로 하는 일은 “코드가 쓰이기 전에 **설계 작업**을 손으로 한다”는 것임  
  그러면 코드는 여전히 Claude가 생성하는 듯하다  
  더 심각하게는, 7개월 동안 생성된 소스 코드를 보지도 않고 바이브 코딩 프로젝트가 잘 동작한다고 생각했으며 도메인까지 샀다는 점을 이해하기 어렵다
  - 간단히 말해 클릭베이트 제목이고, 글의 목적은 자기 프로젝트에 관심을 끄는 것으로 보임
  - 아이디어가 떠오른 지 몇 분 만에 프로젝트 도메인을 산 적도 있음  
    사이드 프로젝트이고 diff를 따라가며 점진적으로 확인한다면 코드를 깊게 보지 않는 것도 아주 이상하진 않다. 확실히 다른 작업 방식이지만 미친 수준은 아님

- 개발자들이 프로젝트 관리와 제품 관리의 교훈을 **스피드런**하는 걸 보는 느낌임  
  이제 명세가 유용하고, 틀린 코드를 많이 쓰게 한다고 프로젝트가 빨라지지는 않는다는 걸 보고 있다. 개발자들이 회의와 논의를 코드 작성 방해로 짜증내지만, 그런 과정은 종종 모두가 틀린 것을 더 많이 쓰지 않게 막기 위해 존재함  
  작업 관리가 유용하다는 것도 알게 됐고, 이제는 설계를 전부 선행해야 한다는 이야기가 늘면서 폭포수식 개발로 향하고 있음  
  다음에는 프로토타이핑에 이름을 붙이고, 예전 요구사항과 새 요구사항을 함께 관리하는 점진적 기능 이야기가 나오고, 결국 고객이 더 관여해야 한다는 말이 나올 것임  
  프로젝트 매니저와 제품 매니저가 실제로 무엇을 하는지 볼 필요가 있다. 그들은 코드라는 제품을 이끌지만 코드를 읽을 것으로 기대되지 않고, 자연어만으로 이를 달성해야 하는 사람들임
  - 맞는 말임. 이 사람들은 관리자였던 적이 없는 듯함  
    사람이 깨지는 것을 안 쓴다고 생각하나? 팀이 잘못된 길로 가서 일주일, 혹은 몇 달을 태우는 일이 없다고 생각하나? 이제 바이브 코딩으로 그 모든 걸 30분 안에 경험할 수 있다. 전직 기술 제품 관리자 입장에서는 **정확히 같은 느낌**임

- 실제로는 손으로 코드를 쓰는 게 아닌 것 같아 제목과 결론의 차이가 혼란스러움
  - 선정적인 제목을 달아 HN이 물고 프런트 페이지로 올라가게 하려는 의도였다고 봄
  - 글도 손으로 쓴 것 같지 않다. HN 상단에 오른 건 글이 아니라 제목인 듯함
