Differences between revisions 6 and 7
Revision 6 as of 2025-12-30 09:58:48
Size: 9527
Editor: 정수
Comment:
Revision 7 as of 2025-12-30 10:00:30
Size: 7517
Editor: 정수
Comment:
Deletions are marked like this. Additions are marked like this.
Line 1: Line 1:
#acl +All:read #acl + All:read
Line 8: Line 8:
== The Story: The Long Tunnel vs. Stepping Stones == == The Story 1: The Long Tunnel vs. Stepping Stones (Programming) ==
Line 10: Line 10:
두 명의 개발자, 민수와 하나가 똑같이 복잡한 리팩토링 작업을 맡았다. 레거시 코드의 거대한 `User` 클래스에서 `Billing` 로직을 분리하는 작업이다. 두 명의 개발자, 민수와 하나가 똑같이 복잡한 리팩토링 작업을 맡았다. 거대한 `User` 클래스에서 `Billing` 로직을 분리하는 작업이다.
Line 13: Line 13:
민수는 생각했다. "이건 다 연결되어 있으니까 한 번에 옮겨야 해."
그는 코드를 자르고 붙여넣기 시작했다. 30개의 파일이 수정되었다. 컴파일 에러가 150개 떴다.
"금방 잡을 수 있어."
3시간 후, 에러는 잡았지만 테스트가 실패했다.
2일 후, 민수는
여전히 깨진 시스템과 씨름하 있었다. 그는 지쳤고, 무엇이 문제였는지 기억조차 희미해졌다. 시스템은 2일 동안 '죽어' 있었다.
민수는 생각했다. "이건 다 연결되어 있으니까 한 번에 옮겨야 해." 그는 코드를 자르고 붙여넣기 시작했다. 30개의 파일이 수정되었 컴파일 에러가 150개 떴다. 민수는 "금방 잡을 수 있어"라고 말했지만, 3시간 후에도 에러는 여전고 무엇이 문제였는지 기억조차 희미해졌다. 시스템은 3시간 동안 '죽어' 있었다.
Line 21: Line 17:
 1. 새로운 `Billing` 클래스를 빈 상태로 만들었다. (테스트 통과)
 2. `User`의 메서드 하나를 `Billing`으로 복사했다. (아직 사용 안 함, 테스트 통과)
 3. `User`가 `Billing`의 메서드를 호출하도록 위임했다. (테스트 통과)
 4. `User` 기존 코드를 지웠다. (테스트 통과)
 1. 새로운 `Billing` 클래스를 빈 상태로 만들었다. (Green)
 2. `User`의 메서드 하나를 `Billing`으로 복사했다. (Green)
 3. `User`가 `Billing`의 메서드를 호출하도록 위임했다. (Green)
하나는 10분마다 커밋했다. 커피를 마시러 갈 때도, 퇴근할 때도 그녀의 코드는 항상 작동했다. 작업이 끝났을 때 민수는 여전히 디버깅 중이었고, 하나는 이미 다음 기능을 만들고 있었다.
Line 26: Line 22:
하나는 10분마다 커밋했다. 커피를 마시러 갈 때도, 퇴근할 때도 그의 코드는 항상 작동했다.
"느려 보이지? 하지만 난 디버깅하느라 멈춘 적이 없어."
Line 29: Line 23:
작업이 끝났을 때, 민수는 아직도 디버깅 중이었고, 하나는 이미 다음 기능을 만들고 있었다. == The Story 2: The Piano Lesson (Ordinary Life) ==

민수와 하나는 취미로 피아노를 배우고 있다. 오늘 숙제는 아주 빠르고 복잡한 쇼팽의 연습곡이다.

'''민수의 연습 (The Whole Song):'''
민수는 곡의 처음부터 끝까지 한 번에 완벽하게 치고 싶어 한다. 그는 악보를 펴고 양손으로 힘겹게 연주를 시작했다. 중간에 계속 틀렸지만, "연습하다 보면 되겠지"라며 멈추지 않고 끝까지 쳤다. 한 시간을 연습했지만 민수의 연주는 여전히 뚝뚝 끊겼고, 어디가 틀렸는지조차 알 수 없게 되었다.

