Differences between revisions 3 and 4
Revision 3 as of 2025-12-30 07:49:53
Size: 23637
Editor: 정수
Comment:
Revision 4 as of 2025-12-30 09:01:46
Size: 7662
Editor: 정수
Comment:
Deletions are marked like this. Additions are marked like this.
Line 1: Line 1:
Retrieved content for LanguageBuilding
Line 9: Line 8:
== The Story: The Evolving Vocabulary == == The Story 1: The Evolving Vocabulary (Programming) ==
Line 11: Line 10:
한 주니어 개발자가 전자상거래 시스템의 주문 처리 코드를 작성했다. 함수 이름은 `processOrder()`였고, 내부는 if-else로 가득했다. 두 명의 개발자, 민수와 하나가 '주문 처리 시스템'의 코드를 작성하고 있다.
Line 13: Line 12:
시니어가 코드를 보며 물었다. "이 함수가 정확히 뭘 하지?" '''민수의 코드 (The Raw Primitives):'''
민수의 코드는 `if-else`와 원시 데이터 연산으로 가득하다.
"주문을 처리해요. 재고를 확인하고, 결제를 하고, 배송을 준비하고..."
하지만 민수의 코드를 읽으면 그 의도가 한눈에 들어오지 않는다. `for` 문과 배열 조작 로직 속에 비즈니스의 의미가 묻혀 있기 때문이다. 코드는 기계의 언어로 쓰여 있다.
Line 15: Line 17:
"주문을 처리해요. 재고를 확인하고, 결제를 하고, 배송을 준비하고..."

"그런데," 시니어가 화면을 가리키며 말했다. "이 코드를 읽으면 그게 보이나? 아니면 if문과 for문만 보이나?"

주니어는 잠시 생각했다. "코드는... 복잡해 보이네요."

시니어가 다른 파일을 열었다:
'''하나의 코드 (The Domain Story):'''
하나는 다르게 시작했다. 그녀는 먼저 비즈니스의 개념을 함수와 메서드로 정의했다.
Line 30: Line 26:
"우리가 새로운 단어를 만든 거예요. 문제 영역의 언어를 만드는 거죠."
하나는 Lisp 프로그래머들의 전통을 따랐다. "프로그램을 언어를 향해 작성하는 동시에 언어를 프로그램을 향해 만든다." 하나의 코드는 전문화된 어휘로 잘 쓰인 산문처럼 읽혔다.
Line 31: Line 29:
"같은 기능이야. 하지만 이건 '''이야기'''처럼 읽혀. 주문이 무엇을 하는지가 바로 보여."
Line 33: Line 30:
"하지만 이 메서드들은 어디서...?" == The Story 2: The Barista's Tongue (Ordinary Life) ==
Line 35: Line 32:
"우리가 만든 거야. 프로그래밍은 단순히 기존 함수를 호출하는 게 아니라, '''새로운 단어를 만들어내는 것'''이기도 해. 문제 영역의 언어를 만드는 거지." 민수와 하나는 커피 전문점에서 바리스타 교육을 받고 있다.
Line 37: Line 34:
주니어의 눈이 커졌다. "언어를... 만든다고요?" '''민수의 표현 (The General Language):'''
민수는 커피 맛을 아주 단순하게만 표현한다. "이 커피는 쓰고 좀 시네요. 뜨거워요." 민수의 언어로는 이 원두와 저 원두의 미묘한 차이를 동료에게 설명하거나 더 나은 맛을 추출하기 위한 구체적인 전략을 세우기 어렵다. 그의 언어는 너무 일반적이다.
Line 39: Line 37:
"그래. Lisp 프로그래머들이 오래전부터 해온 방식이야. Paul Graham이 말했지: '프로그램을 언어를 향해 작성하는 동시에 언어를 프로그램을 향해 만든다.' 좋은 프로그램은 문제를 표현하는 언어를 먼저 만들고, 그 언어로 해결책을 쓰는 거야." '''하나의 표현 (The Specialized Vocabulary):'''
하나는 바리스타들만의 전문 용어를 익혔다. "이 원두는 '브루잉' 과정에서 '바디감'이 묵직하게 살아나네요. '산미'는 오렌지 계열이고요." 하나는 '추출 수율', '로스팅 포인트' 같은 단어들을 사용하여 맛의 세계를 정교하게 정의했다. 이 단어들은 하나가 커피라는 복잡한 문제를 다루는 '이론'이 되었고, 동료들과 한 치의 오차도 없이 소통하게 해주었다. 전문적인 어휘가 전문적인 기예를 가능하게 함을 하나는 알고 있었다.
Line 48: Line 47:
일상적인 상황:
 * 코드가 "어떻게" 하는지만 보이고 "무엇을" 하는지 불명확하다
 * 같은 개념을 여러 곳에서 다르게 표현하고 있다
 * 도메인 전문가와 프로그래머 사이의 대화가 코드에 반영되지 않는다
 * 새로운 팀원이 코드를 이해하는 데 오랜 시간이 걸린다
