Intro 👋
다루지 않는 것들 ⛔
- LangChain, RAG와 같은 LLM을 서비스 관점에서 바라봤을 때 유용한 내용들
- 객관적인 지식 혹은 학술적인 내용들
- 지금 시점 앞의 이야기, 약간이라도 뒤의 이야기들
다루고 싶은 내용들 ✅
- LLM을 사용자 관점에서 바라봤을때 경험한 내용들
- 주관적인 의견 그리고 직접 해보며 경험했던 내용들
- 지금 시점에 유용하다고 느꼈던 것들
영감을 받은 것들 💭
Vibe Coding을 시작해본 계기 🆕
처음에는 반감으로 시작했습니다 🤬
‘XDD’ 뇌절과 비슷한 종류의 피로감
- 용어에 대한 반감은 뇌절에서 시작됩니다.
- 주로 탁월한 아이디어에 뭔가를 더 붙이거나 지엽적으로 분류하여 편승하는 느낌으로 진행되는 것 같습니다
- 한동안 ~DD로 끝나는 용어의 도배로 지지부진함과 피로감을 느꼈던 적이 많습니다.
더군다나 이런 파생 용어를 사용하시는 분들이 약간 더 상대방이 이 용어를 안다고 전제하고 이야기 하시는 경향이 많이 보였어서 조금 더 피곤함을 느꼈다고 생각했던 것 같습니다. “내가 알고 있는걸 모르면 트렌디 하지 못한 사람, 내가 모르고 있는걸 알면 오버엔지니어링 혹은 힙스터”
그런데 요즘 Hacker News, Geek News 등을 보면..
컨텍스트에 대한 피로감
- 이전에 클로드 코드 출시 전에 CLI를 클라이언트로 LLM을 붙여본 경험이 있었음
- 그 때 직관과 상당히 어긋나는 부분은 ‘대화형 인터페이스’ 이면에 숨겨진 컨텍스트를 유지하는 무식한 방식
실제로 인터페이스를 붙여보시면 보이는 것들
- 하나의 대화에서 이전까지의 모든 대화 내용을 보내면서 컨텍스트를 유지해야 한다.
- 즉 위의 이미지에서 점선의 길이가 의미하는것은 아래와 같다
- 개발자 입장에서는 요청 페이로드의 크기
- llm 벤더 입장에서는 입력 토큰의 크기💰
- llm동작 원리상에서는 유지 할 수 있는 컨텍스트의 크기
- 그리고 f(n) = f(n- 1) + n의 구조로 늘어난다
물론 실제로는 ‘에이전트’라고 불리는 서비스들이 서비스의 일환으로 컨텍스트를 압축해주지만, 정말 거의 대부분은 그 역시 llm을 기반으로 하기 때문에, 큰 프로젝트에서 사용하기에는 어느 순간 지나치게 비결정적인 면이 있고, 이부분은 지금도 그렇다고 생각합니다.
그러다가 만나게 된 켄트백의 포스트, 그리고 세가지 통찰
- 바이브코딩을 넘어 증강형 코딩
- 놀랍게도 정말 또 뭔가 듣기 싫은 새로운 용어를 가져다 붙이셨지만, 통찰은 대단하다고 느꼈습니다. (저는 반골 기질이 있어 굳이 ‘증강형’ 코딩이라고 직접적으로 언급하진 않겠습니다)
- 길지 않아 직접 읽어보셨으면 좋겠지만, 요약하자면 다음과 같습니다.
켄트백의 통찰 1 : TDD접근 방법
- 바이브 코딩에서는 코드는 신경쓰지 않고 시스템 동작만 신경쓴다. 에러가 있으면 ‘이런 에러가 있다’고 얘기하고, 고쳐주길 기대한다.
- 증강형 코딩에서는 코드를 신경쓴다. 코드의 복잡도, 테스트, 테스트 커버리지가 중요하다.
- 증강형 코딩에서는 기존의 코딩과 마찬가지로 “Tidy Code That Works”, 즉 ‘작동하는 깔끔한 코드’를 중요시한다. 단지 예전만큼 타이핑을 많이 하지 않을 뿐이다.
켄트백의 통찰 2 : AI의 치팅과 개발자의 개입
- 비슷한 행동을 반복한다 (무한루프 등)
- 내가 요청하지 않은 기능 구현. 그게 논리적인 다음 단계가 맞을지라도.
- 테스트를 삭제하거나 비활성화는 등, AI가 치팅하는 걸로 느껴지는 그 외 모든 신호
참고로 제가 직전까지 겪었던 가장 자주 발생하는 치팅은 하드코딩이었습니다. 처리하기 까다로운 엣지케이스를
else { 하드코딩된 값 }으로 치팅하는 경우가 많아서 욕이 늘었습니다.
켄트백의 통찰 3 : TDD를 주지시키기 위한 프롬프트
## 핵심 개발 원칙
- 항상 TDD 사이클을 따르세요: Red → Green → Refactor
- 가장 간단한 실패하는 테스트를 먼저 작성하세요
- 테스트가 통과하는 데 필요한 최소한의 코드를 구현하세요
- 테스트가 통과한 후에만 리팩토링하세요
- Beck의 "Tidy First" 접근법을 따라 구조적 변경과 행동적 변경을 분리하세요
- 개발 전반에 걸쳐 높은 코드 품질을 유지하세요
## TDD 방법론 가이드
- 작은 기능 증분을 정의하는 실패하는 테스트를 작성하는 것부터 시작하세요
- 행동을 설명하는 의미있는 테스트 이름을 사용하세요 (예: "shouldSumTwoPositiveNumbers")
- 테스트 실패를 명확하고 정보성 있게 만드세요
- 테스트가 통과하도록 하는 데 필요한 코드만 작성하세요 - 그 이상은 안 됩니다
- 테스트가 통과하면 리팩토링이 필요한지 검토하세요
- 새로운 기능을 위해 사이클을 반복하세요
## 예시 워크플로우
새로운 기능에 접근할 때:
1. 기능의 작은 부분에 대한 간단한 실패하는 테스트를 작성하세요
2. 통과하도록 하는 최소한의 것을 구현하세요
3. 테스트를 실행하여 통과하는지 확인하세요 (Green)
4. 필요한 구조적 변경을 하세요 (Tidy First), 각 변경 후 테스트를 실행하세요
5. 구조적 변경사항을 별도로 커밋하세요
6. 다음 작은 기능 증분을 위한 또 다른 테스트를 추가하세요
7. 기능이 완성될 때까지 반복하세요, 행동적 변경사항을 구조적 변경사항과 별도로 커밋하세요
이 과정을 정확히 따르고, 빠른 구현보다는 항상 깨끗하고 잘 테스트된 코드를 우선시하세요.
항상 한 번에 하나의 테스트를 작성하고, 실행하게 한 다음, 구조를 개선하세요. 매번 모든 테스트를 실행하세요 (장시간 실행되는 테스트는 제외).
바이브 코딩을 진행 과정과 결과물 그리고 시연 🫵
- 위의 프롬프트는 그대로 사용했고, 프로젝트에 대한 개요등을 추가적으로 작성했습니다.
- Claude Code를 이용했습니다.
- 여기 : Github 에서 확인 하실 수 있습니다.
$$누구나 지금 당장 설치 가능$$ 평생 무로 $$ 가입 필요? 📵 즉시 설치 가능 brew만 있다면 🫵 바로 fink 유저 누구나 가능하도록 brew에 배포해두었습니다.
brew tap SmallzooDev/fink
brew install fink
작업중 느낀점 🤪
더 작은 기능에 대한 개발 요구, 더 구체적인 요구사항일수록 잘 동작한다
“큰 문제를 작은 문제 여럿으로"와 같은 격언은 아직도 통용되고, 저 이야기가 처음 나온 즈음 이상으로 더 의미있는 구문이 된 것 같습니다.
- 아래 이야기될 내용과 중복될 내용이 많아 아래에서 조금 더 이야기 하겠습니다.
프로젝트의 구조와 설계, 기술 스택등은 개발자가 알고 시작하는게 좋다
“클로드야 Rust가 좋아? 아니면 Go가 좋아?”, “클로드야 ddd가 좋아 아니면 ddd안하는게 좋아?”, 와 같은 질문은 의미가 없는 것 같습니다. 항상 A는 이런면에서 좋으며, B는 이런면에서 좋다고 답변받을 뿐이니까요
- 저는 원래 써보고 싶었던 ratatui라는 tui 라이브러리가 이미 있었습니다, 하지만 그걸 영리하게 찾아오지는 않습니다.
- 구조도 마찬가지입니다 저는 아래와 같은 구조를 원했습니다. 뭔가 cli로 요즘 유행하는 gemini code나, claude code와 같은 것들과 파이프라이닝을 해보고 싶었거든요
결론적으로 적당한 뭔가 라이브러리로 tui를 그려줘, 각 계층을 기능 단위로 나눠서 리팩토링해줘 와 같은 것들은 높은 확률로 실패합니다 . 제가 직접 인터페이스를 빼고 몇가지 구현을 한 뒤에 그걸 이용해서 리팩토링을 요구하면 잘 동작합니다 즉 사전질문 > 개발자의 선택 > 선택을 통한 구체적인 요구사항이 훨씬 효율적입니다.
컨텍스트 유지 보다 중요한 것은 작은 단위의 빠른 컨텍스트 복구이며, 프롬프트(CLAUDE.md를 포함한)와 단위 테스트 코드들은 이것을 달성하는데 엄청 큰 역할을 한다.
- 일단 AI가 치팅을 하거나 이상한 짓을 시작하는 시점은 생각보다 빠르게 옵니다.
- 그리고 대부분의 경우 위에 언급한 두가지를 포함해서 사용자가 모호한 요청, 혹은 코드베이스적으로 너무 광범위한 컨텍스트를 요구하거나 판단이 필요한 요청을 제시했을 가능성이 높은 것 같습니다.
- 그러한 경우 빠르게 checkout을 통한 green phase로의 전환, claude.md혹은 프롬프트를 통한 최소한의 컨텍스트를 제외한 모든 컨텍스트를 clear이 효과적이었습니다.
필요한 경우
-
테스트를 직접 작성해주거나
-
인터페이스 혹은 상위 계층을 직접 정의하거나
-
정의한 인터페이스 사용 예시 코드까지를 작성해두고 컴파일 에러를 유발시키거나
하면 더 치팅을 방지 할 수 있었습니다.
TL; DR 🤓
- 작은 작업 단위, 명확한 요구사항, 테스트코드와 같은 것들은 컨텍스트를 작게 만들어 준다.
- 작은 컨텍스트를 유지 할 수 있다면 실무와 같은 큰 코드베이스에서도 llm agent를 이용한 개발이 가능하다.
- 그리고 위의 두가지를 달성하기 위해서는 적절한 개발자의 개입과 프롬프트를 이용한 빠른 컨텍스트 복구가 매우 중요하다.
- 그런 당신을 위한 프롬프트 관리앱 fink (
$$누구나 지금 당장 설치 가능$$ 평생 무로 $$ 가입 필요? 📵 즉시 설치 가능 brew만 있다면 🫵 바로 fink 유저 누구나 가능)
감사합니다! 🙇♂️
Github 들어가서 fink 별좀요..