|
Size: 10017
Comment:
|
← Revision 4 as of 2025-12-30 10:00:27 ⇥
Size: 7235
Comment:
|
| Deletions are marked like this. | Additions are marked like this. |
| Line 8: | Line 8: |
| == The Story: The Rhythm of Code == | == The Story 1: The Rhythm of Code (Programming) == |
| Line 10: | Line 10: |
| 한 주니어가 세 가지 리포트(PDF, CSV, HTML)를 생성하는 코드를 작성했다. 세 개의 함수는 각각 50줄 정도였고, 서로 미묘하게 달랐다. | 한 주니어가 세 가지 리포트(PDF, CSV, HTML)를 생성하는 코드를 작성했다. 세 개의 함수는 각각 50줄 정도였고, 서로 미묘하게 달랐다. 시니어가 다가와 코드를 '기계적'으로 수정하기 시작했다. 로직은 건드리지 않고, 변수 이름을 통일하고, 들여쓰기를 맞추고, 코드의 순서를 재배치했다. "자, 이렇게 '''줄을 맞춰(Align)''' 보니까 어때?" 주니어는 놀랐다. 세 함수를 나란히 놓으니, 90%의 코드가 토씨 하나 안 틀리고 똑같았다. "전체 흐름은 '''변하지 않는 것(Invariant)'''이고, 포맷팅만 '''변하는 것(Variant)'''이야. 패턴을 찾으려면 먼저 '''리듬'''을 맞춰야 해." |
| Line 12: | Line 12: |
| 시니어가 코드를 훑어보더니 말했다. "이 세 함수, 사실상 같은 코드네." | |
| Line 14: | Line 13: |
| "아니에요," 주니어가 반박했다. "PDF는 헤더를 그리는 방식이 다르고, CSV는 콤마를 찍어야 하고, HTML은 태그가 들어가요. 로직이 완전히 달라요." | == The Story 2: The Shape of Clouds (Ordinary Life) == |
| Line 16: | Line 15: |
| 시니오는 키보드를 잡고 '기계적'으로 코드를 수정하기 시작했다. 로직은 건드리지 않고, 변수 이름을 통일하고, 들여쓰기를 맞추고, 코드의 순서를 재배치했다. | 민수와 하나는 함께 등산을 하고 있다. |
| Line 18: | Line 17: |
| "자, 이렇게 '''줄을 맞춰(Align)''' 보니까 어때?" | '''민수의 관찰 (The Detail):''' 민수는 발밑의 돌부리와 주변의 나무들에 집중한다. "이 나무는 소나무고, 저 꽃은 진달래네." 민수는 눈앞의 사물 하나하나를 개별적으로 관찰하지만, 전체적인 숲의 분위기나 날씨의 변화는 읽어내지 못한다. 갑자기 비가 쏟아지자 민수는 당황했다. |
| Line 20: | Line 20: |
| 주니어는 놀랐다. 세 함수를 나란히 놓으니, 90%의 코드가 토씨 하나 안 틀리고 똑같았다. 오직 데이터를 포맷팅하는 그 '한 줄'만 달랐다. "보이지? 전체 흐름(Loop, 데이터 조회, 에러 처리)은 **변하지 않는 것(Invariant)**이고, 포맷팅만 **변하는 것(Variant)**이야. 이제 이 '변하는 부분'만 쏙 빼내서 파라미터로 넘기면(Dependency Injection), 이 세 함수는 하나로 합쳐질 수 있어." 시니어가 덧붙였다. "패턴을 찾으려면 먼저 **리듬**을 맞춰야 해. 코드를 눈으로 봤을 때 같은 모양이어야, 숨어있는 구조가 드러나거든." |
'''하나의 관찰 (The Pattern):''' 하나는 가끔 걸음을 멈추고 실눈을 떠서 멀리 산맥의 능선과 하늘의 구름을 본다. "구름의 모양이 아까와 달라졌어. 바람의 방향과 습도가 일정한 리듬을 타고 변하고 있네." 하나는 개별적인 나무보다 그것들이 이루는 '''패턴'''을 보았다. "곧 소나기가 올 것 같아. 미리 대피소로 가자." 하나는 사소한 정보들 속에서 반복되는 질서를 포착하여 미래를 예측했다. 패턴을 사냥하는 안목이 위험을 피하고 최선의 길을 찾게 해줌을 하나는 알고 있었다. |
| Line 29: | Line 26: |
| 비슷한 기능을 하는 코드를 여러 번 작성하고 있다. 복사해서 붙여넣기(Copy & Paste)한 후 조금씩 수정한 코드들이 산재해 있다. 중복을 제거하고 싶지만, 서로 미묘하게 달라서 합치기가 까다롭다. | 비슷한 기능을 하는 코드를 여러 번 작성하고 있다. 복사해서 붙여넣기(Copy & Paste)한 후 조금씩 수정한 코드들이 산재해 있다. 중복을 제거하고 싶지만, 서로 미묘하게 달라서 합치기가 까다로운 상황이다. |
| Line 33: | Line 30: |
| * 레거시 코드를 리팩토링해야 하는데 어디서부터 손대야 할지 모르겠다. * 라이브러리나 프레임워크가 제공하는 추상화(Interface, Abstract Class)를 언제 써야 할지 감이 안 온다. |
* 레거시 코드를 리팩토링해야 하는데 어디서부터 손대야 할지 막막하다. * 추상화(Interface, Abstract Class)를 언제 도입해야 할지 감이 안 온다. |
| Line 39: | Line 36: |
| **코드의 '모양'이 다르면 '구조'의 유사성을 보기 어렵다.** 변하는 것과 변하지 않는 것이 뒤섞여 있으면 추상화할 수 없다. | '''코드의 '모양'이 다르면 '구조'의 유사성을 보기 어렵다.''' 변하는 것과 변하지 않는 것이 뒤섞여 있으면 추상화할 수 없습니다. |
| Line 42: | Line 39: |
코드는 텍스트다. 줄바꿈, 변수명, 순서가 다르면 우리 뇌는 "다른 코드"라고 인식한다. * 함수 A는 `user_list`를 쓰고, 함수 B는 `users`를 쓴다면? * 함수 A는 `validate`를 먼저 하고, 함수 B는 `load`를 먼저 한다면? 실제 로직의 구조가 같더라도, 표면적인 차이 때문에 패턴을 놓치게 된다. |
코드는 텍스트입니다. 줄바꿈, 변수명, 순서가 다르면 우리 뇌는 "다른 코드"라고 인식합니다. 실제 로직의 구조가 같더라도, 표면적인 차이 때문에 패턴을 놓치게 됩니다. |
| Line 50: | Line 42: |
대부분의 코드는 **정책(Policy, 변하지 않는 큰 흐름)**과 **세부사항(Detail, 자주 변하는 구체적 구현)**이 섞여 있다. 이 둘을 떼어내지 못하면, 세부사항이 바뀔 때마다 정책 코드도 함께 수정해야 한다. 이는 OCP(Open-Closed Principle) 위반이다. |
대부분의 코드는 정책(Policy, 변하지 않는 흐름)과 세부사항(Detail, 자주 변하는 구현)이 섞여 있습니다. 이 둘을 떼어내지 못하면, 세부사항이 바뀔 때마다 전체 정책 코드가 흔들리게 됩니다. |
| Line 57: | Line 47: |
| **코드를 시각적으로 정렬(Align)하여 패턴을 드러내라.** 그리고 **변하는 부분을 격리(Isolate)**하여 주입하라. | '''코드를 시각적으로 정렬(Align)하여 패턴을 드러내라.''' 그리고 '''변하는 부분을 격리(Isolate)'''하여 주입하라. |
| Line 60: | Line 50: |
추상화를 하기 전에, 먼저 코드를 **똑같은 모양**으로 만들어라. 로직을 바꾸는 게 아니라, 모양을 바꾸는 것이다. 1. **이름 통일:** 서로 다른 함수에서 비슷한 역할을 하는 변수명을 `items`, `processor`, `result` 등으로 통일하라. 2. **순서 조정:** 실행 순서에 영향이 없다면, 코드 블록의 순서를 동일하게 맞춰라. 3. **대칭성 확보:** `if` 블록과 `else` 블록의 무게감을 비슷하게 맞춰라. 코드가 시각적으로 **리듬(Rhythm)**을 타기 시작하면, 반복되는 패턴과 툭 튀어나온 '다른 부분'이 명확히 보인다. 이것이 **Pattern Hunting**의 시작이다. |
추상화를 하기 전에, 먼저 코드를 '''똑같은 모양'''으로 만드십시오. 1. '''이름 통일:''' 비슷한 역할을 하는 변수명을 통일하십시오. 2. '''순서 조정:''' 코드 블록의 배치 순서를 동일하게 맞추십시오. 3. '''대칭성 확보:''' `if`와 `else` 블록의 구조적 무게감을 비슷하게 맞추십시오. 코드가 시각적으로 '''리듬(Rhythm)'''을 타기 시작하면, 반복되는 패턴과 툭 튀어나온 '다른 부분'이 명확히 보입니다. |
| Line 70: | Line 57: |
| 리듬을 통해 발견한 것을 분리하십시오. * '''Invariant (변하지 않는 것):''' 공통 함수나 부모 클래스로 올리십시오. * '''Variant (변하는 것):''' 파라미터나 전략 객체(Strategy)로 분리하여 밖에서 주입하십시오. |
|
| Line 71: | Line 61: |
| 리듬을 통해 발견한 것을 분리하라. * **Invariant (변하지 않는 것):** 공통 함수나 부모 클래스로 올린다. (Template Method) * **Variant (변하는 것):** 파라미터, 전략 객체(Strategy), 혹은 하위 클래스의 메서드로 분리한다. {{{ // Before def process_A(): setup() do_A_thing() // Variant teardown() def process_B(): setup() do_B_thing() // Variant teardown() // After (Separated) def process(strategy): setup() strategy.do_thing() // Injected Variant teardown() }}} === Principle 3: Techniques for Separation === 변하는 부분을 분리하는 구체적인 기법들을 연습해야 한다. 1. **Subclassing (Template Method Pattern):** "전체 틀은 같고, 특정 단계만 다르다." -> 상속을 통해 `abstract method`만 구현. 2. **Dependency Injection (Strategy Pattern):** "행동의 알고리즘만 다르다." -> 인터페이스를 정의하고 구현체를 주입. 3. **Parameterization:** "값만 다르다." -> 함수 파라미터로 추출. 4. **Callback/Lambda:** "실행할 로직 조각만 다르다." -> 함수 자체를 인자로 넘김. === Principle 4: The Squint Test (실눈 뜨기) === 모니터에서 조금 물러나 실눈을 뜨고 코드를 보라. 글자가 안 보이고 **모양(Shape)**과 **색깔(Color)**만 보일 때, 구조적 중복이 더 잘 보인다. 들여쓰기의 깊이, 블록의 길이 등이 패턴을 드러낸다. |
=== Principle 3: The Squint Test (실눈 뜨기) === 모니터에서 조금 물러나 실눈을 뜨고 코드를 보십시오. 글자가 안 보이고 '''모양(Shape)'''과 '''색깔(Color)'''만 보일 때, 구조적 중복이 더 잘 보입니다. 들여쓰기의 깊이와 블록의 길이를 관찰하십시오. |
| Line 112: | Line 68: |
| 데이터를 가져오는 로직(Invariant)과 보여주는 UI(Variant)가 섞인 여러 컴포넌트를 발견하고, 'Container-Presenter' 패턴으로 통합하여 코드 중복을 제거한 사례. | |
| Line 113: | Line 70: |
| **상황:** `UserList` 컴포넌트와 `ProductList` 컴포넌트. * 둘 다 데이터를 가져오고(Loading), 에러를 처리하고(Error), 리스트를 렌더링한다. * **Pattern Hunting:** "데이터를 가져오는 로직"과 "보여주는 UI"가 섞여 있다. * **Solution:** * **Container (Invariant):** 데이터 Fetching, Loading/Error 상태 관리. * **Presentational (Variant):** `props`로 받은 데이터를 그리기만 함. * 이제 `ListContainer` 하나로 두 경우를 모두 처리할 수 있다. === Example 2: Test Code Setup === **상황:** 테스트 코드마다 `setup` 과정이 10줄씩 반복되는데, `user_role`만 다르다. * **Pattern Hunting:** 10줄을 나란히 두니 `role="ADMIN"` 부분만 다르다. * **Solution:** `create_user(role)` 팩토리 메서드를 만든다. 변하는 부분(`role`)만 인자로 받는다. === Example 3: Payment Gateway === **상황:** PayPal 결제와 Stripe 결제 코드가 섞여 있다. * **Pattern Hunting:** 둘 다 "요청 -> 승인 -> 기록"의 단계를 거친다. 하지만 API 호출 방식이 다르다. * **Solution:** `PaymentProcessor` 인터페이스(Variant)를 정의하고, 메인 로직(Invariant)은 이 인터페이스에 의존하게 한다(DI). |
=== Example 2: Payment Gateway === 여러 결제 수단(PayPal, Stripe 등)의 코드를 나란히 두고 정렬한 결과, "요청 -> 승인 -> 기록"이라는 공통 리듬을 발견하고 인터페이스로 추상화한 사례. |
| Line 137: | Line 77: |
| 코드가 "비슷해 보인다"는 이유만으로 합치려 한다. 하지만 그 둘이 **변경되는 이유(Reason to Change)**가 다르면 합치면 안 된다. * **Rule of Three:** 3번 반복될 때까지는 기다려라. 2번은 우연일 수 있다. |
단순히 비슷해 보인다는 이유로 합치지 마십시오. '''Rule of Three'''를 기억하십시오. 같은 패턴이 3번 이상 반복될 때 비로소 추상화를 고민하십시오. |
| Line 141: | Line 80: |
| "로직만 맞으면 되지."라며 들여쓰기나 변수명을 엉망으로 둔다. * 시각적 무질서는 패턴 인식을 방해한다. **정리 정돈(Arrange)**이 패턴 발견의 전제 조건이다. === "Over-Engineering" === 단순한 `if`문 하나로 해결될 것을 굳이 복잡한 디자인 패턴(Strategy Factory 등)으로 만든다. * **[[SimpleFirst]]**: 패턴은 복잡성을 줄이기 위해 쓰는 것이다. 복잡성을 늘린다면 잘못 쓰고 있는 것이다. |
시각적 무질서는 패턴 인식을 방해합니다. 정리 정돈(Arrange)이 패턴 발견의 전제 조건입니다. |
| Line 151: | Line 85: |
| '''[[LanguageBuilding]]''' - 발견된 패턴에 이름을 붙이는 순간, 그것은 우리 도메인의 언어가 된다. '''[[ComplexityTaming]]''' - 변하는 것과 변하지 않는 것을 분리하는 것은 복잡성을 격리하는 핵심 기술이다. '''[[CognitiveMicroscope]]''' - 코드를 관찰하고 구조를 꿰뚫어 보는 힘은 현미경에서 나온다. '''[[NamesAsDesign]]''' - 코드를 정렬할 때 가장 중요한 것은 '이름 맞추기'다. 이름이 같아야 패턴이 보인다. '''[[ArtisanMind]]''' - 코드를 예쁘게 정렬하고 리듬을 맞추는 행위는 장인의 정리 정돈과 같다. '''[[DataAsFoundation]]''' - 때로는 로직의 패턴보다 데이터 구조의 패턴을 찾는 것이 더 빠르다. |
* '''[[LanguageBuilding]]''' - 발견된 패턴에 이름을 붙이는 순간, 그것은 도메인 언어가 됩니다. ''발전'' * '''[[ComplexityTaming]]''' - 변하는 것과 변하지 않는 것을 분리하여 복잡성을 격리합니다. ''적용'' * '''[[CognitiveMicroscope]]''' - 코드를 관찰하고 구조를 꿰뚫어 보는 현미경의 힘이 필요합니다. ''도구'' * '''[[NamesAsDesign]]''' - 코드를 정렬할 때 가장 중요한 것은 '이름 맞추기'입니다. ''기반'' |
| Line 166: | Line 92: |
| PatternHunting이 잘 되고 있다는 신호: * **코드가 '리듬'을 탄다:** 읽을 때 예측 가능하다. * **수정이 쉽다:** 기능을 바꿀 때 "여기만 고치면 돼"라고 명확히 지적할 수 있다. * **삭제가 즐겁다:** 중복 코드를 지우고 하나의 우아한 구조로 통합할 때 쾌감을 느낀다. * **새 기능 추가가 빠르다:** 기존의 '틀(Invariant)'에 새로운 '조각(Variant)'만 끼워 넣으면 된다. |
* 코드가 '리듬'을 타서 읽을 때 다음 내용이 예측 가능하다. * 기능을 수정할 때 "여기만 고치면 돼"라고 명확히 지적할 수 있다. * 중복 코드를 지우고 하나의 우아한 구조로 통합할 때 쾌감을 느낀다. * 새로운 기능을 추가할 때 기존의 '틀'에 새로운 '조각'만 끼워 넣으면 된다. |
| Line 176: | Line 99: |
| 프로그래밍은 **차이점(Difference)을 관리하는 예술**이다. | '''프로그래밍은 차이점(Difference)을 관리하는 예술이다.''' |
| Line 178: | Line 101: |
| 모든 코드는 어딘가 비슷하고 어딘가 다르다. * 비슷한 것을 묶으면 **추상화(Abstraction)**가 되고, * 다른 것을 분리하면 **모듈화(Modularization)**가 된다. 어지러운 코드 속에서 **숨은 그림 찾기**를 하듯 패턴을 찾아보라. 줄을 맞추고, 이름을 다듬고, 나란히 놓아보라. 그러면 코드가 스스로 말할 것이다. "나는 사실 이것과 같은 코드야." |
어지러운 코드 속에서 숨은 그림 찾기를 하듯 패턴을 찾아보십시오. 줄을 맞추고, 이름을 다듬고, 나란히 놓아보십시오. 그러면 코드가 스스로 말할 것입니다. "나는 사실 이것과 같은 코드야." |
PatternHunting
주니어 개발자들을 위한 패턴 언어 - 변하는 것과 변하지 않는 것을 구분하고 리듬을 찾아내는 기술
Contents
The Story 1: The Rhythm of Code (Programming)
한 주니어가 세 가지 리포트(PDF, CSV, HTML)를 생성하는 코드를 작성했다. 세 개의 함수는 각각 50줄 정도였고, 서로 미묘하게 달랐다. 시니어가 다가와 코드를 '기계적'으로 수정하기 시작했다. 로직은 건드리지 않고, 변수 이름을 통일하고, 들여쓰기를 맞추고, 코드의 순서를 재배치했다. "자, 이렇게 줄을 맞춰(Align) 보니까 어때?" 주니어는 놀랐다. 세 함수를 나란히 놓으니, 90%의 코드가 토씨 하나 안 틀리고 똑같았다. "전체 흐름은 변하지 않는 것(Invariant)이고, 포맷팅만 변하는 것(Variant)이야. 패턴을 찾으려면 먼저 리듬을 맞춰야 해."
The Story 2: The Shape of Clouds (Ordinary Life)
민수와 하나는 함께 등산을 하고 있다.
민수의 관찰 (The Detail): 민수는 발밑의 돌부리와 주변의 나무들에 집중한다. "이 나무는 소나무고, 저 꽃은 진달래네." 민수는 눈앞의 사물 하나하나를 개별적으로 관찰하지만, 전체적인 숲의 분위기나 날씨의 변화는 읽어내지 못한다. 갑자기 비가 쏟아지자 민수는 당황했다.
하나의 관찰 (The Pattern): 하나는 가끔 걸음을 멈추고 실눈을 떠서 멀리 산맥의 능선과 하늘의 구름을 본다. "구름의 모양이 아까와 달라졌어. 바람의 방향과 습도가 일정한 리듬을 타고 변하고 있네." 하나는 개별적인 나무보다 그것들이 이루는 패턴을 보았다. "곧 소나기가 올 것 같아. 미리 대피소로 가자." 하나는 사소한 정보들 속에서 반복되는 질서를 포착하여 미래를 예측했다. 패턴을 사냥하는 안목이 위험을 피하고 최선의 길을 찾게 해줌을 하나는 알고 있었다.
Context
비슷한 기능을 하는 코드를 여러 번 작성하고 있다. 복사해서 붙여넣기(Copy & Paste)한 후 조금씩 수정한 코드들이 산재해 있다. 중복을 제거하고 싶지만, 서로 미묘하게 달라서 합치기가 까다로운 상황이다.
일상적인 상황:
"이거랑 저거랑 비슷한데, 합치자니 if문이 너무 많아질 것 같아."
- 레거시 코드를 리팩토링해야 하는데 어디서부터 손대야 할지 막막하다.
- 추상화(Interface, Abstract Class)를 언제 도입해야 할지 감이 안 온다.
Problem
코드의 '모양'이 다르면 '구조'의 유사성을 보기 어렵다. 변하는 것과 변하지 않는 것이 뒤섞여 있으면 추상화할 수 없습니다.
The Hidden Structure
코드는 텍스트입니다. 줄바꿈, 변수명, 순서가 다르면 우리 뇌는 "다른 코드"라고 인식합니다. 실제 로직의 구조가 같더라도, 표면적인 차이 때문에 패턴을 놓치게 됩니다.
The Mix of Contexts
대부분의 코드는 정책(Policy, 변하지 않는 흐름)과 세부사항(Detail, 자주 변하는 구현)이 섞여 있습니다. 이 둘을 떼어내지 못하면, 세부사항이 바뀔 때마다 전체 정책 코드가 흔들리게 됩니다.
Solution
코드를 시각적으로 정렬(Align)하여 패턴을 드러내라. 그리고 변하는 부분을 격리(Isolate)하여 주입하라.
Principle 1: Align to Reveal (시각적 리팩토링)
추상화를 하기 전에, 먼저 코드를 똑같은 모양으로 만드십시오.
이름 통일: 비슷한 역할을 하는 변수명을 통일하십시오.
순서 조정: 코드 블록의 배치 순서를 동일하게 맞추십시오.
대칭성 확보: if와 else 블록의 구조적 무게감을 비슷하게 맞추십시오.
코드가 시각적으로 리듬(Rhythm)을 타기 시작하면, 반복되는 패턴과 툭 튀어나온 '다른 부분'이 명확히 보입니다.
Principle 2: Separate the Variant from the Invariant
리듬을 통해 발견한 것을 분리하십시오.
Invariant (변하지 않는 것): 공통 함수나 부모 클래스로 올리십시오.
Variant (변하는 것): 파라미터나 전략 객체(Strategy)로 분리하여 밖에서 주입하십시오.
Principle 3: The Squint Test (실눈 뜨기)
모니터에서 조금 물러나 실눈을 뜨고 코드를 보십시오. 글자가 안 보이고 모양(Shape)과 색깔(Color)만 보일 때, 구조적 중복이 더 잘 보입니다. 들여쓰기의 깊이와 블록의 길이를 관찰하십시오.
Real Examples
Example 1: React Components
데이터를 가져오는 로직(Invariant)과 보여주는 UI(Variant)가 섞인 여러 컴포넌트를 발견하고, 'Container-Presenter' 패턴으로 통합하여 코드 중복을 제거한 사례.
Example 2: Payment Gateway
여러 결제 수단(PayPal, Stripe 등)의 코드를 나란히 두고 정렬한 결과, "요청 -> 승인 -> 기록"이라는 공통 리듬을 발견하고 인터페이스로 추상화한 사례.
Common Pitfalls
"Premature Abstraction" (설익은 추상화)
단순히 비슷해 보인다는 이유로 합치지 마십시오. Rule of Three를 기억하십시오. 같은 패턴이 3번 이상 반복될 때 비로소 추상화를 고민하십시오.
"Ignoring the Visuals"
시각적 무질서는 패턴 인식을 방해합니다. 정리 정돈(Arrange)이 패턴 발견의 전제 조건입니다.
Connection to Other Patterns
LanguageBuilding - 발견된 패턴에 이름을 붙이는 순간, 그것은 도메인 언어가 됩니다. 발전
ComplexityTaming - 변하는 것과 변하지 않는 것을 분리하여 복잡성을 격리합니다. 적용
CognitiveMicroscope - 코드를 관찰하고 구조를 꿰뚫어 보는 현미경의 힘이 필요합니다. 도구
NamesAsDesign - 코드를 정렬할 때 가장 중요한 것은 '이름 맞추기'입니다. 기반
Signs of Success
- 코드가 '리듬'을 타서 읽을 때 다음 내용이 예측 가능하다.
- 기능을 수정할 때 "여기만 고치면 돼"라고 명확히 지적할 수 있다.
- 중복 코드를 지우고 하나의 우아한 구조로 통합할 때 쾌감을 느낀다.
- 새로운 기능을 추가할 때 기존의 '틀'에 새로운 '조각'만 끼워 넣으면 된다.
The Ultimate Insight
프로그래밍은 차이점(Difference)을 관리하는 예술이다.
어지러운 코드 속에서 숨은 그림 찾기를 하듯 패턴을 찾아보십시오. 줄을 맞추고, 이름을 다듬고, 나란히 놓아보십시오. 그러면 코드가 스스로 말할 것입니다. "나는 사실 이것과 같은 코드야."
CategoryPatternLanguage CategoryProgramming CategoryRefactoring CategoryDesign