Line 57: Line 50:
'''범용 언어의 원시 요소만으로는 문제 영역을 자연스럽게 표현하기 어렵다.''' Python, Java, JavaScript - 이들은 강력하고 범용적이다. 하지만 이 언어들은 '''모든 문제'''를 해결하도록 설계되었기에, '''특정 문제'''를 표현하기에는 너무 일반적이다. '''범용 언어의 원시 요소만으로는 문제 영역을 자연스럽게 표현하기 어렵다.'''
Line 59: Line 52:
=== The Generic Code Problem ===

일반적인 프로그래밍 구조만 사용하면:

{{{
def process_data(items):
    result = []
    for item in items:
        if item['status'] == 'pending' and item['date'] < today():
            if item['priority'] > 5:
                result.append({
                    'id': item['id'],
                    'name': item['name'],
                    'urgent': True
                })
    return result
}}}

이 코드가 무엇을 하는가? 코드를 '''전부 읽어야만''' 안다.

'''문제:''' 비즈니스 로직이 구현 세부사항 속에 묻혀있다. `for`, `if`, `append` - 이것들은 '''프로그래밍 언어의 단어'''지, '''문제 영역의 단어'''가 아니다.

=== The Vocabulary Gap ===

도메인 전문가가 이야기하는 방식:
{{{
"우리는 긴급한 초과 작업들을 먼저 처리해야 합니다."
}}}

코드가 표현하는 방식:
{{{
for item in items:
    if item['status'] == 'pending' and item['date'] < today():
        if item['priority'] > 5:
            ...
}}}

'''간극이 보이는가?''' 대화에서 사용하는 단어들(긴급한, 초과, 작업)이 코드에 없다. 코드는 다른 언어로 쓰여있다.

이로 인해:
 * '''의사소통 비용''' - 도메인 지식이 코드로 번역되는 과정에서 왜곡된다
 * '''이해 비용''' - 새로운 개발자가 코드를 읽고 비즈니스 로직을 이해하기 어렵다
 * '''변경 비용''' - 요구사항이 바뀌면 코드 전체를 뒤져야 한다
 * '''일관성 부족''' - 같은 개념을 여러 방식으로 표현한다

=== The Language Construction Barrier ===

"새로운 언어를 만든다고? 그건 너무 큰 일 아닌가?"

이것이 많은 프로그래머들이 가진 오해다. 언어를 만드는 것은:
 * 컴파일러를 작성하는 것이 아니다
 * 새로운 문법을 만드는 것이 아니다
 * 거창한 프레임워크를 구축하는 것이 아니다

언어를 만드는 것은 단순히 '''문제 영역의 개념에 이름을 붙이고, 그것들을 조합할 수 있게 만드는 것'''이다.
 * Python, Java, JavaScript 등은 모든 문제를 해결하기 위해 설계되었기에, '특정 문제'를 표현하기에는 너무 일반적입니다.
 * 비즈니스 로직이 구현 세부사항(for, if, append) 속에 묻히면 의도가 사라집니다.
 * 도메인 전문가와 개발자 사이의 어휘가 다르면 의사소통 비용이 기하급수적으로 증가합니다.
Line 118: Line 58:
'''프로그래밍을 "문제를 표현하는 언어를 만드는 과정"으로 접근하라.''' Lisp 프로그래머들이 수십 년간 해온 방식다: 먼저 문제를 잘 표현할 수 있는 언어를 만들고, 그 언어로 해결책을 작성다. '''프로그래밍을 "문제를 표현하는 언어를 만드는 과정"으로 접근하라.'''

Lisp 프로그래머들이 수십 년간 해온 방식입니다: 먼저 문제를 잘 표현할 수 있는 언어를 만들고, 그 언어로 해결책을 작성하는 것입니다.
Line 121: Line 63:

Peter Naur는 1985년 논문 "Programming as Theory Building"에서 핵심을 짚었다: '''프로그래밍은 단순히 코드를 작성하는 것이 아니라, 문제 영역에 대한 이론(theory)을 구축하는 것이다.'''

이론은:
* 문제 영역의 개념들을 이해하는 것
 * 개념들 간의 관계를 파악하는 것
 * 그것을 표현할 수 있는
어휘를 만드는

