Vibing Best Practices with Claude Code
TMT최근에 자주 반복되는 새로운 밈이 있는데, 이미 Vance 밈을 보는 것보다 이걸 듣는 게 더 지겹지만, 그래도 이 밈의 불길에 기름을 좀 더 붓고자 합니다. 트위터 등에서 오신 분들을 위해 소개하자면, 저는 Nathan LeClaire입니다 — 전 Docker, 전 Honeycomb 해커, Golang 및 오픈 소스 애호가이고, 요즘 많은 사람들처럼 AI 열풍에 빠져들고 있습니다.
“Vibe coding(바이브 코딩)”이란, 예전에는 직접 구현하느라 써야 했던 정신적 노력을 AI에게 넘기고, 그냥 AI가 알아서 하게 두는 것, 즉 피드백과 코드를 복사해서 주고받으며 시도해보는 것을 말합니다. 이 방식은 논란의 여지가 있지만, 저는 이것이 소프트웨어 개발의 미래를 보여준다고 생각합니다 — 소프트웨어 엔지니어들은 쓸 수 있는 에너지가 한정되어 있고, 종종 여러 번 해본 것들을 다시 구현하거나, 예전에는 더 중요한 핵심 기능 때문에 굳이 시도하지 않았던 것들을 원할 때가 있습니다. 게다가 진입 장벽이 낮아지고 있습니다 — 이제는 코딩 경험이 거의 없는 사람도 앱을 만들 수 있게 되었고, 이는 게임 모드나 마인크래프트 레드스톤, 혹은 미래의 개발자를 이끌었던 다른 디딤돌만큼이나 많은 사람들이 이 분야에 진입하는 데 도움이 될 것이라 생각합니다. 앞으로는 우리 모두가 작은 AI 에이전트들을 감독하게 될 것이고, 이 에이전트들은 점점 더 똑똑해질 것입니다.
물론, 많은 개발자들이 지적했듯이, 이 방식을 아무 생각 없이 쓰면 유지보수 불가능한 스파게티 코드가 양산될 수 있습니다. 단기적으로는 정말 빠르게 곤란한 상황에 처할 수 있는 방법이죠. 그래서 Claude Code를 사용해보고 그 잠재력과 유용성에 감탄한 저는, 효과적으로 vibe coding(혹은 약간은 신경질적으로 vibe coding)하는 방법에 대해 생각을 정리해봤습니다.
Claude에게 무엇을 해야 할지 명확히 지시하라 
당연한 이야기 같지만, 실상은 많이 간과될 것 같습니다 — Claude Code는 저장소별로 어떻게 행동해야 하는지, 무엇을 해야 하는지에 대한 지침을 담은 CLAUDE.md
파일을 만들도록 권장합니다. 구체적인 지침을 넣어도 가끔은 무시하긴 하지만, 예를 들어 코드 린트 방법 같은 건 잘 따릅니다. LLM은 마법이 아닙니다 — 계속해서 안내가 필요하고, (아직은) 여러분이 원하는 것과 관례를 추측할 수 없습니다. 그러니 항상 slog를 사용해 구조적 로깅을 하라
, OpenTelemetry Span에 .SetAttribute를 할 만한 필드를 찾아 커스텀 속성으로 추가하라
, 모든 변경 후 go 파일에 goimports를 실행하라
같은 메모를 남겨두면 정신 건강에 큰 도움이 됩니다.
이와 더불어, Claude가 더 뛰어난 도구를 사용할 수 있도록 지시하세요. 예를 들어, 읽기 전용 API 키를 주고 AWS 인프라를 살펴보게 할 수도 있습니다. Claude가 grep
이나 sed
만 쓸 줄 알아도 이미 유용한데, 앞으로 더 정교한 시스템에 연결된다면 얼마나 더 유용해질지 상상해보세요. 그래서 저는 Perplexity API용으로 만든 no frills Golang CLI인 plexctl
을 만들었습니다. Perplexity는 다른 모델들보다 온라인 문서와 최신 정보를 참조하는 데 강점이 있어서, Claude가 필요할 때 사용할 수 있게 하고 싶었습니다.
그리고 계획 세우기 — 사실 Claude Code가 많은 부분에서 3.7 수준의 사고를 하는 것 같긴 하지만, 현실적으로는 o1-pro가 더 똑똑한 모델이라고 생각합니다. Claude는 올바른 지시를 주면 매우 열정적이고 효과적이지만, o1-pro만큼 제 의도와 복잡한 구현을 잘 파악하지는 못하는 것 같습니다. 물론 Anthropic은 매우 똑똑한 연구소라 곧 바뀔 수도 있다고 생각하고, 3.7을 충분히 써보지 않아 강한 의견을 내긴 어렵지만, 어쨌든 당분간은 여러 에이전트와 모델을 오가며 실험하게 될 것 같습니다. 만약 한 모델이 기획과 구현에 뛰어나다면, 그걸로 다른 모델을 안내하는 게 합리적이겠죠. 그래서 Perplexity를 활용해 o1-pro에 이런 프롬프트를 만들어봤습니다:
당신은 매우 뛰어난 AI 아키텍트로, 덜 정교한 AI 어시스턴트가 실행할 수 있도록 명확하고 구조적이며 상세한 구현 계획을 만듭니다. 목표를 정의하고, 순차적이고 관리 가능한 작업으로 나누어 XML 태그로 지시(
<instruction>
), 맥락(<context>
), 코드 예시(<code_example>
)를 구분합니다. 자신을 시니어 소프트웨어 아키텍트, 구현 AI를 꼼꼼한 주니어 개발자로 설정하세요. 특히 까다롭거나 오류가 나기 쉬운 부분은 <code_example>
태그 안에 간결하고 완전히 주석 처리된 코드 스니펫을 제공합니다. 답변은 “Here is your detailed implementation plan:”으로 시작하세요. 잠재적 문제와 엣지 케이스를 단계별로 추론하는 <thinking>
섹션을 포함하고, 모든 코드 스니펫을 표준 린트 도구(예: JavaScript는 ESLint, Python은 Pylint)로 검증하라고 지시하세요.
이렇게 받은 결과를 Claude Code에 복사해서 붙여넣고 실행합니다.
린트, 테스트, 포맷을 집요하게 반복하라 
plexctl
을 vibe coding할 때 저를 지켜준 것은 바로 공격적인 린트와 포맷 규칙을 추가하고, Claude에게 정기적으로 확인하게 한 것이었습니다. 제 golang-cilint 설정은 다음과 같습니다:
run:
timeout: 5m
modules-download-mode: readonly
allow-parallel-runners: true
go: "1.23"
linters:
enable:
- errcheck
- gosimple
- govet
- ineffassign
- staticcheck
- unused
- gocritic
- goconst
- unparam
- stylecheck
- testifylint
- wastedassign
- gosec
- exhaustive
- contextcheck
- paralleltest
- perfsprint
- nonamedreturns
- nilerr
- whitespace
- asasalint
- errorlint
- mnd
- errname
- funlen
- cyclop
- nestif
- protogetter
- gofmt
- goimports
- misspell
- bodyclose
linters-settings:
gofmt:
simplify: true
goimports:
local-prefixes: github.com/nathanleclaire/plexctl
revive:
rules:
- name: exported
severity: error
disabled: false
funlen:
lines: 60
statements: 40
nestif:
min-complexity: 3
cyclop:
max-complexity: 10
issues:
exclude-use-default: false
max-issues-per-linter: 0
max-same-issues: 0
이건 지나치게 엄격하고 때로는 짜증나는 규칙이지만, 애정을 쏟아 관리하다 보면 (그리고 o1-pro나 Claude로 대부분의 오류를 기계적으로 줄일 수 있어서 정신적 에너지를 아낄 수 있습니다) 훨씬 더 이해하기 쉽고 유지보수하기 좋은 코드를 만들어줍니다. 특히 함수 길이, 복잡도, 매직 변수 규칙이 그렇습니다.
현재 LLM들은 기술적으로는 동작하지만, 보기에도, 느낌에도 별로인 코드를 내놓는 걸 좋아합니다. 코드를 정리하지 않고 길게 쓸수록 이런 현상은 선형적으로 심해집니다. 이런 코드를 계속 쌓아두면 LLM의 성능도 점점 떨어질 거라 생각합니다. 하지만 적어도 일부 문제는 프로그램적으로 관리할 수 있습니다.
여기엔 인간의 감각도 필요합니다 — 몇 번의 왕복 작업 후에는 사람이 직접 확인해서 LLM이 너무 이상한 짓을 하지 않았는지 봐야 합니다. 예를 들어, Claude와 o1-pro는 map[string]interface{}
를 남발하는데, 대부분의 Go 개발자라면 당연히 struct를 쓸 자리에 말이죠. 이런 건 시간이 지나면 누구든 미치게 만들 겁니다.
동시성 처리 습관도 좀 의심스럽습니다.
그래서 제 워크플로우는 대략 이렇습니다 —
- 업데이트하고 싶은 부분과 “완료 기준”을 구체적으로 생각한다
- 기존 코드를 o1-pro에 붙여넣고 계획을 요청한다
- Claude Code를 켜고 그 계획을 붙여넣는다
- 기다리며 vibe를 느낀다
- 직접 기능을 테스트한다
- 필요하면 2~5를 반복한다
- 필요하면 직접 포맷과 린트를 한다
- 린트 오류가 산더미처럼 쌓이면 좌절한다
- o1-pro에게 해결 방법을 묻는다
- 결과를 다시 Claude에 붙여넣는다
이렇게 하면 꽤 괜찮은 결과물이 나오고, 마지막엔 “인간 린트”를 하듯 코드 리뷰를 합니다 (사실 Claude Code는 Github 연동으로 이걸 할 수 있다는데, 아직 써보진 않았습니다). LLM이 이상한 짓을 하는 걸 바로잡아야 하지만, 그래도 직접 다 쓰는 것보단 낫다고 생각합니다. Copilot도 물론 유용합니다.
다른 프로젝트에서 LLM에 구현을 맡겼을 때는, 특히 실제 기능을 확인하는 통합 테스트가 큰 도움이 됐습니다. LLM은 여러분이 잘 감독하지 않으면 유닛 테스트를 삭제하거나 하드코딩해버릴 수 있으니, 안전장치가 사라지지 않게 주의해야 합니다.
포기하는 것도 두려워하지 마라 
장난감처럼 코드를 마구 뱉어내는 토큰 생성기를 통제해서, 버그 더미에 파묻히지 않게 하는 건 늘 도전입니다. 이렇게 많은 코드를 빠르게 만들 수 있다는 건 멋진 일이지만, 그만큼 코드가 더 빨리 썩어갑니다. 한 번 잘못된 방향으로 가면, 그 영향이 금방 커집니다. LLM이 여러분의 의도를 잘못 이해하면, 이전의 잘못된 결과에 집착하는 경향이 있으니, 그냥 포기하고 새 스레드에서 다시 시작하는 게 낫습니다.
그리고, 그냥 머리를 식히지 마세요. LLM이 왜 그렇게 코드를 짜는지, 그 코드가 어떻게 동작하는지 생각해보세요. 정말 꼭 필요한 기능인데 LLM이 구현을 못한다면, 너무 많은 시간을 낭비하지 마세요.
바이브 코딩을 할 것인가, 말 것인가 
지금 우리는 프로그래머의 정신적 에너지를 해방하고, 에이전트에게 작업을 넘기는 대규모 변화의 한가운데에 있습니다. 이 변화의 끝이 어떻게 될지는 아무도 모릅니다 — 다만, 주니어 엔지니어에게는 더 힘든 세상이 될 것 같고, 결국 우리 모두가 “프로그래머”라기보다는 “AI 에이전트 오케스트레이터이자 디버거”가 될 것 같습니다. 많은 사람들이 “바이브 코딩”에 너무 몰입한다고 생각하지만, 이 방식은 분명히 계속될 것입니다. 완전히 코딩을 몰라도 앱을 만들 수 있는 시대가 올지는 모르겠지만, 적어도 에이전트와 챗 시스템의 출력을 감독하고, 편집하고, 반복하는 방식은 계속될 겁니다.
이 글이 미래의 괴물 같은 스파게티 코드에서 누군가를 구하는 데 도움이 되길 바랍니다. 다음에 또 만나요, 인터넷. • Nathan