'''하나의 연습 (One Bar at a Time):'''
하나는 다르게 연습했다.
 1. 첫 번째 마디의 오른손만 완벽하게 친다.
 2. 그 마디의 왼손만 완벽하게 친다.
 3. 양손을 합쳐 한 마디를 완벽하게 친다.
하나는 단 한 마디라도 '완벽하고 리드미컬하게' 들릴 때까지 다음으로 넘어가지 않았다. 그녀의 연습은 매 순간 '음악'이었고, 민수보다 느려 보였지만 결국 곡 전체를 가장 먼저 마스터했다. 기예는 작은 성공을 쌓아 올리는 과정임을 하나는 알고 있었다.
Line 34: Line 40:
당신은 무엇을 만들어야 할지 알고 있다([[TinyExperiment]]를 통해). 하지만 그 작업은 크고 복잡하다. 리팩토링이거나, 큰 기능 추가이거나, 라이브러리 교체일 수 있다. 당신은 무엇을 만들어야 할지 알고 있다([[TinyExperiment]]를 통해). 하지만 그 작업은 크고 복잡하다. 리팩토링이거나, 큰 기능 추가일 수 있다.
Line 42: Line 48:
당신은 '''불확실한 결과'''(TinyExperiment의 영역)가 아니라, '''복잡한 과정'''(BabySteps의 영역)과 싸우고 있다.
Line 49: Line 53:
시스템이 컴파일되지 않거나 테스트를 통과하지 못하는 상태는 '''죽은 상태'''다. 죽은 상태가 길어질수록: 시스템이 작동하지 않는 상태가 길어질수록 복잡성은 폭주하고 심리적 압박은 커진다.
Line 51: Line 55:
 * '''심리적 압박:''' "돌아갈 수 없다"는 공포가 생긴다. 앞으로 가는 수밖에 없는데, 앞이 보이지 않는다.
 * '''중단 불가능:''' 긴급한 다른 이슈가 생겨도, 현재 작업이 마무리될 때까지 컨텍스트 스위칭을 할 수 없다.

Christopher Alexander는 말했다. "자연의 모든 생명체는 전체성을 유지하면서 성장한다(Structure-Preserving Transformations)." 애벌레가 나비가 될 때도, 그 중간 단계에서 생명체는 죽지 않는다. 하지만 프로그래머들은 자주 수술 중인 환자를 죽여놓고, 수술이 끝나면 다시 살아날 거라 기대한다.
 * '''심리적 압박:''' "돌아갈 수 없다"는 공포가 생기고, 앞이 보이지 않는다.
 * '''중단 불가능:''' 긴급한 다른 이슈가 생겨도 현재 작업을 멈출 수 없다.
Line 61: Line 62:
BabySteps는 단순히 "작게 하는 것"이 아니다. '''각 단계가 끝날 때마다 시스템이 온전해야(Structure-Preserving) 한다'''는 규율이다. BabySteps는 각 단계가 끝날 때마다 시스템이 온전해야(Structure-Preserving) 한다는 규율이다.
Line 64: Line 65:
Line 66: Line 66:
Line 68: Line 67:

* '''Comment Out and Revert (한 보 후퇴):''' 새로 작성한 코드에서 오류가 발생했을 때, 당황하며 고치려 하지 말고 즉시 해당 코드를 주석 처리(Comment out) 하십시오.    * 한 스텝 뒤로 가서 제대로 동작하는 것을 재확인하고, 그 지점에서 다시 아주 작은 증분을 탐색하십시오.    * 빨간 막대(실패)를 5분 이상 보지 마라.

 * '''Comment Out and Revert (한 보 후퇴):''' 새로 작성한 코드에서 오류가 발생했을 때, 즉시 해당 코드를 주석 처리(Comment out) 하십시오. 한 스텝 뒤로 가서 제대로 동작하는 것을 재확인하고, 그 지점에서 다시 아주 작은 증분을 탐색하십시오.