코드는 단지 이
이론의 '''구현'''일 뿐이다. 진짜 가치는 '''이론'''에 있다.
Peter Naur는 "프로그래밍은 단순히 코드를 작성하는 것이 아니라, 문제 영역에 대한 이론(Theory)을 구축하는 것"라고 했습니다. 코드는 그 이론 구현일 뿐이며, 진짜 가치는 문제 이해하 어휘를 만드는 이론에 있습니다.
Line 132: Line 66:

Lisp 전통에서 배우는 것: '''아래에서 위로 언어를 만들어 올라간다.'''

{{{
# Level 0: 원시 연산
items.filter(...).map(...)

# Level 1: 도메인 개념
def is_overdue(task):
    return task.status == 'pending' and task.date < today()

def is_urgent(task):
    return task.priority > 5

# Level 2: 도메인 언어
def urgent_overdue_tasks(tasks):
    return tasks.filter(is_overdue).filter(is_urgent)

# Level 3: 문제 해결
priority_list = urgent_overdue_tasks(all_tasks)
}}}

각 레벨은 이전 레벨 위에 구축된다. 점진적으로 '''문제 영역의 추상화'''를 만들어간다.

Paul Graham이 표현했듯: "좋은 Lisp 프로그래머는 프로그램을 언어를 향해 작성하는 동시에 언어를 프로그램을 향해 만들어간다."
아래에서 위로 언어를 만들어 올라가십시오.
 1. Level 0: 원시 연산 (filter, map)
 2. Level 1: 도메인 개념 정의 (`is_overdue`, `is_urgent`)
 3. Level 2: 도메인 언어 합성 (`urgent_overdue_tasks`)
 4. Level 3: 문제 해결
Line 159: Line 73:
문제 영역의 단어를 함수로 만드십시오. 코드가 도메인 전문가가 이해할 수 있는 단어들로 구성될 때 가독성과 유지보수성이 극대화됩니다.
Line 160: Line 75:
문제 영역의 단어를 함수로 만들어라:

'''Before: 일반적 프로그래밍 용어'''
{{{
def process_data(items):
    result = []
    for item in items:
        if item['status'] == 'pending' and item['date'] < today():
            result.append(item)
    return result
}}}

'''After: 도메인 어휘'''
{{{
def overdue_tasks(tasks):
    return tasks.that_are(pending).and_past_due()

# 또는 더 간단히
def overdue_tasks(tasks):
    return [t for t in tasks if t.is_pending() and t.is_past_due()]
}}}

차이점: 두 번째는 '''도메인 전문가가 이해할 수 있는 단어'''로 쓰여있다.

=== Principle 4: Fluent Interfaces (Method Chaining) ===

연결 가능한 메서드로 문장처럼 읽히는 코드를 만들어라:

{{{
# 명령형 스타일
order = Order()
order.set_customer(customer)
order.add_item("Book", 2)
order.set_shipping("Express")
total = order.calculate_total()

# Fluent 인터페이스 - 문장처럼
total = Order()
    .for_customer(customer)
    .add_item("Book", quantity=2)
    .with_shipping("Express")
    .calculate_total()
}}}

두 번째는 '''읽는 것만으로 무엇을 하는지''' 알 수 있다.

=== Principle 5: Two Languages - Problem and Solution ===

'''[[TwoWorlds]]'''를 기억하라. 두 종류의 언어가 필요하다:

'''문제 공간의 언어''' - 도메인 개념
{{{
order.validate_inventory()
order.process_payment()
order.prepare_shipment()
}}}

'''해결 공간의 언어''' - 기술적 추상화
{{{
repository.save(order)
cache.invalidate(order.id)
queue.publish(OrderCreatedEvent(order))
}}}

둘 다 필요하지만, '''명확히 분리'''되어야 한다.

=== Principle 6: Representation as Language ===

'''[[DataAsFoundation]]'''에서 보았듯이, 데이터 구조도 언어다.

Peter Norvig와 Paul Graham이 강조한 것: '''표현 언어(representation language)의 선택이 문제 해결의 절반이다.'''

같은 개념, 다른 표현:

{{{
# XML - 계층적이고 명시적
<order>
  <customer>John</customer>
  <items>
    <item quantity="2">Book</item>
  </items>
</order>

# S-expression - 간결하고 프로그래밍 가능
(order
  (customer "John")
  (items (item "Book" :quantity 2)))

# Method chain DSL - 유창한 인터페이스
order()
  .for_customer("John")
  .add_item("Book", quantity=2)

# JSON - 데이터 교환
{
  "customer": "John",
  "items": [{"name": "Book", "quantity": 2}]
}
}}}

각각은 서로 다른 '''언어'''다. 어떤 언어로 문제를 표현하느냐가 해결의 명확성을 결정한다.

=== Principle 7: Grow the Language Organically ===

