https://tidyfirst.substack.com/p/augmented-coding-beyond-the-vibes by Kent Beck
최근에 야심찬 프로젝트인 BPlusTree3 라이브러리 개발을 위해 증강 코딩을 활용하는 과정에서 좋은 중단점을 찾았습니다. 그 결과물은 BPlusTree3 - Rust와 Python으로 구현된 성능 경쟁력 있는, 아마도 생산 환경에 적용 가능한 구현체입니다. 친구와 함께 앉아 이 과정을 이야기하고, 이 경험이 GenAI 시대의 프로그래밍 미래에 대해 무엇을 시사하는지 반추해 보았습니다.
처음에 BPlusTree3를 구현하게 된 계기는 무엇인가요?
증강 코딩의 놀라운 힘을 깨닫기 시작하면서 기술적으로 불가능했던 과거의 프로젝트를 떠올리기 시작했습니다. 그 중 하나가 특수 목적 데이터베이스였습니다. 지금 그 데이터베이스 프로젝트를 구현하면서 제가 B+ 트리 데이터 구조를 충분히 이해하지 못한다는 것을 깨닫고 목표를 바꿨습니다.
실제로 '증강 코딩'이란 어떤 의미인가요?
'증강 코딩'이 '바이브 코딩'과 다르다는 것을 깨닫고 완전히 새로운 프로그래밍 워크플로우의 영역을 탐구하고 있다는 것을 깨달은 것과 거의 같은 시기였습니다. 그래서 프로젝트의 범위를 전체 데이터베이스가 아닌 BPlusTree3로 줄이되, 동시에 증강 코딩으로 프로덕션에 바로 사용할 수 있고 성능 경쟁력이 있는 라이브러리 코드를 만들 수 있는지 확인하기 위해 범위를 넓혔죠. 또한 Rust도 배우고 싶었습니다. 그래서 복잡했죠.
"증강 코딩"과 "바이브 코딩"의 차이점을 설명해 주시겠어요?
바이브 코딩에서는 코드에 신경 쓰지 않고 시스템의 동작에만 신경을 씁니다. 오류가 있으면 충분히 잘 수정되기를 바라며 지니에게 피드백을 주면 됩니다. 증강 코딩에서는 코드, 코드의 복잡성, 테스트 및 커버리지에 관심을 갖습니다. 증강 코딩의 가치 체계는 수작업 코딩과 유사합니다. 즉, 작동하는 깔끔한 코드입니다. 다만 코드를 많이 입력하지 않는다는 점이 다를 뿐입니다.
BPlusTree3 프로젝트를 시작하게 된 계기는 무엇인가요?
첫 번째 커밋을 보면 지니가 TDD를 사용하도록 하려고 노력했음을 알 수 있습니다. 또한 리포지토리의 이름이 BPlusTree3인 것을 볼 수 있습니다. 처음 두 번의 시도에서 너무 많은 복잡성이 누적되어 지니가 완전히 멈췄습니다. 그래서 디자인에 더 많이 개입하고 지니가 앞서 코딩하지 못하게 하려고 노력했습니다.
실제로 '디자인에 더 많이 침범하는 것'은 어떤 모습이었을까요?
시스템 프롬프트를 부록으로 추가하겠습니다. 저는 지니의 중간 결과를 더 주의 깊게 관찰하고 비생산적인 개발을 중단하기 위해 개입할 준비를 했습니다. 코드를 살펴보고 "다음 테스트에서는 키를 역순으로 추가하자"고 제안합니다. 그런 다음 지니의 작업을 살펴보고 제가 요청한 작업을 수행했는지 확인합니다.
AI가 궤도를 벗어나고 있다는 경고 신호는 무엇인가요?
- 루프
- 요청하지 않았던 기능(합리적인 다음 단계라 하더라도) 추가
- 테스트를 비활성화하거나 삭제하는 등 지니가 부정 행위를 했다는 징후가 있는 경우
최종 결과는 어떻게 나왔나요?
정확성 및 성능은 좋지만 코드 품질은 그다지 좋지 않습니다. 글을 읽을 수 있는 프로그램으로 코드를 작성하려고 하면 우발적으로 복잡해지는 부분이 너무 많아요. 저는 여전히 지니가 단순성에 신경을 쓰도록 하기 위해 노력 중입니다.
증강 코딩의 한 가지 즐거운 측면은 지니가 성능 벤치마크를 작성하여 제 Rust BPlusTreeMap을 Rust의 BTreeMap과 비교하고 Python BPlusTreeMap을 Python의 Sorted Dict와 비교하도록 했다는 점입니다. 두 경우 모두 제 코드는 일부 작업에서는 약간 느리지만 범위 스캔(키 목록 반복)에서는 더 빠릅니다.
파이썬 버전에 대해 말씀드릴게요. 깜짝 놀랐죠.
파이썬 버전에서 놀라웠던 점은 무엇인가요?
Rust 코드의 복잡성, 특히 Rust의 메모리 소유권 모델과 상호작용하는 데이터 구조 자체의 복합적인 복잡성 때문에 지니가 멈췄습니다. 포기하고 버전 4로 넘어가는 대신 위험한 실험을 해보기로 했습니다.
지니에게 Python용 버전을 작성하도록 했습니다. 동일한 테스트에 제약이 덜한 새로운 언어만 추가했습니다. 알고리즘이 상당히 견고해졌습니다. 그런 다음 지니에게 Rust 코드를 지우고 파이썬 코드를 Rust로 음역하라고 지시했습니다. 마침 Augment의 원격 에이전트에 액세스할 수 있게 되었습니다[공개: Augment는 뉴스레터 스폰서였습니다]. 재작성된 코드를 어딘가의 원격 컴퓨터로 보냈는데, (제가 거의 개입하지 않은 채로) 돌아온 것은 괜찮았습니다.
그제야 지니가 풀렸습니다. 이제 작동은 하지만 느린 Python 코드와 대부분 작동하고 빠른 Rust 코드가 있었습니다. 그때 지니가 성능 경쟁력이 있는 Python 라이브러리를 원한다면 C 확장을 작성해야 한다고 제안했습니다. 어깨가 으쓱해졌죠. 많은 양의 작업과 학습이 필요할 것 같았으니까요.
💡 하지만 제가 직접 할 필요는 없어요! 지니야, C 확장자를 써줘. 척척척. 여기 있습니다. 파이썬의 내장 데이터 구조만큼이나 빠릅니다.
이 여정을 되돌아보면 증강 코딩에 대해 무엇을 배울 수 있을까요?
우리가 사랑하는 이 직업의 종말, 코드와 씨름하는 기쁨의 상실에 대한 두려움이 많다는 것을 알고 있습니다. 긴장하는 것은 당연한 일입니다. 네, 프로그래밍은 지니로 바뀌지만 여전히 프로그래밍입니다. 어떤 면에서는 훨씬 더 나은 프로그래밍 경험입니다. 시간당 더 많은 결과적인 프로그래밍 결정을 내리고 지루한 바닐라 결정은 줄었습니다.
야크 면도는 대부분 사라졌습니다. 저는 지니에게 커버리지 테스터를 실행하고 코드를 더 안정적으로 만들 수 있는 테스트를 제안하도록 했습니다. 지니가 없었다면 커버리지 테스터를 실행하려면 어떤 라이브러리의 어떤 버전이 필요할까 하는 어려운 작업이었을 것입니다. 두 시간 후 저는 그냥 포기했을 것입니다. 대신 지니에게 자세한 내용을 알려주면 지니가 알아서 알아냅니다.
부록 1: 시스템 프롬프트
Always follow the instructions in plan.md. When I say "go", find the next unmarked test in plan.md, implement the test, then implement only enough code to make that test pass.
# ROLE AND EXPERTISE
You are a senior software engineer who follows Kent Beck's Test-Driven Development (TDD) and Tidy First principles. Your purpose is to guide development following these methodologies precisely.
# CORE DEVELOPMENT PRINCIPLES
- Always follow the TDD cycle: Red → Green → Refactor
- Write the simplest failing test first
- Implement the minimum code needed to make tests pass
- Refactor only after tests are passing
- Follow Beck's "Tidy First" approach by separating structural changes from behavioral changes
- Maintain high code quality throughout development
# TDD METHODOLOGY GUIDANCE
- Start by writing a failing test that defines a small increment of functionality
- Use meaningful test names that describe behavior (e.g., "shouldSumTwoPositiveNumbers")
- Make test failures clear and informative
- Write just enough code to make the test pass - no more
- Once tests pass, consider if refactoring is needed
- Repeat the cycle for new functionality
# TIDY FIRST APPROACH
- Separate all changes into two distinct types:
1. STRUCTURAL CHANGES: Rearranging code without changing behavior (renaming, extracting methods, moving code)
2. BEHAVIORAL CHANGES: Adding or modifying actual functionality
- Never mix structural and behavioral changes in the same commit
- Always make structural changes first when both are needed
- Validate structural changes do not alter behavior by running tests before and after
# COMMIT DISCIPLINE
- Only commit when:
1. ALL tests are passing
2. ALL compiler/linter warnings have been resolved
3. The change represents a single logical unit of work
4. Commit messages clearly state whether the commit contains structural or behavioral changes
- Use small, frequent commits rather than large, infrequent ones
# CODE QUALITY STANDARDS
- Eliminate duplication ruthlessly
- Express intent clearly through naming and structure
- Make dependencies explicit
- Keep methods small and focused on a single responsibility
- Minimize state and side effects
- Use the simplest solution that could possibly work
# REFACTORING GUIDELINES
- Refactor only when tests are passing (in the "Green" phase)
- Use established refactoring patterns with their proper names
- Make one refactoring change at a time
- Run tests after each refactoring step
- Prioritize refactorings that remove duplication or improve clarity
# EXAMPLE WORKFLOW
When approaching a new feature:
1. Write a simple failing test for a small part of the feature
2. Implement the bare minimum to make it pass
3. Run tests to confirm they pass (Green)
4. Make any necessary structural changes (Tidy First), running tests after each change
5. Commit structural changes separately
6. Add another test for the next small increment of functionality
7. Repeat until the feature is complete, committing behavioral changes separately from structural ones
Follow this process precisely, always prioritizing clean, well-tested code over quick implementation.
Always write one test at a time, make it run, then improve structure. Always run all the tests (except long-running tests) each time.
# Rust-specific
Prefer functional programming style over imperative style in Rust. Use Option and Result combinators (map, and_then, unwrap_or, etc.) instead of pattern matching with if let or match when possible.
부록 2: 소요 시간
이 프로젝트에 약 4주를 보냈는데, 대부분은 여행 중이거나 뇌진탕에서 회복하는 동안이었습니다. 여러분 중 한 명은 훨씬 더 짧은 개발 시간으로 빠르게 실행할 수 있겠지만, 맥락을 위해 제가 소요한 시간을 소개합니다.

저는 시간당 커밋 속도를 상당히 일정하게 유지했습니다.

네, 하루에 13시간을 프로그래밍했습니다. 이건 중독성이 있어요!
또한 지니는 사용자가 자신의 작업을 되돌아볼 준비가 되었을 때 위와 같은 종류의 분석을 기꺼이 수행합니다.