여는 인용구 — 존 러스킨, 『The Stones of Venice』(파라프레이즈) 사람을 정밀한 도구처럼 만들 수도 있고, 사람답게 만들 수도 있음. 둘 다는 불가능함. 톱니바퀴처럼 정확함을 요구하면 인간성을 지워야 하지만, 반대로 상상하고 시도하게 두면 그 정밀함은 곧 사라지고 거칠음·실패·멈춤이 뒤따름. 그러나 그 실패와 거칠음 속에서만 인간의 위엄도 함께 드러남.
이 인용구는 책 전체의 주제를 암시함 — 사람을 기계처럼 정밀하게 만들려는 시도와, 사람답게 창조하도록 두는 것 사이의 긴장.
0.1 다시, 우리의 세상을 바꿔봅시다 (Let’s Change Our World—Again)
- 소프트웨어 개발은 본질적으로 복잡함(complex). 문제는 여기서 그치지 않고 **복잡해짐(complicated)**까지 만들어버리는 경향.
complex와complicated의 차이:
| 구분 | 예시 | 특징 |
|---|---|---|
| Complex(복잡함) | 눈송이 | 구성 요소는 많지만 상호작용에 패턴과 규칙이 보임 |
| Complicated(복잡해짐) | 눈송이를 만든 기상 시스템 | 구성 요소는 많지만 패턴을 파악하기 어려움 |
- 소프트웨어의 complexity에는 규칙이 있음. Complications(복잡한 문제)은 그 규칙을 따르지 않을 때 발생.
- Complications은 프로젝트를 이해·예측하기 어려운 뒤엉킨 로직 덩어리로 만들고, 프로젝트 운영 방식과 사람을 대하는 방식까지 방해함.
- Complications을 다루는 일은 규칙을 모르고, 심지어 알 수도 없는 게임을 하는 것과 같음.
- 핵심 통찰: ==대부분의 프로젝트 complications은 자기 유발적(self-induced)==. 멈춰서 생각하지 않고, “정말 이래야 하나?“라는 내면의 목소리를 무시할 때 생겨남.
- 악순환 구조: 단순하게 만들 시간에 투자하지 않는 이유 = 시간이 없어서. 시간이 없는 이유 = 상황이 복잡해서.
- 이 악순환을 끊으려면 시간을 만들어야 함. 그렇지 않으면 배우고 개선할 수 없음.
- 단순함을 받아들이고 단순한 것을 만드는 법을 스스로 발견해야 함. 그렇지 않으면 접시돌리기 곡예사처럼, 무너지는 접시를 막으려 기둥 사이를 뛰어다니는 삶을 살게 됨.
- Ward Cunningham의 명언:
"Do the simplest thing that could possibly work"(가능한 가장 단순한 것을 하라) — 코드 작성법을 넘어 모든 일에 적용되는 원칙. simplicity(단순함) ≠simplistic(단순화·얕음) ≠naive(소박함·무지함). 단순함은 이해하고 변경하기 쉬운 것을 만드는 것, 그리고 “느낌이 맞다(feeling right)“는 모호한 자질을 갖춘 것을 만드는 것을 의미.- 건축가 Christopher Alexander(『A Pattern Language: Towns, Buildings, Construction』 저자)는 이를 **이름 없는 질(quality without a name)**이라 명명. 단순함을 달성하면 이 느낌으로 보상받음.
0.2 내가 하는 대로 하지 마세요 (Don’t Do What I Do)
- Simplicity를 글로 다루는 어려움: 사물을 단순하게 만들어주는 규칙이나 절차가 존재하지 않음. 어떤 코딩 표준이나 설계 방법론도 결과물의 복잡화를 막아준다고 보장하지 못함.
- 핵심 명제: ==Simplicity는 일을 하는 방식(way)이 아니라, 그 일을 하는 정신(spirit)==.
- 이유: 나에게 단순한 것이 당신에게는 아닐 수 있고, 당신이 단순하다고 느끼는 것이 나에게는 낯설 수 있음.
- 저자는 일반적 규칙 집합을 제시할 수 없음(그런 것이 존재하지 않으므로). 대신 예시를 통해 가르치기로 함.
- 책의 구성: 여러 개의 **practice(실천 항목)**로 나눔.
- 각 practice의 형식: 저자가 실제로 필요 이상 복잡하다고 느꼈던 상황 → 그 상황을 단순화하기 위해 취한 단계를 설명.
- 중요한 당부: 저자가 밟은 단계는 문제에 대한 범용 해법이 아님. 독자가 그 단계를 무비판적으로 그대로 따르길 원하지 않음.
- 대신 각 practice를 저자가 단순함을 찾아간 하나의 이야기(story)로 읽고, 이를 예시로 삼아 자기만의 길을 찾을 것을 권함.
0.2.1 용기 (Courage)
- Kent Beck의 『Extreme Programming Explained: Embrace Change』가 현대 소프트웨어 개발의 시작점.
- XP(Extreme Programming)의 5가지 가치:
- Communication(소통)
- Feedback(피드백)
- Respect(존중)
- Simplicity(단순함)
- Courage(용기)
- 저자의 주장: 이 5가지는 프로그래밍뿐 아니라 삶 전반의 핵심 가치.
- 이 책이 독자에게 요구하는 것들:
- 가치 체계를 세우고 지키기(종종 동료 압력에 맞서서)
- 흐름에 휩쓸리지 않고 자신이 하는 일에 대해 생각하기
- 입장을 취해야 할 때를 알아채기
- 복잡함을 만들어내는 모든 요인에 맞서 싸우며 삶에 단순함을 들여오기
- 이 모든 것에는 용기가 필요함. 모든 위대한 변화가 그러하듯.
0.3 온라인에서 만나요 (See You Online)
- 관련 주소(텍스트로만 기재, 클릭 링크 아님):
pragprog.com/titles/dtcode— 책 홈페이지. 토론 포럼, 오탈자(errata) 목록 제공.articles.pragdave.me— 저자가 simplicity 및 기타 관심사에 대한 글·노트를 게시하는 곳.pragdave.me— 저자의 개인 홈페이지.pragprog.com— 저자의 사업체, 지난 20년의 이력.
0.4 감사의 말 (My Thanks)
| 대상 | 내용 |
|---|---|
| 리뷰어(Aino Corry, Bruce Eckel, Derek Sivers, Kim Shrier, Neal Ford, Noel Rappin, Saron Yitbarek, Zachary McKenna) | 틀렸다고 말해주면서도 바보처럼 느끼게 하지 않는, 진정한 친구들 |
| 편집자 Susannah Davidson | 25년 집필 경력 중 처음으로 편집자를 둠. 다시는 편집자 없이 작업하지 않겠다고 밝힘. 큰 그림과 세부사항을 동시에 보고 이를 일관되게 엮는 능력을 갖춘 또 다른 친구 |
| 아내 | 아내이자 친구가 되어준 것에 대한 감사 |
| 독자 전체 | 들어준(귀 기울여준) 것에 대한 감사 |
1. 단순성에 대한 접근 방식 (An Approach to Simplicity)
- 저자는 단순함을 “어떻게” 달성하는지 알려줄 수 없음 — 사람마다 상황(context)이 다르기 때문.
- 대신 저자는 단순화가 필요했던 여러 상황을 제시하고, 그 상황에서 실제로 한 일을 살펴보는 방식을 택함.
- 목적: 과시가 아니라, 복잡함을 인식하고 잘라내는 데 쓸 수 있는 접근법을 보여주는 것.
- 이 접근법의 이름: ==Orient, Step, Learn==(지향, 실행, 학습).
1.1 Orient, Step, Learn (지향 · 실행 · 학습)
- 과학자와 공학자는 미지의 것을 탐구하며 삶을 보냄. 최소 400년간 거의 변하지 않은 접근법 사용:
- 가설을 세운다 (Come up with a hypothesis)
- 그 가설을 반증하기 위한 실험을 설계한다 (Design an experiment to try to disprove it)
- 결과를 분석하고 반복한다 (Analyze the results and repeat)
- 이것이 바로 과학적 방법(scientific method)의 본질.
- 복잡함에 접근할 때도 동일한 방식을 적용할 수 있음.
flowchart LR
A[Orient 지향] --> B[Step 실행]
B --> C[Learn 학습]
C --> A
| 단계 | 핵심 질문 · 행동 | 설명 |
|---|---|---|
| Orient(지향) | 무엇이 너무 복잡한가? | 복잡함을 발견하는 단계 |
| Step(실행) | 가장 단순한 실험은 무엇인가? | 최소한의 시도로 아이디어를 검증하는 단계 |
| Learn(학습) | 무엇을 배웠는가? | 성찰 후 그 배움을 다음 단계에 반영하는 단계 |
1.1.1 Orient (지향하기)
- 너무 복잡하다고 느끼는 대상을 발견하는 단계.
- 발견 방식은 명백할 수도 있음. 예: “이 코드가 어떻게 동작하는지 아무도 모른다.”
- 발견 방식은 미묘할 수도 있음. 예: 무언가 걸리는 듯한, 멈추게 만드는 직관적인 불편함.
- 이런 신호를 감지하면 멈추고 생각함:
- 상황을 더 단순하게 만들 수 있는 접근법은 무엇인가?
- (가장 중요) 결과가 실제로 더 단순해졌는지 어떻게 판단할 것인가?
- 이 과정을 반복하면 spidey-sense(스파이디 센스) — 뭔가 잘못되었음을 경고해주는, 정의하기 어려운 직관 — 이 발달함.
1.1.2 Take a Step (한 걸음 내딛기)
- 핵심 단어:
step(걸음·단계). - 하지 않는 것: 긴 여정을 계획하는 것, 절차 문서를 작성하는 것, 지금 하던 일을 전부 내려놓는 것.
- 하는 것: 아이디어를 검증할 수 있는 가장 단순한 행동을 찾는 것.
- 원칙: 시간과 노력이 적게 들수록, 그 아이디어를 계속 밀고 나갈지 버릴지 더 빠르게 결정할 수 있음.
- 판단이 틀렸더라도 적은 비용으로 끝남 — 리스크가 작음.
1.1.3 Learn (배우기)
- 단계(실험)가 끝나면 성찰(reflect)할 시간.
- 한 번의 step으로는 대개 부족함. 이전 단계에서 배운 것을 다음 단계에 반영하는 일이 중요함.
- 책의 나머지 모든 섹션이
practice(실천 항목)로 불리는 이유: 제2의 본성이 되기 위해 반복 연습이 필요한 것들이기 때문. - ==Orient(복잡함 인식) → Step(실험) → Learn(조정)을 스스로 더 많이 실행할수록, 앞으로 이를 의식적으로 떠올려야 할 필요가 줄어듦==.
- 비유: 운전 — 어떤 페달이 무엇을 하는지 생각하지 않고도 몸이 알아서 하게 되는 것과 같음.
- 실천 팁: “Orient, Step, Learn”을 인덱스 카드에 적어 작업 중 보이는 곳에 붙여두기.
- 실천 팁: 혼자 작업하거나 이해심 있는 동료와 함께라면, 각 단계를 소리 내어 말하는 것이 도움이 됨. 뇌에 자극을 많이 줄수록 더 빨리 타고난 패턴으로 자리잡음.
- 마무리 문장: “자, 이제 나아가 봅시다(Anyway, onward!)”
파트 1. 하는 일을 단순화하라, 일하는 방식을 단순화하라
- 이 파트는 무엇을 할 것인가와 어떻게 할 것인가를 모두 단순화해야 한다는 문제의식에서 출발
- 챕터 2부터는 소프트웨어 개발 과정에서 불필요하게 쌓여가는 복잡성(의존성, 프레임워크, 기능)을 어떻게 덜어낼 것인지를 구체적인 실천(Practice) 단위로 다룸
챕터 2. 코드 다이어트
- 약 100년에 걸친 연구에 따르면 도로망을 x%만큼 확장하면 교통량도 최소 x%만큼 늘어남 → 이를 **유발수요(induced demand)**라고 부름: 무언가가 더 많아지면 사람들은 그것을 더 많이 사용하게 됨
- 저자의 대학 시절 경험
- 학과에서 운영하던 IBM/370-135
- 동시 온라인 사용자 20명(대부분 개발 작업) 지원
- 총 메모리는 384 kB에 불과
- 오늘날에는 그 1,000배 공간(약 384 MB)에도 웹 앱 하나를 겨우 넣을 수 있는 수준
- 오늘날 주머니 속 스마트폰의 연산 능력(GFlops)은 1970년 전 세계 하드웨어를 합친 것보다 크지만, 인류를 달에 착륙시킨 유도 컴퓨터는 다음 사양에 불과했다는 사실은 쉽게 잊힘
- RAM 4 kB
- 저장공간 40 kB 미만
- 클럭 속도 2 MHz
- 무어의 법칙 덕분에 개발자들은 자원 제약을 신경 쓰지 않게 됨 → “언제나 더 많은 GHz와 GB가 있으니 마음껏 써도 된다”는 암묵적 규칙이 자리잡음
- 그러나 이런 팽창에는 대가가 따름
- 환경 문제나 자원 고갈 문제는 논외로 하더라도
- 소프트웨어를 작성하는 좁은 세계 안에서도 비용이 발생
- 코드는 점점 더 비대해지고 있음
- 이해하기 어려워짐
- 유지보수하기 어려워짐
- 앞으로 배포하기도 어려워짐
- 이제는 코드를 더 건강하게 만들어야 할 때, 즉 코드를 다이어트시켜야 할 때
2.1 실천 1: 건강하지 않은 의존성을 줄여라
- 2024년 말 기준, 아무 코드도 추가하지 않은 텅 빈 베이스라인 프로젝트만으로도 의존성 규모가 상당함
| 프레임워크 | 생성 명령어 | 의존성 수 | 용량 |
|---|---|---|---|
| Phoenix | mix phx.new my_project |
43개 | 약 15MB |
| Rails | 기본 프로젝트 생성 | 83개 gem(연쇄적으로 수백 개 추가 로드) | - |
| React | npx create-react-app my-app |
874개 모듈 | 약 350MB(설치하는 동안 차 한 잔 마실 정도의 시간 소요) |
- 저자는 React가 다른 프레임워크보다 특별히 더 비대하다고 주장하는 것이 아니라, 이들 모두가 상당한 짐(baggage)을 짊어지고 있다는 점을 지적
- 실제로 코드를 작성하기 시작하면 여기에 자신만의 의존성이 추가되고, 그 의존성에도 또 다른 의존성이 딸려옴
- 저자는 의존성을 예외 없이 싫어한다고 단언
- 의존성을 하나 추가할 때마다, 잘 알지도 못하고 통제할 수도 없으며 사실상 신뢰할 수도 없는 제3자들의 트리(tree)에게 앱의 미래 일부에 대한 통제권을 넘겨주는 셈
- React가 기본 포함하는 874개 모듈에는 수만 명의 기여자가 참여했을 것으로 추정 → 그중 단 한 명만 문제를 일으켜도 앱이 망가질 수 있음
==아이디어 1== 모든 의존성은 당신 앱의 일부에 대한 통제권을 넘겨주는 행위다
- left-pad 사건(2016년 3월)
- 다작하던 한 JavaScript 라이브러리 개발자가 자신의 모든 라이브러리를 NPM 저장소에서 갑자기 삭제
- 그중에는 문자열 앞에 지정한 길이가 될 때까지 패딩 문자를 채워주는 아주 단순한 라이브러리
left-pad도 포함 left-pad는 수천 개의 다른 라이브러리가 사용했고, 그 라이브러리들은 다시 훨씬 더 많은 라이브러리에 사용됨- 이 2차 라이브러리 중에는 Babel, React처럼 JavaScript 생태계의 핵심 축도 포함
- 결과적으로 개발자 한 명의 결정이 상당수의 JavaScript 앱을 빌드 불가능하게 만듦
- XZ 라이브러리 사건(2024년 3월, CVE-2024-3094)
- XZ 압축 라이브러리에서 악성 코드가 발견되어 전 세계적인 경고가 발령됨
- 이 라이브러리가 OpenSSH 데몬 코드에서 사용되고 있었기에 특히 우려가 컸음
- 안전하다고 여겨지던 데이터 전송 체계 자체가 훼손될 뻔함
- 의존성을 코드에 추가한다는 것은 다수의 개발자를 프로젝트에 초대해 런타임 접근 권한을 주는 것과 같음
- 이들이 악의적일 필요조차 없음 — API를 바꾸거나, 기능을 폐기하거나, 실수로 보안 취약점을 만드는 것만으로도 문제가 발생
flowchart TD
A["내 애플리케이션"] --> B["의존성 A"]
B --> C["의존성 B"]
C --> D["의존성 C"]
D --> E["매우 작은 유틸리티 라이브러리(예: left-pad)"]
E -- "한 명의 개발자가 삭제 또는 변경" --> F["전체 빌드 실패 또는 보안 사고"]
2.1.1 나의 개인적인 의존성 지옥
- 저자가 2023년, 7년의 공백 끝에 Pragmatic Bookshelf로 복귀했을 때 사업의 근간이 되는 소프트웨어가 사실상 방치되어 있었음을 발견
- 여전히 Rails 3 버전으로 운영 중(당시 Rails 최신 버전은 8)
- “문제없다, 한 릴리스씩 최신 Rails로 올리면 된다”고 가볍게 생각했으나
- Rails 3이 사용하던 라이브러리가 또 다른 라이브러리를 사용했는데, 그것이 최신 머신에서는 완전히 다르게 동작 → Rails 3용 gem 자체를 빌드할 수조차 없었음
- 무작정 업데이트를 시도했지만, 릴리스 사이에 워낙 많은 호환성 파괴 변경이 있어 앱을 아예 기동시킬 수도 없었음
- 결국 8년 전 버전의 리눅스를 그대로 담은 Docker 컨테이너를 만들어 옛날 라이브러리를 구동하는 방법 외에는 답이 없었음
- 좌절스러운 한 주 끝에, 지금까지 다뤄본 개발 환경 중 가장 복잡하고 가장 취약한 환경을 갖게 됨
- 저자는 여전히 이 상황에서 벗어나기 위해 마이그레이션을 진행하며 뒷수습 중
2.1.2 의존성 단순화하기
- left-pad 사건을 다시 떠올려보면, 해당 라이브러리의 전체 코드는 다음 수준으로 짧았음(TypeScript로 재구성)
export function leftpad(str: string, len: number, ch: string = ' '): string {
str = String(str);
let i = -1;
len = len - str.length;
while (++i < len) {
str = ch + str;
}
return str;
}
- 총 11줄의 다소 지저분한 코드(특히
str = ch + str부분은 반복적인 문자열 재할당으로 가비지 컬렉션 부담을 유발) - 누군가는 “문자열을 left-pad 해야 한다”는 필요를 느끼고, 라이브러리를 검색해 빌드에 추가하고 임포트해서 쓰는 법을 익힘 → 직접 작성하는 것보다 3배는 더 오래 걸렸을 가능성이 큼
- 오늘날에는
padStart가 JavaScript에 내장되어 있음. 설령 없다 하더라도 괜찮은 구현은 단 3줄로 충분
function leftpad(str: string, len: number, ch: string = ' '): string {
str = String(str);
len = len - str.length;
return len > 0 ? ch.repeat(len) + str : str;
}
==아이디어 2== 11줄짜리 코드는 의존성이 되어서는 안 된다
- 극단적인 사례이긴 하지만, 왜 이렇게 사소한 것도 라이브러리로 찾게 되는지는 생각해 볼 가치가 있음
- 시간 제약 때문이었을 가능성(다만 이 경우엔 라이브러리를 찾는 데 걸린 시간이 코드를 직접 작성하는 시간보다 길었을 것)
- 남의 문제로 떠넘기기(someone-else’s-problem) 함정: 라이브러리 저자에게 코드를 위임하면 문제가 생겨도 내 책임이 아니게 됨
- 물론 의존성은 반드시 필요함(예: SSH 라이브러리를 직접 만드는 건 말이 안 됨)
- 다만 트레이드오프가 존재: 오늘 의존성을 추가하면 오늘의 문제는 해결되지만, 위험과 유지보수 측면에서 미래의 나에게는 복잡성을 더하는 셈
2.1.3 의존성 격리하기
- 과거 저자는 앱 전반에서 광범위하게 사용하던 의존성이 갑자기 API를 변경했을 때 크게 곤란을 겪은 경험이 있음 → 여러 소스 파일에 걸쳐 코드를 수정해야 했음
- 이제는 가능하면 의존성을 단순한 래퍼(wrapper) 함수로 감싸고, 코드의 나머지 부분에서는 이 래퍼 함수만 사용
- 이렇게 하면 API가 바뀌더라도 래퍼 함수만 수정하는 것으로 대응이 가능할 수 있음
// 의존성을 여러 곳에서 직접 호출하지 않고 래퍼로 감싸는 예시
import { externalHttpClient } from 'some-http-library';
export async function fetchJson<T>(url: string): Promise<T> {
const response = await externalHttpClient.get(url);
return response.data as T;
}
// 애플리케이션 코드는 항상 fetchJson만 사용
// 나중에 라이브러리를 교체하더라도 이 함수 내부만 수정하면 됨
2.1.4 의존성 결정 체인
- 프로젝트에 의존성을 추가하기 전에 저자가 스스로 던지는 질문들
| 질문 | 핵심 내용 |
|---|---|
| 정말 그 기능이 필요한가? | 예전에는 흥미로워 보인다는 이유만으로 의존성을 추가했음. 이제는 프로젝트가 그것 없이도 동작한다면 추가하지 않는 절제가 필요(“적은 것이 곧 많은 것이다”). 개인 시간에 실험하는 것은 별개 |
| 그냥 직접 코드로 짜는 게 더 쉽지 않은가? | 함수 하나만 필요하다면 점점 더 라이브러리를 쓰지 않고 직접 작성. 오픈소스이고 라이선스가 허용한다면, 출처를 표기하고 해당 함수만 복사해 와서 직접 검토·수정 가능하게 관리 |
| 정글을 통째로 사는 것은 아닌가? | 조 암스트롱은 바나나 하나가 필요했는데 바나나를 쥔 고릴라와 정글 전체를 떠안게 되는 상황에 이를 비유함. 단순히 HTML 이스케이프 처리만 필요한데, 라이브러리에는 렉싱·들여쓰기 처리를 위한 별도 의존성까지 딸린 전체 HTML 파서·생성기가 포함되는 경우가 그 예. 쓰지 않는 기능이라도 여전히 미래의 부채로 남음 |
| 이 프로젝트에 반드시 필요한 것인가? | 그렇다면 로컬 사본을 반드시 확보해 둘 것. 프로젝트에 꼭 필요한 것을 가져오려는데 404 응답을 받는 것만큼 아찔한 상황도 없음 |
| 특정 버전에 묶여 있지는 않은가? | 라이브러리에는 의존성 관리자를, 도구에는 버전 관리자(저자는 현재 asdf 사용)를 항상 사용해 버전을 명시. 그러나 언젠가 새 의존성을 추가하려는데 기존 의존성의 최신 버전을 요구하게 되고, 그 업데이트가 다른 5개 라이브러리의 요구사항을 깨뜨리는 연쇄 반응이 발생하기도 함. 개발 중에는 매일 또는 매주 의존성을 갱신하는 습관을 들였지만, 1년쯤 지나 프로젝트로 돌아오면 생산적인 작업을 시작하기 전에 한동안 의존성과 씨름해야 함을 각오해야 함. 의존성은 서로 의존하므로, 하나가 늘 때마다 드는 시간은 선형적인 증가보다 더 큼 |
| 보안은 확실한가? | 많은 라이브러리 저장소가 보안 취약점을 모니터링함. 새 패키지를 설치할 때 전체 패키지에 대한 감사(audit)도 함께 수행되는 경우가 많음 |
- npm audit 예시
$ npm add jest
npm warn deprecated glob@7.2.3: Glob versions prior to v9 are no longer supported
added 275 packages, and audited 310 packages in 13s
33 vulnerabilities (6 moderate, 19 high, 8 critical)
Run `npm audit` for details.
- 위 예시에서 폐기(deprecated) 경고는 Jest 추가로 인한 것이지만, 33개의 취약점은 모두 기존에 있던 패키지들에서 발견된 것
- 개발 중에는 의존성을 갱신할 때마다 이런 보고를 확인하고, 이후에는 GitHub가 코드베이스의 보안 이슈를 감지해 경고를 보내줌
==아이디어 3== 각 의존성 도입을 신중한 선택으로 만들어라
- 현재 저자가 실천하고 있는 것
- 함수 하나 때문에 의존성 전체를 쓰지 않는다
- 직접 작성할 수 있는 것은 의존성으로 해결하지 않는다
- 개발 중에는 의존성을 최신 상태로 유지하려 노력한다
- 의존성을 추가하기 전에 출처(provenance)를 잠깐이라도 확인한다
- 앞으로 더 잘하고 싶은 것
- 의존성의 보안 상태를 모니터링해 주는 서비스를 조사한다
- 의존성을 코드의 나머지 부분과 격리하도록 래핑하는 습관을 더 일관되게 들인다
- 분기에 하루 정도를 떼어 오래된 프로젝트를 받아와 빌드해보고 가능하면 의존성을 업데이트한다
- 각자의 체크리스트는 다를 수 있지만, 자신만의 신중한 의사결정 과정을 갖추는 것이 각 의존성이 가져오는 이득을 저울질하는 데 도움이 됨
2.1.5 되짚어보기(연습)
- 현재 진행 중인 프로젝트를 열어 최상위 의존성(나와 팀이 직접 선언한 것들)을 살펴볼 것
- 각각이 무슨 일을 하는지 알고 있는가?
- 코드가 각 의존성을 어떻게 쓰고 있는지 알고 있는가? 그 사용이 사소한 수준인가? 의존성을 제거하고 해당 기능을 직접 구현할 수 있는가?
- 이번에는 명시적으로 포함한 것뿐 아니라 전체 의존성 목록(보통 lock 파일에 들어있음)을 살펴볼 것
- 몇 개나 되는가?
- 그 숫자가 두려운가?
- 이에 대해 뭔가 조치를 취할 것인가?
2.2 실천 2: 프레임워크, 성분표를 확인하라
- 오늘날 대다수의 프로젝트는 어떤 형태로든 프레임워크 위에서 동작함
- 프레임워크는 저수준의 디테일을 처리해주고 애플리케이션 수준의 코드 작성에 집중할 수 있게 해줌 → 원칙적으로 코드를 단순하게 만들어야 함(실제로 그럴 때도 있음)
- 그러나 때로는 프레임워크에 코드베이스를 묶는 것이 폭주하는 기차에 올라타는 것과 같음 — 빠르게 나아가지만 무섭고, 언젠가는 충돌할 가능성이 높음
2.2.1 내 거미줄로 들어와요
- 프레임워크는 애플리케이션 코드를 감싸고 있음 — 주도권은 프레임워크가 쥐고 있고, 내 코드는 콜백과 훅(hook)을 제공하는 역할
- 무언가를 처리해야 할 때는 프레임워크를 호출하는 구조(제어의 역전)
- 프레임워크에 한 번 발을 들이면, 큰 수고 없이는 내 코드를 그로부터 떼어낼 수 없음
- 프레임워크가 API를 통제할 뿐 아니라, 애플리케이션을 어떻게 아키텍처링할지도 결정해버림
- 프레임워크 관리 팀이 장기적인 안정성을 보장해 준다면 받아들일 만하지만, 대부분은 그렇지 않음
flowchart LR
F["프레임워크"] -- "콜백 훅 호출" --> C["내 애플리케이션 코드"]
C -- "API 호출" --> F
F -- "아키텍처 구조를 강제" --> C
2.2.2 인플레이션 소용돌이
- 새 프레임워크를 만드는 것은 상당한 작업이며, 대중에게 소개하고 질문·제안·비난에 대응하는 것은 더 큰 작업. 기여자 관리도 힘들고, 본업도 따로 있음
- 그래서 프레임워크를 성공적으로 만드는 사람들은 열정적이고 추진력이 강한 사람들일 수밖에 없음
- 어떤 프레임워크가 해당 분야에서 최초로 인기를 끌면, 경쟁자들이 등장해 자신이 더 낫다고 주장하는 기능을 추가함 → 원조 프레임워크도 대응해 새 기능을 추가하며 더 우수하다고 주장 → 다른 프레임워크들도 각자 새 기능을 내놓음(기능 인플레이션 소용돌이)
- 때로는 관리자가 더 나은(혹은 그저 더 세련된) 방식을 발견하기도 함 → 운이 좋다면 예전 방식을 한동안 지원 중단(deprecate) 예고한 뒤 새 방식으로 전환
- 이런 과정을 거치며 프레임워크는 끊임없이 진화하고, 새 기능을 추가하며, 더 이상 인기 없는 예전 기능은 버림
- 시간이 지나며 크기와 복잡도가 커지지 않는 프레임워크는 매우 드묾 → 프레임워크가 커질수록 내 앱도 커지고 복잡해지며, 짊어져야 할 의존성의 수도 늘어남
==아이디어 4== 프레임워크는 계속해서 기능을 축적해 나간다
- 복잡한 프레임워크의 위험은 두 가지 방향에서 찾아옴: 그린필드(신규) 프로젝트와 브라운필드(기존) 프로젝트
2.2.3 그린필드 프로젝트
- 저자가 풀스택 JavaScript 앱을 다루던 시절의 경험
- 늘 최신·인기 기술을 리서치하며 “쿨한 개발자”가 되고자 새로운 기능을 찾아 헤맴
- 새롭고 인기 있는 것이 3개월 전 쓰던 프레임워크보다 나을 것이라 판단
- 서로 상충하는 설치 안내 영상 두 개를 보고 둘 다 어설프게 따라함 → 3~4번 시도 끝에 겨우 “Hello, World!” 앱 구동 → “이제부터는 그냥 구현만 하면 된다”고 생각했지만
- 첫 단계인 들어오는 요청 처리 방법조차 선택지가 많음
- 프레임워크에 동기/비동기 옵션 존재
- 비동기 쪽은 콜백 또는 이벤트 방식, 직접 상태 머신을 추가해 더 세밀하게 제어하는 것도 가능
- 메시징 인프라 선택지도 4가지 중 아무거나 설치 가능
- 이 모든 걸 하기도 전에 SSL 인증서부터 생성해야 하는데, 프레임워크의 최신 보안 릴리스는 유효한 루트 인증서를 요구
==아이디어 5== 프레임워크가 옵션을 제공한다면, 그 안에는 당신에게 필요 없는 것들이 들어있다는 뜻이다
- 이런 옵션과 기능들은 모두 대가를 수반함 — 복잡성, 의존성, 비대함이 늘어남
- 사용하지 않고 그저 존재하기만 하는 기능은 언젠가 보안 구멍이 될 수 있음
2.2.4 브라운필드 프로젝트
- V0.8 시절부터 써온 프레임워크와 함께 성장했고, 팀도 그 프레임워크를 잘 알고 있는 상황을 가정
- 시간이 갈수록 변경과 지원 중단(deprecation)의 속도가 발목을 잡기 시작
- 보안 업데이트를 받으려면 반드시 새 프레임워크 릴리스를 통합해야 하지만, 릴리스마다 호환성이 깨지는 문제가 생겨 새 코드를 작성하는 대신 기존 코드를 고치는 데 시간을 씀
- 설상가상으로 팀원들이 프레임워크의 새로운 측면을 탐구하는 것을 즐겨서, 최종 사용자 입장에서는 아무 차이가 없는데도 계속 코드를 새 방식으로 바꾸는 일이 반복됨
==아이디어 6== 프레임워크를 유지보수하는 데 정작 코드 작성보다 더 많은 시간을 쓰고 있다면, 그 프레임워크는 더 이상 도움이 되지 않는 것일 수 있다
- 프레임워크가 새롭고 가벼웠을 때는 앱을 기록적인 속도로 배포하도록 도와주었지만, 이제는 오히려 발목을 잡는 것처럼 느껴짐
2.2.5 그래서, 프레임워크를 쓰지 말라는 건가?
- 당연히 프레임워크는 사용해야 함. 다만 저자의 강력한 조언
- 기능이 가장 많고 가장 최신 기술을 쓰는 프레임워크를 고르지 말 것 — 처음엔 재미있을지 몰라도 1년 뒤에는 후회할 수 있음
- 대신 오랫동안 안정적으로 자리 잡아온 프레임워크를 선택할 것
- 필요한 기능을 제공하면서도 기능 수가 가장 적은 프레임워크를 선택할 것
- 예시
- Ruby로 서버사이드 API를 만든다면 Rails에 손을 뻗기 전에 Sinatra처럼 최소한의 프레임워크를 먼저 검토
- JavaScript라면 Express, 또는 Nest나 Adonis 같은 대안을 고려
- 핵심 원칙: 자신이 사용하는 환경을 더 잘 이해할수록 그 환경은 더 단순하게 느껴짐. 환경이 작을수록 이해하고 유지보수하기 쉬워지며, 신규 개발자를 온보딩하기도 쉬워짐
- 프레임워크에서도 **“적은 것이 곧 많은 것”**이라는 원칙은 그대로 적용됨
2.2.6 당신의 FBQ(프레임워크 비대 지수)는?(연습)
- 반쯤 농담이지만 생각해 볼 가치는 있는 질문
- 현재 사용 중인 프레임워크를 하나 고르고, 문서에서 기능 목록을 찾아볼 것
- 그중 실제로 사용하고 있는 비율은 몇 퍼센트인가?
- 저자가 여러 팀에 이 질문을 던졌을 때 가장 흔한 답변은 10%~20% 수준
- 본인의 답이 이와 비슷하다면, 빙산의 겉으로 드러난 일각만 사용하고 있는 셈
2.3 실천 3: 최고의 기능은 출시하지 않는 기능이다
- 저자는 먹이를 얻으려 버튼을 누르는 실험쥐처럼, 새로운 것을 만들고 전달하는 “기능 배포”를 어쩔 수 없이 좋아함
- 과거에는 고객이 원하는 줄도 몰랐던 기능까지 미리 전달하는 것으로 유명했음 — 일종의 슈퍼히어로 행세
- 하지만 이는 동시에 매우 어리석은 행동이기도 했음
- 추가하는 모든 기능은 미래의 부채를 의미함 — 그 코드는 이후 지원되고, 유지보수되고, 확장되고, 새로 합류하는 개발자들에게 이해되어야 하기 때문
==아이디어 7== 기능(Feature)이란 마케팅 용어로 표현된 ’미래의 부채’다
- 그렇다고 모두가 기능 추가를 멈추는가? 아니다. 멈춰야 하는가? 그렇다, 때로는 그래야 한다.
2.3.1 필요 기반 개발
- 기본 원칙: 그 기능을 필요로 하는 사람이 없다면 코드를 작성하지 말 것. “이거 있으면 멋지지 않을까?“나 “이거 추가하는 거 진짜 쉬울 텐데”는 필요(need)로 인정되지 않음
==아이디어 8== 아무도 요청하지 않은 코드는 작성하지 마라
- 무엇이 “필요”인지는 맥락에 따라 다름
- 대중 시장을 겨냥한 제품이라면 마케팅 부서가 경쟁사를 주시하며 뒤처지지 않기 위한 기능 요청을 만들어냄 — 이는 진짜 필요에 해당
- 단일 고객이나 사내용 소프트웨어라면 “필요”의 개념이 더 미묘해지고, 정치적 요소에 오염될 수 있음
- 소프트웨어 개발 예산을 책임지는 관리자들의 사례
- 매년 무엇을 만들지, 비용이 얼마인지, 어떤 가치를 낼지 보고서를 준비
- 대개 요청한 금액보다 적은 금액을 승인받음
- 그래서 관리자들은 필요할 것 같은 기능을 모두 예산 신청에 미리 포함시킴 → 예산을 부풀리는 동시에 일종의 보험 역할(올해 기본 프로젝트만 신청했다가 내년에 확장 예산을 거절당하고 싶지 않기 때문) — 내부 고객, 외부 고객 모두에 해당하는 현상
- 좀 더 애자일하게 접근하는 방법: “필요”를 논하는 대신, 고객에게 프로젝트에서 어디서 가치를 얻을 것인지, 우선순위가 무엇인지 물어봄
==아이디어 9== 기능이 아니라 점진적인 가치를 전달하라
- 팀 내부적으로 각 가치를 전달하는 데 드는 노력 수준을 대략적으로 산정(예: “쉬움”, “보통”, “어려움”, “알 수 없음”) — 순전히 내부용으로 작업 크기를 가늠하기 위함
- 이 대략적인 추정치를 전달 가치와 비교해, 의존관계가 허락하는 한 초기에 전달했을 때 상당한 비즈니스 가치를 줄 수 있는 기능을 먼저 골라냄
- 이후 고객에게 두 가지 옵션을 제시
- 옵션 1: 기존 방식대로 프로젝트 종료 시점에 소프트웨어를 한 번에 전달
- 옵션 2: 기능 단위로 점진적으로 전달 — 최종 프로젝트가 끝나기 훨씬 전에 특정 기능(X)을 먼저 전달할 수 있고, 이후 어떤 기능(Y, Z)을 다음으로 전달할지도 논의 가능
- 이 방식이 고객에게 주는 두 가지 큰 이점
- 더 일찍 비즈니스 가치를 얻기 시작할 수 있음 — 동일한 가치라도 나중보다 지금이 항상 더 나음
- 첫 번째 결과물을 실제로 사용해 봄으로써, 문서상의 글로는 드러나지 않던 시스템의 특성을 깨닫게 되고, 원래 요청과는 완전히 다른 방향의 아이디어나 제안을 가지고 돌아올 수 있음
- 실제 사례: 어떤 고객은 다섯 개 기능 중 처음 두 개만 전달받고 나서 시스템이 필요한 만큼 충분하다고 말했고, 나머지 세 개 기능은 보험 성격으로 요청했던 것일 뿐 실제로는 필요 없었음
- 이 방식이 개발팀(전달자)에게 주는 큰 이점
- 훨씬 이른 시점에 피드백을 받을 수 있음 — 이상적으로는 매주 대화하는 것이 좋지만 현실적으로 잘 안 되는 경우가 많은데, 무언가를 전달하면 확실히 고객의 관심을 끌 수 있음
- 고객과 신뢰 관계를 쌓기 시작함 — 프로젝트 시작 시점에는 고객이 팀에 대한 위험을 감수하는 입장이지만, 무언가를(완전하지 않더라도) 전달받으면 더 편안함을 느끼고 더 개방적이고 유용한 상호작용이 가능해짐
- 문제를 조기에 발견할 수 있음 — 코드 전반에 퍼질 수 있는 오해가 있었다면, 이를 일찍 발견하는 것이 이후의 대규모 재작업을 막아줌
==아이디어 10== 복잡도는 기능 수보다 더 빠르게 증가한다
xychart-beta
title "기능 수 증가에 따른 복잡도 증가"
x-axis ["기능 1", "기능 2", "기능 3", "기능 4", "기능 5", "기능 6"]
y-axis "복잡도" 0 --> 100
line [5, 12, 22, 38, 62, 95]
2.3.2 단순성
- 아무 기능도 없는 소프트웨어를 출시할 수는 없음
- 그러나 불필요한 장식이나 복잡함 없이, 꼭 필요한 것만 전달하도록 기능의 수를 줄이고 더 일찍 전달하기 위해 노력할 수는 있음
- 기능은 언제든 나중에 추가할 수 있음
2.3.3 되돌아보기(연습)
- 지금까지 전달했던 프로젝트들을 되돌아볼 것
- 각 프로젝트에서 매일 사용되는 코드의 비율은 얼마나 될까?
- 전혀 사용되지 않을 것으로 추정되는 코드의 비율은 얼마나 될까?
3. 프로젝트를 단순화하라 (Simplify Your Projects)
- 지금까지는 기술을 다루는 방식을 단순화하는 데 집중함 — 개인이 가장 큰 통제력을 가진 영역이기 때문
- 저자는 1인 사업자로서 특권적 위치에 있음을 인정 — 그래서 프로젝트·팀 이야기를 위해 별도로 조사를 진행
- 2023년 말, 캘린더를 열어 전 세계 팀들을 초대 → 이후 몇 달간 약 50개 팀과 대화
- 발견 1: 팀들은 좋은 결과를 내는 데 매우 헌신적이었음 — 무엇이 잘 되고 무엇이 잘 안 되는지 스스로 잘 인지하고 있었고, 개선 계획도 갖고 있었음
- 발견 2: 서로 다른 대륙·문화의 팀들이 동일한 문제를 독립적으로 동일한 방식으로 해결한 사례가 놀라울 만큼 많았음
- 질문: “당신의 팀은 성공적이라고 생각하는가?” → 그렇다는 팀에게: “무엇이 그 성공을 이끌었다고 생각하는가?”
- 반복적으로 언급된 성공 요인 3가지
- 소통(Communication): 정보가 팀 내에서 빠르고 정확하게 전달됨. 팀원끼리 의견이 달라도 서로의 관점을 인정함
- 상호 보완적 스킬(Complementary skills): 서로 다른 사람이 서로 다른 업무를 담당하며, 어떤 업무도 다른 업무보다 더 가치 있게 취급되지 않음
- 경험을 아낌없이 나눔(Generous with experience): 자신의 지식과 경험을 동료에게 전달하는 것을 책임으로 여김
- 3장은 이 세 영역을 저자 본인 경험과 인터뷰한 팀들의 경험을 통해 탐구함
- 원칙: 이 장의 내용은 저자와 인터뷰 대상 팀들에게는 효과가 있었던 것 — 그대로 베끼지 말고 **평가하고 조정(assess and adapt)**할 것
3.1 Practice 4 — 팀을 디커플링하라 (Decouple Your Teams)
어떤 조직이든 시스템(넓은 의미에서)을 설계하면, 그 설계는 조직의 커뮤니케이션 구조를 그대로 반영하게 된다 — 멜빈 콘웨이, 1968
- 좋은 코드 작성에 대한 개발자들의 오랜 직관
- 디커플링된 코드는 변경하기 쉽다
- API와 경계(boundary)는 코드를 이해하기 쉽게 만든다
- 작은 단위는 큰 단위보다 작성하기 쉽다
- 추상화 계층은 복잡성을 다스리는 데 도움이 된다
- 저자는 이를 “법칙”이 아니라 “직관”이라 표현 — 스스로 규칙·법칙을 만들었다고 주장하는 사람들에게: 최근에 제대로 된 규모의 코드를 언제 짜봤는지 되물을 만함
- 질문: 좋은 코드에 대한 이 직관을 프로젝트 운영 방식에도 적용할 수 있지 않을까?
3.1.1 디커플링 (Decoupling)
- 코드 디커플링 = 변경의 영향 범위를 제한하기 위해 의존성을 관리하는 것
- 코드가 커플링되어 있으면 한 곳의 변경이 다른 곳을 깨뜨릴 수 있음 — 어떤 변경도 국소화되지 않음. 커플링된 코드로 작업하는 것은 끊임없는 저글링이며 중력(문제)은 항상 이김
- 팀원도 이와 같은 방식으로 커플링될 수 있음 — 한 사람이 다른 사람에게 의존하면 변화가 더 어려워지고 오래 걸림. 상호 의존성이 많을수록 팀은 취약해짐
- 여기서 말하는 커플링은 팀원이 작성하는 코드가 아니라 프로세스와 관행에 관한 것
- 이 맥락의 커플링 = 여러 팀원이 다른 팀원을 기다리게 만드는 모든 것 → 저자는 이를 The Pragmatic Programmer 20주년 기념판에서 ==시간적 커플링(temporal coupling)==이라 명명함
Idea 11. 커플링은 팀과 코드 모두에 영향을 미친다
- 팀에서 나타나는 시간적 커플링의 예시
- 팀원이 한 번에 하나의 기능만 작업 → 완료되어도 다른 사람의 리뷰가 끝날 때까지 진행 불가
- Hassan의 코드가 Jan의 코드에 의존 → Jan이 휴가 중이면 Hassan은 진행 불가
- Jan이 갑작스럽게 전체 회의를 소집 → 사람들이 하던 일을 멈추고 모였는데 결국 자판기 음료 종류를 논의하다 끝남. 회의가 지속되는 동안 모두가 시간적으로 커플링됨. 회의 후에도 대화를 정리하는 데 10분, 원래 맥락을 되찾는 데 다시 30분이 소요됨
- 팀이 2주 스프린트를 사용 → 2주마다 전달·회고·백로그 정리·계획을 함께 진행. 이때 발생하는 커플링이 막대함: 개별 기능들이 동기화되어야 하고, 다음 스프린트 전까지 새 작업이 멈출 수 있으며, 회의 연쇄로 모두가 하던 일을 멈추고, 문제가 하나 생기면 팀 전체가 헛도는 상태가 됨
3.1.2 비동기적인 삶 (The Async Life)
- 너무 많은 팀이 이벤트 기반으로 운영됨 — 무언가 발생하면 하던 일을 멈추고 대응해야 한다는 기대. 이후 다른 두 사람까지 끌어들이게 되고, 집중력의 균열은 얇은 얼음의 금처럼 퍼져나감
- 리뷰어 Noel Rappin의 코멘트(원고 리뷰 중 언급)
- 이런 상태를 “유지보수 모드(maintenance mode)“라 부르는 팀들을 봤는데, 사실상 무기한 그 상태에 머무름
- 팀 사기에 나쁘고, 업무 우선순위를 정하는 능력을 잃게 되며, 항상 쳇바퀴를 도는 느낌을 받게 됨
- 많은 애자일 관행에는 “폴백 모드(fallback mode)“가 있다고 생각함 — 폴백 모드는 완전한 애자일 관행보다 일은 적지만 효과도 훨씬 떨어짐. ==이벤트 기반 운영이야말로 애자일 계획의 폴백 모드==라고 생각함
- 팀을 일종의 인간 이벤트 처리 시스템으로 조직하는 것이 옳은 경우도 아주 가끔 있음 — 하지만 이것이 기본 동작이 되어서는 안 됨
- 대신 이벤트를 디커플링할 방법을 찾을 것 — 사람들이 강제로 끌려가기보다 스스로 할 일을 조회(poll)할 수 있게 할 것
- 이메일, 공유 작업 목록, 칸반 보드 모두 디커플링 수단
- 저자 사례: Andy와 함께 The Pragmatic Programmer 2판을 집필할 때 20년 전과 동일한 워크플로 사용
- 한 사람이 글을 쓰고 리뷰를 위해 넘김 → 리뷰어가 수정 → 다시 돌려받음 → 더 이상 수정할 것이 없을 때까지 반복 → 책에 반영(이후 다시 재작성됨)
- GitLab 칸반 보드로 관리: 글의 각 덩어리 = 카드, 카드가 속한 컬럼 = 워크플로 단계
- 내용을 주고받을 때 카드를 Review 컬럼으로 옮기고 담당자를 상대방으로 변경
- 이 방식으로 비동기적으로 소통 — 누군가 작업을 마치면 보드를 업데이트하고, 상대방은 즉시 새로운 작업을 확인
Idea 12. 비동기적으로 상호작용할 방법을 찾아라
- 시사점: 코드 리뷰 결과를 반드시 기다려야 하는가, 아니면 계속 진행하다가 리뷰 완료 후 내부 문제를 고치면 안 되는가? (일반적인 리뷰에서 나오는 내용에 따라 다름 — 탐구해볼 가치가 있음)
- 다음 질문: 회의에 사람들을 참석시켜서 시간을 커플링해야만 하는가? → Practice 5로 이어짐
3.1.3 실천해보기 (Try this)
- 하루 업무 중 다른 사람을 기다리게 되는 일들을 목록으로 기록 — 각 항목마다 대략 손실된 시간도 기록
- 1주일 후, 항목들을 상위 카테고리로 그룹화 (예: 회의 시작 대기, 코드 리뷰 대기, 다른 사람 코드 완료 대기, 기술적 질문에 대한 답변 대기)
- 각 상위 카테고리별로 대기를 없애거나 완화할 방법을 고민
- 팀원들과 논의해 실험 의향이 있는지 확인 → 동의하면 변화를 도입하고 계속 기록
- 한두 달 후, 실험 전/후의 총 대기 시간을 합산 비교 → 필요에 따라 조정
3.2 Practice 5 — 회의, 빌어먹을 회의 (Meetings, Bloody Meetings)
- 도널드 럼즈펠드 식 화법을 빌리면, 모든 사람에게는 세 부류의 지식이 있음
- 자신이 안다는 것을 아는 것
- 자신이 모른다는 것을 아는 것
- 자신이 모른다는 것조차 모르는 것
- 회의를 여는 전반적인 기대: 1번 그룹이 2·3번 그룹을 깨우쳐주는 것
- 현실: 1번과 3번 그룹은 각자의 “현실”을 놓고 논쟁하고, 2번 그룹은 SNS나 하고 있음
- 회의에도 여러 층위가 있음 — 두 사람의 아이디어 논의부터 전체 회의(all-hands)까지
- 저자는 2~3인 소규모 회의에는 반대하지 않음(Practice 6의 회의 예절을 지킨다는 전제) — 하지만 대규모 회의, 특히 애자일 세계에 만연한 전체 회의에는 강한 거부감을 가짐
- 참고: 이 실천의 제목(“Meetings, Bloody Meetings”)은 존 클리즈(John Cleese)의 유명한 회의 관련 교육 영상 제목에서 따온 것
3.2.1 그래도 회의는 “애자일”하지 않은가? (But Meetings Are “Agile”)
- 모든 “애자일” 방법론은 팀 내 소통의 중요성을 강조함 → 회의야말로 모두를 같은 페이지에 두는 좋은 방법이라 여겨짐
- 저자 생각: 아니다
Idea 13. 회의는 “애자일”하지 않다
- 어쩌면 거대 애자일(big-A agile) 진영도 은근히 이를 믿지 않는지도 모름 — 최근 회의를 “세리머니(ceremony)“로 재명명하는 추세가 그 방증
3.2.2 회의는 비효율적이다 (Meetings Are Inefficient)
- 참가자 12명이 모인 회의에서 5분이 소요되면 최소 1시간의 전체 생산성 손실과 맞먹음
- “최소”인 이유
- 회의는 자주 늦게 시작하고, 많은 회의가 예정보다 길어짐
- 소수 그룹이 사담을 나누는 동안 나머지가 기다림
- 화제와 별 관련 없는 이야기를 멈추지 않는 참가자가 항상 있음
- 준비되지 않은 사람들이 많아 나머지가 그들이 따라올 때까지 기다림
- 저자가 가장 자주 겪는 것: 프로젝터가 발표자 기기와 연결되지 않음
- 회의 형식 자체도 비효율을 조장 — 한 사람이 말하면 사람들이 코멘트를 얹고, 그 코멘트가 다시 다른 코멘트를 촉발함. 다음 사람이 말하면 코멘트 연쇄가 다시 시작됨 → **알고리즘이라면 O(n²)**에 해당
Idea 14. 회의의 부정적 영향은 회의실 밖까지 멀리 퍼진다
3.2.3 회의는 불공평하다 (Meetings Are Unfair)
- 회의(특히 온라인 회의)는 가장 선임이거나, 가장 지식이 많거나, 가장 공격적이거나, 가장 무례한 사람에게 쉽게 지배당함
- 언어폭력을 행사하는 사람이 없더라도, 소극적이거나 주니어인 사람들은 진행자가 명시적으로 참여시키지 않는 한 의견을 내지 않는 경향이 있음
- 결과: 같은 사람들이 같은 의견을 반복해서 냄 — 지식이 공유되는 것이 아니라 강요됨
3.2.4 회의는 업무를 방해한다 (Meetings Are Disruptive)
- 회의 참석 = 정해진 시각에 하던 일을 멈춰야 함 — 몰입 중이어도, 대화 중이어도 멈춰야 함
- 그 자체로 방해가 되며 시간 낭비이기도 함 — 회의가 예정되어 있으면 시작 10분 전부터는 새로운 일을 시작하지 않게 됨(시간은 때우지만 진전은 없음)
- 회의가 끝난 후에는 하던 일의 맥락을 되찾는 데 보통 15~30분이 낭비됨
3.2.5 회의는 비용이 많이 든다 (Meetings Are Expensive)
- 스크럼을 예시로 사용(다른 방법론보다 나빠서가 아니라 가장 흔하고 회의가 잘 문서화되어 있기 때문)
- 2주 스프린트 기준 전체 회의(all-hands) 시간
| 회의 | 횟수 | 회당 시간 | 총 시간 |
|---|---|---|---|
| 스프린트 플래닝 | 1 | 4시간 | 4시간 |
| 백로그 정제 | 1 | 2시간 | 2시간 |
| 데일리 스크럼 | 10 | 15분 | 2.5시간 |
| 스프린트 리뷰 | 1 | 2시간 | 2시간 |
| 회고 | 1 | 3시간 | 3시간 |
| 합계 | 13.5시간 |
- 2주(총 80시간) 중 13.5시간이 회의에 사용됨
- 개발자 시간당 오버헤드 비용을 $200라고 가정하고 팀원이 10명이면, 2주마다 $27,000이 회의에 소요됨
- 이 시간과 비용이 그만한 가치가 있는지는 측정해봐야만 알 수 있음 — 유일한 측정 방법은 실제로 방식을 바꿔보고 그 영향을 관찰하는 것
Idea 15. 회의는 비효율적이고, 불공평하고, 업무를 방해하며, 비용이 많이 든다
3.2.6 애초에 왜 모이는가 (Why Meet at All?)
- “원래 그렇게 해왔으니까”라는 답을 제외하고 사람들이 밝힌 회의 목적
- 데일리 스탠드업: 한 일·할 일·겪는 문제를 공유할 기회. 팀이 전체 맥락을 이해하고 서로 조언할 기회를 주는 것이 목적
- 설계 세션: 아키텍처·설계·구현 이슈를 정리. 대안이 논의되고 새 아이디어가 유입되며 주니어 팀원이 새로운 언어·프레임워크·패턴을 학습
- 계획 회의: 팀에게 맥락을 제공 — 어떤 작업을 해야 하는지, 우선순위가 무엇인지 이해시키고 일정에 대한 주인의식을 심어줌
- 리뷰 회의: 성공을 축하하고, 실패를 이해·완화·용서할 기회
- 전체 회의(all-hands): 팀 빌딩 이벤트로 여겨짐 — 서로를 더 잘 알게 되고 신뢰를 쌓을 수 있다고 느껴짐
- 질문: 같은 효과를 더 효율적으로 얻을 다른 방법은 없을까?
3.2.7 무소속 개발자 (Developer Without Portfolio)
- 저자가 만난 성공적인 팀 다수는 새로운 역할을 만들어냄 — “프로젝트 리드”나 “아키텍트”라는 명칭은 다른 함의(baggage)가 있어 저자는 **무소속 개발자(Developer Without Portfolio, DWP)**라는 용어를 사용
- DWP의 유일한 책임: 팀이 원활하고 효과적으로 돌아가게 하는 것 — ==팀이 엔진이라면 DWP는 윤활유==
Idea 16. 무소속 개발자는 팀의 마찰을 줄인다
- DWP는 정기 회의나 데일리 체크인이 아니라 실제로 사람들과 교류함으로써 이를 수행
- 예: 무작위 팀원과 페어 프로그래밍을 하며 대부분의 시간을 보냄
- 예: 노트북을 들고 여러 자리를 옮겨 다니며 상황을 듣고 적절히 대화에 참여
- 정해진 규칙은 없음 — 개인의 성향에 따라 방식은 달라짐
- 결과는 동일해야 함: DWP는 모든 사람이 무엇을 하고 있는지, 어떤 어려움과 오해가 있는지, 어떤 성공과 좌절이 있는지 파악하고 있어야 함
- 이를 바탕으로 DWP는 장애 요인(impediment)을 제거하는 일을 함
3.2.8 데일리 스탠드업 회의 대체하기 (Replacing the Daily Standup Meeting)
- DWP는 팀을 순회하며 계속해서 팀 상황에 대한 전체 그림을 그려나감 — 사람들에게 계획과 문제를 직접 물어봄
- 개발자가 계획을 설명하면 DWP는 이를 프로젝트 전체 맥락에 끼워 맞춤 — 계획한 작업이 불필요함을 발견하거나, 이미 다른 팀원이 도움이 될 코드를 작성해두었음을 알아챌 수 있음
- 개발자가 문제를 겪고 있다면
- 작은 문제: DWP가 직접 설명해 해결
- 큰 문제: DWP가 도움을 줄 수 있는 다른 팀원과 연결
- 이 과정에서 DWP는 진행 상황도 함께 모니터링
3.2.9 리뷰 회의 간소화하기 (Streamlining the Review Meetings)
- DWP는 이미 개발이 어떻게 진행되었는지 좋은 그림을 갖고 있음 — 이슈·오해, 추정치가 빗나간 작업, 성공·고전 사례를 목격해왔음
- “돌아가며 발표하기”식 전형적 리뷰 대신, DWP가 회의 전에 관찰 내용을 미리 공유 → 팀은 해결책에 집중할 수 있음
3.2.10 설계 회의 간소화하기 (Streamlining Design Meetings)
- DWP는 명시적으로 팀의 아키텍트가 아니며 설계 결정에 특별한 권한을 갖지 않음
- 그러나 팀원 중 프로젝트 전체를 가장 잘 조망하는 사람이며 경험도 갖추고 있어, 제안을 하거나 대안을 제시하기에 좋은 위치에 있음
- 설계는 협업으로 진행하는 것이 가장 좋은 경우가 많음 — 이는 회의가 필요할 수 있음을 의미
- 저자의 강력한 제안: 참석 인원을 3~4명으로 제한하고, Practice 6의 회의 예절 가이드라인을 따를 것
3.2.11 코딩하지 않는 팀원을 추가하라고요? (You Expect Us to Add an Extra Team Member Who Isn’t Coding?)
- 저자: 기대하지는 않지만, 시도해보길 바람
- 정당화 근거: 회의에 쓰이는 개발자 시간을 모두 더해보면 — 10명 팀 기준 2주 스프린트당 약 135시간
- DWP 도입으로 이 회의들을 없애면 개발자 1.5명 이상을 추가로 투입한 것과 동일한 효과
- 회의 시간을 절반만 줄이더라도 개발자 1명분 시간의 80퍼센트가 확보됨 — DWP의 다른 이점들이 나머지 20퍼센트를 쉽게 메꿔줌
3.2.12 피할 수 없는 회의도 있다 (Some Meetings Are Unavoidable)
- 프로젝트 착수, 대규모 프로젝트 마무리, 회사 공지 등 팀 전체에 실제로 영향을 미치는 사안에는 전체 회의가 분명히 적절함 — 통제권이 있다면 회의 예절 가이드라인을 따를 것
- 그 외에도 설계 세션, 경영진 보고, 하위팀 조율 등 회의는 필요함 — 소규모로 유지하고(필요시 사람을 추가로 부르면 됨) 회의 예절을 따를 것
3.2.13 실천해보기 (Try this)
- DWP 개념을 팀과, 이후 관리자와 논의 → 모두 동의하면 실험 진행
- 중요한 지표를 정하고 실험 시작 전 3개월치 데이터를 확보
- DWP 역할을 맡을 사람을 물색 — 경험이 충분하고, 사람들과 대화하는 것을 즐기며, 자신의 의견을 잠시 내려놓을 수 있는 사람
- 몇 달에 걸쳐 새로운 업무 방식으로 전환하며 지표를 다시 수집
- 6개월 후 지표를 비교하여 지속 여부 결정
- 일반적인 성과 지표와 함께 개발자 만족도(sentiment) 지표도 반드시 포함할 것
3.3 Practice 6 — 예의범절: 회의를 꼭 해야 한다면 (Decorum: If You Have to Have a Meeting)
- 저자가 가장 꺼리는 회의: “사람들 모아서 X에 대해 얘기해보자”라는 식으로 소집되는 회의 — 거의 확실하게 비구조적이고 비생산적이며 불쾌할 가능성이 높음
- 저자는 회의 자체를 완전히 반대하지는 않음 — 다만 회의를 해야 한다면 즐겁고 생산적으로 만들 책임을 누군가 져야 함
- 회의를 사려 깊고 효과적으로 운영하기 위해 필요한 요소
flowchart TD
A[명확한 목표 설정] --> B{회의가 정말 필요한가}
B -- 필요없음 --> Z[회의 취소, 다른 방법으로 해결]
B -- 필요함 --> C[준비: 자료를 사전에 공유]
C --> D[참석자 선정: 핵심 인원만]
D --> E[일정 잡기: 배려 있는 시간 선택]
E --> F[진행: 목표 안내, 발언 기회 분배, 페이싱 관리]
F --> G[마무리와 후속 조치: 합의 기록, 요약 공유]
- 모두 기본적인 내용이지만 보통 무시되거나 대충 이루어짐 — 단순성(그리고 효율성)은 규칙을 지키는 데서 나옴
Idea 17. 좋은 회의는 오직 잘 준비된 회의뿐이다
3.3.1 명확한 목표 (Clear Objective)
- 목표 = “애초에 왜 이 회의를 하고 싶은가?“에 대한 답
- 모든 애자일 행동과 마찬가지로 목표는 측정 가능해야 함 — 회의가 의도한 바를 달성했는지 확인할 수 있어야 함. 즉 목표는 경계가 분명하고 측정 가능해야 함
| 좋은 목표 예시 | 검증 방법 |
|---|---|
| 영업 모듈의 인수인계를 레드팀에서 블루팀으로 계획한다 | 회의 말미에 참석자들이 계획을 함께 검토해본다 |
| 연휴 기간 지원팀 휴가 일정에 합의한다 | 모든 지원 인력의 가능 여부를 보여주는 캘린더가 만들어지고 빈 공백이 없는지 확인한다 |
| 운영팀이 데이터베이스 스키마 마이그레이션에 필요한 것을 갖추도록 한다 | 회의가 끝나기 전 마이그레이션 계획을 함께 검토하고 문제없음을 모두가 동의한다 |
| 나쁜 목표 예시 | 문제점 |
|---|---|
| 다음 릴리스 배포에 대해 논의한다 | 논의 자체는 문제 없지만 목표가 아님 — 결과를 측정할 방법이 불분명함 |
| 프로젝트 추정치를 개선한다 | 가치 있는 목표일 수 있으나 회의 자체에서 측정 가능한 결과가 없음. 예: “이전 추정에서 잘못된 점 하나를 찾아내고 재발 방지 방법을 마련한다”로 바꾸는 것이 좋음 |
3.3.2 회의가 정말 필요한가 (Is a Meeting Needed?)
- 회의는 실제 업무를 하는 것보다 쉽다는 이유로 소집되는 경우가 매우 많음
- 스스로에게 솔직해질 것 — 다른 팀원들을 끌어들이는 대신 자신이 직접 시간을 투자하면 전체 노력을 절약할 수 있지 않은가?
- 예시: 휴가 일정 합의 — 공유 캘린더가 있는데 왜 모두를 한 방에 모아야 하는가? 충돌이 생기면 에너지가 필요하겠지만 전체 노력 측면에서는 명백한 이득
- 불필요한 회의를 피하는 데 창의적일 것
3.3.3 회의를 뒤집어라 (Flip This Meeting)
- 전통적 학교: 수업 시간에 배우고 숙제로 지식을 테스트
- 일부 학교의 플립 클래스룸(flipped-classroom) 모델: 학생이 수업 전 교사가 준비한 자료로 스스로 학습 → 수업에서는 이해를 다지는 활동을 하고 교사는 지도와 도움을 제공
- 우리는 회의를 전통적 교실처럼 운영하는 경우가 너무 많음 — 강의를 들으러 참석했다가 자리로 돌아가 과제를 받음
- 회의에서 긴 강의를 할 핑계는 없음 — 동료들을 모은 이유는 문제를 해결하기 위함이지 이슈를 듣기 위함이 아님
- 회의를 뒤집을 것: 회의를 공지하기도 전에 참석자에게 줄 자료를 준비 — 이 자료는 필요한 배경 지식을 다뤄, 모두가 모이자마자 바로 본론으로 들어갈 수 있게 함
Idea 18. 회의를 뒤집어라: 사전에 공유하고, 함께 모여서는 검토하라
- 아젠다 작성 여부는 스타일에 따라 다름 — 아젠다를 두고/두지 않고 모두 시도해보고 어느 쪽이 나은지 확인할 것
- 숨은 효과: 자료를 준비하는 과정에서 문제를 잘못 이해하고 있었음을 스스로 깨닫는 경우가 많음 — 회의의 필요성 자체가 사라짐, 모두에게 이득
3.3.4 참석자 선정 (Selecting Attendees)
- 핵심은 절약(parsimony)
- 브리핑 자료를 준비한 뒤, 결과 도출에 필요한 핵심 인원만 선정
- 요약본을 보내 본인이 참석해야 한다고 생각하는지 물어보고, 참석자 목록에 빠진 사람이 없는지도 확인
- 그룹이 작을수록 토론의 질이 좋아지고 회의도 빨리 끝남
3.3.5 일정 잡기 (Scheduling)
- 회의를 잡기 가장 좋은 시간에 대한 온갖 속설이 존재함 — 점심 직후는 피해라(졸림), 퇴근 직전은 피해라(집에 가고 싶어함), 월요일 아침은 피해라(밀린 일 처리 중) 등
- 저자 생각: 절대적으로 좋은 시간은 없지만 다른 사람을 배려할 수는 있음
- 회의는 사실상 참석자 전원이 하던 일을 방해하는 것임을 기억할 것 — 자연스러운 휴식 지점(예: 아침 첫 시간)에 시작하도록 시도해볼 것
- 여러 시간대에 걸친 팀이라면 배려가 필요함 — 근무일이 전혀 겹치지 않는다면 누가 일찍 일어날지 돌아가며 정할 것
- 혹은, 그보다 나은 방법: 아예 회의를 하지 않는 것
3.3.6 진행 (Direct)
- 회의를 소집했다면 모두가 회의에서 가치를 얻도록 하는 것이 소집자의 책임 — 진행을 이끌어야 함
- 정시에 시작하고, 지각자를 위해 앞의 내용을 다시 요약하지 말 것
- 회의의 측정 가능한 결과가 무엇인지 모두가 이해하도록 할 것 — 아젠다가 있다면 그대로 따를 것
- 모든 사람이 발언 기회를 갖도록 할 것 — 말이 많은 사람을 부드럽게 제지해야 할 수도 있음. 두 사람이 논쟁에 빠지면 한두 번 주고받는 것은 괜찮지만 그 이상이면 “Jan은 X라고 생각하고 Pat은 Y라고 생각하는군요. 다른 의견 있으신 분?“과 같이 개입할 것
- 동시에 여러 대화가 진행되는 회의를 저자는 싫어함 — 이를 막는 전형적 방법: 회의에 토큰(예: “Mr. Squeaky” 같은 물건)을 가져와 그 토큰을 쥔 사람만 말할 수 있다는 규칙을 정하는 것(실물 세마포어). 부수 효과: 토큰이 없다는 이유로 임원(VP)에게 “아직 말할 수 없다”고 말하는 데서 오는 소소한 통쾌함도 있음
- 회의 소집자의 역할 중 하나는 논의를 조정(moderate)하는 것 — 사적인 논의를 막고 조용한 참가자의 발언을 독려. 결국 진행자가 존중을 모범 보이고 필요하면 강제하면 사람들도 배우게 됨
- 회의 운영에서 가장 까다로운 부분 중 하나는 페이싱(pacing) — 목표를 종료 시각 전에 달성하도록 관리해야 함. 회의 시작 전에 마일스톤과 시각을 미리 적어두면 시간 관리뿐 아니라 논의를 이끌어가는 로드맵 역할도 함
- 초보자가 흔히 하는 실수: 회의가 끝나면 바로 흩어지는 것 — 항상 마무리를 위한 몇 분을 남겨둘 것
3.3.7 마무리와 후속 조치 (Wrap Up. Follow Up)
- 회의에는 측정 가능한 목표가 필요함을 기억할 것 — 단순히 “모두 동의했다”는 것만으로는 부족함
- 결과를 처음부터 다시 짚어가며 각 합의 사항을 말로 확인하고, 소집자나 서기가 이를 기록해둘 것
- 참석자들에게 감사를 표하고, 예정된 종료 시각 전에 또는 정시에 마무리할 것
- 회의 후에는 실제 결과를 목표와 비교하는 시간을 가질 것 — 회의가 원하는 바를 달성했는가? 어느 쪽이든, 무엇이 이루어졌고 앞으로 어떤 영향을 미칠지 요약한 간단한 메모를 모든 참석자에게 보낼 것
3.3.8 탐색적 회의 (Exploratory Meetings)
- 위의 제안들이 적용되지 않는 한 종류의 회의가 있음
- 예시: 애자일 소프트웨어 개발 선언(Agile Manifesto)을 만든 17명의 첫 모임
- 시작할 때 알고 있던 것은 소프트웨어 개발 관행에 대한 아이디어를 비교하고 공통점을 찾고 싶다는 것뿐 — 계획도 아젠다도 없었음
Idea 19. 탐색적 회의는 규칙이 다르다
- 진행 방식
- 테이블을 치우고 의자를 타원형으로 배치
- 서로 자기소개 후 인덱스 카드 여러 장을 돌림 — 중요하다고 생각하는 것을 카드 한 장에 하나씩 적음
- 카드를 얼마나 정확하게 프리스비처럼 던져 무더기에 넣을 수 있는지 겨루며 즐거운 시간을 보냄
- 휴식 시간 동안 누군가 카드를 주제별로 정리
- **도트 투표(dot vote)**로 다룰 순서를 정함
- 그 순서는 약 90분간 유지되었고, 그 무렵 충분한 논의가 이루어져 점심시간 동안 초기 “우리는 X보다 Y를 더 가치 있게 여긴다” 식의 선언문 초안이 나옴
- 탐색적 회의는 의사결정 회의와는 매우 다르며 제대로 하기가 훨씬 어려움 — 일부는 외부 진행자를 고용해야만 진행함. 저자는 이런 회의를 한 번 경험했는데 매우 성공적이었다고 함
참고 — 도트 투표(Dot Votes)
- 그룹이 여러 옵션 중 집단적으로 선택할 수 있게 하는 방법
- 특징: 한 사람이 한 표가 아니라 여러 표를 가짐
- 표를 한 항목에 몰아줄 수도, 여러 항목에 나눠줄 수도 있음
- 최종적으로 각 항목이 받은 표 수가 순위를 결정
- 항목이 10개라면 보통 한 사람당 3~4표를 배정
- 경우에 따라 이해관계자(stakeholder)에게 추가로 1~2표를 더 주어 영향력을 높이기도 함
- 이름의 유래: 전통적으로 화이트보드에 항목을 적고, 사람들이 나와서 원하는 항목에 색깔 스티커(점)를 붙였기 때문
3.3.9 실천해보기 (Try this)
- 평소 회의 진행자가 아니라 참석자 역할이라면 무엇이 효과 있고 없는지 배우기 좋은 위치에 있는 것 — (조심스럽게) 메모를 해둘 것. 언젠가 직접 회의를 운영하게 될 때 도움이 됨
- 예절을 모범 보이기(Model decorum): 다른 모든 사람이 이렇게 행동해주길 바라는 방식으로 스스로 행동할 것 — 뜻이 맞는 동료들을 함께 끌어들일 것
- 단순하게 운영하기(Run simple): 회의 운영이 본인 역할의 일부라면 이런 관행들을 도입해볼 것 — 참석자들에게 무엇을, 왜 하는지 설명하고 도움을 요청하며 피드백을 받을 것
Idea 20. 즐겁게 지내라
3.4 Practice 7 — 스킬을 퍼뜨려라 (Spread Your Skills)
배움 속에서 가르치게 되고, 가르침 속에서 배우게 된다 — 필 콜린스, “Son of Man”
- 저자의 2000년대 중반 경험: 한 컨퍼런스에서 초보 프로그래머가 경험 많은 참가자와 페어링할 수 있는 방이 마련됨
- 당시 저자는 서구권에서 가장 경험 많은 루비 프로그래머 중 한 명으로 여겨졌고, 몇 시간 들어가 학생들에게 지혜를 나눠주겠다는 생각으로 참여
- 결과: 그 몇 시간 동안 지난 2년간 배운 것보다 프로그래밍에 대해 더 많이 배움
- 6년 넘게 루비를 해오며 자신만의 스타일에 정착해 있었음 — 같은 방식으로 문제에 접근하고 같은 관용구를 사용
- 함께한 학생들은 단 한 단어로 이 모든 것을 흔들어놓음: “왜(Why)?”
- 왜 for 루프 대신 each를 쓰는지, 왜 배열 인덱스는 0부터 시작하는지, 왜 항상 인덱스 변수를 i라고 부르는지 — 마치 조숙한 세 살짜리와 코딩하는 느낌
- 모든 질문이 저자를 멈춰 세우고 생각하게 만듦 — 겉보기에 기초적인 질문에 저자가 30초간 침묵하며 답을 찾으려 애쓰는 모습에 학생들도 놀랐을 것
- 질문뿐 아니라, 초보자에게 개념을 설명할 방법을 찾기 위해 스스로 깊이 파고들어야 했음 — Hash나 File 객체를 이해되면서도 유용한 추상화로 어떻게 설명할 수 있을까 고민
- 세션이 끝난 후 저자는 한껏 고무되면서도 지쳐 있었음 — 필 콜린스의 말대로 가르치는 가운데 배우게 된다는 것을 새삼 확인함
3.4.1 스킬을 나누면 스킬이 증폭된다 (Sharing Skills Amplifies Skills)
- 초콜릿 바를 나누면 내가 먹을 몫이 줄어듦 — 하지만 스킬을 나누면 나와 상대방 모두 더 많이 갖게 됨. 이것은 포지티브섬 게임
- 이것이 나눔의 이기적인 이유 — 나눔은 나 자신을 더 나은 개발자로 만듦
Idea 21. 배우기 위해 가르쳐라
- 물론 반대편도 있음: 나눔의 대상이 되는 사람도 내 경험과 지혜로부터 얻는 것이 있음 — 그들은 더 강해지고 팀도 더 강해짐
3.4.2 평등이 목표가 아니다 (Equality Is Not the Goal)
- 모든 팀원이 모든 스킬에 똑같이 능숙한 팀이 있다면 그 팀은 지독하게 비효율적일 것
- **전문화(specialization)**는 팀이 구성원들의 다양한 지식과 경험으로부터 이득을 얻게 해줌 — 이런 다양성은 팀의 회복탄력성도 높임
- 비유: 대리석가재, 치타, 채널 아일랜드 여우는 개체 간 유전적 다양성이 매우 낮은 종의 예 — 이 때문에 취약함. 환경 변화나 질병이 하나를 덮치면 전체에 영향을 미침
- 동질적인 팀도 마찬가지 — 모두가 Java와 Oracle 전문가인데 세상이 Postgres와 JavaScript로 이동하면 팀 전체가 순식간에 시대에 뒤처짐
- 시각화 예시: 5인 팀, 각자 3개 데이터베이스(Postgres, Oracle, MySQL)·3개 언어(Java, JavaScript, Rust)·3개 환경(백엔드, 프론트엔드, SPA)에 대해 0~5점의 경험치를 가짐
- 팀 전체의 스킬을 단순 합산할 수는 없음 — 예를 들어 팀원1이 Oracle 레벨4, 팀원2가 레벨3이라면 상당 부분 지식이 겹칠 가능성이 높기 때문
- 저자가 사용한 방식: 각 항목별로 팀원들의 개인 값을 정렬한 뒤, 가장 높은 값부터 가중치 1, 1/2, 1/4, 1/8, 1/16을 곱해 합산 → 항목별 “팀 전체 경험치” 산출(겹치는 지식을 보정하기 위함)
// 팀의 항목별(예: 특정 언어·DB·환경) 총 경험치를 계산하는 함수
// 값이 큰 순서로 정렬한 뒤 1, 1/2, 1/4, 1/8, 1/16 순으로 가중치를 곱해 합산
// -> 팀원 간 지식이 겹칠 가능성을 반영해 단순 합산보다 보수적으로 계산
function calculateTeamExperience(memberScores: number[]): number {
const weights = [1, 1 / 2, 1 / 4, 1 / 8, 1 / 16];
return [...memberScores]
.sort((a, b) => b - a)
.reduce((total, score, index) => {
const weight = weights[index] ?? 0;
return total + score * weight;
}, 0);
}
// 예시: 5명 팀원의 특정 항목 경험치가 [4, 3, 4, 2, 3]인 경우
calculateTeamExperience([4, 3, 4, 2, 3]);
// 정렬 결과 [4, 4, 3, 3, 2]
// 4*1 + 4*0.5 + 3*0.25 + 3*0.125 + 2*0.0625 = 8.125
- 결과 비교
- 다양한 스킬을 가진 팀: 개인별로는 각 항목에서 약하더라도, 전체적으로 3개 데이터베이스·3개 언어·3개 환경 모든 영역에서 꽤 건강한 커버리지가 나타남
- 동질적인 팀(모두 Oracle·Java·백엔드 전문가): 해당 세 영역에서만 강함. 그런데 흥미롭게도 이 세 영역의 팀 전체 점수를 다양한 팀의 동일 세 영역 점수와 비교하면, 개인별로는 더 강함에도 불구하고 팀 전체 점수는 크게 높지 않음 — 스킬을 더할 때 겹치는 부분이 많기 때문
- 즉, ==다양한 팀은 원래 강점 영역에서 거의 손해를 보지 않으면서 다른 영역의 커버리지를 사실상 공짜로 얻는 셈==
Idea 22. 모든 경험은 유용하다. 다양한 경험이 더 많은 것을 가르쳐준다
- 팀을 꾸릴 때는 당장 필요한 스킬을 찾되, 가끔은 예상 밖의 인재(wild card)를 배제하지 말 것
- 합류할 팀을 고르는 입장이라면, 팀의 주력 분야가 자신의 익숙한 영역에서 살짝 벗어나 있을 때 더 많이 배우게 됨
- 다양성은 회복탄력성이다 — 동물 종의 유전적 다양성이든 팀 내 개발자들의 다양성이든 마찬가지
3.4.3 교사이자 학생이 되어라 (Become a Teacher and a Student)
- 사람들을 돕는 조용한 리듬에 익숙해질 것 — 나서지 않으면서 경험을 나눌 것
- 누군가를 바로잡고 싶어지는 순간, 행동하기 전에 멈추고 생각할 것 — 무엇이 그 실수를 하게 만들었는가? 문제를 직접 고쳐주기보다 원인을 함께 탐구하고 스스로 해결책을 찾도록 할 수는 없는가?
- 누군가 내 작업물에서 문제를 발견했다면 방어적으로 반응하지 않을 것 — 대신 그들이 한 말을 곰곰이 생각해볼 것. 이해가 된다면 감사를 표할 것. 이해가 되지 않는다면 실수의 근본 원인을 배울 수 있도록 더 깊이 파고들어달라고 요청할 것
3.4.4 좋은 교사가 하는 일 (What Good Teachers Do)
- 대부분의 사람에게는 인생을 바꿔준 스승이 한둘 있음 — 저자에게는 코드네임 “Albert”라 불리던 Knight 선생님이었음(수학을 가르쳤고 3년간 담임이기도 했음)
- 이런 스승들은 배우는 과정의 일부로 느껴지게 만드는 방식으로 사람을 끌어들임 — 스승 자신이 몰입해 있기에 학생도 몰입하게 됨. 이는 부분적으로는 성격이지만 배울 수 있는 측면도 있음
- 좋은 교사는 자신에게 **사명(mission)**이 있다고 느끼고, 그것에 대해 이야기하길 좋아함
- 저자가 집필 중 참고한 자료: 좋은 교사의 자질에 관한 짧은 글 3편(NU, Teachers of Tomorrow, WGU 블로그)
- 교실 환경을 겨냥한 글이라 상황에 맞게 조정이 필요하지만, 좋은 교사는 원래 적응력이 좋음
- 오늘 스승에게 감사 인사를 전하자: 인생의 그 특별한 스승이 아직 계신지 확인하고 연락해볼 것 — 근황도 묻고 학창 시절의 무용담도 다시 들어볼 것. 부모라면 자녀를 변화시킨 선생님께 학년 말에 소소한 감사 인사를 전하는 것도 좋음
3.5 Practice 8 — 정보를 자유롭게 흐르게 하라 (Let the Information Go Free)
- 소통은 A가 B에게 정보를 전달하는 기계적 과정 이상의 것
- 저자가 만난 성공적인 팀들은 하나같이 정보가 자유롭게 이용 가능해야 한다고 강조함
- 어떤 팀은 질문하기 쉬운 환경을 만드는 것에 대해 이야기했고, 다른 팀은 정보가 마치 대기(ambient)처럼 존재해 팀원들이 의식하지 못한 채 자연스럽게 흡수하는 환경을 만드는 것에 대해 이야기함
- 무엇을 하든, 정보가 흐르게 하려면 소통에 대한 적극적인 관리가 필요함
3.5.1 정보 사일로 없애기 (Eliminate Information Silos)
- 자연스러운 현상: 프로젝트의 특정 영역을 담당하는 소규모 팀에서 일하면 그 팀에 애착을 갖게 됨
- 팀마다 코드의 개념에 특화된 은어(jargon)가 생기고, 기능과 설계를 이야기할 때 줄임말을 사용하게 됨
- 시간이 지날수록 다른 개발자와 자신의 작업에 대해 이야기하기가 점점 어려워짐 — 멋진 내용을 이야기하기 전에 먼저 배경 지식을 채워줘야 하기 때문
- 결과적으로 굳이 그러지 않게 됨 — 정보와 지식이 각 소규모 팀에 국한되어 정보 사일로를 형성
Idea 23. 쌓아둔 지식은 썩어가는 지식이다
- 그러나 나눈 지식은 곱해지는 지식임 — 지식을 나누는 것은 단순히 주는 것이 아니라 상대방의 기존 지식을 증폭시키는 새로운 아이디어를 주는 것
- 이 새로운 아이디어는 가만히 있지 않고 기존의 모든 아이디어와 상호작용하며 완전히 새로운 직관을 합성해냄 — ==지식 두 조각의 합은 지식 두 조각보다 크다==
3.5.2 스스로를 퍼뜨려라 (Spread Yourself Around)
- 지식 이전을 중요하게 여기는 팀들이 효과를 내는 방식
- 첫째, 팀의 모든 사람에게 가르칠 것과 배울 것이 있음을 명시적으로 밝힘
- 가르치는 것과 단순히 무엇을 하라고 알려주는 것의 큰 차이를 설명함 — 가르침은 발견의 과정이며, 교사는 모든 것을 아는 신탁이 아니라 안내자·코치 역할을 함
- 팀원들은 질문하고 이슈를 논의하도록 독려됨
- 때로는 답을 실험을 통해서 찾아야 함 — 오래된 orient(정향)-step(실행)-learn(학습) 루프
- 이런 배경과, 다양성이 팀을 강하게 만든다는 발견을 바탕으로 성공적인 팀들이 스킬을 퍼뜨리기 위해 사용하는 기법들
| 기법 | 설명 |
|---|---|
| 페어 프로그래밍 | 페어를 자주 교체하고, 시간이 지나며 모든 사람이 서로 한 번씩은 페어링하도록 함 |
| 무소속 개발자(DWP) | 질문을 가진 사람과 답을 가진 사람을 연결. 명시적인 질문이 없어도 어려움을 겪는 사람을 발견하면 도와줄 사람을 보냄 |
| 브라운백 런치 | 점심 시간에 모여 관심사를 논의. 때로는 매주 한 챕터씩 읽는 북클럽 형태, 때로는 돌아가며 발견한 흥미로운 것을 발표 |
| 개발자 셔플 | 몇 달마다 각 팀에서 한 명씩 다른 팀으로 이동. 지식이 자연스럽게 퍼지고, 개인은 다른 문제 영역을 경험하며 때로는 자신의 열정을 발견함. 부수 효과: 정체된 프로젝트가 새로운 시각을 얻고, 새 팀원을 받은 팀은 자신의 코드를 새로운 눈으로 다시 보게 되어 정리·리팩터링이 촉발됨 |
| 코드 리뷰 | 팀 외부 사람이 코드 리뷰를 수행하도록 함 |
| 몹 룰(Mob rule) | 여러 팀을 하루 동안 합쳐서 로깅 개선·빌드 속도 향상·라이브러리 업데이트 등 팀들이 공통으로 해결해야 할 일반적인 문제를 몹 프로그래밍으로 해결 |
| 스킬 믹스 | 상호 보완적인 스킬을 가진 사람들로 팀을 구성 — 자연스럽게 지식을 나눌 수밖에 없게 됨 |
| 경험 다양성 | 경험 수준이 다른 사람들로 팀을 구성. 주니어는 시니어의 경험에서 이득을 얻고, 주니어의 질문이 시니어로 하여금 당연하게 여겨왔던 것을 다시 생각하게 만듦. 단, 경험 격차가 너무 크면 소통 자체가 불가능해질 수 있으므로 주의 — 매우 경험 많은 개발자는 명시적 패턴이 아니라 직관으로 일하는 경우가 많아 “그냥 당연한 거죠”라는 답변은 누구에게도 도움이 되지 않음 |
| 정규 교육 | 부서가 기술 플랫폼을 바꿀 때 며칠을 따로 떼어 적응을 도움. 성과는 엇갈림 — 준비와 가르치는 사람의 열의에 따라 결과가 크게 갈림 |
| 금요일 놀이 시간 | 일주일 중 반나절을 프로젝트 외 활동에 할당. 한 사람이 프로젝트를 제안하고 다른 사람이 그것을 배우고 싶어 페어링하도록 장려. 도구 작업, 새로운 언어 학습, ESP32 실험 등 흥미로운 것이면 무엇이든 가능 |
Idea 24. 나눈 지식은 곱해진 지식이다
- “금요일 놀이 시간” 사례가 보여주는 중요한 점: 스킬 이전(및 학습 전반)이 반드시 업무 관련 기술에 국한될 필요는 없음 — 소프트웨어는 전문 분야 간에 스킬이 쉽게 전이되는 산업 중 하나
- 다만, 리뷰어 Noel Rappin의 지적: 대규모 조직에서는 **경영진의 동의(management buy-in)**가 반드시 필요함 — 이런 활동들은 대체로 단기 성과에 초점을 맞춘 관리자의 인센티브와 상충하기 때문. 경영진이 동의하지 않으면 모두가 “너무 바쁘다”는 이유로 계속 이런 활동을 미루게 됨
- 그리고 물론, 상위 경영진을 설득하는 데에도 소통 능력이 필요함
3.5.3 실천해보기 (Pick an Activity)
- 위 목록 중 팀이 아직 하지 않고 있는 것을 하나 골라 실행해볼 것
- 다른 팀원들에게 이를 제안하고, 필요하다면 경영진과도 협상해야 할 수 있음
- 저자의 농담 섞인 한마디: “Dave가 보내서 왔다고 말해도 좋다”
3.6 부록 — 이 장의 핵심 아이디어(Idea) 모음
| 번호 | 소속 실천 | 핵심 아이디어 |
|---|---|---|
| 11 | Practice 4 | 커플링은 팀과 코드 모두에 영향을 미친다 |
| 12 | Practice 4 | 비동기적으로 상호작용할 방법을 찾아라 |
| 13 | Practice 5 | 회의는 “애자일”하지 않다 |
| 14 | Practice 5 | 회의의 부정적 영향은 회의실 밖까지 멀리 퍼진다 |
| 15 | Practice 5 | 회의는 비효율적이고, 불공평하고, 업무를 방해하며, 비용이 많이 든다 |
| 16 | Practice 5 | 무소속 개발자는 팀의 마찰을 줄인다 |
| 17 | Practice 6 | 좋은 회의는 오직 잘 준비된 회의뿐이다 |
| 18 | Practice 6 | 회의를 뒤집어라: 사전에 공유하고, 함께 모여서는 검토하라 |
| 19 | Practice 6 | 탐색적 회의는 규칙이 다르다 |
| 20 | Practice 6 | 즐겁게 지내라 |
| 21 | Practice 7 | 배우기 위해 가르쳐라 |
| 22 | Practice 7 | 모든 경험은 유용하다. 다양한 경험이 더 많은 것을 가르쳐준다 |
| 23 | Practice 8 | 쌓아둔 지식은 썩어가는 지식이다 |
| 24 | Practice 8 | 나눈 지식은 곱해진 지식이다 |
파트 2. 환경을 단순화하라
- 개발자가 매일 마주하는 도구와 환경(데스크톱, 터미널, 에디터, 개발 머신)을 어떻게 자동화하고 단순화할지 다루는 파트
- 이번 노트 범위에서는 이 파트의 첫 챕터인 챕터 4(업무 자동화)를 다룸
챕터 4. 업무 자동화
- 저자는 뇌에 지주막 낭종(arachnoid cyst) 보유 — 왼쪽 측두엽에 6cm 크기의 공동이 있으며, 청각 변별력과 기억 일부를 담당하는 부위에 해당
- 결과: 시끄러운 곳에서 대화를 알아듣기 어려움(입 모양 읽기 + 남들 웃을 때 따라 웃기로 보완), 기억하고 싶은 것은 잘 기억 못 하지만 관계없는 정보(첫 차 번호판 등)는 오히려 잘 기억함
- 기억력 문제 보완책: 데이북(daybook) [3], 목록, 캘린더, 냉장고 메모 등 외부 장치에 정보를 위임(offload)
- 소프트웨어 작업에서 뇌 용량 부족을 보완하는 가장 중요한 기법이 바로 ==자동화==
- 자동화할수록 기억해야 할 것이 줄어듦
- 버튼 클릭이나 명령어 한 줄로 배포할 수 있으면 체크리스트를 뒤질 필요가 없고, 17번째 단계를 놓치는 일도 없음
- 파일 저장 시 테스트를 자동 실행하면 “테스트 돌렸나?“를 되뇌는 뇌의 일부를 쓸 필요가 없음
- 환경 설정을 자동화하면 라이브러리 버전 문제를 추적하며 시간을 낭비하지 않음
- 저자만의 특이한 상황이 아니라, 관찰한 훌륭한 개발자들은 모두 인지 부하를 줄이기 위해 자동화를 만듦 → 스트레스 감소, 더 행복해짐, 더 나은 소프트웨어 결과로 이어짐
- 이 챕터에서 저자가 자동화하는 영역들을 소개함(독자마다 초점은 다를 수 있음을 전제)
이 챕터에서 다루는 실천법 개요:
| 실천 | 제목 | 핵심 주제 |
|---|---|---|
| 실천 9 | 데스크톱이 나를 위해 일하게 하라 | 워크스페이스와 창 관리자로 산만함 줄이기 |
| 실천 10 | 터미널이 나를 위해 일하게 하라 | 터미널/셸의 힘을 활용해 생산성 높이기 |
| 실천 11 | 나머지 모든 것을 자동화하라 | 0일차부터 배포 자동화, 단일 명령 배포 |
| 실천 12 | 에디터를 온전히 내 것으로 만들라 | 에디터를 다듬고 마우스 의존 줄이기 |
| 실천 13 | 개발 머신 설정을 자동화하라 | 도구/설정을 버전 관리로 복구 가능하게 만들기 |
실천 9. 데스크톱이 나를 위해 일하게 하라
- 하루 중 대부분의 시간을 모니터를 들여다보며 보냄 — 소프트웨어 개발이라는 추상 세계로 들어가는 창
- 코드 작성/유지, 프로그램 실행, 리서치, 잡다한 인터넷 서핑 등에 사용
- 이 환경을 자신의 필요와 프로젝트에 맞게 최대한 맞춤화하는 것이 중요
- 핵심은 두 가지: 자신에게 맞는 색상 테마 찾기 + 최대한 자동화하기
9.1 워크스페이스
- 하루 동안 여러 다른 작업을 수행함(집필, 툴체인 업데이트, 이메일 처리, 소셜미디어/웹 탐색 등)
- 저자는 스스로 산만해지기 쉬움을 인정 — 코딩 중 이메일 창이 업데이트되면 하던 일을 멈추고 확인하게 됨
아이디어 25. 시간을 정리하듯 데스크톱을 정리하라
- 해결책: ==워크스페이스==(각각 고유한 창과 앱을 가진 완전히 별도의 데스크톱)를 구성하고 화면에서 전환하며 사용
- 여러 모니터를 두는 것과 비슷하지만, 한 번에 하나만 보이도록 배치하는 방식
- 예시 구성
- 이메일용 워크스페이스: 이메일 클라이언트 + 브라우저 창(이메일 답장 시 웹 콘텐츠 참고 필요)
- 집필용 워크스페이스: 에디터 + 북 빌더 + PDF 프리뷰어
- 프로젝트별 워크스페이스: 프로젝트마다 하나씩 구성
- 장점: 하던 일에서 벗어났다가 나중에 정확히 그 상태로 복귀 가능 → 산만함 감소, 수많은 창이 겹쳐 쌓이는 상황 방지
- 결과: 삶이 단순해지고 생산성이 높아진 느낌을 받음
9.2 무엇을 사용하는가
- 주로 macOS(OS X)에서 작업, 가끔 Linux 박스로 이동
- 예전에는 macOS 기본 Spaces 기능 사용 — 불편할 때가 있었고, 완전한 키보드 제어가 안 되는 점이 아쉬웠음
- 지금은 타일링 창 관리자(tiling window manager)를 사용하며 매우 만족
- 창이 겹치지 않고 수평/수직 스택으로 배치됨
- 처음엔 걸림돌이 될까 걱정했지만 오히려 더 깔끔하다고 느끼게 됨
- 키보드로 완전히 제어 가능 → 마우스 없이 워크스페이스 간 창을 이동(fling) 가능
실습 — 일하는 자신을 관찰하라
- 창 관리자의 키 바인딩을 배우는 것이 그만한 가치가 있을지 의문이 들 수 있음
- 먼저 현재 화면을 어떻게 관리하는지 스스로 관찰해볼 것: 원하는 창을 찾으려고 Alt-Tab을 반복하는가? 주기적으로 창 정리를 해야 하는가? 마우스로 탐색해야 하는가?
- 해당된다면 창/워크스페이스 관리 도구를 1주일 정도 실험해볼 가치가 있음
- 워크스페이스 사용해보기: 아직 워크스페이스를 안 쓰고 있다면 사용 가능한 도구를 찾아 1주일간 시도. 설정에 며칠 걸릴 수 있음. 이후 작업을 구획(partition)하는 습관을 들여보고 자신에게 맞는지 확인
- 창 관리자 사용해보기: 워크스페이스 개념에 확신이 없더라도 타일링 창 관리자를 써볼 것. 처음엔 많은 창을 한 번에 다 볼 수 없어 답답할 수 있으나, 그것이 핵심일 수도 있음(정말 그 많은 창이 동시에 다 필요한지 자문)
실천 10. 터미널이 나를 위해 일하게 하라
- 저자는 터미널에서 대부분의 작업을 함 — 많은 독자는 그렇지 않을 수 있으나 예외적으로 강하게 권장
아이디어 26. 유능한 개발자는 터미널 사용법을 잘 안다
- 터미널을 쓰는 이유
- 컴퓨터로 할 수 있는 모든 일이 몇 번의 키 입력 거리에 있음
- 명령어를 다양하게 조합 가능: 순차 실행, 한 명령의 출력을 다른 명령의 입력으로 연결(파이프), 한 명령을 다른 명령의 인자로 사용
- 텍스트 기반 작업이며, 수십 년간 텍스트를 다양한 방식으로 처리하는 유틸리티가 축적되어 있음
- GUI 앱만 사용하면 그 앱에 내장된 기능으로 제한됨 — “보이는 것이 전부(WYSIAYG)“인 셈(WYSIWYG의 반대 개념으로 저자가 비유)
- 이미지 편집 같은 특수 작업에는 GUI가 적합하지만, 생산적인 프로그래머의 요구는 예측 불가능한 경우가 많음
- 셸을 알면 GUI 앱의 한계에 부딪혔을 때 작업 방식이 단순해짐 — 운영체제 전체에 접근해 스크립트 작성과 앱 조합으로 거의 모든 것을 달성 가능
일상적으로 사용하는 터미널 명령 예시:
# src/ 안의 모든 .ts 파일을 .bak 확장자로 이름 변경
$ mv src/*.{ts,bak}
# 책에 사용한 단어 수는 대략 몇 개인가
$ wc -w **/*.md
# 파일이 변경될 때마다 소스로부터 책을 빌드
$ ls **/*.md | entr ./rake clean screen
# "colour"라는 영국식 철자를 (대소문자 무관) 어디서 썼는가
$ rg -i colour
# 포켓몬 뮤츠(Mewtwo) 정보를 담은 JSON 문서를 가져와
# 사용 가능한 기술(move) 목록을 출력
$ curl -s 'https://pokeapi.co/api/v2/pokemon/mewtwo' | jq -r '.moves[] | .move.name'
- 포켓몬 정보를 매일 가져오는 것은 아니지만, 비슷한 형태의 명령으로 JSON API를 시험해보거나 자체 시스템에서 즉석 리포트를 뽑아냄
10.1 셸과 터미널의 차이
- 과거: 전화선으로 중앙 컴퓨터에 접속 → 키보드와 문자 출력 장치를 갖춘 터미널(초기엔 종이 롤+타자기, 이후 CRT) 사용
- 접속 흐름: 컴퓨터가 수신 전화 감지 → 응답해 데이터 연결 수립 → 프로그램 하나를 실행시켜 입출력을 그 연결에 연결 → 사용자가 입력한 모든 것이 이 프로그램으로 전달되고, 프로그램 출력은 다시 터미널로 반환
- 이 프로그램에 셸(shell) 또는 커맨드 셸이라는 이름이 붙게 됨
- 오늘날에도 동일한 기본 개념 사용 — 새 창을 열면 애플리케이션에 연결이 성립됨
- 터미널 에뮬레이터: 정교한 물리적 터미널인 척하는 앱
- 전화를 거는 대신 pty(pseudo-teletype, 가상 텔레타이프)라는 장치를 열어 연결 성립
- 연결이 이루어지면 OS가 마치 전화가 울린 것처럼 알림을 받아 응답하고 셸을 시작함
- 즉 터미널과 셸, 두 구성 요소가 협력해 명령 입력을 가능하게 함 — 종류가 다양해 취향대로 선택 가능
flowchart LR
A["터미널 에뮬레이터<br/>(사용자가 보는 창)"] --> B["pty<br/>(가상 텔레타이프 장치)"]
B --> C["운영체제<br/>(연결 알림 수신 및 응답)"]
C --> D["셸 실행<br/>(입력 해석, 명령 실행)"]
D --> A
10.2 시작하기
- 터미널도 다른 앱과 마찬가지로 그냥 하나의 애플리케이션
- 대부분의 컴퓨터에 기본 터미널 앱이 딸려 있음: macOS는 Terminal, Linux는 xterm 등, Windows 기본값은 cmd
- 기본 터미널들도 잘 작동하며 처음 시작할 때 사용하기 좋음 — 나중에 더 정교한 터미널 앱을 설치하고 싶어질 수 있음
- 진짜 재미있는 부분은 셸(shell) 쪽
- 모든 셸의 공통 기본 기능
- 명령 입력 프롬프트 표시 및 실행
- 입출력 리다이렉션
- 명령 기록(history) 보관, 이전 명령 재호출/편집
- 삶을 가장 단순하게 만드는 기능 — 자동완성(completion):
⇥(Tab) 키로 문맥에 맞게 입력 완성(명령 이름, 파일 이름, 특정 명령의 옵션 등) - 스크립팅 지원 — 자체 내장 언어로 명령 시퀀스를 자동화 가능
10.3 터미널에서 찾는 부가 기능
터미널 프로그램을 평가할 때 살펴보는 항목:
| 항목 | 설명 |
|---|---|
| 속도 | 요즘 터미널 에뮬레이터는 GPU 렌더링이 기본 — 출력이 매우 빠르게 스크롤됨(저자의 Mac Mini 기준 16,000줄 파일을 45ms만에 스크롤) |
| 스크롤백 | 최소 2,000줄, 가급적 더 많은 출력을 보관해야 함 — 최신 로그 파일은 매우 장황해 실제 에러까지 거슬러 올라갈 큰 버퍼가 필요 |
| 테마 | 사소해 보이지만, 하루가 끝날 때 눈이 아프거나 두통이 없는 테마를 고르는 데 공들임 |
| 키 매핑 | 터미널과 에디터가 창 전환 등에 같은 키를 쓰도록 맞출 수 있으면 매우 편리 |
| 다중 패널/창 | 저자에게는 필수 기능 |
아이디어 27. 터미널은 화면 표시(프레젠테이션)를 담당한다
10.4 다중 패널
- 창을 여러 패널로 분할하고 각 패널에서 별도 셸을 실행할 수 있는 터미널을 선호
- 패널 레이아웃은 설정 가능해야 하고, 패널별로 글꼴 크기를 독립 조절할 수 있어야 함(로그를 별도 창에 작게 띄워 놓고 지켜보는 경우가 많기 때문)
- 일부 사용자는 터미널 멀티플렉서(tmux가 대표적)를 사용
- 저자는 tmux의 정교한 세션 관리 기능까지는 필요 없다고 판단 — 배우고 익혀야 할 키 시퀀스가 하나 더 느는 셈이기 때문
- 대신 키보드만으로 패널을 직접 만들고 배치할 수 있는 터미널을 사용하고, 터미널 창 전환과 에디터 버퍼 전환에 동일한 키 시퀀스를 쓰도록 설정
10.5 셸에서 찾는 부가 기능
- 문맥 기반 명령 자동완성(contextual command completion)에 매료됨
- 모든 셸에서
edit intro⇥처럼 입력하면 “intro”로 시작하는 파일명을 찾아 제안 - 대부분의 셸은 명령어 레벨에서도 비슷한 기능 제공:
ed⇥입력 시 “ed”로 시작하는 명령어 제안 - 문맥 기반 자동완성은 한 단계 더 나아가, 과거 입력한 명령과 그 명령을 입력했던 디렉터리를 기억 → 현재 디렉터리에 맞는 완성을 우선 제안
- 사소해 보여도 실제로는 큰 차이를 만듦 — 특히 작업별로 워크스페이스를 나눠 쓰는 경우, 워크스페이스를 전환하면 셸이 해당 디렉터리에 맞는 제안을 해줘서 각 공간이 더 독립적으로 느껴짐
- 모든 셸에서
- 괜찮은 스크립팅 언어도 중요한 평가 기준
- 대부분의 셸은 POSIX 호환이라 스크립팅 언어가 비슷하게 생김 — 강력하지만 난해함
- 저자는 풀타임 셸 스크립트 작성자가 아니므로 더 인간적인(humane) 수준에서 코드를 작성하고 싶어함
아이디어 28. 셸은 강력한 힘을 제공한다
10.6 저자가 매일 사용하는 도구
- macOS에서 터미널로 Kitty, 셸로 Fish를 사용
- 각자 자신에게 맞는 것을 찾을 때까지 다양한 옵션을 테스트해볼 것을 권장
실습 — 탐구하고, 실험하고, 즐기고, 최적화하고, 커스터마이징하라
- 현재 터미널 앱을 쓰고 있는가? 아니라면 실행해서 디렉터리 변경, 파일 목록 확인, 텍스트 파일 표시, 디렉터리 내 파일에서 특정 단어 검색 등을 연습해볼 것
- 터미널을 안 쓰고 있다면, 다음에 버전 관리 저장소에서 로컬 파일을 업데이트할 때 터미널로 해보고, 이후 프로젝트를 빌드/실행하는 방법도 찾아볼 것
- 터미널 커스터마이징: 자신에게 맞는 키보드 단축키와 색상 테마 설정. 셸 커스터마이징: 프롬프트를 원하는 대로 꾸미는 데 시간을 들여볼 것(사람들이 만든 화려한 프롬프트를 온라인에서 찾아볼 수 있음)
- 마우스 없이 얼마나 오래 버틸 수 있는가? 저자는 시간이 걸렸지만 이제 코딩할 때 하루 종일 마우스 없이 가능
- 숙련된 터미널/셸 사용자라면, 불편한 점 목록을 만들어두고 가끔씩 이를 해결해줄 새 터미널/셸이 있는지 찾아볼 것
실천 11. 나머지 모든 것을 자동화하라
- 예전에는 컴파일러를 돌리고 오브젝트 파일을 링크해 실행 이미지를 만든 뒤, 수동으로 다른 컴퓨터에 복사하고, 설정 파일을 몇 개 편집하고, 대상 머신을 재시작하며 문제 없기를 바라는 방식으로 작업함
- 당연히 빌드와 배포는 항상 위험한 일이었고, 시스템을 자주 다운시키거나 잘못된 버전을 배포하거나 운영 시스템을 온갖 방식으로 망가뜨림
- 오늘날에는 빌드, 배포, 업데이트를 처리하는 도구가 많음 — 일관성과 반복 가능성을 보장하고, 문제 발생 시 롤백도 가능
- 이런 도구들은 실행 중인 애플리케이션의 신뢰성을 높이는 데 가장 크게 기여하는 요인
아이디어 29. 자동화는 곧 신뢰성이다
- 개발자 관점에서도 수동 대안보다 훨씬 단순하고 스트레스가 적음
- 그럼에도 여전히 이런 도구를 쓰지 않는 팀이 있다는 것은 말이 안 됨
11.1 자동화 먼저, 코드는 그다음
- 저자는 프로젝트 자동화를 우연에 맡기지 않기 위한 간단한 트릭을 사용
- 새 프로젝트를 시작하면 첫 한 시간 정도는 항상 환경 설정에 씀(프레임워크가 많은 걸 생성해주기도 하고, 빈 디렉터리부터 직접 구조를 만들기도 함)
- 어떤 방식이든 규칙 하나: 애플리케이션 코드를 한 줄이라도 작성하기 전에 다음을 할 수 있어야 함
- 로컬 프로젝트 파일을 삭제하고 버전 관리 시스템에서 다시 복원할 수 있어야 함
- 단일 명령으로 모든 프로젝트 의존성을 설치할 수 있어야 함
- 단일 명령으로 소스로부터 프로젝트를 빌드할 수 있어야 함(그 소스가 아직 “Hello, World!” 수준이라도)
- 빌드 과정에 자동으로 포함되거나 단일 명령으로 테스트를 실행할 수 있어야 함
- 단일 명령으로 프로젝트를 목표 환경에 배포할 수 있어야 함
- 언뜻 일이 많아 보이지만, 꾸준히 하다 보면 이런 기능이 이미 갖춰진 템플릿 프로젝트들이 쌓여 약간의 커스터마이징만 필요해짐
- 코드를 쓰기도 전에 이렇게 하는 이유: 그렇게 하지 않으면 계속 미루게 됨을 (직접 겪어) 배웠기 때문 — 다음 기능을 동작시키는 것이 항상 행정적인 관리 업무보다 더 급해 보임
- 저자는 과거 1~4단계는 대체로 잘 지켰지만, 배포는 늘 나중으로 미루는 대상이었고 그 결과 항상 악몽 같았음
- 반대로 애플리케이션을 ==0일차(day zero)==부터 배포하면 단순해짐 — 추가로 설정할 환경도, 초기화할 데이터베이스도, 설정할 메일러도, 시작할 메시지 큐도 없이 그냥 다른 머신에서 “Hello, World!“를 돌리는 것뿐
- 이것부터 동작시킨 뒤 앱에 기능을 조금씩 추가하면서 계속 배포
- 예: 데이터베이스가 필요해지면, 다음 배포에서 데이터베이스를 프로비저닝하고 스키마를 초기화하는 방법을 알아내면 됨
- 이렇게 점진적으로 하는 것이 완성된 애플리케이션 전체를 한 번에 설정/구동하려는 것보다 훨씬 단순함
- 배포를 마지막까지 미루면, 무엇이든 테스트하기 전에 모든 구성 요소가 완벽히 동작해야 하고, 앱이 실패하는 이유가 DB 문제인지, 환경 변수 누락인지, 메시지 큐 URL 오타인지 확신할 수 없게 됨
- 이 접근법의 또 다른 큰 장점
- 저자는 클라우드에 저렴한 서버를 띄우고 즉흥적인 DNS 이름을 연결한 뒤, 하루에도 여러 번 앱을 배포함
- 배포가 잘 되는지 스스로 확인할 수 있을 뿐 아니라, 소프트웨어를 만들어주는 대상 사용자들이 언제든 최신 변경 사항을 써볼 수 있게 됨
- 궁금한 점이 있으면 앱의 특정 위치를 방문해 보여줄 수 있음
flowchart LR
A["Hello World 배포<br/>(0일차)"] --> B["기능 추가"]
B --> C["다시 배포"]
C --> D["필요 시 DB/큐 등<br/>리소스 추가"]
D --> C
11.2 단일 명령 배포는 단순히 앱을 푸시하는 것이 아니다
- 흔히 보는 문구(풍자): “Whizzo 앱을 명령 하나로 배포하세요! 서버를 한 번만 설정해두면 그 다음부터는 준비 완료입니다.”
- 서버를 준비하기 위한 예시 단계들(풍자적으로 나열됨): 새 유닉스 시스템 프로비저닝 → 패키지 다수 설치 → 애플리케이션 사용자/그룹 생성 → 배포 디렉터리 생성 및 소유권 설정 → 각종 설정 파일 편집 → (중간 다수 단계 생략) → 임시 파일 제거 → 로컬 설정 파일이 새 서버를 가리키도록 편집
- 이렇게 해두면 배포 명령 하나로 앱이 실행됨 — 놀랍다고 광고하지만…
- 이는 단지 앱의 새 버전을 배포하는 데만 유효함. 비상 상황에서 새 서버 5대를 배포해야 한다면 이 방식은 무용지물
- 대안
- 관리형 애플리케이션 호스팅 환경(설정 단계가 이미 다 되어 있고 애플리케이션만 제공하면 되는 경우) 사용
- 컨테이너 또는 WASM으로 배포
- 저자 개인적으로는 Ansible을 사용해 서버 설정과 애플리케이션 설정/업데이트를 모두 처리
아이디어 30. 빈 머신에서 단 한 번의 명령으로 앱을 실행 상태까지 만들어라
- 어떤 메커니즘을 쓰든 황금률은 하나: SSH 키가 설치된 갓 준비된(fresh) 대상 시스템이 있다면, 운영체제 설정, 의존성 로드, 서비스 기동, 애플리케이션 배포/설정, 데이터 마이그레이션까지 모두 ==단일 명령==으로 할 수 있어야 함
- 이상적으로는 이 명령이 여러 하위 명령으로 구성되어, 이미 설정된 머신에서는 초기 환경 설정 단계를 건너뛸 수 있어야 함
11.3 운에 맡기지 마라
- 저자는 항상 0일차에 이 단일 명령 배포를 동작시키려 함 — 애플리케이션이 커지고 새 리소스를 쓰기 시작하면서, 프로비저닝 스크립트를 애플리케이션 코드와 함께 계속 업데이트할 수 있게 됨
- 개발 중 약 일주일에 한 번, 원격 머신을 OS + SSH 키만 있는 초기 상태로 리셋한 뒤, 배포 명령만으로 앱이 여전히 잘 동작하는지 확인
- 새벽 3시에 온갖 경보가 울리는 상황이 오면, 이렇게 해둔 것에 감사하게 됨
실습 — 직접 해보기
- 시간이 날 때, 원하는 언어/프레임워크로 “Hello, World!” 앱을 만들고, 새로 체크아웃한 소스와 새로 준비한 대상 환경에서 시작해 앱이 배포되어 실행되기까지의 자동화를 만들어볼 것
- 앱을 수정한 뒤 대상 머신에서 업데이트까지 해볼 것
시간이 없다면
- 팀에 단일 단계 배포 전략이 없다면, 프로젝트 리더 등과 시간을 내어 하루 정도 시도해볼 기회를 요청해볼 것
실천 12. 에디터를 온전히 내 것으로 만들라
- 다른 모든 앱을 합친 것보다 에디터에서 보내는 시간이 더 많음 — 코드를 작성하고, 글을 쓰고, 메모를 하고, 그냥 이것저것 만져보는 곳
- 약 20년간 Emacs 사용자였음 — 확장성이 좋고 터미널에서 실행되어 저대역폭 원격 접속 환경에서도 쓸 수 있다는 점이 좋았음
- 한동안은 Emacs 설정을 만지작거리는 데 실제 작업보다 더 많은 시간을 쓴다는 것을 깨달음
- 이후 TextMate, VS Code를 거쳐 지금은 다시 터미널 기반으로, Vim(정확히는 Neovim)을 사용 중
12.1 에디터를 가꾸어라
- 에디터는 자신의 스타일, 취향, 워크플로에 맞춰 적응해야 하는, 자신의 생각만큼이나 개인적인 것 — 적어도 그래야 함
- 저자는 이 개인적인 관계에 참견하지 않고 자신이 하는 일 일부만 설명함
아이디어 31. 뇌에서 코드로 가는 가장 짧은 경로는 에디터다
- 예전에는 에디터 관리가 다소 통제 불능이었음
- 수천 줄의 Emacs Lisp을 직접 작성했고, 다운로드한 확장 기능도 수만 줄에 달했음
- 한동안은 이메일 처리와 뉴스그룹 읽기도 Emacs 안에서 직접 함
- 하지만 가능하다고 해서 좋은 생각인 것은 아니었음
- 세월이 흐르며 실제로 필요하고 사용하는 것이 무엇인지 배우면서 에디터 설정을 점점 줄여옴
- Vim으로 전환은 이를 다시 점검할 좋은 기회가 됨
- (asdf를 통해) 완전히 기본(vanilla) 상태의 Neovim 설치로 시작해 며칠간 사용
- 그 기간이 끝날 때, 하고 싶지만 할 수 없었던 일들의 목록을 작성
- 30분 정도 조사해보니 그중 약 70%는 이미 Vim에 내장되어 있었고 단지 사용법을 몰랐던 것뿐이었음. 나머지는 확장 기능을 설치해 해결
- 확장 기능은 한 번에 하나씩 추가하고, 방금 설치한 것에 필요한 키 입력을 익힌 뒤에야 다음 것을 추가
- Vim 초보 시절 영상/글을 보다가 멋져 보이는 것을 발견하면 바로 설치해보고 싶어지곤 했음 — 신기함이 사그라들면 삭제하는 규율을 지키려 노력함
- Neovim을 약 3년째 사용 중이며, 정착한 확장 기능 종류
- 패키지 매니저
- 마음에 드는 다크 테마 두어 개
- 사용하는 언어들을 위한 LSP 및 Treesitter 지원
- Copilot 지원(보통은 비활성화)
- LSP 및 Git 상태 정보를 포함한 개선된 상태 표시줄
- 여러 줄에 걸친 필드를 정렬해주는 확장
- 다중 커서 지원
- 블록 중첩을 표시해주는 기능
- 문서 구문(Treesitter 기반) 기반 내비게이션 지원
- 마크다운 미리보기
- 이 짧은 목록이 필요한 거의 모든 것을 커버함
12.2 마우스를 미워하라
- 에디팅 중 마우스를 쓰지 않는 것을 목표로 삼음
- 먼저 버퍼 내에서 빠르게 이동하는 키보드 명령부터 익힘(마우스의 주된 용도였기 때문)
- 아직도 새로운 요령을 배우는 중이지만, 이미 마우스를 쓸 때보다 훨씬 빠르고 정확해졌고 손이 키보드를 떠나지 않게 됨
- 다음 과제는 텍스트 선택 — 마우스 드래그가 자연스러워 보였지만, Vim의 ==텍스트 오브젝트(text-objects)==를 발견
- 텍스트 오브젝트: 파일 내 의미 단위(semantic element)에 명령을 적용할 수 있게 해줌
- 예: 방금 함수 안에 들어갔고 그 함수를 정돈하고 싶다면
vif=입력(현재 함수 안쪽을 비주얼 선택 후=로 재정렬) - 예: 현재 문단을 리랩(줄바꿈 재조정)하고 싶다면
vapgq(ap는 이 문단 전체,gq는 재랩)
- 마지막 남은 마우스 의존은 긴 목록에서 파일명 등을 선택하는 것이었는데, Vim의 훌륭한 퍼지 파인더(fuzzy finder) 확장 덕분에 몇 글자 입력만으로 원하는 파일(예: Automate/Editor.md)을 열어 해결
- 이제 에디팅 중에는 완전히 마우스 없이 작업 가능 — 흥미롭게도 하루 일과가 끝나도 오른쪽 손목이 더는 아프지 않게 됨
도전 과제 — 에디터 조작 연습
- 현재 쓰는 에디터에서 다음을 어떻게 하는지 알아볼 것: if문 본문 재정돈, 현재 함수 재정돈, 파일 전체 재정돈
- 텍스트 줄바꿈 컬럼을 75로 설정한 뒤 주석을 다시 랩할 것
- 커서를 현재 함수의 시작/끝으로 이동
- 닫는 중괄호에 대응하는 여는 중괄호로 커서 이동
- 문자열을 감싸는 작은따옴표를 큰따옴표로 교체
- 마크다운 문서의 단어 수를 세어볼 것
도전 과제 — 마우스 없이 사용하기
- 지금 쓰는 에디터를 마우스 없이 사용할 수 있는가?
탐구해보기
- 재미 삼아 개인 프로젝트에 다른 에디터를 써볼 것. 평소 쓰던 에디터보다 나은 점이 있는가? 더 나쁜 점은?
실천 13. 개발 머신 설정을 자동화하라
- 지난주 낙뢰가 마당에 떨어짐 — 몇 개의 차단기가 내려갔지만 대체로 괜찮아 보였는데, 스페이스바를 눌러도 Mac Mini가 깨어나지 않음
- 원인 파악: 전원 쪽은 UPS로 보호되고 있었지만, 길고 차폐되지 않은 이더넷 케이블이 유도 전압을 충분히 타서 컴퓨터와 반대쪽 끝의 스위치를 모두 태워버림
- 컴퓨터를 수리 맡기고 한동안 안 쓰던 노트북을 꺼냄
- Vim 설정 파일, 터미널 설정 변경 사항, 작업 중이던 모든 저장소를 포함해 약 5분 만에 Mac Mini와 같은 상태로 동기화됨
- 수리된 컴퓨터를 돌려받았을 때는 완전히 초기화된 상태
- OS 설치에 40분, 환경 구성에 필요한 기본 소프트웨어 설치에 약 20분 소요
- 이후 설정과 개발 파일을 복구하는 데는 1~2분밖에 안 걸림
아이디어 32. 개발 머신도 운영 서버처럼 프로비저닝하라
- 이 복구는 두 요소로 구성됨: 작업 파일(working files)과 설정(configuration)
- 작업 파일은 쉬운 부분 — 버전 관리 하에 두고, 잃어버리면 아쉬울 지점마다 커밋/푸시(저자의 커밋 이력 약 80%가 “stash”라는 라벨)
13.1 도구와 설정 복구하기
- 컴퓨터를 그저 배포 대상 머신 중 하나로 취급하는 관점 — 개발 환경 자체를 배포하는 것이지, 앱을 배포하는 것이 아님
| 접근 방식 | 특징 |
|---|---|
| Docker 컨테이너 | 도구+설정을 컨테이너 이미지로 관리, 프로젝트 파일은 호스트에 버전 관리. 이미지나 Compose 스크립트를 버전 관리에 저장. 새 노트북 프로비저닝은 Docker/Podman 설치 → 컨테이너 기동 → 프로젝트 파일 체크아웃 순서 |
| Nix | 도구를 Nix로 설치하면 환경이 어떤 모습이어야 하는지 이미 알고 있으므로 다른 머신에서 재현이 쉬움. 다만 학습 곡선이 있음(저자는 몇 년 전 1주일간 설정을 시도하다 포기) |
| 저자의 방식(asdf + Homebrew + yadm) | 도구 설치는 asdf/Homebrew로, 설정 파일 복구는 yadm(Git 기반)으로 분리 관리. 상대적으로 소박하지만 실용적 |
13.2 도구 재설치하기
- 두 애플리케이션 설치 도구 사용: asdf와 Homebrew
| 도구 | 주 용도 | 장점 | 단점 |
|---|---|---|---|
| asdf | 개발과 직접 관련된 도구(컴파일러, 언어 런타임, 일부 에디터) | 프로젝트별로 여러 버전을 언어별 전용 도구(예: Ruby의 rbenv) 없이 쉽게 관리 가능 | 개발 관련 도구 외에는 커버 범위가 제한적 |
| Homebrew | asdf가 다루지 않는 나머지 애플리케이션 전반 | 커버리지가 넓고 애플리케이션 설명이 잘 관리됨 | 프로젝트별 로컬 환경 처리가 asdf만큼 좋지 않고 다소 느림 |
13.3 설정 복구하기
- 설정 파일이 파일 시스템 곳곳에 흩어져 있음 — 일부는 XDG 디렉터리(
.config,.local등), 일부는 홈 디렉터리, 일부는 특정 앱이 정한 임의의 위치 - 이 모든 것을 버전 관리 하에 저장해야 하고, 문제가 생기거나 새 머신을 얻었을 때 개별적으로든 한꺼번에든 복구할 수 있어야 함
- 여러 도구가 이를 지원 — 일부는 GNU Stow를 애용하지만, 저자는 개인적으로 ==yadm==(Yet Another Dotfile Manager)을 선호
- yadm은 Git 명령을 아주 얇게 감싼 래퍼: 하드 드라이브 전체를 Git 관리 하에 두되, Git의 sparse checkout 기능을 이용해 지정한 파일만 관리
yadm add로 파일 추가,yadm commit으로 커밋,yadm push로 원격 저장소에 푸시,yadm clone으로 새 머신에 복원- 사실상 시스템의 임의 파일에 대한 Git이나 마찬가지 — 한 번 추가한 파일의 변경 사항을 계속 추적하므로
yadm status로 변경된 파일을 확인 가능
13.4 사용하는 도구 기록해두기
- Homebrew와 asdf 이야기에서 빠뜨린 부분: 어떤 도구를 설치해야 하는지, 어떤 버전을 써야 하는지 어떻게 아는가
아이디어 33. 도구를 설치할 때마다 기록해두라
- 다행히 Homebrew와 asdf 둘 다 현재 설정을 기록하는 방법을 제공
- Homebrew: 홈 디렉터리에서
brew bundle dump실행 → Brewfile 생성. 같은 디렉터리에서brew bundle실행으로 현재 Homebrew 설정을 재설치 가능 - asdf: 전역(global)과 프로젝트 로컬(local) 의존성을 모두 설정 가능
- 어디서나 쓰는 것(예: 최신 Ruby, Python)은 전역 기본값으로, 특정 프로젝트에서 다른 버전이 필요하면 로컬 기본값으로 설정
- 이 기본값들은
.tool-versions파일에 저장됨 — 전역용은 홈 디렉터리에, 로컬용은 각 프로젝트 최상위 디렉터리에 - 어떤 디렉터리에서든
asdf install을 입력하면 전역과 로컬 도구를 모두 설치
- Homebrew: 홈 디렉터리에서
- 홈 디렉터리의 Brewfile과
.tool-versions파일을 yadm에 추가 — 새 머신에 설치할 때는 홈 디렉터리로 이동해brew bundle실행 후asdf install실행만 하면 됨 - Homebrew 설정 덤프와 yadm 업데이트는 하루 두 번 실행되는 cronjob이 관리. asdf 쪽은 프로젝트에 설정 파일이 함께 있으므로 스스로 알아서 처리됨
13.5 깨끗하게 정리하기
- 솔직히 컴퓨터가 죽었을 때 그리 실망하지 않았음 — 새로 시작할 수 있다는 것도 나름 좋은 점
- 시간이 지나면 드라이브가 고아가 된(orphaned) 온갖 잡동사니로 가득 참. 새 머신에서는 필요할 때마다 다시 설치하면 됨
- 왠지 더 정돈된 머신이 조금 더 빠르고 덜 말썽을 부리는 느낌이 듦
flowchart TD
A["개발 머신 장애 발생"] --> B["새 머신 준비<br/>(OS 설치)"]
B --> C["도구 재설치<br/>(asdf install / brew bundle)"]
C --> D["설정 파일 복구<br/>(yadm clone)"]
D --> E["작업 파일 복구<br/>(git clone / pull)"]
E --> F["이전과 동일한 개발 환경으로 복귀"]
생각해보기
- 오늘 아침 출근했더니 컴퓨터가 녹아버렸다고 상상해볼 것(무슨 일이 있었는지는 일단 제쳐두고). 새 머신에서 다시 생산적으로 일할 수 있어야 하는 상황
- 전날 업무를 마쳤을 때의 상태로 돌아가는 데 시간이 얼마나 걸리는가?
탐구해보기
- 내 컴퓨터의 다양한 설정 파일이 어디에 있는지 알고 있는가?
직접 해보기
- 노트북과 데스크톱이 있다면, 둘 다에 yadm이나 stow 같은 도구를 설치해볼 것
- 먼저 한 머신에 더미 파일 몇 개를 여러 디렉터리에 만들어 yadm에 추가, 커밋, 공유 저장소에 푸시
- 다른 머신에서 그 저장소를 클론해 파일이 올바른 위치에 설치되는지 확인
- 파일 내용을 변경하고 상태를 확인한 뒤 커밋. 첫 번째 머신에서 변경 사항을 pull 해볼 것
진짜로 해보기
- 다음에 설정이 있는 애플리케이션(예: 에디터)을 설치할 때는 Homebrew, asdf, Chocolatey 같은 패키지 매니저를 사용할 것
- 그런 다음 앱의 초기 설정을 구성. 이를 재설치/재설정하는 데 필요한 모든 정보를 저장해둘 것
- 두 번째 머신에서 패키지 매니저 목록과 설정을 가져와 패키지 매니저로 앱을 설치하고 실행. 첫 번째 머신에서 설정한 것과 동일한 설정으로 동작하는지 확인할 것
부록. 아이디어(Idea) 전체 요약
| 번호 | 실천 위치 | 핵심 메시지 |
|---|---|---|
| 아이디어 25 | 실천 9 | 시간을 정리하듯 데스크톱을 정리하라 |
| 아이디어 26 | 실천 10 | 유능한 개발자는 터미널 사용법을 잘 안다 |
| 아이디어 27 | 실천 10 | 터미널은 화면 표시(프레젠테이션)를 담당한다 |
| 아이디어 28 | 실천 10 | 셸은 강력한 힘을 제공한다 |
| 아이디어 29 | 실천 11 | 자동화는 곧 신뢰성이다 |
| 아이디어 30 | 실천 11 | 빈 머신에서 단 한 번의 명령으로 앱을 실행 상태까지 만들어라 |
| 아이디어 31 | 실천 12 | 뇌에서 코드로 가는 가장 짧은 경로는 에디터다 |
| 아이디어 32 | 실천 13 | 개발 머신도 운영 서버처럼 프로비저닝하라 |
| 아이디어 33 | 실천 13 | 도구를 설치할 때마다 기록해두라 |
각주: [3] 데이북(daybook)에 관한 저자의 별도 글 참조(원서 각주에 출처 표기됨)
5. 변화의 수용
원문 제목: Chapter 5 - Embrace Change
“나이가 들어서 노는 것을 멈추는 게 아니라, 노는 것을 멈추기 때문에 나이가 드는 것이다.” — 조지 버나드 쇼 (
George Bernard Shaw)
장 도입 — 코드가 아닌 나 자신을 개발하기
- 이번 장의 초점 전환: 코드 개발에서 자기 자신의 개발(지식, 기술, 커리어)로
- 핵심 질문
- 지식, 기술, 커리어를 어떻게 쌓아갈 것인가
- 흐릿하고 불확실한 미래를 어떻게 준비할 것인가
- 이전 장에서 강조한 “작은 발걸음” 원칙과의 연결
- 갈 곳이 불확실할 때는 작은 걸음으로 절벽에서 떨어질 위험을 줄임
- 다만 이는 앞이 전혀 보이지 않는다는 전제에서 나온 전략일 뿐
정찰대 비유
- 탐험가가 미지의 땅을 답사할 때 정찰대를 여러 방향으로 보내 정보를 모으는 것과 유사
- 소프트웨어 개발에서의 정찰대 = 프로토타입
- 개인 개발에서의 정찰대 = 연구(research)
- 프로토타입과 연구 모두 의사결정을 더 자신 있고 정확하게 만들어 줌
연구를 해야 하는 이유 1 — 실용적 이유
- 저자가 프로그래밍을 처음 시작했을 때 방식(Basic 프로그램을 종이테이프에 펀칭)을 그대로 고수했다면, 지금은 실직 상태였을 것
- 소프트웨어 산업은 지금까지 만들어진 산업 중 가장 변동성이 큰 축에 속함
- 원칙: 적응하고 수용하지 않으면 도태된다
연구를 해야 하는 이유 2 — 저자의 신념
- 개발자는 마법사와 같은 존재
- 상상 속에서만 존재하던 것을 실제로 만들어냄
- 대체로 모두를 위한 좋은 일을 하기 위함
- 그리고 이 과정 자체가 즐겁다
아이디어 34. 소프트웨어 개발은 관리된 변화다 (
Software development is managed change)
연구는 어른들의 놀이다
- 저자는 인생의 많은 시간을 연구에 사용
- 가족들은 이를 “노는 것”이라 부름 — 어쩌면 그 말이 맞고, 그게 저자가 연구를 많이 하는 이유일 수도 있음
- ==필요해서 배우는 것은 노동이고, 원해서 배우는 것은 즐거움이다==
- 저자의 학습 동기
- 세상의 변화 속도가 무섭도록 빨라졌다는 인식이 근본적 동력
- 흥미로워 보이거나, 다른 것들을 새로운 방식으로 연결하는 주제를 스스로 선택해 탐구하는 것이 실제로 배우고 싶게 만드는 요인
켄트 벡과 변화의 수용 철학
- 켄트 벡(
Kent Beck)의 저서 『Extreme Programming』[Bec00]의 부제가 바로 “Embrace Change” - 벡의 철학: 변화하는 환경, 요구사항, 기술 등에 유연하게 적응할 줄 아는 팀이 그렇지 못한 팀보다 더 성공적이고 유리한 위치에 섬
- 저자의 확장 해석: 이 철학은 팀뿐 아니라 개인에게도 동일하게 적용됨
- 변화 곡선이 기하급수적으로 느껴지는 시대 — 모두가 각자의 대처법을 찾아야 함
- 새로운 것들의 쓰나미에 빠져 죽는 듯한 가벼운 공황 상태를 가라앉힐 방법이 필요
- 결론: 모두가 변화를 수용해야 한다
해법 — 과정을 단순화하라
- 연구 과정을 구조화하기
- 새로운 것을 가지고 놀 여유를 스스로에게 허락하면서도, 업무에서는 검증되고 익숙한 것을 계속 사용하기
- 이어지는 실천 14, 실천 15는 저자에게 실제로 효과가 있었던 방법
5.1 실천 14 — 실용적인 것과 공상적인 것을 뒤섞어라
원문 제목: Practice 14 - Mix the Practical and the Fanciful
- 저자는 자신이 만든 소프트웨어가 실제로 쓰이는 모습을 볼 때 큰 즐거움을 느낌
- 이를 경험하려면 점점 정교해지고 요구 수준이 높아지는 시장에서 필요한 기술을 갖추고 있어야 함
- 그래서 연구에는 두 가지 목표가 있음
- 실용적 목표 — 앞으로 유용하게 쓰일 기술과 기법을 파악하는 것
- 공상적 목표 — 자신에게 개인적으로 가장 보람 있는 개발 영역이 무엇인지 이해하는 것
생각해보기 (
Ponder...)
- 커리어 개발에 도움이 될 만한 기술 영역은 무엇인가
- 업무와 무관하게 흥미로워 보이는 기술 영역은 무엇인가
저자의 2025년 기준 답변
| 실용적 연구 주제 (2025) | 공상적 연구 주제 (2025) |
|---|---|
| 헤드리스 CMS 시스템 | 마이크로컨트롤러 (ESP32 등, 센서·디스플레이 포함) |
| 프론트엔드 프레임워크 | 실용적 함수형 프로그래밍 (사랑할 만한 순수 함수형 언어 탐색 중) |
| 엣지 서비스 | 대수적 효과(Algebraic effects) |
| 최소 다운타임 데이터베이스 마이그레이션 기법 | Unison 언어 (대수적 효과 + 불변 코드 개념) |
| 덜 복잡한 배포 옵션 | |
Rails, Phoenix의 최신 동향 |
- 실용적 주제를 조사하는 이유: 사이트 업그레이드를 계획 중이기 때문
- 공상적 주제 보충 설명
- 대수적 효과: 함수형 언어에서 부작용(side effect)을 격리하는 방법. 모나드(monad)가 해결하는 문제와 같은 문제를 해결하지만, 저자가 느끼기에 훨씬 더 깔끔한 방식
Unison: 대수적 효과를 지원하는 함수형 언어이자, 코드를 불변(immutable)으로 다루는 개념을 가짐. 불변 코드 덕분에 별도의 패키지 관리가 필요 없고, 512비트 체크섬을 서버에 전송하는 것만으로 앱 배포가 가능함
아이디어 35. 연구는 어른들이 노는 것을 부르는 말이다 (
Research is what grownups call play)
5.1.1 연구는 유효한 활동이다
원문 제목: Research Is a Valid Activity
- 연구를 여가 시간에나 하는 것으로 여기기 쉬움 — 이는 함정
- 이 함정에 빠졌을 때 벌어지는 일
- 여가 시간이 절대 나지 않고, 그 결과 죄책감 때문에 실제로는 전혀 놀지 못함
아이디어 36. 일만 하고 놀지 않으면 데이브는 부루퉁해진다 (
All work and no play makes Dave a grumpy boy)
- 놀지 않는 기간이 길어지면 압박이 쌓여 결국 억지로라도 연구를 해야 하는 상태가 됨
- 이 시점엔 보고 싶은 목록이 너무 많이 쌓여, 이것저것 건너뛰기만 하고 깊이 파고들지 못함
- 결과적으로 제대로 된 성과를 얻지 못함
대응책 — 연구 시간을 의도적으로 확보하라
- ==연구는 여가 시간에 우연히 하는 선택이 아니라, 의도적으로 시간을 배정해야 하는 활동이다==
- 하루 20분이든, 저녁 3시간이든 자신의 삶과 학습 방식에 맞는 시간을 정할 것
- 저자의 방식: 아침에 이메일을 처리한 뒤 30분, 또는 밤에 잠자리에서 독서
연구를 효과적으로 만드는 절차
- 각 세션의 목표를 데이북(daybook)에 적는다
- 예: “분산
Unison프로그램 작성하기” - 예: “서버사이드 렌더링이 배포에 미치는 영향은?”
- 예: “José Valim은 요즘 뭘 하고 있나?”
- 예: “분산
- 그 목표를 달성하기 위해 시도한다
- 훑어보다가 산만해지기 쉬우므로, 데이북을 펼쳐 놓고 메모하며 진행한다
- 다른 흥미로운 영역이 눈에 띄면 적어두고 동그라미 표시를 한다 — 이번 세션의 목표는 탈선시키지 않고, 해당 항목은 향후 세션에서 다시 다룸
flowchart TD
A[세션 목표를 정한다] --> B[데이북에 목표를 기록한다]
B --> C[연구 세션을 진행한다]
C --> D{흥미로운 곁가지를 발견했는가}
D -->|예| E[데이북에 메모하고 동그라미 표시]
E --> C
D -->|아니오| F[세션 목표 달성]
F --> G[다음 세션에서 곁가지 항목 검토]
5.1.2 나에게도 놀이 시간을 허락한다
원문 제목: I Allow Myself Some Playtime
- 연구를 실용적인 데 집중하려 하지만, 순전히 재미로 하는 활동을 위한 시간도 스스로에게 허락함
- 때로는 그냥 독서이지만, 더 자주는 코드와 관련된 활동
- 아이디어를 짧게 탐구해보는 코드를 끊임없이 작성 — 결과가 어떻게 나오는지 확인하는 것이 목적
- 하드웨어를 가지고 노는 것도 즐김
Raspberry Pi를 이용해 Fisher Price의 클래식 Pull-A-Tune 자일로폰을 터치 감응MIDI컨트롤러로 개조 (저자가 가장 좋아하는 프로젝트)- Teddy Ruxpin 곰인형에 서보모터를 달아 디지털 녹음 소리에 반응하도록 개조
- 이 과정에서 사운드 처리, 저수준 네트워킹, “플라스틱을 함부로 다루면 안 되는 이유”를 배움
아이디어 37. 사용하지 않으면 잃는다 (
Use it or lose it)
- 무언가를 배운 뒤, 적절하다고 판단되면 빠르게 프로토타입을 만들어 실제로 이해했는지 재확인
- 프로토타입 코드는 지저분해도 상관없음 — 확인이 끝나면 삭제할 것이기 때문
- 이 과정에서 배운 점 역시 데이북에 기록
연구를 계획하라 (
Plan your research)
- 세 항목씩, 두 개의 목록을 작성한다
- 목록 1: 정말로 더 알아야 하는 것들
- 목록 2: 그냥 궁금한 것들
- 연구를 위한 정기적인 시간을 정하고 목록을 하나씩 진행한다
- 처음에는 “해야 할 일” 항목과 “재미로 할 일” 항목을 번갈아 가며 진행해본다
- 연구 중 새롭게 관심이 생긴 항목은 해당 목록에 추가한다
- 메모 남기는 것을 잊지 않는다
| 목록 1 — 더 알아야 할 것 | 목록 2 — 궁금한 것 |
|---|---|
| (직접 채워보기) | (직접 채워보기) |
5.2 실천 15 — 미래에는 놀고, 과거에는 일하라
원문 제목: Practice 15 - Play in the Future, Work in the Past
- 그림 1 (원서): 로마 신화의 신 야누스(Janus) — 과거와 미래를 동시에 바라보는 두 얼굴을 가진 신. 이번 실천 전체를 관통하는 핵심 은유
- 새롭고 흥미로운 것을 발견하면 곧바로 다음 프로젝트에 적용하고 싶은 유혹이 생김
- 신선함, 새로운 아이디어, 실전 경험을 쌓고 싶은 욕구 때문
저자가 경험으로 배운 것 — 이는 나쁜 생각이다
- 신기술은 빠르게 변화 — 코드를 새 릴리스 사이에서 마이그레이션하는 데, 정작 코딩하는 시간보다 더 많은 시간을 쓰게 됨
- 새로운 것은 오래된 것보다 버그가 많음
- 문서화가 부실하고, 온라인 지원이 적고, 확장 기능이 부족하고, 튜토리얼도 드묾
- 더 교묘한 함정: 특정 문제를 해결해줄 때 새로운 아이디어에 빠지게 됨
- 반짝이는 새 도구가 그 문제 하나는 해결해도, 기존 소프트웨어가 잘 처리하던 다른 문제들은 처리하지 못하는 경우가 많음
- 결과: 오래된 것과 새로운 것을 억지로 이어붙인 **“프랑켄프로젝트(Frankenproject)”**가 탄생
까치(magpie) 성향 길들이기
- 저자가 오랜 시간에 걸쳐 배운 것: 반짝이는 새 물건을 수집하려는 충동(까치 성향)을 통제하는 법
- 반짝이는 것을 모으는 대신, 오래되고 익숙하며(그리고 실제로 잘 작동하는) 도구로 묵묵히 나아갈 것
- ==매일 의지하는 도구와 라이브러리에 관한 한, 지루한 것이 흥미로운 것을 항상 이긴다==
연구 시간이 빛을 발하는 지점
- 연구를 위한 시간을 따로 확보해 두면, 그 시간에 새로운 것들을 마음껏 가지고 놀 수 있음을 알기 때문에 나머지 시간에 익숙한 도구로 작업하는 것이 훨씬 견딜만해짐
- 여기서도 데이북이 유용하게 쓰임
- 예: 새 프로젝트에
Rails29.32-beta를 써도 되지 않을까 고민이 될 때 (PHP로의 재작성이 흥미로워 보일 때도 마찬가지) - 데이북에 메모만 남기고, 지금까지 걸어온 익숙한 길을 계속 감
- 여전히 관심이 있다면, 다가오는 연구 세션에서 그 기술을 가지고 놀 수 있음을 알고 있기 때문
- 예: 새 프로젝트에
연구하는 데이브 & 실전 데이브
- “연구하는 데이브(Research Dave)“는 앞길을 미리 정찰하는 정찰병
- 그의 또 다른 자아인 “실전 데이브(Production Dave)“를 위해 정보를 모아옴
flowchart LR
A[연구하는 데이브<br/>미래를 정찰한다] --> B[데이북에 학습 내용 기록]
B --> C[검증된 지식만 선별]
C --> D[실전 데이브<br/>과거의 검증된 도구로 작업한다]
D --> E[안정적인 프로덕션 결과물]
돌이켜보기 (
Think back...)
- 과거 프로젝트 중, 팀이나 본인이 최신(bleeding-edge) 라이브러리, 프레임워크, 도구를 선택해서 오히려 더 어려워진 적이 있었는가
- 결국 그 기술들이 약속한 만큼의 가치를 냈는가
- 그것이 가져다준 가치가, 추가로 발생한 스트레스를 감수할 만큼 컸는가
함께 연구하기 (
Pair research)
- 팀 동료들도 각자의 연구 목록을 가지고 있다면 서로 비교해볼 것
- 두 명 이상이 같은 주제(예:
OCaml)에 관심이 있다면 힘을 합쳐, 기술적 활동이자 사교적 활동으로 만들 수 있음- 저자의 예상: 단위 시간당 배우는 양이 더 많아질 것
핵심 통합 인사이트
- 연구는 개인 개발판 프로토타입 — 불확실한 미래에 대한 정찰 활동
- 연구에는 실용적 목표와 공상적 목표가 함께 있어야 지속 가능함
- 연구는 의도적으로 시간을 배정하는 활동이며, 데이북 기록이 몰입과 추적을 동시에 가능하게 함
- 새로운 기술은 연구 시간에 마음껏 실험하되, 실제 업무(프로덕션)에는 검증되고 안정적인 도구를 사용할 것 — 야누스처럼 미래를 보는 눈과 과거(검증된 것)에 발을 딛는 몸을 동시에 유지하는 전략
빠른 참조 — 아이디어 및 실천 인덱스
| 번호 | 유형 | 원문 제목 | 한국어 제목 |
|---|---|---|---|
| 34 | 아이디어 | Software development is managed change |
소프트웨어 개발은 관리된 변화다 |
| 14 | 실천 | Mix the Practical and the Fanciful |
실용적인 것과 공상적인 것을 뒤섞어라 |
| 35 | 아이디어 | Research is what grownups call play |
연구는 어른들이 노는 것을 부르는 말이다 |
| 36 | 아이디어 | All work and no play makes Dave a grumpy boy |
일만 하고 놀지 않으면 데이브는 부루퉁해진다 |
| 37 | 아이디어 | Use it or lose it |
사용하지 않으면 잃는다 |
| 15 | 실천 | Play in the Future, Work in the Past |
미래에는 놀고, 과거에는 일하라 |
파트 3. 상호작용을 단순화하라
- 파트 3의 주제는 사람과 사람 사이의 상호작용에서 발생하는 마찰을 줄이는 것
- 이어지는 챕터 6 “소프트 스킬”에서 다루는 실천들은 모두 팀에서의 소통, 신뢰, 공감을 통해 협업을 단순하게 만드는 방법에 관한 것
챕터 6. 소프트 스킬
- 저자에게 이 챕터를 쓰는 것은 일종의 참회임 - 이 책에서 다루는 모든 것 중 저자 본인이 가장 개선이 필요한 영역이기 때문
- 어떤 사람들에게는 소프트 스킬이 자연스럽게 따라오지만, 저자처럼 더 잘할 수 있다고 느끼는 사람들을 위해 자신의 경험을 공유함
6.1 왜 신경 써야 하는가
- 과거에는 한 명의 뛰어난 프로그래머가 프로젝트 전체를 설계하고 코딩하며, 대부분의 세부사항을 머릿속에 담아둘 수 있었음
- 오늘날은 대체로 그렇지 않음: 소프트웨어는 팀 단위로 만들어짐
- 팀은 사람으로 이루어지고, 사람은 의견, 감정, 두려움의 영향을 받음
- 팀에서 일하는 것은 큰 보람이 될 수도 있지만, 한 번의 잘못된 발걸음이 하루를 망칠 수 있는 지뢰밭이 될 수도 있음
6.2 이것이 단순성과 무슨 관계가 있는가
- 마찰(friction)은 낭비된 노력
- 기어박스에서 마찰은 열로 낭비됨
- 팀에서 마찰은 좌절감과 시간 낭비로 나타남
- 신뢰와 명확한 소통이 없으면, 원래는 표현하기 단순했을 것들이 훨씬 복잡해지고 오해가 만연해짐
- 상대가 내 말을 올바르게 이해했다는 확신이 없으면 무언가를 단순하게 만들기 어려움
- 이 챕터는 이러한 지뢰밭을 더 자신 있고 덜 스트레스 받으며 통과하는 방법들을 다룸
6.3 실천 16. 의견 대립은 제로섬 게임이 아니다
- 현시대의 문제 하나: 사람들은 자신이 이기려면 반드시 누군가가 져야 한다고 확신함
- 많은 사람이 장기적인 결과보다 이기는 것 자체에 더 신경을 씀 -> 역기능적이고 시끄러운 세상으로 귀결
- 단순한 의견 차이가 대립으로 번질 수 있음. 어떤 사람은 대립을 즐기고, 어떤 사람은 자리를 피함 - 어느 쪽도 실제로는 승리하지 못함
아이디어 38. 제로섬 논쟁에서는 아무도 이기지 못한다.
- 대안적 접근: 세상일은 좀처럼 흑백으로 나뉘지 않으며, 어느 쪽도 100퍼센트 옳은 경우는 거의 없음을 받아들이는 것
- 이를 받아들이고, 논쟁이 될 뻔한 것을 하나의 탐구(exploration)로 바꾸는 것이 핵심
- 이 접근법을 **변증법적 사고(dialectical reasoning)**라고 부름
- 양쪽 의견 모두에 진실의 요소가 있다고 전제함
- 두 의견을 종합(synthesize)하여 더 크고 새로운 이해를 만들어내는 이점을 강조함
- 대립이 될 뻔한 상황을 흥미로운 토론으로 바꿈
직접 해보기 - 변증법적 사고 연습하기
- 누군가와 의견이 다를 때, 상대를 바보 취급하며 넘겨짚지 말 것
- 상대가 틀렸음을 증명하려 하지 말고, 오히려 내가 틀렸을 수도 있다는 것을 진심으로 알아보려 할 것
- 상대가 그런 입장에 이르게 된 배경에 진심으로 관심을 가질 것 (트집을 잡기 위해서가 아니라 통찰을 얻기 위해서)
- 목표는 내가 옳거나 상대가 틀렸음을 증명하는 것이 아니라, 양쪽 입장에서 타당한 부분을 찾아 더 나은 새로운 이해를 종합해내는 것
- 까다로운 지점: 잘못하면 잘난 척하거나 교활하게 보일 수 있음
- 시작하는 말 예시 - “말씀하신 걸 들었는데, 제 경험과 딱 맞아떨어지지 않는 부분이 있어서 조금 더 이해가 필요합니다. 어떻게 그런 생각을 하시게 됐는지 보여주실 수 있나요?”
- 절대 반박하지 말 것. 이기려는 것이 아니라 상대의 관점에서 보려는 연습이며, 부수 효과로 무언가를 배우게 될 수도 있음
직접 해보기 - 칼로 논쟁하기
- 점심이나 저녁 자리에서 절반은 한쪽 입장을, 절반은 반대쪽 입장을 지지할 때 할 수 있는 게임
- 찬성하는 사람은 칼(또는 숟가락, 펜 등)을 테이블 중앙을 향하게 놓음 (원래 식사용 칼을 세팅하는 방향)
- 반대하는 사람은 칼을 테이블 가장자리와 평행하게 90도로 돌려놓음
- 몇 분간 토론을 진행한 뒤, 의견이 다른 한 쌍을 골라 칼을 뒤집음 - 이전에 찬성했던 사람은 이제 반대 입장에서 논쟁하고, 반대했던 사람은 이제 그것을 설득해야 함
- 몇 분마다 한 쌍씩 계속 칼을 바꾸며, 모두가 양쪽 입장을 한 번 이상 돌아가며 경험하게 함
- 목표는 승자나 패자를 가리거나 합의를 찾는 것이 아니라, 친구들과 30분을 흥미롭게 보내는 것
- 동의하지 않는 것을 지지하기 위해 아는 것을 어떻게 동원할지 궁리하는 게임 - 고등학교 토론과 비슷함
- 집에 가는 길에 자신의 확고했던 신념을 재평가하게 될 수도 있음
아이디어 39. 절대성은 단순함의 적이다.
- 100퍼센트 무언가를 확신하는 사람을 떠올려볼 것 - 반대 증거 앞에서도 그 믿음을 유지하려면 얼마나 많은 에너지가 필요한가
- 부분적으로만 사실인 것에 근거해 결정을 내리며 얼마나 많은 시간을 낭비하는가
- 절대성 위에서 운영되는 팀은 결코 효율적일 수 없음
- 세상이 변할 때 바뀌기가 훨씬 어려움
- 다양한 관점을 종합해서 나오는 더 나은 아이디어를 놓치게 됨
6.4 실천 17. 공감 능력을 길러라
- 소프트 스킬 중 딱 하나만 훈련해야 한다면, 그것은 공감(empathy)
- 정의: 공감이란 다른 사람이 어떻게 느끼는지 이해하고, 그들을 대할 때 그것을 고려하는 능력. 상대에게 동의해야 한다는 뜻이 아니라, 그들과 그들의 입장을 고려하고 존중해야 한다는 뜻
- 공감은 사회적 윤활유 역할을 하여 사람들이 상호 만족스러운 결정에 이르도록 도움
- 둘 이상이 함께 일할 때는 각자가 아는 것, 믿는 것, 계획하는 것을 공유할 방법이 필요함 - 이를 효율적으로 할수록 일이 단순해지고 애자일해짐
- 오래된 커플들은 몸짓과 가끔의 눈맞춤만으로 소통하고 문제를 해결함
- 고객과 새 프로젝트를 논의할 때, 자신의 눈뿐 아니라 고객의 눈으로 문제를 보려 하면 고객이 실제로 필요로 하는 것을 전달하는 기술적 솔루션을 만들 수 있고, 고객이 이해하는 언어로 설명할 수도 있음
- 공감이 있으면 시행착오를 크게 줄일 수 있음
- 상대가 나를 신뢰할 것을 알기에 더 작은 단계로 나아갈 수 있음
- 실수해도 상대가 비난하기보다 이해해줄 것을 알기에 더 자유롭게 실험할 수 있음 (감정적 안전망)
- 공감이 없을 때 벌어지는 일
- 사람들은 자기 자신만 챙기고, 자신의 아이디어와 계획을 타인의 의견으로부터 방어함
- 업무가 사일로화되고, 결정이 절대적으로 변하는 경향이 생김
- 조직이 더 위계적으로 변함 (권위를 보존하기 쉬워지므로)
- 좌절에 대한 반응이 학습이 아니라 비난이 됨
- 공감은 그룹 전체에 관한 것이지만, 각자가 자신만의 방식으로 표현하고 사용하는 법을 찾아야 하며, 그 다음 팀이 각자의 상호작용을 받아들이고 증폭시키는 것이 중요
- 저자의 자기평가: 10점(완전한 공감)에서 0점(테드 번디) 척도라면 자신은 약 4점 정도라고 밝히며, 이 책을 위해 조사하며 그것으로는 부족하다는 것을 깨닫고 공감 능력을 계발 중이라고 함
6.4.1 사람들에게 시간을 내주라
- 시간은 어떤 면에서 가장 제한된 자원 - 항상 시간 압박 속에 살고, 생산적이지 않으면 죄책감을 느낌
- 저자의 가장 큰 문제: 업무상 상호작용을 거래(transactional)로 여기고, 정보 교환으로 취급해 효율적으로 끝낸 뒤 넘어가려 함
- 이는 잘못된 것 - 그럴 때마다 상대와의 연결이 약해지고, 상대를 인간이 아닌 자원으로 대하게 됨
- 연습법: 거래를 상호작용의 부차적인 것으로 만들 것. 정보뿐 아니라 사람을 위한 시간을 낼 것. 다음 불을 끄러 급히 가고 싶어 하는 것처럼 보이지 말고, 잠깐이라도 다른 인간과 함께 있는 것을 즐길 것
- 팁: 모르는 사람과 연습하는 편이 오히려 더 쉬움. 사람들이 편하게 이야기하도록 만들 일반적인 질문들을 미리 준비해두는 것도 방법
- 흔히 “스몰토크”라 불리지만 실은 부족(집단)을 하나로 묶는 접착제와 같음
아이디어 40. 스몰토크는 말하기가 아니라 듣기에 관한 것이다.
- 여기서 배우는 것은 타인의 관점에서 사물을 보는 법 - 누군가와 편하게 대화할 수 있다는 것은 그들과 그들의 삶에 대해 기억할 만큼 신경 쓴다는 뜻
6.4.2 나는 다른 사람에게 어떻게 비치는가
- 저자는 영국인 - 감정을 침대 밑 금고에 넣어둔 케블라 상자에 봉인해두도록 자랐다고 비유적으로 표현
- 화가 났든, 좌절했든, 지루하든, 집중하든, 기쁘든 어떤 기분이든 사람들이 알아채지 못하는 것에 항상 놀람
- 치통이 있다는 것을 굳이 말하지 않고 사람들이 알아채길 기대하다가, 짧은 대답 때문에 상대가 짜증을 내면 실망하곤 함
- 자문할 질문
- 내 감정이 명백하다고 믿는다면, 사람들이 내 기분을 제대로 파악하지 못할 때 놀라는가
- 명백하지 않다고 느낀다면, 사람들이 내 기분을 몰라줄 때 상처받는가
- 어느 쪽이든, 극단으로 가지 않으면서도 사람들이 내 감정을 더 쉽게 알 수 있게 하려면 무엇을 할 수 있을지 고민해볼 것
- 저자의 실천: 무언가를 강하게 느낄 때는 사람들에게 말하려고 함. 모든 것이 자기 얘기처럼 들리지 않게 하면서도, 누군가에게 퉁명스럽게 굴었다는 것을 깨달으면 “미안합니다, 좀 퉁명스러웠죠. 이가 아파서 집중하기가 힘드네요” 같은 식으로 덧붙임
아이디어 41. 상대가 내 기분을 안다고 가정하지 말고, 직접 말해주라.
- 만약 사람들이 내 기분을 잘못 해석한다면, 나도 그들에게 똑같이 했을 가능성이 큼 - 그렇다면 그들이 실제로 어떻게 느끼는지 어떻게 알아낼 수 있을까
직접 해보기 - 상대 기분 알아내기
- 사람들은 “너 기분 나빠?“라고 계속 물어보는 사람을 매우 지겨워함. 대신 상대가 좋게 느끼면서도 확인할 수 있는 다른 방법들이 있음
- 예시 대화 흐름: “Jan, 뭐 하고 있었어?” - “오전 내내 리포트 포맷터 버그를 추적했어” - “아이고, 그런 건 정말 짜증나겠다” - “사실 난 꽤 즐기는데” - “정말? 왜 그런데…”
- 게임처럼 만들기: 주기적으로 상대의 기분을 관찰만으로 판단해보고, 직접 묻지 않으면서 확인할 방법을 찾아보는 연습을 할 것
6.4.3 나에게 힘이 있는가
- 매일의 좌절 속에서 개발자들이 상당한 힘을 가지고 있다는 것을 잊기 쉬움 - 우리는 일이 일어나게 만드는 사람들
- 현실 세계와 스키마·아키텍처라는 추상 세계를 모두 이해함. 종종 회사 내 다른 누구보다 비즈니스 전반을 더 잘 알기도 함
- 이 때문에 상호작용에서 특권적인 위치에 서게 됨 - 상대 위에 군림하거나, 난해한 지식을 휘둘러 스스로를 특별하고 강력하게 느끼거나, 점수를 딸 수 있는 선택지가 있음
- 안타깝게도 세상 사람들이 “프로그래머들, 원래 그래… 안됐다”며 넘어가주기 때문에 이런 행동이 종종 용인됨
직접 해보기 - 힘 다루기
- 신적인 지식을 과시하며 점수를 따고 있는 자신을 발견하면 규칙을 바꿀 것
- 새로운 게임: 상대가 내가 말하는 요점을 충분히 이해해서 자신의 말로 바꿔 말할(paraphrase) 수 있을 때만 점수를 얻는 것으로
6.4.4 위쪽 사슬에서도 마찬가지다
- 소프트웨어 업계의 특이한 점: 개발자는 실제로 자신의 관리자에 대해서도 상당한 힘을 가짐
- 이것이 마찰의 한 원인이 됨 - 관리자는 (암묵적으로) 부하 직원들이 통제 불능이 되는 것을 막기 위해 통제력을 행사해야 한다고 느낄 수 있음
- 그러므로 관리자와 대화할 때도 공감을 실천할 것: 내가 위협적인 존재라는 느낌을 없애고, 모든 대화가 끝날 때 그들이 스스로에 대해 더 좋게 느끼도록 만들 것
6.4.5 황금률을 실천하라
- 존 스튜어트 밀의 표현: “남에게 대접받고 싶은 대로 남을 대접하라”
- 흔한 해석은 “재수 없게 굴지 말라”이지만, 훨씬 더 긍정적인 측면도 있음
- 친절은 포지티브섬 게임(positive-sum game)
직접 해보기 - 한 달간 친절 실천하기
- 한 달 동안 사람들에게 능동적으로, 요청받지 않은 친절한 일을 할 것. 거창할 필요는 없으며, 상대가 무엇을 원할지 생각했다는 것을 보여주는 것들이면 충분함
- 예시 - 내 컵을 다시 채울 때 옆자리 동료에게도 커피를 가져다주기
- 예시 - 동료에게 최근 PR이 정말 좋았다고 말해주기
- 예시 - 팀원이 아이를 데리러 조금 일찍 나가야 할 때, 대신 테스트를 마무리해주겠다고 제안하기
- 효과 1: 다른 사람과 그들이 필요로 하는 것을 생각하게 됨 - 이것이 바로 공감
- 효과 2: 기분이 좋아짐 - 친절을 베풀면 기분이 좋고, 상대는 인정받는다고 느낌
6.4.6 공감은 예견된 피드백이다
- 공감은 단순함을 만드는 데 핵심 역할을 함
- 공감은 스스로에 대한 필수적인 피드백을 모으는 방법: 어떤 상황을 다른 사람의 관점에서 상상하고, 이를 이용해 자신의 행동을 조정함
- 이를 통해 더 빠르게, 마찰을 덜 겪으며 움직일 수 있음
- 아무 생각 없이 무언가를 하고 나중에 반응을 처리할 수도 있지만, 상황을 오판했다면 모든 일을 되돌리고 신뢰를 다시 쌓아야 함 - 비효율적
- 공감을 가진 둘 이상의 사람은 이를 크게 우회할 수 있음: 비언어적 신호와 예상되는 필요에 반응해 많은 작은 결정을 내리고, 암묵적으로든 명시적으로든 그것을 시도해볼 수 있음
- 공감으로 일하면 거의 즉각적인 피드백을 얻고 지속적으로 미세 조정을 하게 됨 - 이것이 일을 훨씬 단순하게 만듦
조하리의 창(Johari Window) - 서로를 얼마나 잘 아는가
- 자신에 대한 스스로의 시각과 타인의 시각이 얼마나 일치하는지 발견하도록 돕는 자기이해 연습
- 스스로의 의견과 타인의 의견 사이의 놀라운 차이를 보여줄 뿐 아니라, 신뢰를 쌓는 방법으로서 마음을 열고 싶은 부분을 탐색하도록 도와줌
- 참여하지 않는 사람이 세션을 진행하도록 하는 것이 좋음 (중립성을 유지하기 위해)
| 나에게 알려짐 | 나에게 알려지지 않음 | |
|---|---|---|
| 타인에게 알려짐 | 공개 영역 (Public) | 맹점 (Blind Spot) |
| 타인에게 알려지지 않음 | 사적 영역 (Private) | 미발견 영역 (To be Discovered) |
6.5 실천 18. 사물에도 공감하라
- 누구나 물리적 세계에 대해 “힘이 곧 정의(might-makes-right)“인 방식으로 접근하는 친척이 한 명쯤 있음
- 열쇠가 안 돌아가면 부러질 때까지 더 세게 돌림
- 차가 시동이 안 걸리면 배터리가 방전될 때까지 계속 시도함
- 병뚜껑이 안 열리면 애지중지하던 칼끝을 구부려가며 지렛대로 씀
- 이런 사람들은 자신이 시스템의 일부이며, 다루는 물리적 대상에는 그 나름의 제약과 선호하는 작동 방식이 있다는 생각을 못 함
- “이 열쇠가 틀린 걸까? 거꾸로 됐나?”
- “왜 시동이 안 걸리지? 뭘 빠뜨렸지?”
- “병은 다른 어떤 방법으로 열리지? 뚜껑을 비틀어봐야 하나?” 같은 생각을 하지 않음
아이디어 42. 자신의 환경을 이해하라; 맞서 싸우지 말라.
- 사람에 대한 공감은 그들의 관점에서 경험하고, 그들이 어떻게 작동하는지, 무엇을 할 수 있는지, 한계가 무엇인지 이해하는 것
- 물리적 사물은 내적 관점은 없지만 작동 방식, 할 수 있는 것, 한계는 있음
- 이를 알고 실천하는 것도 공감의 한 형태이며, 사회적 공감과 마찬가지로 이를 활용하면 일이 더 쉽고 단순해짐
- 저자의 요트 일화
- 요트를 공동 소유했던 저자와 파트너는 숙련된 스키퍼와 주말 항해 훈련을 진행함
- 저자와 공동소유자는 방향을 억지로 왔다갔다 힘으로 조종했고, 시트를 한 번에 30센티미터씩 풀거나 조였음 - 항해가 매우 피곤했음
- 숙련된 스키퍼는 거의 움직이지 않았음. 손가락을 키(helm)에 살짝 얹고, 손가락을 살짝 오므렸다 펴며 (때로는 거의 안 보일 정도로) 조종했음. 세일을 한 번 맞춘 뒤 필요에 따라 몇 인치씩만 시트를 조정했음
- 그는 보트와 싸우지 않고, 보트를 느끼며 보트와 합의에 이르렀음 - 공감을 가지고, 에너지를 덜 쓰며, 보트는 더 빠르고 부드럽게 나아감. 항해를 단순하게 만듦
6.5.1 코드에 귀 기울이기
- 코드에 귀 기울이기 시작하면 자신도 모르게 작은 조정을 하게 됨 - 이것이 단순함
- 코드에 귀 기울이는 방법을 스스로에게 물어볼 것
- 함수를 작성하면서 불편함을 느끼거나, 화면의 코드를 보고 글자를 의식적으로 읽기도 전에 “뭔가 잘못됐다”고 느낀 적이 있는가
- 코드를 작성하며 코드가 반발하는 느낌, “이건 원래보다 더 어려워야 정상이 아닌데”라는 생각이 계속 드는가
- 요령은 이런 느낌을 인지하고, 그 느낌이 떠오를 때 뭔가 조치를 취하는 것
- 디버깅에서 시작하기 좋음: 디버깅을 잘하는 사람들의 공통된 비결
- 버그를 실수로 여기지 않음 - 대신 버그는 시스템이 지시받은 대로 작동하는 것일 뿐이라고 봄
- “어떤 코드가 잘못됐나”를 찾지 않고, 시스템 전체를 보며 “무엇이 저렇게 반응하게 만들 수 있을까”를 물음
- 데이터와 제어의 흐름을 생각하며, 그 일부를 어떻게 격리할 수 있을지 고민함
- 진단의(diagnostician)가 환자를 대하듯 시스템을 대함
- 디버깅에 어려움을 겪는다면 관점을 바꿔볼 것: 불쌍한 코드가 어떤 실수 때문에 나쁘게 행동하도록 강요받았다고 생각해볼 것. 무엇이 그렇게 만들었을까? 코드가 무엇을 하고 있는지 더 잘 이해하려면 어떤 질문을 던질 수 있을까
- 디버깅을 넘어, 자신의 무의식이 작업에 대해 보내는 경고 신호를 찾아볼 것 - 증상은 종종 미묘함
- 평소보다 자주 자리를 떠나 물을 가지러 감
- 계속 이름을 바꿈 (리네이밍을 반복함)
- 타이핑이 힘든 일처럼 느껴짐
- 멈추고 다른 걸 하고 싶어짐
- 그 외 여러 회피 행동들
- 저자의 아내 일화 (뉴욕)
- 길을 걸을 때 아내가 가끔 저자를 길 쪽으로 살짝 밀치곤 했음 (hip check)
- 본인은 그런 행동을 인식하지 못하고 있었음 - 무의식적 반응이었음
- 알고 보니 누군가 숨어있을 수 있는 통로나 출입구를 지날 때마다 무의식적으로 멀어지는 반사 행동이었음 (의식적인 것이 아니었음)
6.5.2 두려움이라는 선물
- 게빈 드 베커(Gavin de Becker)의 회사는 공격 대상이 될 수 있는 사람들에게 개인 경호 서비스를 제공함
- 저서 The Gift of Fear에서, 경호원을 고용할 형편이 안 되는 사람들도 위험을 피하기 위해 할 수 있는 것들을 설명함
- 가장 기억에 남는 조언: 자신의 직감을 신뢰하는 법을 배우라
- 길에서 강도를 당한 대부분의 사람들은 사건 전에 불안함을 느꼈지만 그냥 넘겼다고 보고함
- 불안한 느낌은 무의식이 스스로도 인지하지 못한 패턴을 인식한 것 - 그림자의 움직임을 알아챘거나, 숨소리나 발소리를 들었을 수 있음
- 의식적인 뇌에 “조심해”라고 직접 전달할 방법이 없어서, 대신 낮은 수준의 반응을 촉발함: 심박수 증가, 약간의 메스꺼움이나 한기 등
- 그는 고객들이 이런 느낌을 인지하고 그에 따라 행동하도록 돕는 훈련을 함
- 요점: 이런 반사작용은 원시시대에 생존을 돕던 것 - 지금 이를 무시하는 것은 수천 년의 집단 경험과 자신의 수십 년 경험을 버리는 것
직접 해보기 - 파충류의 뇌에 귀 기울이기
- 뭔가 이상하게 느껴지고 상황이 조금 어긋난 느낌이 들 때, 무시하지 말고 그 느낌을 들여다볼 것
- 그 느낌이 시작됐을 때 무엇을 하고 있었는가? 그것이 어떤 감정을 불러일으키는가?
- 팁: 사람들은 종종 샤워할 때나 걸을 때 최고의 아이디어를 얻는다고 함 - 의식이 유휴 상태일 때 무의식이 뚫고 나올 공간이 생기기 때문
- 저자의 방법: 스파이디 센스가 발동했는데 원인을 바로 짚어낼 수 없을 때 휴식을 취함. 걷거나, 낙서를 하거나, 정리정돈을 함 - 종종 그 원인이 저절로 머릿속에 떠오름
6.6 실천 19. 이야기를 지어내라
- 도입 일화: 엘리베이터에서 상사의 상사(boss²)가 “왜 필요한 것보다 4배나 많은 저장공간에 비용을 내고 있나?“라고 물음
- 첫 번째 시도 (기술적 정답)
- “인덱스 때문에 필요한 여분 공간입니다”라고 답하자 “인덱스가 뭔데, 왜 그거에 돈을 내는 거야?“라는 추가 질문을 받음
- 저자는 정확한 기술적 정의로 답변함 (인덱스는 한 개 이상 컬럼 값과 그 값을 포함한 행 사이의 매핑이며, 빠른 접근을 위해 해시되거나 정렬되고, 인덱스에서 값을 찾으면 해당 행으로 바로 갈 수 있다는 내용)
- 결과: boss²는 인상을 찌푸리고 무슨 소린지 모르겠다고 중얼거리며 다음 층에서 내림
- 훌륭하고 사실적인 답이었지만, 아무것도 전달하지 못함
6.6.1 두 번째 시도
- 같은 장면을 다시, 이번엔 이 챕터의 아이디어를 연습한 버전의 저자로 재생함
- 상사²는 똑똑하지만 비기술적임을 인지 - 인덱스가 공간을 차지한다는 사실 자체보다, 왜 인덱스가 필요한지 설명할 이야기(비유)가 필요하다고 판단함
- 답변(비유 사용): 시스템은 수십억 개의 정보 중 하나를 찾아내야 하고, 이를 위해 인덱스를 만듦. 인덱스는 책의 색인과 같아서, 빠르게 검색한 뒤 원하는 페이지로 바로 데려다줌. 여러 방식으로 조회해야 하므로 인덱스가 여러 개 필요하고, 책의 색인이 지면을 차지하듯 데이터베이스의 인덱스도 저장공간을 차지함
- 이야기를 짜는 동안 boss²의 표정을 관찰함 - 고개를 끄덕이고 눈을 떼지 않음
- 끝에는 미소 지으며 “아, 이제 완전히 이해되네요. 당신 상사도 이렇게 설명해주면 좋겠어요”라고 말함
6.6.2 픽션이지 마찰이 아니다
- 반론 가능성: 그 이야기는 정확하지 않음. 실제 데이터베이스 인덱스는 훨씬 복잡하고 끊임없이 재구성되지만, 물리적 책의 색인은 거의 그렇지 않음
- 그래서 어떻다는 것인가 - 대부분의 상황에서 진짜 임무는 정보를 전달하고 소통하는 것. 그러려면 상황을 고려해야 함
- 이 맥락에서 어떤 정보가 중요한가
- 그것을 전달하는 데 효과적인 어휘는 무엇인가
- 그 정보를 그 어휘에 매핑하는 비유나 은유가 있는가
- 비유로 인해 손실되거나 왜곡되는 정보가 청자의 이해에 영향을 미치는가
- 위 엘리베이터 대화에 적용한 결과
- 중요한 정보: 저장공간 총량이 실제 데이터양보다 많은 이유는 애플리케이션이 많은 인덱스를 사용하기 때문
- 효과적 어휘: boss²는 해싱이나 B-트리 이야기를 따라오지도, 흥미로워하지도 않을 것이므로 비기술적인 이야기가 필요함
- 비유: 데이터가 인덱싱되고 그 인덱스가 공간을 차지하는 실제 세계 사례를 찾음 - 책의 색인이 명백한 유사물임
- 손실/왜곡 정보의 영향: 인덱스는 접근을 빠르게 하지만 공간을 차지한다는 것만 전달하면 충분하다고 판단함
- 좋은 비유는 청자에게 통하는 언어로 전달해야 할 것을 보여줌. 완전한 진실은 아니며, 세부사항에서는 부정확할 수도 있음
아이디어 43. 비유는 유용한 거짓말이다.
- 100퍼센트 정확한 저수준 답변은 청자가 나만큼 세부사항에 익숙하지 않은 한 마찰을 일으킴
- 대체로 정확한 비유나 은유는 그 마찰을 약간의 픽션(fiction) 도입 위험과 맞바꿈
6.6.3 도메인 고르기
- 이런 스토리텔링에서 가장 어려운 부분 중 하나는 도메인(이야기가 펼쳐지는 세계)을 고르는 것
- 어려운 이유: 메시지를 전달할 만큼 풍부해야 하지만, 동시에 나와 청자 모두 쉽게 이해할 수 있어야 함
- 그래서 나와 청자가 공통된 배경을 공유할 때만 이 기법을 쓰는 것이 좋음
- 피해야 할 것들
- 둘 중 한쪽만 깊이 아는 영역: 야구광이라도 인필드 플라이 룰에 빗댄 설명은 청자가 같은 열정을 공유하지 않으면 실패함. 반대로 청자가 잘 알고 나는 모르는 영역도 피해야 함 (예: 청자가 오페라 애호가라 해도, 내가 모차르트를 좋아하지 않는다면 <피가로의 결혼>을 키 교환 알고리즘의 은유로 쓰지 말 것)
- 정치, 종교, 사회적 이슈 등
- 설정하는 데 시간이 오래 걸리는 세계: 정보를 원하는 사람들은 비유의 등장인물과 배경을 15분씩 설명하는 것을 참지 못함 - 배경지식이 거의 필요 없는 것을 골라야 함
- 시사성 있는 인물이나 소셜미디어 이슈: 모두가 특정 화제를 따라가는 것은 아님
- 대신, 공유하는 세계를 찾을 것 - 고전 영화, 실생활 경험, 사무실 네트워크의 특징, 날씨 등. 누군가와 스몰토크에 쓸 수 있는 소재라면 대체로 괜찮은 후보임
6.6.4 비유의 본질
낯선 개념을 설명할 이야기를 만들 때 실제로 하는 일은 상대의 관점에서 상황을 보는 것임. 단순해 보이는 질문에 이해 못할 전문용어 뭉치로 답하는 것을 받는 것이 어떤 느낌일지 상상해볼 것 - 받는 사람은 위축되고, 무지하다고 느끼고, 좌절하거나 그냥 화가 날 것임. 그래서 대신, 공감을 이용해 우리의 세계를 그들의 세계로 매핑하는 방법을 만듦
- 비유가 왜 작동하는지 살펴볼 것
- 우리는 청자의 편안한 영역(comfort zone) 밖에 있는 무언가를 설명하려 함
- 설명하려는 대상에는 여러 구성요소가 있고, 이 구성요소들은 예측 가능한 방식으로 상호작용함. 문제는 청자가 이 중 아무것도 모른다는 것
- 그래서 청자가 이해하는 다른 도메인을 가져와, 원래 설명의 각 구성요소를 비유 도메인의 대응 구성요소로 매핑함
- 좋은 비유는 그 이상을 담음: 설명하려는 대상이 실제 세계에서 어떻게 작동하는지, 즉 실제 구성요소가 어떻게 상호작용하고 결과를 만들어내는지를 보여주려 함
- 비유가 효과적이려면, 비유 도메인의 대응 구성요소들도 상호작용해야 하고, 그 상호작용이 실제 세계로 다시 매핑되는 결과를 만들어내야 함
인덱스 예시의 매핑
| 실제 세계 (데이터베이스) | 비유 (책) |
|---|---|
| 인덱스는 특정 데이터의 위치를 매우 빠르게 찾게 해줌 | 색인은 책 속 특정 내용을 매우 빠르게 찾게 해줌 |
| 인덱스는 저장 공간을 차지함 | 색인은 지면(공간)을 차지함 |
| 인덱스가 많을수록 데이터를 더 빠르게 찾는 방법이 많아짐 | 색인이 많을수록 뭔가를 찾는 방법이 많아짐 |
| 인덱스가 많을수록 더 많은 저장공간을 차지함 | 색인이 많을수록 더 많은 공간을 차지함 |
- 매핑이 완벽한가? 전혀 아님
- 가장 큰 부정확함: 책 색인의 공간 오버헤드는 데이터베이스 인덱스의 오버헤드보다 훨씬 적음
- 인덱스가 할 수 있는 데이터베이스 수준의 여러 기능도 생략됨
- 하지만 상관없음 - 요점을 전달하기에 충분히 정확하면 그걸로 충분함
아이디어 44. 이야기 속 개념들은 실제 세계의 대응물처럼 상호작용해야 한다.
6.6.5 비유 만들기
- 가장 간단한 방법: 설명하려는 대상의 이름을 살펴보는 것. 인덱스라는 단어에서 책이라는 연상이 떠오르고, 거기서부터 그리 어렵지 않게 이야기가 이어짐
- 두 번째 예시: 이벤트 티켓 판매 애플리케이션 운영 중, 피크 부하 시 데이터베이스 락 경합으로 성능 문제가 발생함. 비기술적인 관리자가 “왜 서버 열두 대를 더 추가해서 고치지 못하냐”고 물음
- 체크리스트를 이용해 비유를 만드는 과정
- 이 맥락에서 어떤 정보가 중요한가: 문제는 초기 요청을 처리하는 능력이 아님. 모든 요청이 처리 끝에 데이터베이스에 쓰기 위해 줄을 서야 하는 것이 원인이며, 데이터베이스 밖의 추가 처리 능력은 도움이 안 된다는 것을 설명해야 함
- 효과적인 어휘는 무엇인가: 들어오는 이벤트가 처음엔 병렬로 처리될 수 있지만 결국 어떤 자원에 대한 독점적 접근을 위해 줄을 서야 하는 상황.
queue(큐)라는 단어는 즉시 풍부한 시나리오를 떠올리게 함 - 모두가 줄을 서서 기다려본 경험이 있음 - 매핑할 비유나 은유가 있는가: 공항에서 수하물을 체크인하는 과정으로 설명 가능. 승객은 먼저 항공사에 가방을 맡기고, 그다음 보안검색대를 통과하기 위해 줄을 섬. 가방을 맡기는 상호작용은 시스템의 초기 요청 처리와, 보안검색 줄을 기다리는 것은 데이터베이스 대기열과 대응됨
- 정보 손실이나 왜곡이 이해에 영향을 주는가: 충분히 좋은 매칭이라 판단하고 이야기를 시작함
- 실제 사용된 비유 (기차역)
| 실제 세계 | 비유 (기차역) |
|---|---|
| 사용자 | 승객 |
| 요청을 처리하는 서버 | 발권기 |
| 초기 요청 처리 | 표를 사는 행위 |
| 데이터베이스 쓰기 대기 (락 경합) | 보안 검색대에서 줄서기 |
| 서버를 늘려도 병목이 해결되지 않음 | 발권기를 늘려도 보안검색 줄은 줄어들지 않음 |
| 해결책: 데이터베이스 병목(잠금 경합) 자체를 해결해야 함 | 해결책: 보안검색 처리 속도를 높여야 함 |
- 실제 사용한 설명의 흐름
- 시스템을 기차역에 비유함. 승객이 도착해 발권기에서 표를 삼(사용자가 서버에서 요청을 처리시키는 것과 동일)
- 표를 산 뒤 보안검색을 통과하기 위해 줄을 서야 하며 한 번에 한 명씩만 통과 가능함(요청이 데이터베이스 업데이트를 기다리며 대기하는 것과 동일)
- 모두가 기차를 타기까지 얼마나 걸리는지 불평함. 발권기를 늘리면 도착 승객을 더 많이 병렬 처리할 수 있을 것 같지만, 그래도 사람들은 여전히 보안검색에서 대기하게 되므로 도움이 안 됨
- 대신 보안 병목에 집중해야 함: 병렬로 더 많은 레인을 운영할지, 승객당 상호작용 횟수를 줄일지, 처리 속도를 높일지 고민해야 함
- 결론: 그래서 시간을 데이터베이스 병목을 살펴보는 데 쓰는 것이 낫다고 판단함
- 이 비유가 좋은 이유: 실제 세계의 개념(애플리케이션, 요청 처리, 데이터베이스 경합)이 비유(기차역, 발권기, 보안검색)로 잘 매핑됨. 실제 세계의 상호작용이 이야기 속 상호작용으로 모델링되며, 그 결과 비유에서 이끌어낸 결론이 실제 세계에도 그대로 적용됨
6.6.6 발표 불안
- 즉석에서 좋은 비유를 떠올리기는 어려움 - 좌절스러운 대화를 마치고 집에 가는 길에야 완벽한 비유가 떠오르는 경우가 수없이 많았다고 함
- 하지만 연습이 가능하며, 연습하면 즉석에서 생각하는 능력이 생김
직접 해보기 - 오프라인 스토리텔링 연습하기
- 누군가와 대면하지 않아도 이야기를 활용할 수 있음. 사실 비동기로 하는 편이 더 쉬움
- 이메일은 즉각 응답해야 하는 스트레스 없이 기법을 연습하기 좋은 수단
- 받은편지함에 질문이 도착하면, 답하기 전에 멈추고 발신자가 실제로 무엇을 필요로 하는지, 어떻게 가장 잘 설명할 수 있을지 자문할 것. 체크리스트를 기억 보조 도구로 활용할 것
- 비유를 사용해 답변을 작성한 뒤 “제가 명확히 설명했는지 모르겠네요. 이해가 되셨나요?” 같은 문구를 덧붙일 것
직접 해보기 - 소셜 스토리텔링 연습하기
- 이모가 또 찾아와서 “요즘 무슨 일 하니?“라고 묻거나, 삼촌이 “SSID가 대체 뭐냐?“고 묻는 상황을 활용할 것
- 이보다 더 친절한 청중은 없음. “새로운 설명 방법을 찾는 연습을 하고 있는데, 실험 대상이 되어줄래요?“라고 말하고 최선을 다해볼 것. 진행하면서 말이 되는지 물어볼 것
- 단점: 가족 내 모든 기술 질문의 해결사가 될 수 있음
직접 해보기 - 즉흥극 수업 듣기
- 저자는 즉흥 코미디언과 오후를 보내는 컨퍼런스 세션에 몇 번 참석함
- 일부 참가자들이 오후가 진행되며 성장하는 모습이 흥미로웠음 (저자 본인은 그중 하나가 아니었다고 함)
- 그러나 최악의 참가자조차도 대화를 이어가면서 생각할 시간을 버는 데 유용한 기법(“yes, and…”)을 배움
6.6.7 나 자신에게도 이야기를 들려주라
- 저자는 무엇을 해야 할지 완전히 이해하지 못한 채 소프트웨어를 작성하려 할 때가 많음
- 보통은 다양한 해결책을 탐구하기 위해 버릴 프로토타입을 작성함. 하지만 풀려는 문제에 대한 비유를 떠올리는 데 몇 분을 쓰는 것도 유용하다는 것을 발견함
- 이야기의 도메인 안에서 생각하기 시작하면, 그 허구의 객체들이 어떻게 상호작용해서 (허구의) 결과를 달성할지 볼 수 있음. 때로는 그 행동을 원래 문제 도메인으로 다시 매핑할 수 있음
스스로에게 물어보기 기술에 익숙하지 않은 친구를 위해 다음 기술적 질문들에 답할 비유를 떠올려볼 것
- 스택(또는 LIFO 큐)은 어떻게 작동하는가
- FIFO 큐는 어떻게 작동하는가
- SSID란 정확히 무엇인가
- 컴퓨터의 메모리와 저장장치의 차이는 무엇인가
각주 4 - 조하리의 창(Johari Window)에 관한 참고 자료는 mindtools.com 등 온라인에서 다수 찾아볼 수 있음 (원문 각주 참고)
파트 4. 코드를 단순화하라
- 이 파트는 책 전체에서 예외적인 위치를 차지함
- 다른 파트들은 대체로 상위 수준(high level) 논의였지만, 이 파트는 실제 코드 수준까지 깊이 들어감
- 두 챕터로 구성되며 서로 성격이 매우 다름
- 7장: 코드가 데이터를 단순 처리하는 것이 아니라, 데이터가 코드의 동작을 이끌도록 만드는 방법을 탐구
- 코드 단순화의 유일한 방법이라서가 아니라, 구현이 쉽고 이익이 크며 실무에서 널리 쓰이지 않기 때문에 다룸
- 8장: 저자가 매일 코딩할 때 사용하는 한두 페이지 분량의 매우 낮은 수준(low-level)의 실천법 모음
- 7장: 코드가 데이터를 단순 처리하는 것이 아니라, 데이터가 코드의 동작을 이끌도록 만드는 방법을 탐구
- 이 파트를 넣을지에 대해 저자 내부적으로 논쟁이 많았음
- 저자가 가장 하고 싶지 않은 일이 “어떻게 코딩해야 하는지 알려주는 것”이었기 때문
- 그럼에도 포함한 이유: 코딩의 메커니즘에 대한 사고방식, 즉 ==걸림돌을 만날 때마다 단순화할 방법을 찾아보는 태도==를 보여주기 위함
챕터 7. 데이터 주도 개발
- 저자의 과거 인식: 프로그래머의 일은 데이터를 조작하는 코드를 작성하는 것(입력이 출력이 되고 데이터가 정보가 됨)
- OO나 함수형 언어를 쓸 때도 암묵적으로 “코드가 주도권을 가진다”는 전제로 명령형에 가깝게 구조화해 옴
- 이 전제는 틀렸음을 깨달음
- 코드는 주도권을 가진 존재가 아니라, 가치를 전달하기 위해 사용하는 하나의 도구일 뿐
- 새로운 관점: 개발이란 데이터와 코드가 서로 주도권을 주고받는 과정
- 때로는 코드가 데이터를 생성
- 때로는 데이터가 코드를 주도
- 이점
- 데이터는 코드보다 다루기 쉬움 — 추론하기 쉽고, 조작하기 쉽고, 변경하기 쉬움
- 코드를 데이터로 더 많이 구동할수록, 코드도 이런 속성(다루기 쉬움)을 물려받음
아이디어 45. 데이터는 코드보다 단순하다
- 모든 애플리케이션은 이미 입력 데이터에 의해 어느 정도 구동됨(조건문·반복문에 영향을 주는 값)
- 그러나 이 장에서 다루는 것은 더 깊은 층위의 데이터, 즉 애플리케이션 로직의 일부가 되는 데이터
- 이를 구현하는 방법은 다양하며, 이 장에서는 두 가지를 다룸
- 테이블로 코드 실행 순서를 결정하는 방법
- 이를 확장한 상태 머신(state machine)
- 이는 빙산의 일각일 뿐, 데이터가 코드를 구동하는 다른 사례들도 존재함
- 애플리케이션 설정(configuration)도 동작을 제어하는 데이터임 — 공급·갱신 방식이 앱을 단순하게도, 복잡하게도 만들 수 있음
- 애플리케이션 내부를 이동 중인 데이터(전송 중 데이터)도 다음에 무엇을 할지 결정할 수 있음
- 데이터 구조로 어떤 코드가 언제 실행될지 제어할 수 있음 — 테스트를 단순화하는 훌륭한 방법
- 진정한 제어가 필요하면 상태 머신을 사용 — 생각보다 복잡하지 않으며, 깊이 중첩된 코드를 깔끔한 선형 코드로 바꾸는 훌륭한 방법
실천 20. 데이터가 주도하게 하라
- 저자가 코딩하며 가장 큰 만족감을 느끼는 일 중 하나는, 반복적이거나 반복에 가까운 코드를 데이터로 매개변수화되는 단일 함수로 압축하는 것
아이디어 46. 데이터는 코드보다 변경하기 쉽다
- 예시: 도형을 그리는 프로그램에서 도형을 하나씩 추가하는 코드
- 처음에 자연스럽게 쓰게 되는 반복적 명령형 코드
let shape = addShape(Triangle, { sides: 3 });
shapesBySides[3].push(shape);
shape = addShape(Square, { sides: 4 });
shapesBySides[4].push(shape);
shape = addShape(Rhombus, { sides: 4 });
shapesBySides[4].push(shape);
// : :
- 세 번째 도형쯤에서 “변하는 데이터”와 “변하지 않는 코드”를 분리해야겠다는 생각이 듦
const shapeDefs: [ShapeClass, number][] = [
[Triangle, 3],
[Square, 4],
[Rhombus, 4],
// : :
];
shapeDefs.forEach(([cls, sides]) => {
const shape = addShape(cls, { sides });
shapesBySides[sides].push(shape);
});
- 데이터 주도 버전이 원본보다 코드 줄 수는 약간 많지만, 두 가지 이유로 더 단순함
- 도형 목록이 명시적으로 드러나 훑어보고 유지보수하기 쉬움
- 두 가지 다른 개념(도형 목록 vs 도형을 시스템에 추가하는 코드)이 분리됨 — 원래 코드에서는 이 둘이 뒤섞여 있었음
- 결과적으로 새 도형 추가 = 테이블에 한 줄 추가
에러 처리가 포함된 메서드 체인 관리하기
- 상황: 도형에 기하학적 변형(transform)을 적용. 각 transform 함수는
[status, newShape]튜플을 반환status가"ok"면newShape는 변형된 새 도형- 아니면 에러 발생,
newShape는 무효
- 중첩된 조건문으로 작성한 명령형 코드
type Status = "ok" | "error";
type TransformResult = [Status, Square | undefined];
const square = new Square(10);
const [status1, square1] = scale(square, 0.5);
if (status1 === "ok") {
const [status2, square2] = rotate(square1!, -5);
if (status2 === "ok") {
const [status3, square3] = skew(square2!, 0.1);
if (status3 === "ok") {
display(square3!);
}
}
}
- 이를 데이터 주도 방식으로 평탄화(flatten)한 버전
type TransformFn = (shape: Square) => TransformResult;
const transforms: TransformFn[] = [
(shape) => scale(shape, 0.5),
(shape) => rotate(shape, -5),
(shape) => skew(shape, 0.1),
];
let shape: Square | undefined = new Square(10);
for (const cmd of transforms) {
const [status, result] = cmd(shape!);
if (status !== "ok") {
shape = undefined;
break;
}
shape = result;
}
if (shape) {
display(shape);
}
- 저자 스스로도 이 정도 예제에서 데이터 주도 코드가 “더 단순하다”고 단정 짓기는 어렵다고 인정
- 이 예제는 매우 단순한 사례임. 실제 세계에서는 중첩 코드가 더 복잡하고 깊어지는 경우가 많으며, 그럴 때 이점이 훨씬 명확해짐
- 그럼에도 단순한 사례에서조차 데이터 주도 접근이 성취하는 것들
- 변형 순서 명세와 실제 변형 수행 코드가 분리되어 파악·변경이 쉬움(scale과 skew 순서를 바꾸는 난이도 비교)
- 변형 명세가 이제 단순한 값(value)이므로 다른 곳에서도 재사용 가능
- 중첩을 반복문으로 대체함. 중단 가능한(interruptible) reduce를 지원하는 언어라면 반복문조차 제거 가능
- 각 변형을 개별적으로 테스트할 수 있게 됨
아이디어 47. 데이터는 무엇을, 코드는 어떻게를 담당한다
- 위 논의의 핵심 요약: 데이터는 **무엇(what)**을 할지 규정하고, 코드는 그것을 어떻게(how) 수행할지를 담당
데이터 주도 코드는 인터프리터다
- 저자는 오랫동안 “인터프리터를 작성하듯 코드를 작성하라”고 주장해왔지만 매번 시큰둥한 반응을 받아옴
- 설명이 부족했음을 깨달음 — “인터프리터”라는 말을 들으면 사람들은 Java, Ruby, Python, Elixir 같은 언어를 실행하는 거대하고 복잡한 환경을 떠올림
- 그러나 인터프리터가 복잡할 필요는 없음
- 인터프리터의 본질: 데이터에 의해 무엇을 할지 지시받는 코드
- 프로그래밍 언어라면 그 데이터는 구문 트리(syntax tree)나 바이트코드, 인터프리터는 그것을 훑으며 동작을 수행하는 코드
- 앞선 도형 그리기 예제도 하나의 인터프리터임 — 도형 이름/변의 수로 이뤄진 테이블이 데이터, 이를 순회하며 적절한 동작을 호출하는 반복문이 실행부
- 이런 단순한 인터프리터는 코드를 변경하기 쉽게 만드는 핵심 전략(또한 애초에 작성하기도 쉽게 만들어줌)
외부 데이터로 주도하기
- 지금까지의 예제는 모두 코드 안에 리터럴 값으로 데이터를 정의했으나, 이것이 필수는 아님
- 데이터는 동적일 수 있음 — 다른 코드에서 생성되거나 사용자 상호작용에 대한 응답으로 생성될 수도 있음
- 예: 그리기 앱에서 사용자가 변형 시퀀스를 직접 만들고 이름을 붙이면, 이후 사용자가 선택한 도형에 그 시퀀스를 적용
- 결론: 반복적인 코드 뭉치를 발견할 때마다, 변하는 부분과 변하지 않는 부분을 분리해 변하는 부분을 데이터 구조로 옮길 수 있는지 살펴볼 것
생각해보기(Ponder)
- 변형 예제는 반복문을 구동하기 위해 함수들의 리스트를 사용함 → 이 리스트를 만드는 코드는 함수들과 같은 스코프에 있어야 함
- 만약 이
transforms리스트가 한 애플리케이션에서 만들어지고, 다른 애플리케이션에서 해석(interpret)되어야 한다면?- 이런 상황을 처리하려면 코드를 어떻게 바꿔야 할지 생각해볼 것
실천 21. 테이블로 테스트를 단순화하라
- 저자의 경험담: 1970년대 가장 인기 있던 미니컴퓨터 PDP-11의 어셈블러와 에뮬레이터 개발
- 반드시 정확해야만 하는 종류의 시스템
- 명령어 디코딩/실행의 미묘한 버그는, 에뮬레이터가 실행 중인 코드가 우연히 그 버그를 건드리기 전까지 드러나지 않을 수 있음
- 에뮬레이터가 실행하려는 기계 명령어를 정확히 식별하는지 검증하는 테스트가 필요
- PDP-11 명령어 형식은 16비트 워드 하나에 최대한 많은 정보를 담도록 설계되어 쉽지 않았음
- 한 워드가 opcode + 두 개의 주소 지정 모드 + 두 개의 레지스터를 나타내기도 하고, 다른 경우 상대 오프셋을 가진 분기 명령어를 담기도 함
- 수백 개의 명령어 디코딩 테스트 필요 → 초기 접근: 하나씩 개별 작성(복사·붙여넣기 반복)
test("decode mov", () => {
expect(emulator.decode(0o010000)).toBe("mov");
});
test("decode movb", () => {
expect(emulator.decode(0o110000)).toBe("movb");
});
test("decode cmp", () => {
expect(emulator.decode(0o020000)).toBe("cmp");
});
// 페이지 가득 반복...
- 문제점: 반복적 복사·붙여넣기가 많고, 보일러플레이트 때문에 실제로 테스트하려는 대상이 무엇인지 파악하기 어려움
- 해결책: 테이블 주도 코드
const instructionTable: [number, string][] = [
[0o010000, "mov"],
[0o110000, "movb"],
[0o020000, "cmp"],
// : :
[0o160000, "sub"],
];
instructionTable.forEach(([instruction, op]) => {
test(`decode ${op}`, () => {
expect(emulator.decode(instruction)).toBe(op);
});
});
- 테이블은 테스트 케이스 시퀀스를 담음 — 각 행은 명령어를 나타내는 이진 값과 대응하는 연산코드 이름
- 테이블을 순회하며 각 행마다 고유한 테스트 생성
- 연산코드 이름을 테스트 이름에 넣어(가독성 확보)
expect().toBe()로 디코딩 정확성 검증
- 연산코드 이름을 테스트 이름에 넣어(가독성 확보)
아이디어 48. 반복적인 테스트는 테이블로 주도하라
여러 단계 테스트를 데이터로 주도하기
- 테이블은 더 복잡한 테스트에서 진가를 발휘함
- 사례: Shopify의 GraphQL API로 로열티 시스템에 주문 데이터를 공급
- 문제: API는 주문의 현재 상태는 알려주지만 거기까지 어떻게 도달했는지는 알려주지 않음
- 고객이 구매했지만 아직 결제 안 함 → API는 결제 트랜잭션 없는 주문 반환
- 나중에 결제 완료 → 같은 주문에 결제 트랜잭션과 수정된 합계가 포함되어 반환
- 이후 고객이 쿠폰을 뒤늦게 요청 → 할인 정보, 할인 적용, 환불 트랜잭션 레코드가 3~4개 더 추가됨
- 시스템 측에서는 현재 상태를 마지막으로 알려진 상태와 대조(reconcile)하여 어떤 이벤트가 발생했는지 판별하고, 이를 주문 내 각 타이틀에 대한 금액 배분에 반영해야 함
- 각 테스트는 여러 단계로 구성
- 첫 API 응답으로 시작 → 올바르게 캡처했는지 검증
- 같은 테스트 내에서(이전 상태 필요) 다음 응답을 가져와 변경 사항 해석 검증
- 2단계를 4~5회 반복
- 처음엔 손으로 코딩 — 단일 응답 테스트가 약 135줄, 두 응답 처리 테스트는 200줄 초과
- 패턴이 보이자, 들어오는 데이터와 기대 결과를 제외한 모든 것을 리팩터링해 분리 → 테이블이 곧 테스트가 됨
- 최상위 테스트 데이터 구조(이름 + 단계 목록)
const testCases = [
["three items then refund one", [
/* step1 */,
/* step2 */,
/* step3 */,
]],
];
- 각 단계는 중첩 구조(fixture 식별자 + 기대값)
[
"#2288-a-start-with-one-item",
expectOrder(2288, TEST_USER_FOX_HOLLOW, TEST_USER_FOX_HOLLOW, {
taxTotal: 2.06,
shippingTotal: 0,
discountTotal: 0,
grandTotal: 27.01,
productTotal: 24.95,
lineItems: [
["tvmelixir-p-00", 1, 24.95],
],
royaltyLines: [],
}),
]
- 첫 요소: 로드할 fixture(= GraphQL 메시지 파일) 식별자
- 둘째 요소: 그 메시지 처리 후 기대되는 주문 상태
- 주문은 약 6개 테이블에 걸친 복잡한 존재라, 보일러플레이트를 생성해주고 관심 값만 채우게 해주는
expectOrder헬퍼를 만듦 - 첫 테스트에서 작성한 코드는 모두 그대로 남아있음 — 다만 테이블에서 fixture와 기대값을 받는 형태로 리팩터링된 것
- 새 테스트가 필요하면 입력·출력에만 집중, 추가 코드 작성 불필요
- 이 단순함 덕분에, 250줄짜리 개별 메서드였다면 놓쳤을 시나리오까지 테스트하도록 동기부여됨
테스트는 이미 데이터 주도적이다
- 아주 기본적인 수준을 넘는 테스트는 대부분 입력·출력을 나타내는 데이터 구조를 만들게 됨 → 어떤 의미에서 이미 데이터 주도적
- 같은 패턴을 따르는 여러 테스트(입력·출력만 다름)가 있다면, 테스트 로직을 함수로 추출하고 데이터 테이블로 매개변수화 가능
- 데이터를 리스트에 담아 순회하며 각 행마다 테스트 함수 호출
- 주의: 테스트 실패 시 그 원인이 된 데이터를 반드시 로그로 남길 것
- 효과: 테스트 작성·유지보수가 쉬워질 뿐 아니라 테스트 개수 통계를 늘리는 효과도 있음(농담 섞인 언급)
더 알아보기(Explore)
- 최근 진행한 프로젝트의 테스트 코드를 살펴볼 것
- 그중 얼마나 많은 부분을 데이터 주도 스타일로 리팩터링할 기회가 보이는지 점검
실천 22. 상태 머신으로 로직을 단순화하라
- 테이블 주도 코드는 반복적인 코드 시퀀스 단순화에 훌륭함
- 상태 머신은 여기에 **문맥(context)**과, 주어진 상황에서 어떤 코드를 실행할지 동적으로 결정하는 능력을 더한 한 단계 위의 개념
- 유한 오토마타(finite state automata), 유한 상태 기계(finite state machine)로도 불림
- 상태 머신에 대한 근거 없는 공포(FUD)가 많아 개발자들이 회피하는 경향이 있음 — 저자는 이를 “그저 어리석은 일”이라 단언
- 단순한 상태 머신은 코드를 훨씬 이해하기 쉽고 변경하기 쉽게 만들어줌
아이디어 49. 상태 머신을 만드는 데 라이브러리나 디자인 패턴은 필요 없다
- 저자가 상태 머신을 사용해 개선한 사례들
- 파싱: 거의 CSV(comma separated variable) 형식이지만 백슬래시로 문자를 이스케이프하는 파일 파싱
- Shopify 주문 정보 추출: 다양한 레코드 타입과 복잡한 규칙을 가진 주문에서, 올바른 정보를 올바른 순서로 제시
- 주문 이행(fulfillment) 처리: 단순한 상태 머신으로 이행 상태 변화를 처리하며 필요 시 배송·환불·알림 생성
- 로그 메시지 패턴 감지: 실시간 웹 서버 로그에서 같은 IP가 5초 이내 특정 URL에 3번 접근하는 패턴 탐지(여러 패턴이 스트림 안에서 겹칠 수 있음) → 동적으로 생성되는 상태 머신 집합으로 단순하게 해결
- 이 외에도 사례는 계속됨
- 원칙: 이전 이벤트가 다음 이벤트 처리 방식에 영향을 주는 일련의 이벤트를 다룬다면 상태 머신 사용을 고려
- 이벤트들이 시간에 걸쳐 흩어져 있고 긴 지연이 있을 수 있다면, 상태 머신을 쓸 근거는 더욱 강력해짐
상태 머신 구현하기
- 복잡한 라이브러리나 장황한 패턴 기반 접근 불필요 — 필요한 것은 룩업 테이블(lookup table)뿐
- 상태 머신의 구성 요소
| 구성 요소 | 설명 |
|---|---|
| 이벤트(event) | 상태 전이를 촉발하는 입력 |
| 상태(state) | 머신이 현재 머무는 위치 |
| 전이(transition) | 이벤트에 의해 한 상태에서 다른 상태로 이동하는 것 |
- 예시: 누르면 펜촉이 나왔다 들어갔다 하는 푸시버튼 볼펜
- 이벤트 1개: 버튼이 눌림
- 상태 2개: 펼쳐짐(extended), 접힘(retracted)
- 전이 2개: 펼쳐짐 → 접힘, 접힘 → 펼쳐짐
stateDiagram-v2
Extended --> Retracted: Click
Retracted --> Extended: Click
- 다이어그램이 복잡해지면, 손가락을 한 상태에 올려놓고 들어오는 이벤트를 상상 → 그 이벤트로 라벨링된 화살표를 찾아 손가락으로 다음 상태까지 따라가 보는 방법을 추천
- 구현: 이 상태 머신은 매우 단순해서 불리언(Boolean) 하나로 충분
let penRetracted = true;
eventStream.on("click", () => {
penRetracted = !penRetracted;
});
- 대단해 보이지 않을 수 있지만, 이것도 엄연히 동작하는 상태 머신임
더 복잡한 예: 키패드 잠금장치
- 예시: 1 → 5 → 9 순서로 눌러야 열리는 키패드 잠금장치
- 사용자는 올바른 순서를 입력하기 전에 몇 개의 키든 누를 수 있음
- 잘못된 숫자를 입력하면 처음부터 다시 시작
- 유효한 시퀀스 도중 1을 다시 누르면 시퀀스가 리셋됨
직접 해보기(Try it)
- 아래 상태 다이어그램을 보기 전에 스스로 먼저 그려볼 것
- 손가락 기법을 이용해 159 / 432159 / 15159 시퀀스로 올바르게 열리는지 검증해볼 것
- 저자의 상태 다이어그램 버전
stateDiagram-v2
WaitFirst --> WaitFirst: other
WaitFirst --> WaitSecond: 1
WaitSecond --> WaitSecond: 1
WaitSecond --> WaitFirst: other
WaitSecond --> WaitThird: 5
WaitThird --> WaitSecond: 1
WaitThird --> WaitFirst: other
WaitThird --> Unlocked: 9
- 핵심 트릭: 유효한 시퀀스로 시작했더라도 도중에 1이 눌리면 “두 번째 자릿수 대기” 상태로 되돌아가는 것(처음으로 완전히 리셋되는 것이 아님)
키패드 잠금장치 구현하기
- 구현 방법: 룩업 테이블(딕셔너리·해시·맵·객체) 사용
type LockState =
| "waitForFirstDigit"
| "waitForSecondDigit"
| "waitForThirdDigit"
| "unlocked";
const TRANSITIONS: Record<string, Record<string, LockState> | null> = {
waitForFirstDigit: {
"1": "waitForSecondDigit",
other: "waitForFirstDigit",
},
waitForSecondDigit: {
"5": "waitForThirdDigit",
"1": "waitForSecondDigit",
other: "waitForFirstDigit",
},
waitForThirdDigit: {
"9": "unlocked",
"1": "waitForSecondDigit",
other: "waitForFirstDigit",
},
unlocked: null,
};
- 전체 상태 전이 로직이 이 데이터 구조 하나에 캡슐화됨
- 각 상태가 딕셔너리의 키
- 값은 들어오는 입력에 따라 어떤 상태로 전이할지 정의하는 중첩 맵
- 이를 처리하는 코드는 매우 단순함
let state: string = "waitForFirstDigit";
let key: string | null;
while (TRANSITIONS[state] && (key = getNextKey())) {
const trimmedKey = key.trim();
state = TRANSITIONS[state]![trimmedKey] ?? TRANSITIONS[state]!["other"];
}
- 실제로 상태 머신 구현부는 단 한 줄(마지막 while 루프 안의 대입문)에 불과함
그게 다가 아니다
- 이 책에서 깊이 다루지는 않지만, 상태 머신으로 할 수 있는 확장들
- 각 전이마다 취할 동작(action)을 테이블에 인코딩 가능
- 이벤트 사이에 자신의 상태를 지속(persist)하는, 오래 지속되는(long-running) 상태 머신 구성 가능
- 상태 머신으로 워크플로우를 구동 가능
- 코드 내부의 많은 일들을 이벤트로 전환하면, 그 이벤트 처리 로직 구현에도 상태 머신을 사용 가능
- 저자가 이 주제에 대해 별도로 작성한 글 존재(각주 [5]):
https://articles.pragdave.me/p/simplify-logic-with-state-machines
데이터 주도의 끝
- 이 책의 대부분 챕터는 이런 마무리 요약을 갖지 않지만, 저자는 데이터와 코드에 관한 이 논의가 요약할 만큼 중요하다고 판단
- 저자는 오랜 세월에 걸쳐, 코드와 그것을 구동하는 데이터 사이의 올바른 균형점에서 달성되는 우아함과 단순함의 가치를 깨달아 옴
- 코드가 너무 많으면 유연성을 잃음
- 데이터가 너무 많으면 코드가 무엇을 하는지 파악하기 어려워짐
- ==데이터가 코드를 구동하면서도, 코드가 데이터와 다른 코드 모두로부터 디커플링(decoupled)되어 있는 스위트 스폿(sweet spot)==이야말로 마법 같은 지점
- 제안: 단순한 데이터 구조로 코드를 구동하는 실험을 부드럽게 시작 → 직관이 쌓이면 점점 더 많이 데이터를 활용하는 방향으로 스스로를 밀어붙일 것
- 저자는 그 끝에 도달한 결과를 독자가 마음에 들어할 것이라 확신한다고 밝힘
기회를 찾아 눈을 크게 뜨라(Keep your eyes open for opportunities)
- 앞으로 몇 달간, 더 데이터 주도적인 스타일로 전환할 기회를 찾아볼 것
- 마주칠 수 있는 상황들
- 몇 개의 값만 다르고 반복되는 코드 단락을 본다면, 리스트로 주도되도록 만들어볼 것
- 일련의 동작이 취해지고 그 순서가 상황에 따라 달라진다면, 단순한 인터프리터로 구현할 수 있음
- 들어오는 이벤트 스트림을 처리해야 하고 이전 이벤트가 다음 이벤트 처리에 영향을 준다면 상태 머신을 검토할 것
- 이 목록은 상상력을 자극하기 위한 씨앗일 뿐 — 더 많이 찾아볼수록 로직을 코드 밖으로 옮기고 삶을 단순하게 만들 방법을 더 많이 발견하게 될 것
- 마무리 문구: “Carpe datum!” (Carpe diem을 데이터에 빗댄 저자의 말장난, “데이터를 붙잡아라”는 의미)
챕터8. 코드 최전선에서 (Down at the Codeface)
- 저자의 코드 포맷팅 철학
- 2칸 들여쓰기냐 4칸이냐, 중괄호를 다음 줄에 쓰느냐 마느냐 같은 지엽적 스타일 논쟁에는 관심 없음
- 저자가 실제로 중요하게 보는 것은 오직 두 가지
- 읽기 쉬운가
- 수정하기 쉬운가
- 위 두 질문을 아래 세 가지 능력으로 구체화
- 원하는 것을 빠르게 찾을 수 있는가
- 구조를 한눈에 파악할 수 있는가
- 최소한의 부수적 수정만으로 항목을 추가/삭제할 수 있는가
- 이 챕터의 범위
- 코드의 설계(design) 자체가 아니라, 에디터 버퍼에 보이는 소스 파일의 표현 방식에 관한 이야기
- 저자의 제안
- 자신의 코드를 스스로 관찰할 것
- 변경을 어렵게 만드는 패턴을 쓰고 있지는 않은지, 코드를 스캔하며 원하는 것을 찾느라 헤매고 있지는 않은지 주기적으로 점검할 것
8.1 주석 없애기 (Practice 23: No Comment)
Idea 50: ==코드를 읽기 쉽게 만들고, 주석은 줄여라==
-
문제의식
- 모든 파일, 모듈, 함수, 변수, 파라미터마다 장문의 주석을 다는 극단적 사례를 풍자적으로 언급 (타입, 예외 목록까지 전부 주석으로 나열하는 습관)
- 저자는 이런 과도한 주석 문화를 나쁜 습관으로 규정
-
과도한 주석이 나쁜 이유
- 코드를 바꿀 때마다 관련 주석도 함께 고쳐야 하므로 작업량이 사실상 두 배
- 주석은 테스트 대상이 아니므로, 고친 주석이 실제로 맞는 내용인지 보장할 방법이 없음
- 시간이 지나면 코드와 주석이 어긋나면서 아무도 주석을 신뢰하지 않게 되고, 결국 변경을 방해하는 짐(보트 앵커)으로 전락
-
주석을 추가해도 좋은 정당한 이유 3가지
| 순번 | 이유 | 설명 |
|---|---|---|
| 1 | 문서화 도구 활용 | 주석을 추출해 API/커맨드라인 툴 문서를 자동 생성하는 경우. API 문서화의 최선책으로 평가 |
| 2 | 의도(왜) 설명 | 예상 밖의 방식으로 구현한 이유를 미래의 코드 독자에게 설명. 예: “환불 API 필드가 음수로 오기 때문에 총액에 그대로 더한다” |
| 3 | 플래그 표시용 | #TODO 등 작업 표시를 남기기 위함 (8.2 참고) |
-
참고: 일부 언어는 주석 안에 테스트를 내장 가능
- Elixir는 주석 블록에 예제 코드와 기대 결과를 함께 적어두면 이를 추출해 유닛 테스트로 실행 가능
- 유사 기능을 제공하는 라이브러리: JavaScript용 Doctest-JS, Python용 doctest 등
-
저자가 실제로 가장 자주 쓰는 이유: 코드 구조를 시각적으로 구분/강조하는 용도
- 이 책의 원고 자체가 약 50개의 마크다운 파일과 상위 매니페스트 파일로 구성되는데, 매니페스트 안에서 파트(part) 제목을 별표(
#) 줄로 감싸 눈에 띄게 처리 - 코드에서도 섹션 구분용으로 별표 줄을 사용하거나, 함수 이름 주변에 주석 박스를 그려주는 에디터 플러그인을 활용
- 이 책의 원고 자체가 약 50개의 마크다운 파일과 상위 매니페스트 파일로 구성되는데, 매니페스트 안에서 파트(part) 제목을 별표(
// ***********************
// * 사용자 인증 관련 로직 *
// ***********************
-
저자의 결론 (The Pragmatic Programmer 20주년판에서도 동일하게 언급)
- ==코드에 위 세 가지 외의 다른 이유로 주석이 필요하다면, 그것은 대개 코드 자체의 문제==
-
실전 연습: 골디락스 존 찾기
- 1단계 - 자신에게 맞는 주석의 적정선을 찾기 (너무 많지도 너무 적지도 않게)
- 2단계 - 한 달 정도 최소 주석 원칙을 유지한 뒤 코드를 되돌아보며 정리
- 코드가 “어떻게” 동작하는지 설명하는 주석 → 삭제 (코드 스스로 설명하게 만들기)
- 파라미터를 설명하는 주석 → 삭제하고 파라미터 이름 자체를 명확하게 리네이밍
- 모든 주석이 자신의 존재 이유를 스스로 증명하게 만들 것
8.2 TODO를 남길 것인가, 말 것인가 (Practice 24: TODO or Not TODO)
- 상황
- 코드를 한창 작성하던 중, 지금 하는 변경이 다른 코드에 영향을 준다는 사실을 발견하거나
- 작업과 무관한 버그를 인접 코드에서 우연히 발견하는 경우가 잦음
- 과거 방식의 문제
- 즉시 다른 곳으로 넘어가 수정한 뒤 원래 작업으로 복귀하면, 컨텍스트를 잃어버려 다시 코드를 읽고 분석하는 데 시간이 듦 → 변경 속도 저하
- 저자의 현재 방식: 부수적 변경 사항의 크기에 따라 대응을 분기
- 단순 오타, 파라미터 값 변경처럼 작은 것 → 그 자리에서 바로 처리하고 원래 작업 계속
- 그보다 규모가 큰 것 →
TODO주석만 남기고 원래 작업에 집중
flowchart TD
Start["작업 중 부수적으로 발견한 변경 사항"] --> Check{"작은 수정인가? (오타, 파라미터 값 등)"}
Check -->|"예"| Fix["즉시 수정하고 원래 작업 계속"]
Check -->|"아니오"| Todo["TODO 주석만 남기고 원래 작업 지속"]
Todo --> Later["여유 시간(회의 전 5분 등)에 처리"]
// TODO: 이 shape을 그냥 원(circle)으로 단순화할 수 있지 않을까?
const shape = createEllipse(r, r);
// ...지금 실제로 수정 중인 코드...
-
부가 효과
- 회의 시작 전 5분처럼 자투리 시간에 처리할 수 있는 작은 작업 목록이 자연스럽게 쌓이는 것을 발견
-
에디터 지원 활용
- 저자가 쓰는 Neovim은 코드 안의 TODO류 주석을 하이라이트 처리
- Neovim은 프로젝트 전체의 TODO 주석을 한곳에 모아 보여주는 요약(그룹) 기능도 제공
- VS Code에도 TODO 하이라이터 플러그인이 있으며, 설치 수 500만 건이 넘을 만큼 대중적
-
실전 팁
- 자신이 쓰는 에디터의 TODO 관련 기능(하이라이트, 전체 검색/집계)을 찾아 활용할 것
8.3 줄 맞춰 정렬하기 (Practice 25: Line ’em Up)
-
핵심 문제의식
- 사람의 뇌는 패턴 인식에 매우 뛰어남
- 항목들을 열 맞춰 정렬해두면 이상 징후(오류)가 시각적으로 바로 드러남
-
예시 1: 목록 요소 누락 오류
- 아래 배열에는 EU 회원국 27개가 있어야 하는데 실제로는 26개만 인식됨
- 한 줄로 길게 늘어놓으면 원인을 찾기 어렵지만, 여러 줄로 나눠 정렬하면
"France"와"Germany"사이에 쉼표가 빠진 것이 바로 보임
// 정렬 전: 오류를 찾기 어려움
const countries = [
"Austria", "Belgium", "Bulgaria", "Croatia", "Republic of Cyprus",
"Czech Republic", "Denmark", "Estonia", "Finland", "France" "Germany",
"Greece", "Hungary", /* ...중략... */ "Spain", "Sweden",
];
console.log(countries.length); // 26 (실제로는 27개가 되어야 함)
// 정렬 후: 세로로 나란히 놓이면서 쉼표 누락이 눈에 띔
const countriesAligned = [
"Austria", "Belgium",
"Bulgaria", "Croatia",
"Cyprus", "Czech Republic",
"Denmark", "Estonia",
"Finland", "France" "Germany", // <- 쉼표 누락이 보임
"Greece", "Hungary",
// ...
];
-
참고: 자바스크립트/타입스크립트는 인접한 문자열 리터럴을 자동으로 이어붙이지 않으므로, 실제로는 이 경우 컴파일 단계에서 구문 오류로 즉시 드러남. 다만 Ruby, Python처럼 인접 문자열을 자동 연결하는 언어에서는 컴파일러의 도움 없이 조용히 잘못된 값이 만들어지므로, 정렬을 통한 시각적 점검이 훨씬 중요해짐. TS에서도 컴파일 전에 코드를 눈으로 스캔할 때는 이 기법이 여전히 유효
-
예시 2: 변수명 오타 오류
- 한 줄씩 따로 보면 놓치기 쉬운 오타가, 등호(
=)를 기준으로 정렬하면 바로 드러남
- 한 줄씩 따로 보면 놓치기 쉬운 오타가, 등호(
// 정렬 전: 오타가 코드 속에 숨어 잘 보이지 않음
const weight = containerContents.weight;
const size = containerContent.size; // 오류1: 's'가 빠진 변수명(containerContent)
const items = contaierContent.items; // 오류2: 철자가 뒤바뀐 변수명(contaierContent)
// 정렬 후: 세 줄의 객체 이름이 나란히 놓이며 서로 다른 철자라는 사실이 바로 보임
const weight = containerContents.weight;
const size = containerContent.size;
const items = contaierContent.items;
Idea 51: ==정돈된 레이아웃으로 뇌가 오류를 잘 찾도록 도와라==
-
실무 팁 (Practicalities)
- 저자는 에디터가 정렬을 쉽게 지원하지 않으면 이 기법을 쓰지 않았을 것이라고 언급
- Neovim + EasyAlign 플러그인 사용 예: 코드 선택(
vas) 후 등호 기준 정렬(ga=) 명령 실행 - VS Code를 포함해 대부분의 에디터에 유사한 정렬(align) 플러그인이 다수 존재
-
주의할 점: 이름 길이 차이가 매우 클 때
- 전체를 한 번에 정렬하면 눈이 오히려 줄을 잃기 쉬움
- 대안: 길이가 비슷한 줄끼리 그룹을 지어 부분적으로만 정렬
// 길이 차이가 큰 변수들을 억지로 한 번에 정렬 -> 눈이 이탈하기 쉬움
const name = "Nshuti";
const address = "123 Some Street";
const shippingCountry = "Rwanda";
const postalCode = "12345";
const date = "2025/01/01";
// 대안: 비슷한 길이끼리 묶어서 부분 정렬
const name = "Nshuti";
const date = "2025/01/01";
const address = "123 Some Street";
const shippingCountry = "Rwanda";
const postalCode = "12345";
8.4 목록 끝에 쉼표 남겨두기 (Practice 26: Dangle That Comma)
- 논쟁적인 주제
- 목록(리스트) 마지막 요소 뒤에도 구분자(쉼표)를 남길 것인가 말 것인가
- C, C#, C++, JavaScript, Python, Ruby 등 다수 언어가 마지막 요소 뒤의 트레일링 쉼표를 허용
const nums = [1, 2, 3]; // 일반적인 표기
const numsTrailing = [1, 2, 3,]; // 마지막에 쉼표를 남겨도 원소는 여전히 3개
- 트레일링 쉼표가 변경을 쉽게 만드는 이유
- 목록을 정렬하거나 순서를 바꿀 때, 트레일링 쉼표가 없으면 원래 마지막이었던 요소가 중간으로 이동하면서 구문 오류가 발생
// 트레일링 쉼표 없이 정렬 -> 이전 마지막 요소가 중간으로 이동하며 앞 줄에 쉼표가 없어 오류 발생
const bigLakes = [
"Caspian",
"Huron",
"Michigan"
"Superior", // 구문 오류: 앞 줄(Michigan)에 쉼표가 없음
"Victoria",
];
- 트레일링 쉼표를 쓰면 마지막 요소가 “특별 취급”되지 않으므로, 목록을 자유롭게 재배치/추가/삭제 가능
- 실무 시사점
- 정렬, diff 최소화, 항목 추가/삭제 리팩터링 시 이 습관이 사소한 구문 오류를 줄여줌
- 대부분의 린터/포매터(예: Prettier)에서 trailing comma 옵션으로 강제 가능
8.5 정렬해서 나열하기 (Practice 27: Sort ’em Out)
-
원칙
- 컬렉션 리터럴을 작성할 때, 항목의 순서가 의미를 갖지 않는다면 정렬해서 나열
- 목적: 중복 추가나 항목 누락 같은 실수를 시각적으로 쉽게 발견
-
예시 1: TypeScript enum (열거형 값을 알파벳순으로 정렬)
enum Purple {
Eggplant,
Lavender,
Lilac,
Mauve,
Rebecca,
Royal,
Violet,
}
- 예시 2: 옵션 객체를 함수 인자로 넘길 때 키를 정렬 + 값도 함께 정렬(alignment)
drawCircle({
fill: "RED",
stroke: "BLACK",
text: "push me",
xPos: 231,
yPos: 34,
});
- 전제 조건
- 에디터가 선택 영역을 정렬해주는 기능(sort selected lines)을 지원해야 실용적
- 대부분의 현대 에디터가 이 기능을 기본 또는 플러그인으로 지원
- 강제 규칙이 아니라, 목록 작성 중 발생하는 실수를 잡아내는 보조 도구로 활용 권장
8.6 세로가 가로보다 낫다 (Practice 28: Tall Beats Wide)
-
배경
- 과거 80컬럼(펀치카드 기준 폭) × 24라인, 초록색 CRT 터미널 시절을 회고하며 시작하는 챕터
- 현재는 56인치 8K급 초대형 고해상도 모니터가 흔해짐
-
문제
- 일부 개발자가 에디터를 풀스크린으로 켜두고 화면 오른쪽 끝(200~300자)까지 코드를 한 줄로 작성하는 경향
-
연구 결과
- 가독성이 가장 좋은 한 줄 길이는 45~75자 사이
- 그보다 길어지면 눈이 줄을 놓쳐 처음부터 다시 스캔해야 하고, 줄의 시작점을 다시 찾는 것도 어려워짐
-
예시: DB 쿼리 코드 비교
- 한 줄로 압축된 쿼리는 읽기 어렵고, 새 테이블/조인 조건을 추가할 편집 지점을 찾기 힘듦
- 절(select/from/where/order by) 단위로 세로로 나눠 배치하면 각 부분이 한눈에 들어오고 유지보수가 쉬움
// 한 줄로 압축된 예 (유지보수 어려움)
const products = db.query(
`select distinct products.* from products, skus, author_sku_royalties where skus.product_id = products.id and author_sku_royalties.sku_id = skus.id and author_sku_royalties.user_id = ? order by products.title`,
[userId]
);
// 절 단위로 세로로 나눈 예 (유지보수 용이, 새 조인 조건 추가가 쉬움)
const products = db.query(
`
select distinct products.*
from products, skus, author_sku_royalties
where skus.product_id = products.id
and author_sku_royalties.sku_id = skus.id
and author_sku_royalties.user_id = ?
order by products.title
`,
[userId]
);
- 역사적 뒷이야기
- 펀치카드 시절에는 줄을 최대한 길게 쓰는 것이 카드 매수를 줄여, 무거운 카드 뭉치를 나르는 물리적 부담(허리 통증)을 줄여주는 실질적 이유가 있었음
- 현대에는 그런 물리적 제약이 사라졌으므로, 세로로 길게(tall) 코드를 풀어서 쓰는 자유를 누릴 것을 권장
- 유머 섞인 조언
- 그래도 모니터를 남김없이 활용하고 싶다면, 모니터를 90도로 회전시켜 세로로 쓰라는 제안
8.7 지역성 유지하기 (Practice 29: Keep It Local)
8.7.1 컨벤션의 명과 암 (The Good and Bad of Conventions)
- 전통적 배경지식
- 모듈화와 캡슐화는 유지보수성의 핵심 원칙으로 배워옴 (변경의 영향 범위를 모듈 경계 안으로 제한)
- 그러나 예외
- 프로젝트 초기 단계에서는 무조건적인 모듈 분리가 오히려 변경을 어렵게 만들 수 있음
- 예시 프레임워크: Rails (“Convention over configuration”)
- 정해진 위치에 정해진 이름으로 파일을 두면 별도 설정 없이 자동 인식
- 장점: 어떤 Rails 프로젝트를 봐도 구조가 동일해 낯설지 않음
- 단점: 컨벤션을 맹목적으로 따르면, 특히 개발 초기에 변경 속도를 오히려 늦출 수 있음
8.7.2 뷰(View)의 해부 (Anatomy of a View)
- Rails 컨벤션에 따른 리소스(customer) 하나의 표준 구성 요소 5가지
- 컨트롤러:
app/controllers/customers_controller - 뷰 템플릿:
app/views/customers - 헬퍼:
app/helpers/customers_helper.rb - CSS:
app/assets/stylesheets하위 - JavaScript:
app/javascript/controllers하위
- 컨트롤러:
- 문제점: 관련 코드가 디렉터리 트리 전반에 흩어져 있어, 특히 처음 작성할 때 작업 속도가 느려짐
graph TD
subgraph BEFORE["Rails 컨벤션 방식: 파일 5개로 분산"]
A1["assets/stylesheets/customers.css"]
A2["javascript/controllers/customers.js"]
A3["controllers/customers_controller.rb"]
A4["helpers/customers_helper.rb"]
A5["views/customers/show.html.erb"]
end
subgraph AFTER["저자의 방식: 파일 2개로 압축"]
B1["controllers/customers_controller.rb (대부분 보일러플레이트)"]
B2["views/customers/show.html.erb (스타일+스크립트+헬퍼+템플릿 통합)"]
end
-
저자의 대안: 컨트롤러 + 뷰 템플릿, 단 2개 파일만 사용
- 컨트롤러는 대체로 보일러플레이트 코드라 크게 신경 쓰지 않음
- 실제 작업은 대부분 뷰 템플릿 하나에서 이루어짐: CSS, JavaScript, 헬퍼 함수, 마크업까지 한 파일에 모두 배치
-
파일 내부 구성 순서
| 파일 내 위치 | 담당 역할 |
|---|---|
| 최상단 | 이 뷰 전용 CSS |
| 그다음 | 이 뷰 전용 JavaScript |
| 그다음 | 뷰 헬퍼 함수(포맷팅 로직 등) |
| 마지막 | 실제 템플릿 마크업 |
- TypeScript/React 관점에서 유사한 개념: 스타일-로직-마크업을 한 컴포넌트 파일에 모아두는 콜로케이션(colocation) 패턴
// 하나의 파일 안에 스타일, 헬퍼 로직, 마크업을 함께 배치하는 콜로케이션 예시
import { useMemo } from "react";
// -- 이 뷰 전용 스타일
const styles = `
.customer-card { padding: 1rem; }
`;
// -- 이 뷰 전용 헬퍼(포맷팅 로직 등)
function formatJoinDate(date: Date): string {
return date.toLocaleDateString();
}
// -- 실제 템플릿(마크업)
export function CustomerShow({ customer }: { customer: { name: string; joinedAt: Date } }) {
const joinDate = useMemo(() => formatJoinDate(customer.joinedAt), [customer.joinedAt]);
return (
<div className="customer-card">
<style>{styles}</style>
<h1>{customer.name}</h1>
<p>가입일: {joinDate}</p>
</div>
);
}
- 확장 규칙
- 헬퍼 함수가 여러 뷰에서 재사용되기 시작하면, 그때 비로소 별도의 helper 파일로 추출 (템플릿 자체에는 영향 없음)
- 저자의 관찰
- 아직까지 이 방식으로 인한 부작용을 겪지 않았으며, 문제가 생기면 파일을 나누는 것은 언제든 쉬운 작업
- Elm, React 개발자들은 이미 오래전부터 이런 콜로케이션 방식을 채택해왔다는 점을 언급하며, 이들의 성공 사례가 이 접근법이 Rails 뷰에 국한되지 않고 더 넓게 적용 가능하다는 확신을 줬다고 언급
8.7.3 일반 소스 코드 분할하기 (Splitting Regular Source Code)
- “한 파일에 모아두기” 철학은 Rails 뷰뿐 아니라 일반적인 코드 작성 전반에도 동일하게 적용
- 저자의 작업 흐름
- 새 코드를 작성할 때 하나의 파일로 시작
- 개요를 구체화하면서 새 모듈/클래스가 필요해져도 일단 같은 파일에 계속 추가
- 코드가 어느 정도 안정화되면 전체를 다시 살펴봄
- 스스로에게 질문: “6개월 뒤 이 코드를 다시 볼 때, 하나의 파일로 되어 있으면 이해하기 얼마나 어려울까?”
- 그대로 두는 경우도 많지만, 필요하면 이 시점에 여러 소스 파일(하위 디렉터리 포함)로 분리
- 다른 모듈과 기능을 공유해야 하는 순간이 오면, 공유할 부분만 즉시 별도 파일로 추출해 의존성 범위를 최소화
- 한 파일에서 시작하는 이유
- 구조를 실험하며 리팩터링하기가 훨씬 쉬움 (코드 이동, 이름 변경, 검색이 모두 더 간단)
8.7.4 내가 직접 실천하기 (Eating My Own Dog Food)
- 저자는 이 책 자체도 같은 원칙으로 집필
- 마크다운으로 작성하며, 챕터별로 독립 빌드가 필요했기 때문에 챕터마다 별도 파일을 두는 구조만 처음부터 정함
- 그 이상의 세부 구조는 정하지 않고, 집필 중 계속 내용을 옮기며 작업
- 집필 몇 달 후 실습(Practice) 단위로 재구성할 필요가 생겨, 현재는 챕터 하나가 상위 파일 1개 + 하위 디렉터리의 실습별 파일 7개로 분리된 상태 (이 개수도 앞으로 바뀔 수 있다고 언급)
8.7.5 실전 연습: 비관습적으로 시도해보기 (Be unconventional)
- 점검 질문
- 지금 일하는 환경이 관습적으로 여러 파일에 코드를 분산시키도록 강제하고 있는가?
- 모든 것을 한 파일에 넣으면 정말로 문제가 생기는가?
- 제안
- 팀에게 “특이한 구조”로 비칠 것이 걱정된다면, 새 기능을 만들 때 개인적으로만 이 기법(한 파일에서 시작)을 적용
- 변경 사항을 푸시하기 전에 여러 파일로 분리
- 이 과정에서 단일 파일 방식이 실제로 더 단순한지 스스로 판단
- 효과가 있다면 팀에게 공유해 함께 실험해볼 것을 제안
- 팀이 동의하지 않더라도, 본인에게 효과가 있다면 계속 사용할 것을 권장
8.8 챕터 8 한눈에 정리
| 실천 | 핵심 메시지 | 바로 적용하기 |
|---|---|---|
| 8.1 주석 없애기 | 주석보다 읽히는 코드가 우선, 정당한 이유 없는 주석은 삭제 | “왜”를 설명하는 주석만 남기고, “무엇을” 설명하는 주석은 코드/네이밍으로 대체 |
| 8.2 TODO 남기기 | 흐름을 끊지 않도록 작은 이슈는 TODO로 남기고 나중에 처리 | 에디터의 TODO 하이라이트/집계 기능 확인 및 활용 |
| 8.3 줄 맞춰 정렬하기 | 정렬된 코드는 오류를 시각적으로 드러냄 | 에디터의 정렬(align) 단축키/플러그인 설정 |
| 8.4 쉼표 남겨두기 | 트레일링 쉼표는 목록 재배치를 안전하게 만듦 | 린터/포매터에서 trailing comma 옵션 켜기 |
| 8.5 정렬해서 나열하기 | 순서 무관한 목록은 정렬해 중복·누락 예방 | enum, 옵션 객체 키를 일관된 규칙으로 정렬 |
| 8.6 세로가 가로보다 낫다 | 한 줄 45~75자가 가독성 최적, 세로로 풀어 쓰기 | 긴 쿼리/체이닝 코드를 절 단위로 줄바꿈 |
| 8.7 지역성 유지하기 | 초기에는 관련 코드를 한 파일에 모아두는 것이 변경에 유리 | 새 기능은 한 파일에서 시작, 안정화 후 분리 검토 |
챕터 9. 마치며
이 챕터가 다루는 질문
- 저자는 책 전체를 통틀어 가장 어려운 질문, 즉 단순함이란 무엇인가에 대한 답을 마지막까지 미뤄왔다고 고백함.
- 질문을 구체화하면: 어떤 방식 S가 다른 방식 C보다 더 단순하다는 것을 어떻게 판단할 수 있는가.
- 정답은 맥락(context)에 따라 달라지지만, 저자가 실제로 판단할 때 살펴보는 세 가지 관점을 제시함.
- S가 C보다 이해하기 쉬운가
- S가 C보다 구성 요소가 적은가
- S가 C보다 문제를 더 직접적으로 반영하는가
판단 기준 1 — S가 C보다 이해하기 쉬운가
- S가 무엇을 하는지, 어떻게 하는지 C보다 더 빨리 파악할 수 있는가.
- S가 하는 일을 다른 사람에게 C보다 더 단순한 문장으로 설명할 수 있는가.
- 지저분한 코드나 낯선 라이브러리를 파헤쳐야 할 때, S가 C보다 멈칫거리는 순간(pause)이 더 적은가.
- S를 변경해야 할 때, C를 변경할 때보다 더 자신감을 느끼는가.
판단 기준 2 — S가 C보다 구성 요소가 적은가
- S가 C보다 컴포넌트, 의존성, 규칙, 조건의 수가 더 적은가.
- S가 C와 동일한 결과를 더 적은 단계로 달성할 수 있는가.
판단 기준 3 — S가 C보다 문제를 더 직접적으로 반영하는가
- S가 C보다 문제 자체의 관용구(idiom)와 언어에 더 밀접하게 부합하는가.
- C에는 S가 제거해버린 불필요한 기능, 중복, 잡음(noise)이 들어있는가.
한눈에 보기 — S(단순) vs C(복잡) 체크리스트
| 판단 기준 | 확인 질문 |
|---|---|
| 이해 용이성 | 무엇을/어떻게 하는지 더 빨리 파악되는가 |
| 이해 용이성 | 더 단순한 문장으로 설명 가능한가 |
| 이해 용이성 | 낯선 코드를 다룰 때 덜 멈칫거리는가 |
| 이해 용이성 | 변경할 때 더 자신감이 생기는가 |
| 구성 요소 | 컴포넌트/의존성/규칙/조건이 더 적은가 |
| 구성 요소 | 더 적은 단계로 같은 결과를 내는가 |
| 문제 반영도 | 문제 도메인의 언어와 더 밀접한가 |
| 문제 반영도 | 불필요한 기능/중복/잡음이 없는가 |
판단 흐름 요약
flowchart TD
A[두 방식 S와 C를 비교한다] --> B{S가 더 이해하기 쉬운가}
B -->|예| C{S의 구성 요소가 더 적은가}
B -->|판단 보류| E[C 쪽으로 기울 수 있음]
C -->|예| D{S가 문제를 더 직접적으로 반영하는가}
C -->|판단 보류| E
D -->|예| F[S가 더 단순하다고 볼 수 있음]
D -->|판단 보류| E
기준 적용 예시 (원서에는 없는, 이해를 돕기 위한 보충 예시)
아래 코드는 챕터 9의 세 가지 기준을 실제 코드에 적용해보기 위해 별도로 구성한 예시임.
// C: 복잡한 버전 - 규칙 엔진 형태로 과도하게 추상화됨
interface ValidationRule<T> {
validate(value: T): boolean;
message: string;
}
class RuleBasedValidator<T> {
private rules: ValidationRule<T>[] = [];
addRule(rule: ValidationRule<T>): this {
this.rules.push(rule);
return this;
}
validate(value: T): string[] {
return this.rules
.filter((rule) => !rule.validate(value))
.map((rule) => rule.message);
}
}
const emailValidator = new RuleBasedValidator<string>()
.addRule({ validate: (v) => v.includes("@"), message: "이메일에는 @가 포함되어야 합니다" })
.addRule({ validate: (v) => v.length > 5, message: "이메일은 5자보다 길어야 합니다" });
// S: 단순한 버전 - "이메일을 검증한다"는 문제를 코드에 그대로 직접 반영
function validateEmail(email: string): string[] {
const errors: string[] = [];
if (!email.includes("@")) errors.push("이메일에는 @가 포함되어야 합니다");
if (email.length <= 5) errors.push("이메일은 5자보다 길어야 합니다");
return errors;
}
- S는 클래스, 인터페이스, 체이닝 API 없이도 같은 결과를 낸다 → 기준 2(구성 요소가 적음) 충족.
- S는 “이메일을 검사한다”는 문제 자체가 코드에 그대로 드러난다 → 기준 3(문제를 직접 반영) 충족.
- 다만 규칙이 수십 개로 늘어나거나 재사용성이 중요해지는 순간에는 오히려 C의 구조가 더 단순해질 수 있음. 즉, 어느 쪽이 단순한지는 맥락에 따라 뒤집힐 수 있다는 것이 이 챕터의 핵심 메시지와 연결됨.
단순함에 대한 철학적 정리
- 단순함은 명료함(clarity), 미니멀리즘(minimalism), 편안함(ease)의 본질임.
- 단순함이란 불필요한 것을 걷어내어 진짜 중요한 것을 드러내는 것.
- 물리적 사물 디자인에서는 깔끔한 선과 직관적인 기능으로 나타남.
- 삶에서는 의미 있는 것에 집중하고 방해 요소를 없애는 것으로 나타남.
- 코드에서는 위의 모든 것이 동시에 적용됨.
- 단순함은 그냥 쉽게 얻어지는 것이 아니라, 의도적이고(intentional) 정제되어 있으며(refined) 효과적(effective)이어야 얻어짐.
- 단순함이 가져다주는 것: 평온함(peace), 효율(efficiency), 우아함(elegance).
단순함은 절대적이지 않다
- 단순함에는 절대적 기준이 없음 — 누군가에게 단순한 것이 다른 사람에게는 아닐 수 있음.
- 같은 사람이라도 작년에는 단순하지 않았던 것이 지금은 단순하게 느껴질 수 있음(경험/숙련도에 따라 변함).
- 단순함에는 리트머스 시험지 같은 절대적 판별법이 없음 — 정해진 지표(metric)도, 규칙(rule)도 없음.
저자에게 있어 “단순함”의 최종 정의
- 궁극적으로 저자에게 무언가가 단순하다는 것은 **“그것에 대해 기분이 좋다(feel good about it)”**는 뜻임.
- 크리스토퍼 알렉산더(Christopher Alexander)가 말한 **“이름 없는 특질(quality without a name)”**을 가지고 있는가라는 질문으로 연결됨.
- 키보드에서 손을 떼고 나서도 찜찜한 의심(nagging doubts) 없이 떠날 수 있는가.
- ==그냥 느낌이 맞는가(Does it feel right)?== 라는 직관적 질문이 최종 기준이 됨.
노트: “이름 없는 특질(quality without a name)“은 건축가 Christopher Alexander가 제시한 개념으로, 참고문헌 [AIS77]과 맥락이 이어짐. 좋은 설계는 말로 완벽히 정의할 수 없는 어떤 자연스러운 속성을 갖는다는 아이디어임.
저자의 마지막 당부
- 코드뿐 아니라 코딩하는 방식, 나아가 삶까지도 단순화할 것을 권함.
- 단순화하면 더 생산적이고 스트레스가 줄어들며, 일에 즐거움(fun)이 되돌아온다고 주장함.
- ==마지막 문장: “당신은 그것을 누릴 자격이 있다(You deserve that).”==
- 저자는 “Dave”로 서명함.
부록 1. 참고문헌
| 인용 키 | 저자 | 제목 | 출판사 / 지역 | 연도 |
|---|---|---|---|---|
| [AIS77] | Christopher Alexander, Sara Ishikawa, Murray Silverstein | A Pattern Language: Towns, Buildings, Construction | Oxford University Press, New York, NY | 1977 |
| [Bec00] | Kent Beck | Extreme Programming Explained: Embrace Change | Addison-Wesley Longman, Boston, MA | 2000 |
| [de 97] | Gavin de Becker | The Gift of Fear | Little, Brown and Company, New York, NY (초판) | 1997 |
| [TH19] | David Thomas, Andrew Hunt | The Pragmatic Programmer, 20th Anniversary Edition | The Pragmatic Bookshelf, Dallas, TX | 2019 |
참고문헌 배경 메모 (일반 상식 수준의 보충 설명)
- [AIS77] Alexander 등의 저서는 건축 분야의 패턴 언어(pattern language) 개념을 제시한 고전이며, 챕터 9에서 언급된 “이름 없는 특질” 개념의 뿌리가 되는 저자임.
- [Bec00] Extreme Programming Explained는 애자일 방법론인 XP(익스트림 프로그래밍)를 소개하는 책으로, 단순함(simplicity)을 XP의 핵심 가치 중 하나로 명시함.
- [de 97] The Gift of Fear는 위험을 감지하는 인간의 직관과 신호를 다루는 책으로, 본 챕터의 “그냥 느낌이 맞는가”라는 직관적 판단 기준과 주제적으로 맞닿아 있음.
- [TH19] The Pragmatic Programmer는 본서(Simplicity)의 저자 Dave Thomas가 Andrew Hunt와 함께 쓴 대표작이며, 실용주의적 세계관을 공유함.
부가 페이지: 출판사 안내와 추천 도서
감사 인사 및 혜택 안내
- 출판사(The Pragmatic Bookshelf)는 독자에게 감사 인사를 전하며 다음 도서 구매를 유도함.
pragprog.com사이트에서 쿠폰 코드BUYANOTHER2025를 사용하면 다음 전자책 구매 시 30% 할인 제공.- 단, 지역에 따라 제한될 수 있으며 The Pragmatic Programmer 전자책에는 적용되지 않음.
- 저술 아이디어가 있는 독자에게는 최대 50% 로열티 조건으로 저자 제안을 받는다는 안내도 포함됨.
추천 도서 목록
| 제목 | 저자 | 페이지 | ISBN | 가격 | 한 줄 요약 |
|---|---|---|---|---|---|
| Agile Web Development with Rails 7.2 | Sam Ruby | 472 | 9798888651049 | $67.95 | Rails 7.2의 Hotwire(Stimulus, Turbo)와 import maps를 활용해 적은 복잡도로 SPA 수준의 경험을 구현하는 방법을 다룸 |
| Programming Ruby 3.3 (5th Edition) | Noel Rappin, with Dave Thomas | 716 | 9781680509823 | $65.95 | 일명 “Pickaxe Book”. Ruby 3.3까지의 패턴 매칭과 Ractor를 포함한 언어 전반을 다루는 표준 레퍼런스 |
| Programming Elixir 1.6 | Dave Thomas | 410 | 9781680502992 | $47.95 | Erlang VM 기반의 함수형 동시성 언어 Elixir 입문서, 경험 많은 개발자를 대상으로 함 |
| Engineering Elixir Applications | Ellie Fairholm, Josep Giralt D’Lacoste | 458 | 9798888650677 | $61.95 | BEAMOps 패러다임으로 Elixir 애플리케이션의 빌드/테스트/배포/디버그 전 과정을 다룸 |
| Serverless Apps on Cloudflare | Ashley Peacock | 275 | 9798888650714 | $53.95 | Cloudflare 플랫폼에서 서버리스 API와 AI 이미지 분석까지 구축하고 배포를 자동화하는 방법을 다룸 |
| Seven Obscure Languages in Seven Weeks | Dmitry Zinoviev | 270 | 9798888650639 | $55.95 | C 스타일 문법이 정착되기 이전의 오래된 7개 언어를 통해 프로그래밍에 대한 새로운 관점을 제공함 |