처음부터 완벽한 언어를 만들려 하지 마라. '''[[OrganicGrowth]]''' 방식:

'''1단계: 작동하는 코드 먼저'''
{{{
# 먼저 직접 작성
if task.status == 'pending' and task.date < today():
    process(task)
}}}

'''2단계: 패턴을 발견하면 이름 붙이기'''
{{{
# 같은 패턴이 여러 번 보이면
def is_overdue(task):
    return task.status == 'pending' and task.date < today()

if is_overdue(task):
    process(task)
}}}

'''3단계: 조합 가능하게 만들기'''
{{{
# 다른 개념과 조합
overdue_and_urgent = tasks.filter(is_overdue).filter(is_urgent)
}}}

'''4단계: 언어로 진화'''
{{{
# 이제 이것은 "언어"다
priority_tasks = tasks
    .that_are(overdue)
    .and_are(urgent)
    .sorted_by(priority)
    .take(10)
}}}

언어는 '''사용하면서 발견'''된다. 미리 설계하지 마라.
=== Principle 4: Two Languages - Problem and Solution ===
[[TwoWorlds]]를 기억하십시오. 문제 공간의 언어(도메인 개념)와 해결 공간의 언어(기술적 추상화)는 명확히 분리되되, 각각 풍부하게 구축되어야 합니다.
Line 305: Line 82:
RSpec은 테스트를 "User는... correct password로 인증한다"는 명세(Specification)로 읽히게 만들었습니다. 이것은 테스트를 쓰는 새로운 언어입니다.
Line 306: Line 84:
'''Before: 일반적인 테스트'''
{{{
def test_user_authentication():
    user = User("john@example.com", "password123")
    assert user.authenticate("password123") == True
    assert user.authenticate("wrong") == False
}}}

'''After: RSpec의 언어'''
{{{
describe User do
  it "authenticates with correct password" do
    user = User.new("john@example.com", "password123")
    expect(user.authenticate("password123")).to be_true
  end

  it "rejects incorrect password" do
    user = User.new("john@example.com", "password123")
    expect(user.authenticate("wrong")).to be_false
  end
end
}}}

RSpec은 테스트를 '''명세(specification)'''로 읽히게 만들었다: "User는... correct password로 인증한다."

이것은 '''새로운 언어'''다. 테스트가 아니라 '''명세를 쓰는 언어'''.

=== Example 2: Build DSL - Rake vs Make ===

'''Makefile - Shell 명령 나열'''
{{{
output.txt: input.txt
 cat input.txt | sort | uniq > output.txt
}}}

'''Rakefile - Ruby의 풍부한 언어'''
{{{
file "output.txt" => "input.txt" do
  lines = File.readlines("input.txt")
  unique_lines = lines.sort.uniq
  File.write("output.txt", unique_lines.join)
end
}}}

Rake는 빌드를 '''Ruby 언어로 표현'''할 수 있게 했다. 프로그래밍 언어의 모든 힘을 빌드 스크립트에 가져왔다.

=== Example 3: Rails - Convention over Configuration ===

Rails는 웹 개발을 위한 언어를 만들었다:

{{{
class User < ApplicationRecord
  has_many :posts
  validates :email, presence: true, uniqueness: true
end
}}}

이 짧은 코드가 표현하는 것:
 * User는 데이터베이스 테이블이다
 * User는 여러 Post를 가진다
 * email은 필수이고 유일해야 한다
 * 자동으로 finder 메서드들이 생긴다
 * 자동으로 관계 메서드들이 생긴다

'''선언적 언어'''로 의도를 표현하면, 프레임워크가 "어떻게"를 처리한다.

=== Example 4: Query Languages - 관점의 차이 ===

같은 쿼리, 다른 언어:

{{{
# SQL - 테이블과 조인의 언어
SELECT users.name, COUNT(posts.id) as post_count
FROM users
LEFT JOIN posts ON posts.user_id = users.id
GROUP BY users.id
HAVING post_count > 10

# ActiveRecord - 객체의 언어
User.joins(:posts)
    .group(:id)
    .having('COUNT(posts.id) > 10')
    .select('users.name, COUNT(posts.id) as post_count')

# 더 나아가 - 도메인 언어
User.prolific_writers # has_many :posts, -> { where('posts.count > 10') }
}}}

각 언어는 다른 '''사고 방식'''을 표현한다.

=== Example 5: 실제 경험 - Invoice Generator ===

실제 프로젝트에서의 진화:

'''Version 1: 절차적 코드'''
{{{
def generate_invoice(data):
    pdf = PDF()
    pdf.add_text(data['customer_name'])
    pdf.add_text(data['date'])
    for item in data['items']:
        pdf.add_row(item['name'], item['price'])
    total = sum(item['price'] for item in data['items'])
    pdf.add_text(f"Total: {total}")
    return pdf
}}}