Line 78: Line 70:

기존 것을 지우고 새 것을 넣는 것이 아니라, '''새 것을 옆에 만들고 서서히 옮겨라.'''
 1. '''확장(Expand):''' 새 기능을 추가한다. (기존 기능은 그대로 둠)
기존 것을 지우고 새 것을 넣는 것이 아니라, 새 것을 옆에 만들고 서서히 옮겨라.
 1. '''확장(Expand):''' 새 기능을 추가한다.
Line 84: Line 75:
이 과정의 모든 순간에 시스템은 작동한다.
Line 87: Line 76:

Kent Beck의 조언: "Make the change easy (warning: this may be hard), then make the easy change."
 * 구조를 바꾸면서 기능을 추가하지 마라.
 * 기능을 추가하기 쉽게 구조를 먼저 바꿔라(리팩토링).
 * 그 다음 기능을 추가하라.

=== Principle 4: The Mikado Method ===

목표가 너무 멀어 보일 때:
 1. 목표를 시도한다. (실패: A가 필요함)
 2. 돌아간다(Revert).
 3. A를 시도한다. (실패: B가 필요함)
 4. 돌아간다(Revert).
 5. B를 구현한다. (성공!)
 6. A를 구현한다. (성공!)
 7. 목표를 구현한다. (성공!)

의존성의 그래프를 그리며 가장 끝단(Leaf)부터 하나씩 해결해 나간다.
Kent Beck의 조언: "Make the change easy, then make the easy change."
 * 기능을 추가하기 쉽게 구조를 먼저 바꿔라(리팩토링). 그 다음 기능을 추가하라.
Line 110: Line 83:

함수 인자를 `process(a, b)`에서 `process(a, b, c)`로 바꾸고 싶다.

'''Bad Way (Big Bang):'''
 1. 함수 정의를 바꾼다.
 2. 컴파일러가 에러를 낸 50군데 호출 코드를 전부 찾아다니며 수정한다.
 3. 30분 동안 시스템은 컴파일되지 않는다.

'''Baby Steps Way:'''
 1. `process(a, b, c=None)`으로 변경한다. (기본값 제공, 기존 코드 수정 불필요, '''Green''')
 2. 함수 내부에서 `c`가 있으면 사용하도록 로직 추가. ('''Green''')
 3. 호출하는 곳을 하나씩 `process(a, b, new_value)`로 수정. (한 번에 하나씩, '''Green''')
 4. 모든 호출이 수정되면 `c`의 기본값을 제거. ('''Green''')
인자를 추가할 때, 기존 호출부를 다 고치는 대신 기본값(Default value)을 주어 먼저 작동하게 만든 뒤, 하나씩 호출부를 수정해 나가는 방식.
Line 125: Line 86:

`Manager`를 `Service`로 바꾸고 싶다.

 1. `Manager`를 상속받는 `Service` 클래스를 만든다. ('''Green''')
   {{{ class Service extends Manager {} }}}
 2. `Manager`를 사용하는 코드를 하나씩 `Service`로 타입을 바꾼다. ('''Green''')
 3. 다 바꿨으면 `Manager`의 내용을 `Service`로 옮기고 상속을 끊는다. ('''Green''')
 4. `Manager`를 삭제한다. ('''Green''')
기존 클래스를 상속받는 새 이름의 클래스를 만들고, 사용하는 곳을 하나씩 옮긴 뒤 마지막에 옛 클래스를 삭제하는 방식.
Line 137: Line 91:
=== "It's slower!" ===
직관적으로는 느려 보인다. 1번 할 일을 4번에 나눠서 하니까.
하지만 '''통합 비용'''과 '''디버깅 시간'''을 고려하면 훨씬 빠르다. 토끼와 거북이다. 거북이는 멈추지 않기 때문에 빠르다.
=== "It's slower!" (느려 보여요!) ===
토끼와 거북이다. 거북이는 멈추지 않기 때문에 빠르다. 디버깅 지옥에 빠지지 않는 것이 가장 빠른 길이다.
Line 141: Line 94:
=== "Just one more line..." ===
"이것만 고치면 될 것 같은데..."라는 유혹.
이 유혹에 넘어가는 순간 3시간의 디버깅 지옥이 시작된다. 5분이 지나도 해결이 안 되면 무조건 돌아가라(Revert).

