> For the complete documentation index, see [llms.txt](https://wonjoon.gitbook.io/joons-til/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://wonjoon.gitbook.io/joons-til/books/the-pragmatic-programmer/4-pragmatic-paranoia.md).

# #4 Pragmatic Paranoia

### 💻 실용주의 프로그래머: Topic 23 계약에 의한 설계 (Design by Contract, DBC)

***

#### 1. 핵심 개념 — “좋은 프로그램은 신뢰의 계약으로 이루어진다”

> “상식과 정직만큼 사람을 놀라게 하는 건 없다.”\
> — 랄프 월도 에머슨

소프트웨어는 사람과 시스템의 약속이다.\
\&#xNAN;**‘계약에 의한 설계(Design by Contract)’** 는 그 약속을 **명문화하여 코드로 보증하는 방법론**이다.

이는 단순한 패러다임이 아니라,\
“소프트웨어 구성 요소 간의 신뢰를 수학적으로 정의”하려는 철학이다.

***

#### 2. 배경 — “사람의 계약에서 시스템의 계약으로”

우리는 이미 사회적으로 수많은 계약 속에 살아간다.

* 근로 계약: “회사는 급여를 지급하고, 직원은 의무를 수행한다.”
* 임대 계약: “사용 권리와 책임이 상호 명시된다.”

이처럼 **권리와 의무의 균형**을 명확히 규정하는 것이 계약이다.\
소프트웨어에서도 같은 개념이 적용된다.\
→ “함수와 호출자 간의 약속을 코드 수준에서 정의하자.”

***

#### 3. DBC의 기본 구조 — “선행 조건 · 후행 조건 · 불변식”

버트란드 마이어(Bertrand Meyer)는 이를\
에펠(Eiffel) 언어에 처음 도입하며 이렇게 정의했다.

> “루틴(메서드)은 어떤 일을 하기 전에 전제 조건을 만족해야 하고,\
> 일을 마친 후에는 결과 조건을 보장해야 하며,\
> 그 사이의 상태는 항상 일관되어야 한다.”

| 구분                            | 설명                        | 예시              |
| ----------------------------- | ------------------------- | --------------- |
| **선행 조건 (Precondition)**      | 루틴이 호출되기 전에 만족해야 하는 조건    | 입력 금액이 0보다 커야 함 |
| **후행 조건 (Postcondition)**     | 루틴이 종료된 후 보장해야 하는 상태      | 거래가 계좌에 추가됨     |
| **클래스 불변식 (Class Invariant)** | 객체의 내부 상태가 항상 유지되어야 하는 규칙 | 계좌 잔액 ≥ 0       |

***

#### 4. 코드 예시 — Clojure 스타일

```clojure
(defn accept-deposit [account-id amount]
  {:pre  [(> amount 0)
          (account-open? account-id)]
   :post [(contains? (account-transactions account-id) %)]}
  (create-transaction account-id :deposit amount))
```

* **:pre** → 선행 조건: 금액이 양수이며, 계좌가 열려 있어야 함
* **:post** → 후행 조건: 트랜잭션 목록에 거래가 포함되어야 함

💡 즉, 함수의 계약이 위반되면 예외를 던진다.\
→ “이 함수는 **거짓말을 하지 않는다.**”

***

#### 5. 예외 발생 — 계약 위반의 결과

```
Exception in thread "main"...
Caused by: java.lang.AssertionError: Assert failed: (> amount 0)
```

금액이 0 이하라면 “선행 조건 위반” 예외가 발생한다.\
이것은 단순 버그가 아니라 **계약 위반의 증거**다.

> DBC는 “누구의 책임인가?”를 명확히 한다.
>
> * 호출자가 선행 조건을 어겼다면 호출자 책임.
> * 피호출자가 후행 조건을 어겼다면 함수 책임.

***

#### 6. 다른 언어에서의 DBC 예시

**✅ Elixir**

```elixir
defmodule Deposits do
  def accept_deposit(account_id, amount) when amount > 0 do
    # 거래 생성 및 처리
  end
end
```

→ “guard clause”로 DBC 구현 (명시적 전제 조건)

**✅ Kotlin (assert 활용)**

```kotlin
fun deposit(amount: Int) {
    require(amount > 0) // precondition
    println("Deposited $amount")
}
```

**✅ TypeScript (타입 기반 계약)**

```typescript
function deposit(amount: number): Transaction {
  if (amount <= 0) throw new Error("Invalid amount");
  return { type: "deposit", value: amount };
}
```

***

#### 7. DBC와 테스트 주도 개발(TDD)의 비교

| 항목    | TDD       | DBC       |
| ----- | --------- | --------- |
| 초점    | 외부 동작의 검증 | 내부 계약의 보증 |
| 단위    | 테스트 케이스   | 코드 자체의 선언 |
| 실패 시점 | 실행 후      | 실행 중 즉시   |
| 목표    | 기능 검증     | 신뢰성 확보    |

> 둘 다 “품질 보증”을 위한 도구지만,\
> DBC는 **계약을 코드 안에 포함시킨 TDD**라고 볼 수 있다.

***

#### 8. 의미론적 불변식 (Semantic Invariant)

불변식은 단순한 변수 상태를 넘어서\
\*\*“의미 수준의 일관성”\*\*을 지켜야 한다.\
예를 들어, 신용카드 결제 시스템에서

> “거래 승인 없이 결제 처리가 되어선 안 된다.”\
> 는 규칙이 바로 의미론적 불변식이다.

***

#### 9. 확장된 개념 — 동적 계약과 에이전트

* 정적 계약: 코드 컴파일 시점에 보장
* **동적 계약:** 런타임에 조건을 검증 (테스트/모니터링 기반)
* **에이전트(Agent):** 계약을 자동으로 체결하고 위반 시 복구하는 지능형 컴포넌트

즉, 미래의 시스템에서는 **AI 기반 계약 시스템**이\
스스로 조건을 감시하고, 위반을 탐지·복구할 수도 있다.

***

#### 10. 연습 문제 요약

| 번호        | 내용                                         |
| --------- | ------------------------------------------ |
| **연습 14** | 믹서 인터페이스에 선행/후행 조건, 불변식 추가하기               |
| **연습 15** | 수열(0, 5, 10, 15…)에 포함된 숫자 개수 계산 — 계약 기반 구현 |

***

#### 11. 관련 항목

* **항목 24. 죽은 프로그램은 거짓말을 하지 않는다**
* **항목 25. 단정적 프로그래밍**
* **항목 42. 속성 기반 테스트**
* **항목 45. 요구 사항의 구체화**

***

> **“계약 없는 신뢰는 환상이다.”**\
> 함수와 모듈은 반드시 자신의 의무와 권리를 코드로 증명하라.

### 💻 실용주의 프로그래머: Topic 24

#### 죽은 프로그램은 거짓말을 하지 않는다 (Dead Programs Tell No Lies)

***

#### 1. 핵심 개념 — “실패를 숨기지 말고, 즉시 드러내라”

> **“거짓 없는 프로그램이란, 실패를 감추지 않는 프로그램이다.”**

프로그램이 잘못된 상태로 계속 실행되면,\
문제는 훨씬 커지고 더 찾기 어려워진다.

따라서 실용주의 프로그래머는\
**잘못된 상태에서는 프로그램을 멈추는 것이 정직한 선택**이라고 말한다.

> 💡 즉, “죽더라도 정직하게 죽어라.”

***

#### 2. 배경 — “문제를 숨기면 더 큰 재앙이 온다”

많은 경우, 오류는 “조용히” 발생한다.

* 비어 있는 리스트를 반환한다.
* default case로 빠져버린다.
* 예외를 덮어쓰고 로그만 남긴다.

이런 코드는 겉보기엔 정상 동작처럼 보여도\
실제론 \*\*논리적 오류를 감추는 ‘거짓말하는 프로그램’\*\*이다.

> “없는 일이 일어날 리 없다”는 생각이 가장 위험하다.\
> 반드시 “불가능한 상황은 언젠가 일어난다.”

***

#### 3. 잘못된 예 — “예외를 삼켜버리는 코드”

```ruby
try do
  add_score_to_board(score)
rescue InvalidScore
  Logger.error("잘못된 점수! 하지만 그냥 로그만 찍고 계속 진행함.")
end
```

이 코드는 겉보기에 친절하지만,\
문제를 로그로만 처리하고 프로그램을 계속 진행시킨다.\
결과적으로 **데이터 불일치, 무한 루프, 손상된 상태**로 이어질 수 있다.

***

#### 4. 올바른 방식 — “명확하게 실패하라”

```ruby
try do
  add_score_to_board(score)
rescue InvalidScore
  Logger.error("잘못된 점수: 프로그램 종료")
  raise
end
```

이렇게 하면 프로그램은 즉시 종료되고,\
문제의 원인을 명확히 드러낸다.\
\*\*“실패를 인정하는 프로그램”\*\*이 되는 것이다.

> Tip 38. **일찍 작동을 멈춰라. (Fail Fast)**

***

#### 5. 철학 — “일찍 멈추는 것이 덜 아프다”

문제가 생긴 상태로 계속 진행하면,\
나중에는 더 많은 데이터와 시스템이 오염된다.

오류를 조기에 탐지하고 중단시키면

* 문제를 쉽게 복구할 수 있고
* 원인을 명확히 추적할 수 있으며
* 전체 시스템의 신뢰성이 높아진다.

***

#### 6. 실제 적용 — Erlang과 Elixir의 철학

Erlang의 창시자 **Joe Armstrong**은 이렇게 말했다.

> “방어하지 말고, 그냥 멈추게 놔둬라.\
> 실패하도록 설계하라!”

Erlang은 **Supervisor 트리** 구조를 통해\
프로세스가 실패하면 상위 Supervisor가 자동으로 재시작한다.\
즉, \*\*“죽을 수 있게 설계된 언어”\*\*이다.

```erlang
% 프로세스는 실패하도록 설계
spawn(fun() -> error("boom!") end).
```

이 철학은 **고가용성(Fault-tolerant)** 시스템의 핵심이다.\
프로그램이 죽어도 전체 시스템은 살아남는다.

***

#### 7. 비교 — “계속 달리는 시스템 vs 멈추는 시스템”

| 방식                  | 설명                   | 결과               |
| ------------------- | -------------------- | ---------------- |
| **방어적 프로그래밍**       | 오류를 억제하고 프로그램을 계속 실행 | 문제 누적, 데이터 손상    |
| **Fail-fast 프로그래밍** | 오류 즉시 발생, 프로그램 종료    | 빠른 탐지, 명확한 원인 파악 |

> “멈추는 것이 용기다.\
> 거짓말하는 프로그램은 결국 더 큰 거짓을 만든다.”

***

#### 8. 현대적 적용 — Spring, Kotlin, Node.js에서의 Fail-fast 예시

| 환경              | 구현 예시                                        |
| --------------- | -------------------------------------------- |
| **Spring Boot** | `@Validated` 검증 실패 시 즉시 400 응답 반환            |
| **Kotlin**      | `require(amount > 0)` / `checkNotNull(user)` |
| **Node.js**     | `throw new Error("Invalid input")` 즉시 종료     |
| **CI/CD**       | 테스트 실패 시 파이프라인 중단 (Fail Fast)                |

***

#### 9. 관련 항목

* **항목 23. 계약에 의한 설계** — 계약을 어기면 멈춰라
* **항목 25. 단정적 프로그래밍** — assert로 거짓을 막아라
* **항목 43. 바깥에서는 안에 주의하라** — 경계에서 실패를 포착하라

***

> **“죽은 프로그램은 거짓말하지 않는다.”**\
> 오류를 덮지 말고, 빠르게 드러내라.\
> \&#xNAN;*Fail fast, fail honestly.*

### 💻 실용주의 프로그래머: Topic 25

#### 단정적 프로그래밍 (Assertive Programming)

***

#### 1. 핵심 개념 — “믿지 말고, 단정(assert)하라”

> “자기 비난에는 사치성이 있다.”\
> — 오스카 와일드

모든 개발자는 코드에서 일어날 수 없는 상황을 **명시적으로 검증해야** 한다.\
즉, “그럴 리 없어”라고 믿는 대신,\
**단정문(assertion)** 을 통해 **“그럴 리 없음”을 코드로 증명**하라는 것이다.

> 💡 믿음이 아니라 검증이다.\
> “그럴 리 없다”는 말은 프로그램의 적이다.

***

#### 2. 단정문의 역할 — “거짓을 드러내는 장치”

단정문(assert)은

> “이 시점에서 반드시 이 조건이 참이어야 한다.”\
> 는 것을 코드에 명시하는 장치다.

예:

```java
assert result != null : "결과가 null이면 로직 오류!";
```

이 조건이 거짓이면 프로그램은 즉시 중단된다.\
즉, **죽은 프로그램은 거짓말하지 않는다**는 원칙을 자동으로 실행하게 된다.

***

#### 3. 단정의 올바른 사용법

| 구분      | 설명             | 예시                                                   |
| ------- | -------------- | ---------------------------------------------------- |
| ✅ 사용    | 불가능해야 하는 상황 검증 | `assert user != null;`                               |
| ❌ 금지    | 정상적인 오류 처리 대용  | `assert input != null` 대신 `if (input == null) throw` |
| ✅ 주석 대용 | 의도를 명확히        | `assert size > 0 : "빈 리스트면 안 됨";`                    |
| ✅ 테스트   | 로직의 전제 조건 보증   | `assert isSorted(list);`                             |

***

#### 4. 잘못된 예 — “단정으로 로직을 대체하지 말라”

```ruby
puts "Y/N을 입력하세요:"
ch = gets
assert((ch == 'Y') || (ch == 'N'))
```

이건 잘못된 코드다.\
사용자 입력은 언제나 유효하지 않을 가능성이 있으므로\
`if` 문과 예외 처리가 필요하다.\
**단정은 절대로 외부 입력 검증용이 아니다.**

> 단정은 “코드 내부의 논리적 가정”만 검증해야 한다.\
> 외부 환경은 신뢰하지 말고 방어적으로 처리하라.

***

#### 5. 단정과 부작용 (Side Effect)

다음 코드는 단정문이 부작용을 일으켜 버그를 만든다.

```java
while (iter.hasNext()) {
    assert(iter.next() != null);
    Object obj = iter.next(); // ❌ next()가 두 번 호출됨
}
```

`assert` 내부에서 `next()`를 호출해 반복자를 이동시켜버렸기 때문.\
단정문은 **상태를 바꾸지 않고, 오직 확인만 해야 한다.**

***

#### 6. 단정 기능을 켜 두어라 — “죽이지 말고 살려둬라”

테스트 중에는 단정문을 활성화(`-ea` 옵션 등)해야 한다.\
많은 개발자들이 단정이 성능을 저하시킨다고 꺼두지만,\
**단정은 디버그 도구이며, 안전장치**다.

> “성능이 문제 되기 전까지 단정을 지우지 말라.\
> 단정은 코드의 가드 레일이다.”

***

#### 7. 실무 사례 — “실 서비스에서도 단정은 도움 된다”

> 작은 네트워크 장비 회사를 운영하던 한 엔지니어는\
> “단정문을 배포 코드에 그대로 남겨두는 것”을 결정했다.
>
> 버그가 발생하면 단정이 실패한 시점의 데이터를 모두 수집했고,\
> 결과적으로 훨씬 안정적인 제품을 만들 수 있었다.
>
> 👉 단정은 **품질 확보를 위한 자동 로그 시스템**이 될 수도 있다.

***

#### 8. 단정의 한계 — “모든 버그를 잡진 못한다”

단정은 논리적 오류(Internal bug)에만 유효하다.\
다음과 같은 외부 문제에는 효과가 없다.

* 하드웨어/네트워크 오류
* 사용자 입력 오류
* 경쟁 조건(race condition)

즉, 단정은 **“내가 통제할 수 있는 코드의 진실만 확인”** 한다.

***

#### 9. 연습 문제 요약

> **연습 16.** “불가능한 일” 목록 중 실제 일어날 수 있는 것은?

예시 질문:

1. 한 달이 28일보다 적은 달이 있다?
2. 시스템 콜 오류 메시지: “디렉터리에 접근할 수 없음.”
3. `a=2; b=3;`인데 `a+b=5`가 아님.
4. 삼각형이 60초가 아닌 180도?

👉 결론: “절대 일어나지 않는다”는 것은 없다.\
**단정으로라도 대비하라.**

***

#### 10. 관련 항목

* **Topic 23. 계약에 의한 설계** — 계약을 위반하면 단정이 실패한다
* **Topic 24. 죽은 프로그램은 거짓말을 하지 않는다** — 단정은 실패 시 죽게 만든다
* **Topic 26. 견고함(Resilience)** — 단정 이후의 복원력을 다룬다

***

> **“단정(assert)은 코드의 양심이다.”**\
> 스스로 증명할 수 없는 믿음을 코드에 두지 말라.\
> \&#xNAN;*Assert early, assert often.*

### 💻 실용주의 프로그래머: Topic 26

#### 리소스 사용의 균형 (Balancing Resources)

***

#### 1. 핵심 개념 — “자신이 시작한 것은 자신이 끝내라”

> “촛불 하나를 켜는 건 그림자도 하나 던지는 거란 말이다.”\
> — 어슐러 K. 르 귄, 《어스시의 마법사》

모든 프로그램은 리소스를 소비한다.\
메모리, 트랜잭션, 스레드, 파일, 네트워크 연결, 락(lock), DB 커넥션, 타이머 등\
**한정된 자원을 빌려 쓰고 해제해야 한다.**

> 💡 **원칙:** 리소스를 사용했으면, 반드시 해제하라.\
> 사용한 주체가 해제의 책임도 져야 한다.

이것이 바로 “자신이 시작한 것은 자신이 끝내라 (Tip 40)”의 의미다.

***

#### 2. 문제 인식 — “대부분의 개발자는 해제를 잊는다”

우리는 자원 할당(`open`, `connect`, `beginTransaction`)은 잘하지만,\
그에 대응하는 해제(`close`, `disconnect`, `commit/rollback`)는 종종 잊는다.

이런 무책임한 사용은 누적되면 다음과 같은 문제를 만든다.

* 파일 핸들이 닫히지 않아 I/O 장애 발생
* 메모리 누수(memory leak)
* DB 커넥션 풀 고갈
* 트랜잭션 미종료로 인한 Deadlock

즉, 리소스 사용의 균형이 무너진 것이다.

***

#### 3. 잘못된 예시 — “공유된 리소스와 결합된 루틴”

```ruby
def read_customer
  @customer_file = File.open(@name + ".rec", "r+")
  @balance = BigDecimal(@customer_file.gets)
end

def write_customer
  @customer_file.rewind
  @customer_file.puts(@balance.to_s)
  @customer_file.close
end

def update_customer(transaction_amount)
  read_customer
  @balance += transaction_amount
  write_customer
end
```

겉보기엔 괜찮지만, `read_customer`와 `write_customer`가\
**같은 인스턴스 변수(@customer\_file)** 를 공유하며 **결합(coupling)** 되어 있다.\
이로 인해 다음과 같은 위험이 생긴다:

* 파일이 정상적으로 닫히지 않을 수 있다.
* 예외 발생 시 리소스가 해제되지 않는다.
* 여러 스레드에서 동시에 접근하면 레이스 컨디션이 발생한다.

***

#### 4. 리팩터링 — “책임을 한 함수 안으로 모아라”

```ruby
def update_customer(transaction_amount)
  File.open(@name + ".rec", "r+") do |file|
    @balance = BigDecimal(file.gets)
    @balance += transaction_amount
    file.rewind
    file.puts(@balance.to_s)
  end
end
```

이제 파일을 여는 것(`open`)과 닫는 것(`close`)이\
**하나의 블록 스코프 안에서 해결된다.**

* 파일이 블록을 벗어나면 자동으로 닫힘.
* 공유 상태가 사라짐.
* 해제 책임이 명확히 한 곳에 모임.

> ✅ “열면 닫아라. 시작한 곳에서 끝내라.”

***

#### 5. Tip 41 — “지역적으로 행동하라”

리소스의 수명을 **가능한 한 짧게** 두어라.\
스코프를 작게 유지할수록

* 관리가 쉽고,
* 오류가 적고,
* 테스트가 용이하다.

예를 들어, 파일 객체를 전역 변수로 들고 다니지 말고\
필요한 시점의 함수 안에서 열고 닫아라.

```ruby
File.open("data.txt") do |f|
  process(f)
end
# 블록 종료 시 f는 자동 close
```

***

#### 6. 중첩 합당 — “여러 리소스를 다룰 때 순서를 지켜라”

리소스가 여러 개일 때는 항상 **반대 순서로 해제**해야 한다.

```ruby
lock1.acquire
lock2.acquire
# ...
lock2.release
lock1.release
```

> 💡 “마지막에 연 것은 가장 먼저 닫는다.”\
> Stack 자료구조처럼 LIFO로 관리해야 Deadlock을 피할 수 있다.

***

#### 7. 예외와 리소스 — “스코프가 닫히면 리소스도 닫혀야 한다”

리소스를 다루는 블록 안에서 예외가 발생하더라도\
그 리소스는 반드시 해제되어야 한다.

언어별 예시 👇

| 언어                         | 리소스 자동 해제 방식                              |
| -------------------------- | ----------------------------------------- |
| **C++ / Rust**             | 변수의 스코프를 벗어나면 소멸자(`destructor`) 자동 실행     |
| **Java / Kotlin / Python** | `try-with-resources`, `use {}`, `with` 블록 |
| **JavaScript**             | `finally` 절 활용                            |

예:

```java
try (var file = new FileReader("input.txt")) {
    process(file);
} // 자동 close
```

***

#### 8. 나쁜 예외 처리 방식 🚫

```ruby
begin
  thing = allocate_resource()
  process(thing)
ensure
  deallocate(thing)
end
```

이건 얼핏 좋아 보이지만,\
`allocate_resource()`에서 예외가 발생하면 `thing`이 정의되지 않아\
해제 코드가 동작하지 않는다.

✅ **올바른 구조:**

```ruby
thing = allocate_resource()
begin
  process(thing)
ensure
  deallocate(thing)
end
```

***

#### 9. 리소스 계층의 균형 — “최상위 구조는 하위 구조를 해제해야 한다”

리소스 구조는 트리 형태로 관리돼야 한다.

* 최상위 구조는 자신이 소유한 하위 리소스를 해제한다.
* 하위 구조는 자신이 가진 내부 리소스만 정리한다.

> 💡 “누가 시작했는가?”\
> → 그가 반드시 끝내야 한다.

***

#### 10. 실용적 점검 — “리소스의 생애를 추적하라”

리소스 누수를 방지하기 위해,\
프로그램의 실행 중에 **리소스 점검 도구(memory profiler)** 를 사용하라.

또한 서버 프로그램이라면\
“요청 한 건이 끝날 때마다 리소스가 제대로 해제되었는가?”를\
정기적으로 모니터링하라.

***

#### 11. 관련 항목

* **Topic 24. 죽은 프로그램은 거짓말을 하지 않는다**\
  → 예외는 숨기지 말고 종료하라.
* **Topic 25. 단정적 프로그래밍**\
  → 불가능한 상태는 코드로 검증하라.
* **Topic 33. 시간적 결합 깨뜨리기**\
  → 리소스 생명주기를 결합하지 말라.

***

> **“시작한 자가 끝내라. 열면 닫아라.”**\
> 자원은 빌려 쓰는 것이다.\
> \&#xNAN;*Manage scope, manage life.*

### 💻 실용주의 프로그래머: Topic 27

#### 헤드라이트를 앞서가지 말라 (Don’t Outrun Your Headlights)

***

#### 1. 핵심 개념 — “예측은 어렵다, 특히 미래에 대해서는”

> “예측은 힘들다. 특히 미래에 대해서는.”\
> — 요기 베라(Yogi Berra)

야간 운전 중, 급커브를 돌다가 **헤드라이트가 비추는 범위를 벗어난 속도**로 달리면\
아무리 브레이크를 밟아도 이미 늦는다.\
빛이 닿지 않는 영역은 **“보이지 않는 위험 구간”**, 즉 **미래**다.\
운전과 마찬가지로 소프트웨어 개발에서도 **우리의 헤드라이트(시야)** 는 제한되어 있다.

> 💡 “미래를 내다보려 하기보다, 지금 비추는 빛 안에서만 안전하게 움직여라.”

***

#### 2. 비유의 의미 — “투사 거리(throw distance)”

헤드라이트의 투사 거리(빛이 닿는 범위)는 물리적으로 제한되어 있다.\
시속 60km일 때 정지거리는 약 52m,\
시속 100km일 때는 약 116m 정도다.

즉, 아무리 빠른 차라도 **헤드라이트보다 멀리 볼 수는 없다.**\
미래를 예측하려는 시도는 결국 빛의 속도를 앞지르려는 것과 같다.

***

#### 3. 소프트웨어의 교훈 — “우리의 헤드라이트도 제한되어 있다”

개발자들은 종종 “이번 프로젝트가 언제 완성될까?”,\
“사용자 요구가 어떻게 변할까?” 같은 질문에 답하려 한다.\
하지만 **너무 먼 미래를 확신하려는 순간, 통제 불가능한 변수들이 생긴다.**

> 예측 대신, **작은 단계와 짧은 피드백 루프**를 통해 앞으로 나아가라.\
> 그게 바로 실용주의 프로그래머의 방식이다.

***

#### 4. Tip 42 — “작은 단계를 밟아라, 언제나.”

작업을 작게 나누고, 각 단계를 진행하기 전에 **피드백을 확인하고 조정하라.**

| 단계          | 피드백 형태                 |
| ----------- | ---------------------- |
| **REPL** 결과 | 코드와 알고리즘이 의도대로 동작하는가?  |
| **단위 테스트**  | 특정 코드에 대한 즉각적인 검증      |
| **사용자 피드백** | 기능이 실제 사용자의 니즈를 충족하는가? |

너무 먼 단계로 “예언”하려 하지 말고,\
작은 단위에서 실험과 피드백을 반복하라.\
그것이 경험과 통계 기반의 \*\*“무모한 예측이 아닌, 반복 가능한 추론”\*\*이다.

***

#### 5. 예측 대신 설계하라

* “다음 달 완성일”보다 “다음 스프린트 완료 목표”를 세워라.
* “향후 확장성”보다 “오늘 가능한 리팩터링”에 집중하라.
* “사용자의 미래 행동”보다 “지금의 사용 패턴”을 분석하라.
* “예상 기술 트렌드”보다 “현재 검증된 기술”을 활용하라.

> 💡 미래를 위한 가장 좋은 대비책은 **현재의 구조를 유연하게 설계하는 것**이다.

***

#### 6. 불확실성과 블랙 스완 (Black Swan)

나심 탈레브(Nassim Taleb)는 『블랙 스완』에서 이렇게 말했다.

> “모든 예측은 결국 ‘평균적 세상’을 가정한다.\
> 하지만 진짜 변화는 예외적 사건(black swan)에서 온다.”

즉, 예측이 빗나가는 이유는 우리가 **비정상적 사건을 무시**하기 때문이다.\
따라서 실용주의 프로그래머는 예측을 덜 하고,\
**변화와 예외를 견디는 구조**를 만든다.

***

#### 7. GUI 전쟁의 교훈 — “예측은 거의 항상 틀린다”

책의 1판이 나올 당시, 세상은 “데스크톱 GUI 전쟁”에 열광했다.\
모티프(Motif) vs 오픈룩(OpenLook) —\
하지만 누가 이겼는가? **둘 다 졌다.**

세상을 바꾼 건 **웹 브라우저**였다.\
즉, “이길 것 같다”는 예측은 아무 의미가 없었다.

***

#### 8. Tip 43 — “예언하지 말라”

> “대부분의 경우 내일은 오늘과 크게 다르지 않다.\
> 그러나 확신하지는 말라.”

너무 멀리 예언하지 말고,\
지금 눈앞에 비치는 헤드라이트의 범위 안에서,\
가장 좋은 결정을 반복하라.

***

> **“예측하지 말고, 피드백으로 조정하라.”**\
> 작은 단계를 밟으며, 지금 보이는 빛 안에서 움직여라.\
> \&#xNAN;*Don’t predict. Adapt.*