'''Version 2: 송장의 언어'''
{{{
invoice = Invoice()
    .for_customer(customer)
    .dated(today())
    .add_line_item(description="Consulting", amount=1000)
    .add_line_item(description="Development", amount=2000)
    .with_payment_terms("Net 30")
    .with_notes("Thank you for your business")
    .generate_pdf()
}}}

'''차이점:'''
 * 도메인 전문가(회계사)가 읽고 이해할 수 있다
 * 의도가 코드에서 즉시 보인다
 * 새로운 기능 추가가 자연스럽다
 * 테스트하기 쉽다

'''결과:''' 코드가 '''송장을 만드는 언어'''가 되었다.

=== Example 6: Configuration as DSL ===

설정도 언어가 될 수 있다:

'''Before: 중첩된 딕셔너리'''
{{{
config = {
    'database': {
        'host': 'localhost',
        'port': 5432,
        'name': 'mydb'
    },
    'cache': {
        'type': 'redis',
        'ttl': 3600
    }
}
}}}

'''After: Fluent Configuration'''
{{{
config = Configuration()
    .database(host='localhost', port=5432, name='mydb')
    .cache(type='redis', ttl=3600)
    .logging(level='INFO', output='file')
}}}

설정이 '''문장'''처럼 읽힌다.


== The Pattern in Practice ==

=== Start Small: Single Function ===

언어 구축은 단일 함수에서 시작한다:

{{{
# 패턴 발견
if user.role == 'admin' or user.id == resource.owner_id:
    allow_access()

# 이름 붙이기
def can_access(user, resource):
    return user.role == 'admin' or user.id == resource.owner_id

# 사용
if can_access(user, resource):
    allow_access()
}}}

작은 시작이지만, 이미 '''도메인 언어'''가 생기기 시작했다.

=== Compose: Build on Building Blocks ===

작은 조각들을 조합한다:

{{{
def can_edit(user, post):
    return can_access(user, post) and not post.is_locked

def can_delete(user, post):
    return user.role == 'admin' or user.id == post.author_id

# 조합
if can_edit(current_user, post):
    show_edit_button()
}}}

각 함수는 '''단어'''다. 조합하면 '''문장'''이 된다.

=== Abstract: Create Higher-Level Concepts ===

패턴이 보이면 추상화한다:

{{{
class Permission:
    def __init__(self, user, resource):
        self.user = user
        self.resource = resource

    def can_read(self):
        return True # Everyone can read

    def can_edit(self):
        return self.can_access() and not self.resource.is_locked

    def can_delete(self):
        return self.user.is_admin() or self.user.owns(self.resource)

# 사용
permission = Permission(current_user, post)
if permission.can_edit():
    show_edit_button()
}}}

이제 '''권한의 언어'''가 생겼다.

=== Evolve: Let the Language Grow ===

사용하면서 언어를 발전시킨다:

{{{
# 새로운 요구사항: 시간 기반 권한
class Permission:
    def can_edit(self):
        return (self.can_access()
                and not self.resource.is_locked
                and self.within_edit_window())

    def within_edit_window(self):
        return datetime.now() < self.resource.created_at + timedelta(hours=24)
}}}

언어가 '''살아있고 진화'''한다.
=== Example 2: Rails - Convention over Configuration ===
Rails는 웹 개발을 위한 고유한 언어를 만들었습니다. 선언적 언어로 의도를 표현하면, 프레임워크가 복잡한 세부 사항을 처리합니다.
Line 552: Line 91:

언어를 만든다는 말을 듣고, 거대한 프레임워크를 구축하려 한다.

'''문제:''' 문제를 충분히 이해하기 전에 추상화를 만든다. 결과는 사용되지 않는 복잡한 코드.

'''해결:''' '''[[WorkingFirst]]''' - 먼저 작동하는 코드를 만들고, 패턴이 보일 때 추상화하라. 3번 반복되면 추상화하라(Rule of Three).

=== "Every Concept Needs a Class!" ===

모든 도메인 개념을 클래스로 만든다.

{{{
class OverdueChecker:
    def check(self, task):
        return task.status == 'pending' and task.date < today()

class UrgentChecker:
    def check(self, task):
        return task.priority > 5
}}}

'''문제:''' 과도한 추상화. 간단한 것을 복잡하게 만든다.

'''해결:''' 간단한 함수로 충분하다:
{{{
def is_overdue(task):
    return task.status == 'pending' and task.date < today()

def is_urgent(task):
    return task.priority > 5
}}}