=== "Dirty Code during transition" ===
중간 단계에는 코드가 지저분해 보일 수 있다(구버전과 신버전의 공존).
이것은 '''건축 중인 건물의 비계(Scaffolding)'''다. 완성을 위해 필요한 과정이지, 더러운 것이 아니다. 작업이 끝나면 제거된다.
=== "Just one more line..." (한 줄만 더...) ===
이 유혹에 넘어가는 순간 3시간의 디버깅이 시작된다. 5분이 지나도 해결이 안 되면 무조건 돌아가라(Revert).
Line 152: Line 100:
 * '''[[TightLoop]]''' - 빠른 피드백 루프가 있어야 보폭을 작게 유지할 용기가 생깁니다. 루프가 느리면 보폭이 커지는 유혹에 빠집니다. ''동력''
 * '''[[AtomicCommit]]''' - 각 Baby Step의 끝이 바로 원자적 커밋의 시점입니다. 작은 보폭은 깨끗한 역사를 만듭니다. ''기록의 단위''
 * '''[[GreenRefuge]]''' - 발을 헛디뎠을 때(Test Fail), 즉시 안전한 곳(Green State)으로 돌아가십시오. BabySteps의 유일한 안전망입니다. ''보완 관계''
 * '''[[TinyExperiment]]''' - 보폭을 떼기 전 어디로 가야 할 모른다면 TinyExperiment로 먼저 길을 찾으십시오. ''선행 관계''
 * '''[[TightLoop]]''' - 빠른 피드백 루프가 있어야 보폭을 작게 유지할 용기가 생깁니다. ''동력''
 * '''[[AtomicCommit]]''' - 각 Baby Step의 끝이 바로 원자적 커밋의 시점입니다. ''기록의 단위''
 * '''[[GreenRefuge]]''' - 발을 헛디뎠을 때 즉시 돌아갈 안전 지대입니다. ''보완''
 * '''[[TinyExperiment]]''' - 보폭을 떼기 전 어디로 지 먼저 길을 찾으십시오. ''선행''
Line 159: Line 107:
 * '''스트레스가 없다.''' 코딩이 평온한 리듬을 탄다.
 * '''디버거를 켜지 않는다.''' 문제가 생겨도 방금 작성한 3줄 안에 원인이 있다.
 * '''언제든 퇴근할 수 있다.''' 10분만 정리하면 작업 끝이다.
 * '''코드 리뷰가 쉽다.''' 동료가 작은 커밋들을 보며 의도를 쉽게 파악한다.
 * 코딩이 평온한 리듬을 탄다. 스트레스가 없다.
 * 디버거를 켜지 않는다. 문제가 생겨도 방금 작성한 3줄 안에 원인이 있다.
 * 언제든 퇴근할 수 있다. 10분만 정리하면 작업 끝이다.
 * 코드 리뷰가 쉽다. 동료가 작은 커밋들을 보며 의도를 쉽게 파악한다.
Line 168: Line 116:
BabySteps는 멈추지 않게 해주는 기술다. 시스템을 항상 살아있게 하. 그것이 가장 빠르고 안전하게 목적지에 도달하는 방법다. BabySteps는 멈추지 않게 해주는 기술입니다. 시스템을 항상 살아있게 하십시오. 그것이 가장 빠르고 안전하게 목적지에 도달하는 방법입니다.

BabySteps

주니어 개발자들을 위한 패턴 언어 - 시스템의 생명력을 유지하며 복잡한 변경을 수행하는 방법

The Story 1: The Long Tunnel vs. Stepping Stones (Programming)

