두 단계로 나누는 코딩 세션: 생각과 타이핑을 분리해 더 깔끔한 기능을 더 빠르게 배포하기
일을 계획/사고 단계와 실행/타이핑 단계로 의도적으로 나누면 코드가 더 깔끔해지고, 결함이 줄어들며, 전체적인 전달 속도가 빨라진다.
두 단계로 나누는 코딩 세션: 생각과 타이핑을 분리해 더 깔끔한 기능을 더 빠르게 배포하기
대부분의 개발자는 본질적으로 전혀 다른 두 가지 활동을 한 번에, 뒤섞인 지저분한 세션으로 처리하곤 합니다.
- Thinking(생각): 요구사항을 이해하고, 플로우를 설계하며, 트레이드오프를 결정하는 일
- Typing(타이핑): 결정된 내용을 코드로 구현하고, 연결하고, 컴파일 에러를 해결하는 일
깊은 설계와 세부 구현을 동시에 하려 들면, 꽤 큰 비용을 치르게 됩니다.
- “큰 그림”과 “극단적으로 작은 디테일” 사이를 계속 왔다 갔다 하는 컨텍스트 스위칭
- 구현 도중에 다시 손보는, 덜 다듬어진 설계
- 의도치 않은 스코프 확장
- 더 많은 버그와 재작성(rewrite)
이에 대한 간단하지만 강력한 대안은, 작업을 두 개의 분명한 단계로 나누어 진행하는 것입니다. 의도적인 계획/사고(Planning/Thinking) 단계와 집중된 실행/타이핑(Execution/Typing) 단계로 나누는 것이죠. 이 둘을 분리하면, 기능을 더 빠르게, 더 적은 결함으로, 더 깔끔하고 유지보수하기 쉬운 코드로 배포할 수 있습니다.
1단계: 계획/사고 모드
계획 단계에서는 IDE를 켜고 싶어지는 충동을 일부러 참습니다. 목표는 손가락이 키보드를 치기 전에 중요한 결정을 웬만큼 다 끝내는 것입니다.
이 단계는 네 가지 핵심 활동에 집중합니다.
- 유저 스토리와 스코프를 명확히 하기
- 아키텍처와 인터페이스 설계하기
- 리스크를 초기에 식별하고 평가하기
- “완료”의 기준을 정의하기
1. 완전히 명확한 유저 스토리를 작성하라
좋은 코드는 좋은 이해에서 시작됩니다. 구현 코드를 한 줄이라도 쓰기 전에, 다음을 분명히 담고 있는 유저 스토리를 먼저 작성하세요.
- 이 기능이 누구를 위한 것인지
- 사용자가 무엇을 하려는지
- 그게 왜 중요한지 (사용자 가치)
- 어떻게 이게 잘 작동하는지 확인할 것인지 (인수 기준, Acceptance Criteria)
탄탄한 유저 스토리는 다음처럼 보일 수 있습니다.
나는 팀 관리자로서, 이메일로 유저를 초대하고 역할을 지정하고 싶다. 그래야 IT 부서를 거치지 않고도 새로운 팀원을 안전하게 온보딩할 수 있기 때문이다.
인수 기준(예시):
- 관리자는 하나 이상의 이메일 주소를 초대할 수 있다.
- 각 초대에는 역할(뷰어, 에디터, 관리자)이 반드시 지정되어야 한다.
- 초대장은 7일 후 만료된다.
- 동일한 이메일을 가진 기존 사용자는 새로 생성되지 않는다.
- 누가 누구를 언제 초대했는지 감사 로그(audit log)에 기록된다.
이렇게 작성해두면 객관적인 스코프와, 모두가 공유하는 성공 기준이 생깁니다. 구현 도중에 “이왕 하는 김에 이것도...” 같은 깜짝 기능이 끼어들 가능성이 크게 줄어듭니다.
2. 확장 가능하고 유지보수하기 쉬운 아키텍처를 설계하라
다음으로, 이 기능이 기존 시스템 속 어디에, 어떻게 들어갈지를 설계합니다. 아직 코드 에디터는 열지 말고, 대신 다음과 같은 도구를 활용하세요.
- 빠르게 그리는 다이어그램(화이트보드, 노트, Miro 등)
- 텍스트 기반 아키텍처 노트
- 시퀀스 다이어그램 또는 API 계약(API Contract)
다음과 같은 질문에 답하는 것이 목표입니다.
- 이 로직은 어디에 둘 것인가? (서비스, 컨트롤러, 클라이언트, 워커 등)
- 어떤 데이터 모델이 관여하는가? (새 테이블, 필드, 객체 등)
- 인터페이스는 어떻게 생겼나? (입력, 출력, 에러 케이스)
- 어떻게 스케일링할 것인가? (트래픽, 데이터 증가, 동시 사용량)
- 어떻게 테스트할 것인가? (단위 테스트, 통합 테스트, 기능 플래그 등)
이 단계에서의 설계 목표는 다음과 같습니다.
- 조합 가능성(Composable): 재사용할 수 있는 작은 유닛들로 쪼개기
- 테스트 용이성(Testable): 경계가 뚜렷하고, 필요한 곳에 의존성 역전(IoC)을 적용
- 확장성(Extensible): 나중에 기능을 추가해도 전부 갈아엎지 않아도 되도록
목표가 50페이지짜리 설계 문서를 만드는 것이 아닙니다. 핵심 설계 결정을 명시적으로 만들어 두는 것이 중요하고, 그래야 나중 구현 단계가 거의 번역 작업에 가깝게 느껴집니다.
3. 초기에 명시적인 리스크 평가를 하라
소프트웨어 프로젝트에서 벌어지는 대부분의 “뜻밖의 문제”는 사실 정말로 뜻밖인 경우가 드뭅니다. 그냥 초기에 드러나지 않았을 뿐입니다.
계획 단계에서 잠깐 멈추고, 다음을 스스로 물어보세요.
-
기술 리스크(Technical risks)
- 익숙하지 않은 API나 레거시 시스템과 연동해야 하는가?
- 우리가 아직 잘 이해하지 못한 성능/스케일링 이슈가 있는가?
- 진행 속도를 늦출 만한 기술 부채(Tech Debt)가 있는가?
-
제품 리스크(Product risks)
- 요청된 동작이 정말로 사용자의 문제를 해결하는가?
- UX가 명확하며, 제품의 나머지 부분과 일관성이 있는가?
- 이 기능이 향후 로드맵에 있는 것들과 충돌할 가능성은 없는가?
-
타임라인 리스크(Timeline risks)
- 다른 팀이나 서비스에 대한 의존성이 있는가?
- 이해가 모호해서 이해관계자의 추가 설명이 필요한 요구사항이 있는가?
- 우리가 한 번도 해본 적 없는 시도를 하려는가?
이 리스크들을 문서로 적어두세요. 그리고 코딩에 들어가기 전에 어떻게 대응할지 결정합니다.
- 작은 스파이크(Spike)나 프로토타입을 만들어 본다.
- 이해관계자와 미리 명확히 한다.
- 가능한 곳은 스코프를 줄인다.
- 기능 플래그나 점진적 롤아웃(rollout) 전략을 추가한다.
미리 리스크를 줄여두면, 이후 구현 단계가 더 예측 가능해지고 훨씬 덜 스트레스받는 과정이 됩니다.
4. “완료”의 정의를 미리 정하라
“끝났다”는 말은 “컴파일된다”거나 “내 로컬에서 돌아간다”를 의미하지 않습니다. **완료(done)**를 다음 기준으로 정의하세요.
- 기능(Functionality): 모든 인수 기준을 충족한다.
- 품질(Quality): 테스트가 작성되어 있고 모두 통과한다. 기본적인 성능도 확인했다.
- 유지보수성(Maintainability): 코드를 읽기 쉽고, 필요한 부분은 적절히 문서화되어 있다.
- 운영 준비도(Operational readiness): 로그, 메트릭, 알람(해당된다면)이 준비되어 있다.
이 체크리스트는 타이핑 단계의 “미래의 나”를 위한 가이드라인이 되어, 너무 일찍 승리 선언을 해버리지 않도록 막아줍니다.
2단계: 실행/타이핑 모드
계획을 마쳤다면, 이제 의도적으로 타이핑 모드로 전환합니다.
이 단계의 규칙은 단순합니다.
- 계획을 따른다.
- 큰 새 결정을 피한다.
- 컨텍스트 스위칭을 최소화한다.
이건 설계도를 보고 집을 짓는 것과 같다고 생각하면 됩니다.
1. 설계를 다시 하지 말고, 계획을 구현하라
이 단계에서 하는 일은 다음과 같습니다.
- 아키텍처 단계에서 내린 결정을 실제 코드로 옮긴다.
- 이미 정한 인터페이스와 데이터 모델을 구현한다.
- 미리 계획해 둔 시나리오에 맞춰 테스트를 작성한다.
구현 중에 계획에 큰 결함이 보인다고 해서, 몰래 전부 다시 설계해버리지 마세요. 대신:
- 작은 메모를 남긴다. (예:
TODO: X에 대한 아키텍처 다시 보기) - 이게 막히는(blocking) 이슈인지, 아니면 개선(improvement) 사항인지 판단한다.
- 막히는 이슈라면, 잠시 멈추고 계획 모드로 되돌아가서 정리한 뒤 다시 구현으로 돌아온다.
이 두 모드를 분리해두면, 코딩 세션이 끝도 없는 설계 논쟁으로 변질되는 것을 막을 수 있습니다.
2. 컨텍스트 스위칭으로부터 플로우를 보호하라
타이핑 모드의 핵심은 **꾸준하고 품질 높은 처리량(throughput)**입니다. 플로우 상태를 지키려면 다음을 실천하세요.
- 로드맵 문서, 슬랙 논쟁, 장문의 기획 문서처럼 “큰 그림 모드”를 유도하는 것들은 닫거나 알림을 꺼둔다.
- 이미 만들어 둔 계획, 태스크 목록, 작업 분해 리스트를 기준으로 움직인다.
- 인수 기준에 대응하는 작은 수직 슬라이스 단위로 구현한다.
목표는 다음 두 가지입니다.
- 짧은 피드백 루프: 코드를 조금 쓰고, 테스트를 돌리고, 동작을 확인하는 사이클을 반복
- 가시적인 진행 상황: 각 슬라이스가 앞서 정의했던 “완료”에 더 가까이 다가가게 할 것
결정은 이미 내려져 있으니, “이건 뭐라고 이름 붙여야 하지?” “이 로직은 어디에 둬야 하지?” 같은 고민 때문에 계속 멈추지 않게 됩니다. 그 고민은 계획 단계에서 이미 끝냈으니까요.
3. 이전 단계의 투자에 기대라
계획 단계는, 특히 바로 에디터부터 켜던 습관이 있다면, 처음에는 “느리다”고 느껴질 수 있습니다. 하지만 타이핑 모드에 들어가면 그 투자 효과가 보이기 시작합니다.
- 아키텍처를 충분히 고민해뒀기 때문에, 재작성(rewrite)이 줄어든다.
- 엣지 케이스들을 미리 고려해 두어, 버그가 줄어든다.
- 코드 리뷰에서 의도가 명확하게 드러나, 리뷰가 빨라진다.
겉으로는 처음에 “추가적인 오버헤드”처럼 보이던 시간이, 결국 엔드 투 엔드(End-to-End) 전달 시간을 단축시키는 셈입니다.
왜 생각과 타이핑을 분리하면 효과가 있을까?
이 두 단계 접근법은 업계에서 널리 쓰이는 여러 베스트 프랙티스와 맞닿아 있습니다.
- Agile: 유저 스토리, 인수 기준, 스프린트 계획
- XP/TDD: 설계와 테스트가 구현을 이끄는 방식
- 아키텍처 리뷰: 만들기 전에 미리 설계를 검토하는 문화
워크플로에 의도적인 설계/계획 모드와 빠르고 자신감 있는 코딩 모드라는 두 가지 분명한 모드를 도입하면 다음과 같은 효과가 있습니다.
- 컨텍스트 스위칭을 줄여 인지 부하(cognitive load)를 낮춘다.
- 머리가 “큰 그림 모드”일 때 더 나은 아키텍처 결정을 내리게 된다.
- 구현할 때는 이미 결정이 나 있기 때문에, 재고민 없이 플로우 상태를 유지할 수 있다.
- 더 짧은 캘린더 시간 안에 더 깔끔하고 유지보수하기 쉬운 기능을 전달한다.
이건 관료주의를 늘리자는 이야기가 아닙니다. 언제 생각하고, 언제 타이핑할지 스스로 선택하자는 이야기입니다. 모든 걸 동시에 하려다 효율을 까먹지 말자는 것이죠.
오늘 당장 두 단계 코딩을 시작하는 방법
이걸 도입하기 위해 프로세스를 전면 개편할 필요는 없습니다. 작게 시작하면 됩니다.
- 다음에 할 기능 하나를 고른 뒤, 캘린더에 두 블록을 명시적으로 예약하세요. “Planning(계획)”과 “Implementation(구현)”으로요.
- 계획 블록 (IDE 금지)
- 유저 스토리와 인수 기준을 작성한다.
- 아키텍처와 책임 분리를 스케치한다.
- 기술/제품/타임라인 리스크를 리스트업한다.
- “완료” 체크리스트를 정의한다.
- 구현 블록 (큰 결정 금지)
- 계획을 따른다.
- 놀란 점, 다음에 개선하고 싶은 점을 메모한다.
- 배포가 끝난 뒤, 간단히 회고한다
- 계획 단계에서 미리 잡지 않았다면 나중에 문제가 되었을 것 같은 것들은 무엇인가?
- 구현 중에도 여전히 큰 결정을 해야 했던 지점은 어디인가?
- 다음에는 두 단계의 경계를 어떻게 더 깔끔히 나눌 수 있을까?
이 과정을 반복하다 보면 습관이 됩니다. 팀은 점점 명확한 유저 스토리, 명시적인 설계, 그리고 집중된 구현 작업을 자연스럽게 기대하게 됩니다.
마무리
생각과 타이핑을 뒤섞어 버리면, 컨텍스트 스위칭, 재작업, 예기치 않은 리스크 때문에 보이지 않는 마찰이 쌓이며 속도가 느려집니다.
일을 의도적으로 계획/사고 단계와 실행/타이핑 단계로 분리하면 다음과 같은 결과를 얻을 수 있습니다.
- 엔드 투 엔드로 봤을 때 기능을 더 빠르게 배포한다.
- 더 깔끔하고 유지보수하기 쉬운 코드를 만든다.
- 결함과 재작성을 줄인다.
- 업계 베스트 프랙티스와 자연스럽게 맞닿는 방식으로 일한다.
초기에 사고 모드에 시간을 투자하세요. 그러면 나중에 구현이 “쉬운 부분”이 되는 순간, 미래의 나와 코드베이스가 그 대가를 충분히 치러줍니다.