'''규칙:''' 함수로 시작하라. 상태가 필요하거나 여러 관련 함수가 생기면 클래스로 만들어라.
문제를 충분히 이해하기 전에 거대한 프레임워크부터 만들려 하지 마십시오. 언어는 필요에 의해 점진적으로 자라나야 합니다([[OrganicGrowth]]).
Line 587: Line 94:

모든 것에 DSL을 만들려 한다.

'''문제:''' 학습 곡선과 유지보수 부담. 팀원들이 프로젝트 고유 언어를 배워야 한다.

'''해결:''' '''The 95% Rule''' - 완벽한 DSL보다 95%의 경우를 커버하는 간단한 추상화가 낫다.

{{{
# ❌ 과도한 DSL
query().select('users').where(field('age').gt(18)).and(field('active').eq(true))

# ✅ 간단한 추상화
users.filter(age__gt=18, active=True)
}}}

=== "Premature Linguistic Abstraction" ===

한두 번 본 패턴을 즉시 추상화한다.

'''문제:''' 실제 패턴인지 확신할 수 없다. 나중에 변경이 어렵다.

'''해결:''' '''Rule of Three''' - 같은 패턴이 3번 나타날 때 추상화하라. 그 전까지는 복사를 허용하라.

=== "Ignoring the Solution Space" ===

문제 공간의 언어만 만들고 기술적 추상화를 무시한다.

'''문제:''' 성능, 보안, 확장성 같은 기술적 관심사가 누락된다.

'''해결:''' '''[[TwoWorlds]]''' - 문제 공간과 해결 공간, 두 언어 모두 필요하다. 명확히 분리하되, 둘 다 만들어라.
모든 것에 고유 언어를 만들면 학습 곡선과 유지보수 부담이 커집니다. [[The95PercentRule]]을 기억하고 적절한 지점에서 멈추십시오.
Line 626: Line 104:
Line 629: Line 106:
LanguageBuilding을 효과적으로 사용하고 있다는 신호:

'''코드가 읽힌다''' - 코드를 읽으면 이야기처럼 흘러간다. "무엇을 하는지"가 즉시 보인다.

'''도메인 대화가 코드에 반영된다''' - 도메인 전문가와의 대화에서 나온 단어들이 코드에 그대로 있다.

'''일관된 어휘''' - 같은 개념을 항상 같은 단어로 표현한다. 코드베이스 전체에 일관성이 있다.

'''자연스러운 확장''' - 새로운 기능을 추가할 때 언어를 확장하는 형태로 자연스럽게 추가된다.

'''자체 문서화''' - 주석이 거의 필요없다. 코드 자체가 무엇을 하는지 말해준다.

'''빠른 온보딩''' - 새로운 팀원이 도메인 언어를 배우면 코드를 빠르게 이해한다.

'''재사용 가능한 조각''' - 작은 함수들을 조합해서 새로운 기능을 만들 수 있다.


== For Teachers and Mentors ==

주니어에게 언어 구축을 가르칠 때:

'''패턴을 가리켜라:'''
"이 코드 블록이 여러 곳에서 반복되지? 이것에 이름을 붙이면 어떨까?"

'''질문하라:'''
 * "이 코드가 무엇을 하는지 한 문장으로 설명할 수 있어?"
 * "그 문장이 코드에 보이나?"
 * "도메인 전문가가 이 코드를 읽고 이해할 수 있을까?"

'''리팩토링을 보여줘라:'''
{{{
# Before
for task in tasks:
    if task.status == 'pending' and task.date < today():
        process(task)

# After
for task in overdue_tasks(tasks):
    process(task)

# 더 나아가
overdue_tasks(tasks).each(process)
}}}

'''Lisp 전통을 소개하라:'''
 * Paul Graham의 "On Lisp"에서 bottom-up 프로그래밍
 * SICP에서 언어 중심 프로그래밍
 * Domain-Specific Languages (Martin Fowler)

'''작게 시작하게 하라:'''
"거대한 프레임워크 말고, 하나의 함수에 이름을 붙이는 것부터 시작해보자."
 * 코드를 읽으면 비즈니스 이야기가 들린다.
 * 도메인 전문가와의 대화가 코드에 그대로 반영된다.
 * 주석이 거의 필요 없는 자체 문서화된 코드가 된다.
 * 새로운 기능을 추가할 때 언어를 확장하는 것처럼 자연스럽게 추가된다.
Line 684: Line 113:
Alan Perlis가 말했다: '''언어는 생각을 형성한다(Language shapes thought).''' '''프로그래밍 = 언어 만들기 + 그 언어로 표현하기'''
Line 686: Line 115:
Peter Naur가 말했다: '''프로그래밍은 프로그래머의 마음속에 이론을 구축하는 것이다.'''