두 명의 개발자, 민수와 하나가 똑같이 복잡한 리팩토링 작업을 맡았다. 거대한 User 클래스에서 Billing 로직을 분리하는 작업이다.

민수의 접근 (The Big Jump): 민수는 생각했다. "이건 다 연결되어 있으니까 한 번에 옮겨야 해." 그는 코드를 자르고 붙여넣기 시작했다. 30개의 파일이 수정되었고 컴파일 에러가 150개 떴다. 민수는 "금방 잡을 수 있어"라고 말했지만, 3시간 후에도 에러는 여전했고 무엇이 문제였는지 기억조차 희미해졌다. 시스템은 3시간 동안 '죽어' 있었다.

하나의 접근 (Baby Steps): 하나는 다르게 시작했다.

  1. 새로운 Billing 클래스를 빈 상태로 만들었다. (Green)

  2. User의 메서드 하나를 Billing으로 복사했다. (Green)

  3. UserBilling의 메서드를 호출하도록 위임했다. (Green)

하나는 10분마다 커밋했다. 커피를 마시러 갈 때도, 퇴근할 때도 그녀의 코드는 항상 작동했다. 작업이 끝났을 때 민수는 여전히 디버깅 중이었고, 하나는 이미 다음 기능을 만들고 있었다.

The Story 2: The Piano Lesson (Ordinary Life)

민수와 하나는 취미로 피아노를 배우고 있다. 오늘 숙제는 아주 빠르고 복잡한 쇼팽의 연습곡이다.

민수의 연습 (The Whole Song): 민수는 곡의 처음부터 끝까지 한 번에 완벽하게 치고 싶어 한다. 그는 악보를 펴고 양손으로 힘겹게 연주를 시작했다. 중간에 계속 틀렸지만, "연습하다 보면 되겠지"라며 멈추지 않고 끝까지 쳤다. 한 시간을 연습했지만 민수의 연주는 여전히 뚝뚝 끊겼고, 어디가 틀렸는지조차 알 수 없게 되었다.

하나의 연습 (One Bar at a Time): 하나는 다르게 연습했다.

  1. 첫 번째 마디의 오른손만 완벽하게 친다.
  2. 그 마디의 왼손만 완벽하게 친다.
  3. 양손을 합쳐 한 마디를 완벽하게 친다.

하나는 단 한 마디라도 '완벽하고 리드미컬하게' 들릴 때까지 다음으로 넘어가지 않았다. 그녀의 연습은 매 순간 '음악'이었고, 민수보다 느려 보였지만 결국 곡 전체를 가장 먼저 마스터했다. 기예는 작은 성공을 쌓아 올리는 과정임을 하나는 알고 있었다.

Context

당신은 무엇을 만들어야 할지 알고 있다(TinyExperiment를 통해). 하지만 그 작업은 크고 복잡하다. 리팩토링이거나, 큰 기능 추가일 수 있다.

일상적인 상황:

  • 코드를 수정하기 시작해서 1시간이 지났는데, 아직 컴파일이 안 된다.
  • "거의 다 됐어, 이것만 연결하면 돼"라고 말하지만, 그 상태가 반나절 지속된다.
  • 변경 사항이 너무 많아서 어디서 버그가 발생했는지 알 수 없다.
  • 동료가 "그거 지금 실행돼?"라고 물으면 "아니, 지금 작업 중이라 안 돼"라고 답한다.

Problem

한 번에 너무 많은 변화를 시도하면, 시스템의 "생명력(Wholeness)"이 깨진다.

시스템이 작동하지 않는 상태가 길어질수록 복잡성은 폭주하고 심리적 압박은 커진다.

  • 복잡성의 폭주: 여러 변경 사항이 서로 얽혀 문제가 생겼을 때 원인을 찾을 수 없다.

  • 심리적 압박: "돌아갈 수 없다"는 공포가 생기고, 앞이 보이지 않는다.

  • 중단 불가능: 긴급한 다른 이슈가 생겨도 현재 작업을 멈출 수 없다.

