|
Size: 24698
Comment:
|
Size: 24856
Comment:
|
| Deletions are marked like this. | Additions are marked like this. |
| Line 766: | Line 766: |
| '''LanguageBuilding''' - 데이터 구조가 복잡해지면 DSL이 된다. DataAsFoundation은 LanguageBuilding의 시작이다. ''자연스러운 발전'' '''ComplexityTaming''' - 복잡성을 관리하는 가장 효과적인 방법 중 하나는 로직을 데이터로 이동하는 것이다. ''직접 적용'' '''TwoWorlds''' - 외부 representation과 내부 representation을 분리하는 것은 문제 공간과 해결 공간을 분리하는 것이다. ''같은 원리'' '''WorkingFirst''' - 먼저 작동하게 만들고, 그다음 데이터 구조를 개선하라. 하지만 너무 오래 기다리지 마라. ''순서와 타이밍'' '''NamesAsDesign''' - 좋은 데이터 구조는 좋은 이름에서 시작한다. 타입 이름, 필드 이름이 명확하면 구조가 명확하다. ''기반'' '''DetectiveWork''' - 버그를 찾을 때, 로직이 아니라 데이터를 먼저 보라. 잘못된 데이터 구조가 복잡한 로직을 만든다. ''디버깅 전략'' '''CognitiveMicroscope''' - 데이터 변환을 명시적으로 만들면, 사고 과정이 더 명확해진다. "이 코드가 무엇을 하는가?"가 즉시 보인다. ''인지 부하 감소'' |
'''[[LanguageBuilding]]''' - 데이터 구조가 복잡해지면 DSL이 된다. DataAsFoundation은 LanguageBuilding의 시작이다. ''자연스러운 발전'' '''[[ComplexityTaming]]''' - 복잡성을 관리하는 가장 효과적인 방법 중 하나는 로직을 데이터로 이동하는 것이다. ''직접 적용'' '''[[TwoWorlds]]''' - 외부 representation과 내부 representation을 분리하는 것은 문제 공간과 해결 공간을 분리하는 것이다. ''같은 원리'' '''[[WorkingFirst]]''' - 먼저 작동하게 만들고, 그다음 데이터 구조를 개선하라. 하지만 너무 오래 기다리지 마라. ''순서와 타이밍'' '''[[NamesAsDesign]]''' - 좋은 데이터 구조는 좋은 이름에서 시작한다. 타입 이름, 필드 이름이 명확하면 구조가 명확하다. ''기반'' '''[[DetectiveWork]]''' - 버그를 찾을 때, 로직이 아니라 데이터를 먼저 보라. 잘못된 데이터 구조가 복잡한 로직을 만든다. ''디버깅 전략'' '''[[CognitiveMicroscope]]''' - 데이터 변환을 명시적으로 만들면, 사고 과정이 더 명확해진다. "이 코드가 무엇을 하는가?"가 즉시 보인다. ''인지 부하 감소'' '''[[MetaphorThinking]]''' - 데이터 구조는 종종 은유를 구현한다. Stack(쌓기), Queue(줄서기). ''구체화'' |
DataAsFoundation
주니어 개발자들을 위한 패턴 언어 - 복잡한 로직을 단순한 데이터 구조로 변환하여 명확성을 달성하는 방법
Contents
The Story: The Tangled Loop
한 주니어 개발자가 API에서 받은 사용자 목록을 화면에 표시하는 코드를 작성했다. 코드는 작동했지만, 시니어 개발자가 리뷰하면서 멈췄다.
"이 for loop 안에서 무슨 일이 일어나고 있지?" 시니어가 물었다.
주니어는 자신있게 대답했다. "API에서 받은 데이터를 우리 형식으로 바꾸고 있어요. 먼저 빈 배열을 만들고, 각 사용자를 순회하면서, 이름이 있으면 포맷을 바꾸고, 권한을 체크해서, 활성 사용자만 추가하고..."
시니어가 고개를 끄덕였다. "그런데 이 loop을 읽는 사람이 그걸 알 수 있을까? 10줄의 코드를 다 읽어야만 '아, 이게 활성 사용자 필터링이었구나'를 알 수 있어."
"그럼 주석을 달까요?" 주니어가 제안했다.
"주석 말고," 시니어가 미소 지으며 말했다. "데이터가 말하게 하자."
시니어는 코드를 다시 썼다:
// Before (Procedural)
const result = []
for (const user of apiUsers) {
if (user.status === 'active') {
const formatted = {
name: user.firstName + ' ' + user.lastName,
role: user.permissions.role
}
result.push(formatted)
}
}
// After (Data-Driven)
const activeUsers = apiUsers.filter(isActive)
const displayUsers = activeUsers.map(toDisplayFormat)
// Data transformations are explicit
const isActive = user => user.status === 'active'
const toDisplayFormat = user => ({
name: `${user.firstName} ${user.lastName}`,
role: user.permissions.role
})"보이지?" 시니어가 설명했다. "로직이 복잡해 보일 때, 사실은 데이터가 복잡한 거야. 로직을 단순하게 만들려면, 먼저 데이터를 올바르게 구조화해야 해. 그러면 로직은 저절로 단순해져."
Context
당신은 기능을 구현하고 있다. API 응답을 처리하거나, 비즈니스 규칙을 적용하거나, 다양한 조건에 따라 다르게 동작하는 코드를 작성한다. 코드가 작동하지만, 읽기 어렵고, 수정하기 어렵고, 버그가 숨어있기 쉽다.
일상적인 상황:
- 외부 API 응답을 내부 형식으로 변환해야 한다
- 비즈니스 규칙이 여러 if-else로 얽혀있다
- 같은 로직이 조금씩 다른 형태로 여러 곳에 반복된다
- 새로운 케이스를 추가할 때마다 코드가 더 복잡해진다
당신의 코드는 "어떻게 하는지" (how)로 가득하지만, "무엇을 하는지" (what)는 보이지 않는다.
Problem
로직에 집중하면, 복잡성이 로직에 쌓인다. 주니어 개발자들은 절차적 사고에 익숙하다: "이것을 하고, 그다음 저것을 하고, 그러면..." 하지만 이런 접근은 여러 문제를 만든다:
읽을 수 없는 Loop
const result = []
for (const item of items) {
// 10-20 lines of complex logic
// What is this loop actually doing?
}루프 안의 로직을 다 읽어야만 "아, 이게 필터링이었구나" 또는 "이게 변환이었구나"를 알 수 있다. 의도가 구현 속에 묻혀있다.
변환의 부재
외부 API의 응답을 받아서 내부 로직에서 사용할 때, 많은 주니어들은:
- 빈 변수를 만들고
- for loop을 돌면서
- 하나씩 요소를 추가한다
const users = []
for (const apiUser of apiResponse.data) {
users.push({
id: apiUser.userId,
name: apiUser.fullName,
active: apiUser.status === 'ACTIVE'
})
}문제: 이것은 데이터 변환(transformation)이지만, 코드는 그렇게 말하지 않는다. 독자는 loop을 읽어야만 "아, 변환이구나"를 알 수 있다.
같은 의미(semantic)를 가진 데이터가 다른 표현(representation)을 가질 때, 그 변환을 명시적으로 만들지 않으면, 코드의 의도가 숨겨진다.
로직의 폭발
비즈니스 규칙이 증가하면, if-else가 증가한다:
function calculatePrice(item, user) {
let price = item.basePrice
if (user.type === 'premium') {
price = price * 0.9
} else if (user.type === 'vip') {
price = price * 0.8
}
if (item.category === 'electronics') {
if (user.country === 'US') {
price = price * 1.1 // tax
}
}
if (isHoliday() && item.onSale) {
price = price * 0.95
}
// ... more rules ...
return price
}문제: 로직이 코드로 하드코딩되어 있다. 새로운 규칙을 추가하려면 코드를 수정해야 한다. 규칙들이 얽혀있어서 하나를 바꾸면 다른 것이 깨질 수 있다.
이 모든 복잡성의 근본 원인: 데이터를 데이터로 다루지 않고, 로직으로 다루고 있다.
Solution
로직을 단순하게 만들고 싶다면, 먼저 데이터를 올바르게 구조화하라. 많은 프로그래밍은 실제로 데이터를 변환하는 것이다. 이것을 명시적으로 만들면, 로직은 저절로 단순해진다.
Principle 1: Make Data Transformations Explicit
절차적 loop 대신, 데이터 변환을 명시적으로 표현하라.
Before:
const result = []
for (const item of items) {
if (item.active) {
result.push(transform(item))
}
}After:
const result = items .filter(isActive) .map(transform)
차이점: 두 번째는 "무엇을 하는지"가 즉시 보인다. 필터링하고, 변환한다. 의도가 구조에 드러난다.
Principle 2: Separate External and Internal Representations
외부 API의 데이터는 외부의 representation이다. 내부 로직에 맞는 representation으로 명시적으로 변환하라.
// External representation (from API)
type ApiUser = {
userId: string
fullName: string
status: 'ACTIVE' | 'INACTIVE'
permissions: { role: string }
}
// Internal representation (for our domain)
type User = {
id: string
name: string
active: boolean
role: string
}
// Explicit transformation
function fromApi(apiUser: ApiUser): User {
return {
id: apiUser.userId,
name: apiUser.fullName,
active: apiUser.status === 'ACTIVE',
role: apiUser.permissions.role
}
}
// Usage is clear
const users = apiResponse.data.map(fromApi)이제 코드 독자는:
외부 데이터가 어떻게 생겼는지 안다 (ApiUser)
- 내부 데이터가 어떻게 생겼는지 안다 (User)
- 변환이 어떻게 일어나는지 안다 (fromApi)
같은 semantic, 다른 representation - 그리고 그 변환이 명시적이다.
Principle 3: Encode Logic as Data
로직이 복잡해지면, 그것을 데이터로 표현할 수 있는지 물어보라.
Before (Logic as Code):
function calculatePrice(item, user) {
let price = item.basePrice
if (user.type === 'premium') {
price = price * 0.9
} else if (user.type === 'vip') {
price = price * 0.8
}
return price
}After (Logic as Data):
const discountRules = {
premium: 0.9,
vip: 0.8,
regular: 1.0
}
function calculatePrice(item, user) {
const discount = discountRules[user.type] || 1.0
return item.basePrice * discount
}더 나아가서, 규칙이 더 복잡해지면:
const pricingRules = [
{
condition: user => user.type === 'vip',
apply: price => price * 0.8
},
{
condition: user => user.points > 1000,
apply: price => price * 0.95
},
{
condition: user => user.firstPurchase,
apply: price => price * 0.9
}
]
function calculatePrice(item, user) {
return pricingRules
.filter(rule => rule.condition(user))
.reduce((price, rule) => rule.apply(price), item.basePrice)
}이제 새로운 규칙을 추가하는 것은 데이터를 추가하는 것이다. 코드를 수정하지 않는다.
Principle 4: Build Domain-Specific Languages
데이터 구조가 충분히 복잡해지면, 그것은 언어가 된다. 개념을 언어로, 즉 데이터로 표현하라.
// Configuration as DSL
const workflow = {
steps: [
{ type: 'validate', schema: userSchema },
{ type: 'transform', mapper: toInternalFormat },
{ type: 'enrich', fetcher: getUserDetails },
{ type: 'save', repository: userRepo }
]
}
// Engine executes the workflow
async function executeWorkflow(data, workflow) {
let result = data
for (const step of workflow.steps) {
result = await executeStep(step, result)
}
return result
}이제 비즈니스 로직은 설정 데이터로 표현된다. 코드는 그 데이터를 해석하는 엔진일 뿐이다.
The LLM Era: Representation Matters More Than Ever
LLM 시대에, representation의 중요성은 더욱 커졌다.
Code as Communication
과거: 코드는 컴퓨터를 위한 명령이었다. 현재: 코드는 인간, AI, 그리고 컴퓨터 사이의 대화이다.
잘 구조화된 데이터는:
- LLM이 이해하기 쉽다
- LLM이 생성하기 쉽다
- LLM이 수정하기 쉽다
// LLM이 이해하기 어려운 코드
const r = []
for (let i = 0; i < d.length; i++) {
if (d[i].s === 1) {
r.push({n: d[i].fn + ' ' + d[i].ln})
}
}
// LLM이 이해하기 쉬운 코드
const activeUsers = users.filter(isActive)
const displayNames = activeUsers.map(toDisplayName)두 번째 코드는 의미론적(semantic)으로 명확하다. LLM은 즉시 이해하고, 정확하게 수정할 수 있다.
Schema as Interface
LLM과 협업할 때, 데이터 구조는 인터페이스가 된다:
type User = {
id: string
name: string
email: string
role: 'admin' | 'user' | 'guest'
}
// LLM에게 명확한 지시
// "User 타입에 lastLogin: Date 필드를 추가해줘"
// LLM은 정확하게 타입을 이해하고 수정할 수 있다타입과 구조가 명확할수록, LLM과의 협업이 효과적이다.
Prompt Engineering as Data Design
LLM 프롬프트도 데이터 구조다:
// Poor structure
"Analyze this user data and tell me important stuff"
// Good structure
{
task: "analyze",
data: userData,
focus: ["activity_patterns", "risk_indicators"],
output_format: {
summary: "string",
metrics: { key: "value" },
recommendations: ["string"]
}
}데이터로 잘 구조화된 프롬프트는 더 일관되고 예측 가능한 결과를 만든다.
Language Beyond Code: Data as Universal Representation
LLM은 Large "Language" Model이다. 여기서 "언어"는 우리가 보통 생각하는 인간의 말과 글보다 훨씬 넓은 개념이다.
세상을 표현하는 언어들:
90년대 말, IT 산업은 XML을 통해 거의 모든 것을 표현하려 했다. 이는 단순한 마크업이 아니라, 도메인 지식을 구조화된 데이터로 표현하는 언어를 만드는 움직임이었다:
MathML - 수학 수식을 표현
MusicXML - 음악과 악보를 표현
KML/GML - 지리 정보를 표현
BPML - 비즈니스 프로세스를 표현
UBL - 전자상거래 정보를 표현
HR-XML - 인사 관리 정보를 표현
XML 외에도, 각 도메인은 자신만의 표현 언어를 발전시켰다:
// 복식부기 장부 (ledger)
2024/01/01 * Opening Balance
Assets:Checking $1000.00
Equity:Opening
// 음악 (csound)
instr 1
kenv linen 0.5, 0.01, p3, 0.1
asig oscil kenv, 440, 1
out asig
endin
// 악보 (lilypond)
\relative c' {
c4 d e f
g2 g
}각각은 특정 도메인의 개념을 데이터 구조로 표현하는 언어다.
LLM이 이해하는 것은 "언어"다:
LLM은 언어로 표현될 수 있는 모든 것을 생성할 수 있다. 음악을 만들고 싶다면? MusicXML 형식으로 요청하라. 회계 장부를 작성하고 싶다면? ledger 형식으로 요청하라. 비즈니스 프로세스를 설계하고 싶다면? BPMN이나 워크플로우 DSL로 표현하라.
// LLM에게 음악 생성 요청 "다음 MusicXML 형식으로 C major 스케일을 생성해줘" // LLM에게 회계 장부 생성 요청 "다음 거래를 ledger 형식으로 기록해줘: 2024년 1월 15일, 사무용품 구매 $50" // LLM에게 비즈니스 로직 생성 요청 "주문 처리 워크플로우를 데이터 구조로 표현해줘"
핵심 통찰: 데이터 구조를 설계하는 것은 곧 도메인 언어를 설계하는 것이다. 그리고 잘 설계된 도메인 언어는:
- 인간이 이해하기 쉽다
- LLM이 생성하기 쉽다
- 도메인 전문가와 소통하기 쉽다
- 다른 시스템과 통합하기 쉽다
DataAsFoundation의 진정한 의미: 좋은 데이터 구조는 좋은 언어다. 그리고 LLM 시대에, 언어로 표현된 것은 모두 생성 가능하고, 변환 가능하고, 진화 가능하다.
Practical Examples
Example 1: API Response Transformation
Procedural Approach (Junior):
async function getUsers() {
const response = await fetch('/api/users')
const data = await response.json()
const result = []
for (const user of data.users) {
if (user.status === 'active') {
const formatted = {
id: user.user_id,
name: user.first_name + ' ' + user.last_name,
email: user.email_address,
admin: user.role === 'administrator'
}
result.push(formatted)
}
}
return result
}문제:
- 무엇을 하는지 코드를 다 읽어야 안다
- 외부 형식과 내부 형식이 섞여있다
- 재사용할 수 없다
Data-Driven Approach (Better):
// 1. Define representations
type ApiUser = {
user_id: string
first_name: string
last_name: string
email_address: string
status: string
role: string
}
type User = {
id: string
name: string
email: string
admin: boolean
}
// 2. Explicit transformations
const isActive = (user: ApiUser) => user.status === 'active'
const toUser = (apiUser: ApiUser): User => ({
id: apiUser.user_id,
name: `${apiUser.first_name} ${apiUser.last_name}`,
email: apiUser.email_address,
admin: apiUser.role === 'administrator'
})
// 3. Compose transformations
async function getUsers(): Promise<User[]> {
const response = await fetch('/api/users')
const data = await response.json()
return data.users
.filter(isActive)
.map(toUser)
}개선점:
- 의도가 즉시 보인다: filter then map
- 변환이 재사용 가능하다
- 타입이 명확하다
- LLM이 쉽게 이해하고 수정할 수 있다
Example 2: Business Rules as Data
Logic as Code (Hard to Maintain):
function processOrder(order, customer) {
let discount = 0
if (customer.type === 'vip') {
discount = 0.2
} else if (customer.type === 'premium') {
discount = 0.1
}
if (order.total > 1000) {
discount += 0.05
}
if (customer.firstOrder) {
discount += 0.1
}
// ... more rules ...
return order.total * (1 - discount)
}Logic as Data (Easy to Maintain):
// Rules are data
const discountRules = [
{
name: 'VIP customer discount',
condition: ({ customer }) => customer.type === 'vip',
discount: 0.2
},
{
name: 'Premium customer discount',
condition: ({ customer }) => customer.type === 'premium',
discount: 0.1
},
{
name: 'Large order discount',
condition: ({ order }) => order.total > 1000,
discount: 0.05
},
{
name: 'First order discount',
condition: ({ customer }) => customer.firstOrder,
discount: 0.1
}
]
// Engine applies rules
function calculateDiscount(order, customer) {
return discountRules
.filter(rule => rule.condition({ order, customer }))
.reduce((total, rule) => total + rule.discount, 0)
}
function processOrder(order, customer) {
const discount = calculateDiscount(order, customer)
return order.total * (1 - discount)
}이점:
- 규칙을 추가하는 것은 데이터를 추가하는 것
- 규칙에 이름이 있어서 추적 가능
- 규칙을 외부 설정으로 이동 가능
- 규칙을 테스트하기 쉬움
Example 3: Workflow as DSL
Imperative Code (Inflexible):
async function processUser(userData) {
// Step 1: Validate
if (!userData.email || !userData.name) {
throw new Error('Invalid user data')
}
// Step 2: Transform
const user = {
id: generateId(),
email: userData.email.toLowerCase(),
name: userData.name.trim(),
createdAt: new Date()
}
// Step 3: Enrich
const profile = await fetchUserProfile(user.email)
user.profile = profile
// Step 4: Save
await db.users.insert(user)
// Step 5: Notify
await sendWelcomeEmail(user.email)
return user
}Declarative DSL (Flexible):
// Define workflow as data
const userRegistrationWorkflow = {
name: 'User Registration',
steps: [
{
name: 'validate',
handler: validateUserData,
required: ['email', 'name']
},
{
name: 'transform',
handler: transformUserData
},
{
name: 'enrich',
handler: enrichWithProfile,
async: true
},
{
name: 'save',
handler: saveToDatabase,
async: true
},
{
name: 'notify',
handler: sendWelcomeEmail,
async: true,
optional: true // Don't fail if notification fails
}
]
}
// Generic workflow engine
async function executeWorkflow(workflow, data) {
let result = data
for (const step of workflow.steps) {
try {
result = step.async
? await step.handler(result)
: step.handler(result)
} catch (error) {
if (!step.optional) throw error
console.warn(`Optional step ${step.name} failed:`, error)
}
}
return result
}
// Usage
const user = await executeWorkflow(userRegistrationWorkflow, userData)이점:
- 워크플로우를 설정으로 정의
- 재사용 가능한 엔진
- 단계를 쉽게 추가/제거/재정렬
- 다른 워크플로우에도 같은 엔진 사용
The Transform Pattern
많은 프로그래밍은 실제로 데이터 변환이다:
Input Data → [Transformation] → Output Data
Common Transformations
Filter: 조건에 맞는 것만 선택
const adults = users.filter(user => user.age >= 18)
Map: 각 요소를 변환
const names = users.map(user => user.name)
Reduce: 여러 요소를 하나로 집계
const total = orders.reduce((sum, order) => sum + order.amount, 0)
Compose: 여러 변환을 연결
const result = data .filter(isValid) .map(transform) .sort(byDate) .slice(0, 10)
각 단계가 무엇을 하는지 즉시 보인다. 데이터의 흐름이 코드의 구조다.
Pipeline Thinking
절차적 사고:
"1. 이것을 하고 2. 그다음 저것을 하고 3. 그러면 결과가 나온다"
데이터 변환 사고:
"데이터가 이 형태에서 → 저 형태로 변한다 각 단계는 명확한 변환이다"
두 번째가 더 명확하고, 더 합성 가능하고, 더 테스트하기 쉽다.
Common Pitfalls
"But Loops are Faster!"
성능 최적화는 나중에. 먼저 명확성.
대부분의 경우, map/filter와 for loop의 성능 차이는 무시할 만하다. 그리고 명확한 코드는:
- 버그가 적다
- 유지보수가 쉽다
- 나중에 최적화하기 쉽다
명확성 먼저, 최적화는 필요할 때.
"But Data Structures are Hard!"
처음에는 그렇다. 하지만:
- 데이터 구조를 설계하는 시간은 나중에 디버깅하는 시간을 절약한다
- 좋은 데이터 구조는 로직을 단순하게 만든다
- 연습할수록 쉬워진다
10분의 데이터 설계가 1시간의 디버깅을 절약한다.
"But Rules Change!"
바로 그래서 데이터로 표현하는 것이다:
- 코드로 된 규칙은 코드를 수정해야 바뀐다
- 데이터로 된 규칙은 설정을 바꾸면 된다
- 설정은 코드보다 바꾸기 쉽다
변하는 것을 데이터로 만들어라.
Connection to Other Patterns
LanguageBuilding - 데이터 구조가 복잡해지면 DSL이 된다. DataAsFoundation은 LanguageBuilding의 시작이다. 자연스러운 발전
ComplexityTaming - 복잡성을 관리하는 가장 효과적인 방법 중 하나는 로직을 데이터로 이동하는 것이다. 직접 적용
TwoWorlds - 외부 representation과 내부 representation을 분리하는 것은 문제 공간과 해결 공간을 분리하는 것이다. 같은 원리
WorkingFirst - 먼저 작동하게 만들고, 그다음 데이터 구조를 개선하라. 하지만 너무 오래 기다리지 마라. 순서와 타이밍
NamesAsDesign - 좋은 데이터 구조는 좋은 이름에서 시작한다. 타입 이름, 필드 이름이 명확하면 구조가 명확하다. 기반
DetectiveWork - 버그를 찾을 때, 로직이 아니라 데이터를 먼저 보라. 잘못된 데이터 구조가 복잡한 로직을 만든다. 디버깅 전략
CognitiveMicroscope - 데이터 변환을 명시적으로 만들면, 사고 과정이 더 명확해진다. "이 코드가 무엇을 하는가?"가 즉시 보인다. 인지 부하 감소
MetaphorThinking - 데이터 구조는 종종 은유를 구현한다. Stack(쌓기), Queue(줄서기). 구체화
Signs of Success
데이터 중심 사고를 효과적으로 사용하고 있다는 신호:
읽기 쉽다 - 코드를 보면 무엇을 하는지 즉시 안다
변환이 명시적이다 - Before와 After가 명확하다
재사용 가능하다 - 같은 변환을 여러 곳에서 쓸 수 있다
확장 가능하다 - 새로운 케이스를 추가하기 쉽다
타입이 안내한다 - 타입을 보면 무엇을 해야 하는지 안다
LLM이 이해한다 - AI가 코드를 쉽게 이해하고 수정할 수 있다
For Teachers and Mentors
주니어들에게 데이터 중심 사고를 가르칠 때:
질문하라:
- "이 loop이 실제로 무엇을 하고 있지?"
- "이것을 데이터 변환으로 표현할 수 있을까?"
- "Before와 After 데이터는 무엇이지?"
- "이 로직을 데이터로 표현할 수 있을까?"
리팩토링하라:
"이 for loop을 보자. 실제로 이것은 필터링이야 - 조건에 맞는 것만 선택하고 있어. 그럼 filter를 사용하면 어떨까?"
대조하라: 같은 기능을 두 가지 방식으로 보여주라:
- 절차적 방식 (how)
- 데이터 변환 방식 (what)
어느 것이 더 명확한지 물어보라.
The Ultimate Insight
프로그래밍의 본질:
데이터 + 변환 = 프로그램
좋은 프로그램:
명확한 데이터 + 명시적 변환 = 이해하기 쉬운 프로그램
LLM 시대의 프로그래밍:
의미론적 데이터 + 명확한 구조 = 인간과 AI가 협업하기 쉬운 프로그램
로직이 복잡해 보이면, 데이터를 보라. 올바른 데이터 구조는 로직을 단순하게 만든다. 그리고 단순한 로직은 버그가 적고, 이해하기 쉽고, 수정하기 쉽다.
이것이 DataAsFoundation이다. 모든 깨끗한 코드의 기반은 잘 정리된 데이터에서 시작된다.
CategoryPatternLanguage CategoryProgramming CategoryDesign CategoryDataStructure CategoryFunctionalProgramming