Paul Graham이 말했다: '''좋은 프로그래머는 프로그램을 언어를 향해 작성하는 동시에 언어를 프로그램을 향해 만든다.'''

이 모든 지혜가 같은 진리를 가리킨다:

{{{
프로그래밍 = 언어 만들기 + 그 언어로 표현하기
}}}

범용 프로그래밍 언어는 '''재료'''다. Python, Java, JavaScript - 이들은 도구다. 하지만 진짜 예술은 '''이 재료로 문제 영역의 언어를 만드는 것'''이다.

좋은 프로그램은:
 * 도메인 개념을 명확한 단어로 표현한다
 * 그 단어들을 조합해서 의미 있는 문장을 만든다
 * 문장들이 모여 이야기가 된다
 * 이야기를 읽으면 문제와 해결책이 보인다

'''언어를 만들어라.''' 문제를 잘 표현하는 언어를 만들면, 해결책은 자연스럽게 따라온다. 그리고 그 언어는 팀의 공통어가 되고, 코드는 살아있는 문서가 되며, 프로그래밍은 예술이 된다.

이것이 LanguageBuilding이다. 단순히 코드를 작성하는 것이 아니라, '''문제를 표현할 언어를 만들고, 그 언어로 해결책을 말하는 것'''이다.
범용 프로그래밍 언어는 재료일 뿐입니다. 진짜 예술은 이 재료로 문제 영역의 언어를 만드는 것입니다. 문제를 잘 표현하는 언어를 만들면 해결책은 자연스럽게 따라옵니다.

LanguageBuilding

주니어 개발자들을 위한 패턴 언어 - 문제 영역의 언어를 만들어 프로그래밍하는 방법

The Story 1: The Evolving Vocabulary (Programming)

두 명의 개발자, 민수와 하나가 '주문 처리 시스템'의 코드를 작성하고 있다.

민수의 코드 (The Raw Primitives): 민수의 코드는 if-else와 원시 데이터 연산으로 가득하다. "주문을 처리해요. 재고를 확인하고, 결제를 하고, 배송을 준비하고..." 하지만 민수의 코드를 읽으면 그 의도가 한눈에 들어오지 않는다. for 문과 배열 조작 로직 속에 비즈니스의 의미가 묻혀 있기 때문이다. 코드는 기계의 언어로 쓰여 있다.

하나의 코드 (The Domain Story): 하나는 다르게 시작했다. 그녀는 먼저 비즈니스의 개념을 함수와 메서드로 정의했다.

order
  .validate_inventory()
  .process_payment()
  .prepare_shipment()
  .notify_customer()

"우리가 새로운 단어를 만든 거예요. 문제 영역의 언어를 만드는 거죠." 하나는 Lisp 프로그래머들의 전통을 따랐다. "프로그램을 언어를 향해 작성하는 동시에 언어를 프로그램을 향해 만든다." 하나의 코드는 전문화된 어휘로 잘 쓰인 산문처럼 읽혔다.

The Story 2: The Barista's Tongue (Ordinary Life)

민수와 하나는 커피 전문점에서 바리스타 교육을 받고 있다.

민수의 표현 (The General Language): 민수는 커피 맛을 아주 단순하게만 표현한다. "이 커피는 쓰고 좀 시네요. 뜨거워요." 민수의 언어로는 이 원두와 저 원두의 미묘한 차이를 동료에게 설명하거나 더 나은 맛을 추출하기 위한 구체적인 전략을 세우기 어렵다. 그의 언어는 너무 일반적이다.

하나의 표현 (The Specialized Vocabulary): 하나는 바리스타들만의 전문 용어를 익혔다. "이 원두는 '브루잉' 과정에서 '바디감'이 묵직하게 살아나네요. '산미'는 오렌지 계열이고요." 하나는 '추출 수율', '로스팅 포인트' 같은 단어들을 사용하여 맛의 세계를 정교하게 정의했다. 이 단어들은 하나가 커피라는 복잡한 문제를 다루는 '이론'이 되었고, 동료들과 한 치의 오차도 없이 소통하게 해주었다. 전문적인 어휘가 전문적인 기예를 가능하게 함을 하나는 알고 있었다.

Context

복잡한 문제 영역을 다루는 프로그램을 작성하고 있다. 코드가 길어질수록 이해하기 어려워지고, 새로운 기능을 추가하기가 점점 더 힘들어진다. 함수와 변수를 만들지만, 전체적으로 무엇을 하는지 점점 불명확해진다.

당신은 DataAsFoundation을 통해 데이터 구조의 중요성을 알고 있고, NamesAsDesign을 통해 좋은 이름의 가치를 인식하고 있다. 하지만 개별 함수와 데이터 구조를 넘어서, 문제 영역 전체를 표현하는 더 높은 수준의 무언가가 필요하다는 느낌을 받는다.