Solution

시스템을 항상 작동하는 상태로 유지할 수 있는 가장 작은 단위로 쪼개어 변경하라.

BabySteps는 각 단계가 끝날 때마다 시스템이 온전해야(Structure-Preserving) 한다는 규율이다.

Principle 1: Always Green (항상 녹색 상태 유지)

가장 중요한 규칙이다. 한 단계를 마쳤을 때, 모든 테스트가 통과해야 한다. 만약 테스트가 깨졌다면? 당신은 보폭을 너무 크게 잡은 것이다.

  • Comment Out and Revert (한 보 후퇴): 새로 작성한 코드에서 오류가 발생했을 때, 즉시 해당 코드를 주석 처리(Comment out) 하십시오. 한 스텝 뒤로 가서 제대로 동작하는 것을 재확인하고, 그 지점에서 다시 아주 작은 증분을 탐색하십시오.

Principle 2: Parallel Change (평행 변경)

기존 것을 지우고 새 것을 넣는 것이 아니라, 새 것을 옆에 만들고 서서히 옮겨라.

  1. 확장(Expand): 새 기능을 추가한다.

  2. 이주(Migrate): 사용하는 곳을 하나씩 새 기능으로 옮긴다.

  3. 수축(Contract): 사용하지 않는 기존 기능을 제거한다.

Principle 3: Make it Easy, Then Make it Right

Kent Beck의 조언: "Make the change easy, then make the easy change."

  • 기능을 추가하기 쉽게 구조를 먼저 바꿔라(리팩토링). 그 다음 기능을 추가하라.

Real Examples

Example 1: Function Signature Change

인자를 추가할 때, 기존 호출부를 다 고치는 대신 기본값(Default value)을 주어 먼저 작동하게 만든 뒤, 하나씩 호출부를 수정해 나가는 방식.

Example 2: Renaming a Class

기존 클래스를 상속받는 새 이름의 클래스를 만들고, 사용하는 곳을 하나씩 옮긴 뒤 마지막에 옛 클래스를 삭제하는 방식.

Common Pitfalls

"It's slower!" (느려 보여요!)

토끼와 거북이다. 거북이는 멈추지 않기 때문에 빠르다. 디버깅 지옥에 빠지지 않는 것이 가장 빠른 길이다.

"Just one more line..." (한 줄만 더...)

이 유혹에 넘어가는 순간 3시간의 디버깅이 시작된다. 5분이 지나도 해결이 안 되면 무조건 돌아가라(Revert).

Connection to Other Patterns

  • TightLoop - 빠른 피드백 루프가 있어야 보폭을 작게 유지할 용기가 생깁니다. 동력

  • AtomicCommit - 각 Baby Step의 끝이 바로 원자적 커밋의 시점입니다. 기록의 단위

  • GreenRefuge - 발을 헛디뎠을 때 즉시 돌아갈 안전 지대입니다. 보완

  • TinyExperiment - 보폭을 떼기 전 어디로 갈지 먼저 길을 찾으십시오. 선행

Signs of Success

  • 코딩이 평온한 리듬을 탄다. 스트레스가 없다.
  • 디버거를 켜지 않는다. 문제가 생겨도 방금 작성한 3줄 안에 원인이 있다.
  • 언제든 퇴근할 수 있다. 10분만 정리하면 작업 끝이다.
  • 코드 리뷰가 쉽다. 동료가 작은 커밋들을 보며 의도를 쉽게 파악한다.

The Ultimate Insight

속도는 "빨리 타이핑하는 것"에서 나오지 않는다. "멈추지 않는 것"에서 나온다.

BabySteps는 멈추지 않게 해주는 기술입니다. 시스템을 항상 살아있게 하십시오. 그것이 가장 빠르고 안전하게 목적지에 도달하는 방법입니다.


CategoryPatternLanguage CategoryProgramming CategoryRefactoring CategoryTDD

BabySteps (last edited 2025-12-30 10:50:17 by 정수)