PatternHunting
주니어 개발자들을 위한 패턴 언어 - 변하는 것과 변하지 않는 것을 구분하고 리듬을 찾아내는 기술
Contents
The Story: The Rhythm of Code
한 주니어가 세 가지 리포트(PDF, CSV, HTML)를 생성하는 코드를 작성했다. 세 개의 함수는 각각 50줄 정도였고, 서로 미묘하게 달랐다.
시니어가 코드를 훑어보더니 말했다. "이 세 함수, 사실상 같은 코드네."
"아니에요," 주니어가 반박했다. "PDF는 헤더를 그리는 방식이 다르고, CSV는 콤마를 찍어야 하고, HTML은 태그가 들어가요. 로직이 완전히 달라요."
시니오는 키보드를 잡고 '기계적'으로 코드를 수정하기 시작했다. 로직은 건드리지 않고, 변수 이름을 통일하고, 들여쓰기를 맞추고, 코드의 순서를 재배치했다.
"자, 이렇게 줄을 맞춰(Align) 보니까 어때?"
주니어는 놀랐다. 세 함수를 나란히 놓으니, 90%의 코드가 토씨 하나 안 틀리고 똑같았다. 오직 데이터를 포맷팅하는 그 '한 줄'만 달랐다.
"보이지? 전체 흐름(Loop, 데이터 조회, 에러 처리)은 변하지 않는 것(Invariant)이고, 포맷팅만 변하는 것(Variant)이야. 이제 이 '변하는 부분'만 쏙 빼내서 파라미터로 넘기면(Dependency Injection), 이 세 함수는 하나로 합쳐질 수 있어."
시니어가 덧붙였다. "패턴을 찾으려면 먼저 리듬을 맞춰야 해. 코드를 눈으로 봤을 때 같은 모양이어야, 숨어있는 구조가 드러나거든."
Context
비슷한 기능을 하는 코드를 여러 번 작성하고 있다. 복사해서 붙여넣기(Copy & Paste)한 후 조금씩 수정한 코드들이 산재해 있다. 중복을 제거하고 싶지만, 서로 미묘하게 달라서 합치기가 까다롭다.
일상적인 상황:
"이거랑 저거랑 비슷한데, 합치자니 if문이 너무 많아질 것 같아."
- 레거시 코드를 리팩토링해야 하는데 어디서부터 손대야 할지 모르겠다.
- 라이브러리나 프레임워크가 제공하는 추상화(Interface, Abstract Class)를 언제 써야 할지 감이 안 온다.
Problem
코드의 '모양'이 다르면 '구조'의 유사성을 보기 어렵다. 변하는 것과 변하지 않는 것이 뒤섞여 있으면 추상화할 수 없다.
The Hidden Structure
코드는 텍스트다. 줄바꿈, 변수명, 순서가 다르면 우리 뇌는 "다른 코드"라고 인식한다. * 함수 A는 user_list를 쓰고, 함수 B는 users를 쓴다면? * 함수 A는 validate를 먼저 하고, 함수 B는 load를 먼저 한다면?
실제 로직의 구조가 같더라도, 표면적인 차이 때문에 패턴을 놓치게 된다.
The Mix of Contexts
대부분의 코드는 정책(Policy, 변하지 않는 큰 흐름)과 세부사항(Detail, 자주 변하는 구체적 구현)이 섞여 있다. 이 둘을 떼어내지 못하면, 세부사항이 바뀔 때마다 정책 코드도 함께 수정해야 한다. 이는 OCP(Open-Closed Principle) 위반이다.
Solution
코드를 시각적으로 정렬(Align)하여 패턴을 드러내라. 그리고 변하는 부분을 격리(Isolate)하여 주입하라.
Principle 1: Align to Reveal (시각적 리팩토링)
추상화를 하기 전에, 먼저 코드를 똑같은 모양으로 만들어라. 로직을 바꾸는 게 아니라, 모양을 바꾸는 것이다.
1. 이름 통일: 서로 다른 함수에서 비슷한 역할을 하는 변수명을 items, processor, result 등으로 통일하라. 2. 순서 조정: 실행 순서에 영향이 없다면, 코드 블록의 순서를 동일하게 맞춰라. 3. 대칭성 확보: if 블록과 else 블록의 무게감을 비슷하게 맞춰라.
코드가 시각적으로 리듬(Rhythm)을 타기 시작하면, 반복되는 패턴과 툭 튀어나온 '다른 부분'이 명확히 보인다. 이것이 Pattern Hunting의 시작이다.
Principle 2: Separate the Variant from the Invariant
리듬을 통해 발견한 것을 분리하라.
* 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)만 보일 때, 구조적 중복이 더 잘 보인다. 들여쓰기의 깊이, 블록의 길이 등이 패턴을 드러낸다.
Real Examples
Example 1: React Components
상황: 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).
Common Pitfalls
"Premature Abstraction" (설익은 추상화)
코드가 "비슷해 보인다"는 이유만으로 합치려 한다. 하지만 그 둘이 변경되는 이유(Reason to Change)가 다르면 합치면 안 된다. * Rule of Three: 3번 반복될 때까지는 기다려라. 2번은 우연일 수 있다.
"Ignoring the Visuals"
"로직만 맞으면 되지."라며 들여쓰기나 변수명을 엉망으로 둔다. * 시각적 무질서는 패턴 인식을 방해한다. 정리 정돈(Arrange)이 패턴 발견의 전제 조건이다.
"Over-Engineering"
단순한 if문 하나로 해결될 것을 굳이 복잡한 디자인 패턴(Strategy Factory 등)으로 만든다. * SimpleFirst: 패턴은 복잡성을 줄이기 위해 쓰는 것이다. 복잡성을 늘린다면 잘못 쓰고 있는 것이다.
Connection to Other Patterns
LanguageBuilding - 발견된 패턴에 이름을 붙이는 순간, 그것은 우리 도메인의 언어가 된다.
ComplexityTaming - 변하는 것과 변하지 않는 것을 분리하는 것은 복잡성을 격리하는 핵심 기술이다.
CognitiveMicroscope - 코드를 관찰하고 구조를 꿰뚫어 보는 힘은 현미경에서 나온다.
NamesAsDesign - 코드를 정렬할 때 가장 중요한 것은 '이름 맞추기'다. 이름이 같아야 패턴이 보인다.
ArtisanMind - 코드를 예쁘게 정렬하고 리듬을 맞추는 행위는 장인의 정리 정돈과 같다.
DataAsFoundation - 때로는 로직의 패턴보다 데이터 구조의 패턴을 찾는 것이 더 빠르다.
Signs of Success
PatternHunting이 잘 되고 있다는 신호:
코드가 '리듬'을 탄다: 읽을 때 예측 가능하다.
수정이 쉽다: 기능을 바꿀 때 "여기만 고치면 돼"라고 명확히 지적할 수 있다.
삭제가 즐겁다: 중복 코드를 지우고 하나의 우아한 구조로 통합할 때 쾌감을 느낀다.
새 기능 추가가 빠르다: 기존의 '틀(Invariant)'에 새로운 '조각(Variant)'만 끼워 넣으면 된다.
The Ultimate Insight
프로그래밍은 차이점(Difference)을 관리하는 예술이다.
모든 코드는 어딘가 비슷하고 어딘가 다르다. * 비슷한 것을 묶으면 추상화(Abstraction)가 되고, * 다른 것을 분리하면 모듈화(Modularization)가 된다.
어지러운 코드 속에서 숨은 그림 찾기를 하듯 패턴을 찾아보라. 줄을 맞추고, 이름을 다듬고, 나란히 놓아보라. 그러면 코드가 스스로 말할 것이다. "나는 사실 이것과 같은 코드야."
CategoryPatternLanguage CategoryProgramming CategoryRefactoring CategoryDesign