Problem

범용 언어의 원시 요소만으로는 문제 영역을 자연스럽게 표현하기 어렵다.

  • Python, Java, JavaScript 등은 모든 문제를 해결하기 위해 설계되었기에, '특정 문제'를 표현하기에는 너무 일반적입니다.

  • 비즈니스 로직이 구현 세부사항(for, if, append) 속에 묻히면 의도가 사라집니다.
  • 도메인 전문가와 개발자 사이의 어휘가 다르면 의사소통 비용이 기하급수적으로 증가합니다.

Solution

프로그래밍을 "문제를 표현하는 언어를 만드는 과정"으로 접근하라.

Lisp 프로그래머들이 수십 년간 해온 방식입니다: 먼저 문제를 잘 표현할 수 있는 언어를 만들고, 그 언어로 해결책을 작성하는 것입니다.

Principle 1: Programming as Theory Building

Peter Naur는 "프로그래밍은 단순히 코드를 작성하는 것이 아니라, 문제 영역에 대한 이론(Theory)을 구축하는 것"이라고 했습니다. 코드는 그 이론의 구현일 뿐이며, 진짜 가치는 문제를 이해하고 어휘를 만드는 이론에 있습니다.

Principle 2: Bottom-Up Language Construction

아래에서 위로 언어를 만들어 올라가십시오.

  1. Level 0: 원시 연산 (filter, map)
  2. Level 1: 도메인 개념 정의 (is_overdue, is_urgent)

  3. Level 2: 도메인 언어 합성 (urgent_overdue_tasks)

  4. Level 3: 문제 해결

Principle 3: Domain Vocabulary as Functions

문제 영역의 단어를 함수로 만드십시오. 코드가 도메인 전문가가 이해할 수 있는 단어들로 구성될 때 가독성과 유지보수성이 극대화됩니다.

Principle 4: Two Languages - Problem and Solution

TwoWorlds를 기억하십시오. 문제 공간의 언어(도메인 개념)와 해결 공간의 언어(기술적 추상화)는 명확히 분리되되, 각각 풍부하게 구축되어야 합니다.

Real Examples

Example 1: Test DSL - RSpec

RSpec은 테스트를 "User는... correct password로 인증한다"는 명세(Specification)로 읽히게 만들었습니다. 이것은 테스트를 쓰는 새로운 언어입니다.

Example 2: Rails - Convention over Configuration

Rails는 웹 개발을 위한 고유한 언어를 만들었습니다. 선언적 언어로 의도를 표현하면, 프레임워크가 복잡한 세부 사항을 처리합니다.

Common Pitfalls

"Let's Build a Framework First!"

문제를 충분히 이해하기 전에 거대한 프레임워크부터 만들려 하지 마십시오. 언어는 필요에 의해 점진적으로 자라나야 합니다(OrganicGrowth).

"DSL for Everything!"

모든 것에 고유 언어를 만들면 학습 곡선과 유지보수 부담이 커집니다. The95PercentRule을 기억하고 적절한 지점에서 멈추십시오.

Connection to Other Patterns

  • DataAsFoundation - 언어의 명사(Nouns)를 결정하는 과정입니다. 단단한 데이터 구조가 좋은 언어의 시작입니다. 기반

  • LivingVocabulary - 구축된 언어는 고정되지 않고, 시스템이 성장함에 따라 함께 살아 움직이며 진화해야 합니다. 지속적 정제

  • NamesAsDesign - 언어를 만드는 구체적인 행위는 본질적으로 '이름 짓기'라는 설계 활동입니다. 핵심 도구

  • ComplexityTaming - 잘 설계된 도메인 언어는 복잡한 비즈니스 로직을 단순한 문장으로 변환하여 복잡성을 길들입니다. 목표

Signs of Success

  • 코드를 읽으면 비즈니스 이야기가 들린다.
  • 도메인 전문가와의 대화가 코드에 그대로 반영된다.
  • 주석이 거의 필요 없는 자체 문서화된 코드가 된다.
  • 새로운 기능을 추가할 때 언어를 확장하는 것처럼 자연스럽게 추가된다.

The Ultimate Insight

프로그래밍 = 언어 만들기 + 그 언어로 표현하기

범용 프로그래밍 언어는 재료일 뿐입니다. 진짜 예술은 이 재료로 문제 영역의 언어를 만드는 것입니다. 문제를 잘 표현하는 언어를 만들면 해결책은 자연스럽게 따라옵니다.


CategoryPatternLanguage CategoryProgramming CategoryDesign CategoryLanguage CategoryDSL

LanguageBuilding (last edited 2025-12-30 09:01:46 by 정수)