Differences between revisions 3 and 5 (spanning 2 versions)
Revision 3 as of 2025-07-24 13:23:29
Size: 8918
Editor: 정수
Comment:
Revision 5 as of 2025-07-24 16:31:22
Size: 15581
Editor: 정수
Comment:
Deletions are marked like this. Additions are marked like this.
Line 6: Line 6:
복잡한 프트웨어 문제를 해결하거나 새로운 기능을 구현할 때, 또는 버그를 추적할 때. 불확실성이 높고 여러 가능성이 존재하는 상황에서 어떻게 접근해야 할지 막막할 . 복잡한 프로젝에서 문제를 해결하거나 새로운 기능을 구현할 때, 또는 버그를 찾고자 할 때. 불확실성이 높고 여러 가능성이 존재하는 상황에서 무엇 먼저해야 할지 모르는 경우.
Line 9: Line 9:
'''큰 변경의 함정'''

주니어 개발자들이 자주 빠지는 함정이 있다. "이 기능을 구현하려면 A도 바꾸고, B도 수정하고, C도 새로 만들어야 해"라고 생각하며 한 번에 많은 것을 바꾸려 한다.

결과는 참혹하다. 2시간 후 코드는 엉망이 되고, 어디서 문제가 생겼는지 알 수 없다. 원래 상태로 되돌리기도 어렵다. 머릿속 추측만으로는 복잡한 시스템의 동작을 정확히 예측할 수 없고, 긴 피드백 루프는 학습을 늦추며 잘못된 방향으로 오래 가게 만든다.

'''요리사의 실수'''

경험 없는 요리사가 새로운 요리를 만들 때를 생각해보자. 레시피를 보고 "설탕 대신 꿀을 넣고, 소금도 줄이고, 향신료도 바꿔보자"며 한 번에 여러 변경을 시도한다. 결과적으로 맛없는 요리가 나오지만, 무엇 때문에 실패했는지 알 수 없다. 설탕 때문인가? 소금 때문인가? 향신료 때문인가?
'''두 가지 요리사의 이야기'''

어떤 레스토랑에 두 명의 요리사가 있었다. 둘 다 새로운 파스타 요리를 만들라는 임무를 받았다.

첫 번째 요리사는 모험적인 성격이었다. "완벽한 새로운 파스타를 만들겠어!" 특별한 면도 새로 만들고, 소스도 완전 새롭게 하고, 치즈 특유의 맛도 새로 만들고, 향신료도 특별한 것들로만 쓰기로 했다. 3시간 동안 심혈을 기울여 요리를 완성했지만, 이상한 맛이 나와서 먹을 수가 없었다. 손님들은 고개를 절레절레 흔들었다. 무엇이 모험적인 요리사는 어디서 잘못되었는지 알 수 없었다. 면을 잘못했나? 소스를 잘못했나? 치즈를 잘못했나? 처음부터 다시 만들어야 했다.

두 번째 요리사는 체계적인 다른 방식을 선택했다. 먼저 기본적인 기본 파스타를 만들어 맛을 확인했다. 그 다음 소스를 조금 바꿔서 다시 맛을 봤다. "좋다, 이게 맞다." 그리고 치즈 한 종류만 바꿔서 또 맛을 봤다. 각 단계마다 맛이 좋은지 나쁜지 확인했다. 만약 한 변경사항이 맛이 없다면, 바로 이전 단계로 돌아갔다. 2시간 후, 손님들이 "이 파스타 정말 맛있네요!"라고 극찬하는 요리가 완성되었다.

프로그래머들도 마찬가지다. 복잡한 문제를 한 번에 모든 요소를 바꿔려다가 모험적인 요리사처럼 실패하거나, 체계적인 요리사처럼 작은 단계로 차근차근 실험하거나. 차이는 매우 명확히 있다.
Line 20: Line 20:
'''작은 실험의 힘'''

아주 작은 가설을 세우고 빠르게 검증할 수 있는 환경을 만들어라. 마치 과학자가 실험실에서 하나의 변수만 바꿔가며 실험하는 것처럼.

=== 1단계: 명확한 가설 세우기 ===
막연한 "아마 이럴 것이다"가 아니라 구체적인 가설을 세워라.

나쁜 예: "이 코드가 느린 것 같다"
좋은 예: "이 반복문에서 데이터베이스 조회가 N번 발생해서 느릴 것이다"

=== 2단계: 최소 실험 설계 ===
가설을 검증할 수 있는 가장 작은 실험을 만들어라.

예시: 데이터베이스 조회 가설을 검증하려면
- 로그를 추가해서 실제 쿼리 횟수 확인
- 작은 데이터셋으로 테스트
- 쿼리 실행 시간 측정

=== 3단계: 안전망 구축 ===
실패해도 쉽게 되돌아갈 수 있는 안전망을 준비하라.

- git commit으로 현재 상태 저장
- 브랜치 생성해서 실험
- 테스트 코드로 기존 기능 보호
- 백업 데이터 준비

=== 4단계: 빠른 피드백 루프 ===
결과를 즉시 확인할 수 있는 환경을 만들어라.

- 자동화된 테스트 실행
'''실험 가능한 환경을 먼저 만들어라'''

체계적인 요리사의 핵심은 단순했다. 큰 변화를 한번에 시도하는 대신, 각 변경이 어떤 영향을 주는지 확인하고, 실패해도 쉽게 되돌아갈 수 있게 준비하는 것이었다.

프로그래밍에서도 이와 같은 "작은 실험"을 만들 수 있다. 새로운 기능을 구현할 때, 버그를 찾을 때, 코드를 개선하려 할 때 이 접근법을 사용하라.

'''울타리를 쳐서 실험 영역을 격리하라'''

큰 문제에서 특정한 부분만을 분리해내어 그 부분에만 집중할 수 있는 "울타리"를 만들어라. 울타리 안에서는 자유롭게, 빠르게, 안전하게 다양한 실험을 할 수 있어야 한다. 이것이 TinyExperiment의 핵심이다.

체계적인 요리사는 "전체 요리"가 아니라 "이 소스" 또는 "이 치즈"처럼 구체적인 부분에만 집중했다. 프로그래머도 "전체 시스템"이 아니라 "이 함수" 또는 "이 데이터 변환"처럼 구체적으로 범위를 한정하라.

'''실험 가능한 환경을 창의적으로 구축하라'''

이것이 TinyExperiment를 단순한 PDCA 사이클과 구별하는 핵심이다. **실험 가능한 환경을 만드는 것 자체가 훈련된 창의성을 요구한다.**

'''실제 사례: 구글 앱스 스크립트 테스팅 환경'''

한 페어 프로그래밍 세션에서 친구가 구글 폼 설문조사 데이터로부터 분석 보고서 PDF를 생성하는 구글 앱스 스크립트를 만들고 있었다. 하지만 스파게티 코드 때문에 속도가 느려지고 있었고 리팩토링이 필요했다.

문제는 구글 앱스 스크립트에는 제대로 된 유닛 테스트 환경이 없다는 것이었다. 물론 있을 수도 있지만, 1시간이라는 제한된 시간에는 그것을 찾고 설정할 여유가 없었다.

해결책: **매우 간단한 테스팅 환경을 직접 만들었다.** console.log를 사용해 true/false 또는 pass/fail을 테스트케이스 이름과 함께 출력하는 것이다.

많은 사람들이 print문을 사용한다. 하지만 소수만이 이것을 assertion 형태로 활용한다. 임의의 값을 출력하면 기준을 만족하는지 판단하기 어렵다. 하지만 단순히 true/false나 pass/fail을 테스트케이스 이름과 함께 출력하면, 어떤 테스트케이스가 성공했는지 실패했는지 즉시 알 수 있다.

{{{
function testDataTransformation() {
  console.log("테스트: 날짜 파싱", parseDate("2024-01-15") === "2024/01/15" ? "PASS" : "FAIL");
  console.log("테스트: 빈 데이터 처리", handleEmptyData("") === null ? "PASS" : "FAIL");
  console.log("테스트: 계산 로직", calculateScore([1,2,3]) === 6 ? "PASS" : "FAIL");
}
}}}

이렇게 해서 **거의 모든 작업에서, 어떤 상황에서도 TinyExperiment 환경을 만들 수 있다.** 핵심은 기존 도구의 한계에 굴복하지 않고, 창의적으로 실험 환경을 구축하는 것이다.

'''구체적인 가설을 세워라'''

체계적인 요리사는 "이 소스가 더 나을 것이다"라는 구체적인 가설을 세우고 실험했다. 프로그래머도 "이 함수가 느린 것이다"가 아니라 "이 반복문에서 데이터베이스를 N번 호출해서 느린 것이다"처럼 구체적으로 하라.

'''빠른 피드백 루프를 만들어라'''

체계적인 요리사는 각 단계마다 바로 맛을 봤다. 완성된 전체 요리가 아니라 작은 부분만 실험할 수 있게 했다. 프로그래머도 git 브랜치를 잘 활용한다. 새로운 시도를 위한 브랜치를 만들고, 실패하면 쉽게 되돌아간다. 테스트 코드도 전체 시스템을 실행하지 않아도 확인하는데 필요한 부분만 한다.

'''찔러보고 반응을 관찰하라'''

이것이 TinyExperiment의 핵심 리듬이다. 작은 변화를 주고(찔러보고), 시스템의 반응을 관찰하고, 그 결과에서 학습하고, 전략을 조정하고, 다시 찔러본다. 이는 일종의 PDCA 사이클이지만, **실험 가능한 환경을 만드는 데 훨씬 더 많은 에너지를 투입한다는 점**이 다르다.

'''한 번에 하나씩만 바꿔라'''

체계적인 요리사의 가장 중요한 원칙이다. 소스를 바꿀 때는 다른 것은 그대로 두고 소스만 바꿨다. 그래야 결과가 좋아졌는지 나빠졌는지 소스 때문이라는 걸 확실히 알 수 있었다. 프로그래머도 한 번에 하나의 변수만 바꿔야 한다. 함수를 최적화할 때 동시에 데이터베이스 스키마까지 바꾸면, 어떤 것이 진짜 효과가 있었는지 알 수 없다.

== Examples ==

'''주니어 개발자 민호의 첫 번째 버그 해결'''

민호는 "로그인 후 메뉴버튼이 보이지 않는다"는 버그 리포트를 받았다. 처음엔 막막 했다. 로그인 시스템도 복잡하고, 메뉴버튼도 여러 컴포넌트와 연결되어 있었다.

선배개발자 수진이 조언을 해주었다. "체계적인 요리사처럼 해봐. 큰 문제 대신 작은 부분만 울타리 쳐서 보자."

민호는 먼저 "이 버그를 재현할 수 있나?"라는 질문부터 시작했다. 자신의 로컬 환경에서 직접 로그인해보니 문제없이 작동했다. 그런데다 다른 브라우저에서 시도해보니까 Internet Explorer에서만 문제가 발생했다.

"IE에서만 발생한다"는 새로운 정보로 민호는 범위를 더욱 좁혔다. "JavaScript 오류가 있을 것이다." 개발자 도구를 열어보니 실제로 오류 메시지가 있었다. 특정 함수에서 undefined 오류가 발생하고 있었다.

더욱 "이 함수가 왜 값이 undefined이다"라는 구체적인 질문을 만들었다. console.log를 몇 개 추가해서 값들을 확인해보니, IE에서는 특정 API가 지원되지 않아서 undefined가 되고 있었다.

문제를 정확히 파악한 민호는 간단한 브라우저 호환성 체크 코드를 추가했다. 테스트해보니 문제가 해결되었다. 전체 과정은 1시간밖에 걸리지 않았다. 만약 처음부터 "아마 전체 시스템 문제일 것이다"라고 추측하고 모든 연관 코드를 다 뒤졌다면, 하루가 걸릴 수도 있었다.

'''구글 앱스 스크립트 리팩토링 사례 (상세)'''

페어 프로그래밍 파트너는 300줄짜리 구글 앱스 스크립트를 가지고 있었다. 설문조사 데이터를 받아서 복잡한 분석을 하고 PDF 보고서를 생성하는 코드였다. 문제는 모든 로직이 하나의 거대한 함수에 뒤섞여 있어서 수정할 때마다 다른 부분이 깨졌다.

**1단계: 실험 환경 구축**
일반적인 유닛테스트 프레임워크를 사용할 수 없는 상황이었다. 대신 매우 간단한 테스트 환경을 만들었다:

{{{
function runTests() {
  console.log("=== 데이터 파싱 테스트 ===");
  testDataParsing();
  console.log("=== 계산 로직 테스트 ===");
  testCalculationLogic();
  console.log("=== PDF 생성 테스트 ===");
  testPdfGeneration();
}

function testDataParsing() {
  var sampleData = [["이름", "점수"], ["김철수", "85"], ["이영희", "92"]];
  var result = parseFormData(sampleData);
  console.log("파싱 결과 확인:", result.length === 2 ? "PASS" : "FAIL");
  console.log("첫 번째 항목:", result[0].name === "김철수" ? "PASS" : "FAIL");
}
}}}

**2단계: 울타리 치기**
전체 300줄을 한번에 리팩토링하는 대신, 가장 명확한 부분부터 분리했다:
- 데이터 파싱 로직만 별도 함수로 분리
- 계산 로직만 별도 함수로 분리
- PDF 생성 로직만 별도 함수로 분리

**3단계: 하나씩 실험**
각 함수를 분리할 때마다 테스트를 실행해서 기존 기능이 깨지지 않았는지 확인했다. 문제가 발생하면 즉시 이전 상태로 되돌렸다.

**결과:**
1시간 만에 300줄의 스파게티 코드가 10개의 작은 함수로 깔끔하게 분리되었다. 각 함수는 10-20줄 정도였고, 각각 명확한 책임을 가지고 있었다. 가장 중요한 것은 **실험 환경을 먼저 구축했기 때문에 자신있게 리팩토링할 수 있었다**는 점이다.

== The Art of Creating Experiment-able Environments ==

'''왜 실험 환경을 만드는 것이 그렇게 중요한가?'''

TinyExperiment와 일반적인 PDCA의 차이는 여기에 있다. PDCA는 "계획-실행-확인-조치"의 사이클에 집중한다. 하지만 TinyExperiment는 **"확인"을 쉽고 빠르고 안전하게 할 수 있는 환경을 구축하는 것**에 더 많은 에너지를 투입한다.

마치 과학자가 실험실을 먼저 세팅하고 실험을 시작하는 것처럼, 프로그래머도 "실험실"을 먼저 만들어야 한다.

'''훈련된 창의성의 필요'''

실험 환경을 만드는 것은 단순한 기술적 작업이 아니다. **훈련된 창의성**이 필요하다:

- 기존 도구의 한계를 인식하고
- 현재 상황에서 가능한 최선의 대안을 찾고
- 최소한의 노력으로 최대한의 실험 가능성을 만들어내는 것

구글 앱스 스크립트 사례에서 "console.log를 assertion으로 사용하기"는 이런 창의성의 예시다. 완벽하지 않지만, 주어진 제약 조건에서 가능한 최선의 실험 환경이었다.

'''다양한 실험 환경 구축 기법'''

**1. REPL 활용하기**
- 언어별 인터랙티브 셸 사용
- 작은 코드 조각을 즉시실행하며 테스트

**2. 가벼운 테스트 헬퍼 만들기**
- assert 같은 간단한 검증 함수
- 결과를 쉽게 확인할 수 있는 출력 형식

**3. 샌드박스 환경 구축**
- 메인 코드와 분리된 실험 공간
- 안전하게 실패할 수 있는 환경

**4. 빠른 피드백 도구**
- 핫 리로드, 와치 모드
- 즉시 결과를 볼 수 있는 시각화

'''실험 환경의 품질 지표'''

좋은 실험 환경은 다음 특성을 가진다:
- **빠름**: 실험에서 결과까지 몇 초 이내
- **안전함**: 실패해도 전체 시스템에 영향 없음
- **격리됨**: 다른 요소들의 간섭 최소화
- **관찰 가능함**: 결과를 명확하게 확인 가능

== Common Pitfalls ==

'''완벽한 실험 환경을 기다림'''

많은 프로그래머들이 완벽한 실험하기 위해 완벽한 환경을 구축하려고 한다. 모든 테스트를 작성하고, 모든 도구를 설정하고, 모든 문서를 읽고 나서야 실험을 시작하려 한다. 하지만 이런 완벽주의는 실제로는 시간 낭비다.

체계적인 요리사는 바로 맛을 볼 수 있는 기본 셋업만 가지고 바로 실험을 시작했다. 완벽한 주방 시설을 기다리지 않았다. 프로그래머도 print문이나 로그만 있어도 충분히 실험할 수 있다.

'''추측만 하기'''

"아마 이게 문제일 것이다"라는 추측만 하고 실제로는 확인하지 않는 경우들이 많다. 추측은 실험의 시작점이 될 수 있지만, 그 자체로는 의미가 없다. 체계적인 요리사도 "이 소스가 더 나을 것 같다"고 생각했지만, 반드시 실제로 맛을 확인했다.

'''너무 큰 실험'''

한번에 너무 많은 것을 바꾸려고 하는 것도 흔한 실수다. "이 함수와 이 데이터베이스 쿼리와 이 UI를 동시에 개선해보자"는 식으로 접근하면, 결과가 좋아져도 나빠져도 무엇 때문인지 알 수 없다.

== Tools for Your Laboratory ==

실험을 위한 도구들이 필요하다. 체계적인 요리사에게 맛을 볼 수 있는 숟가락이 있었듯이, 프로그래머에게도 실험을 위한 도구들이 있다.

**기본 도구들:**
- 코드를 즉석에서 실행해볼 수 있는 REPL
- 중간 결과를 바로 확인할 수 있는 로그 출력
- 변화를 확인할 수 있는 버전 관리
- 빠른 실행이 가능한 테스트 코드

**고급 도구들:**
Line 51: Line 197:
- 실시간 로그 모니터링
- 간단한 확인 스크립트

=== 5단계: 격리된 환경 ===
다른 요소들의 영향을 받지 않도록 격리하라.

- 테스트용 데이터베이스 사용
- 모의 객체(mock)로 외부 의존성 제거
- 개발 전용 환경에서 실험
- 한 번에 하나의 변수만 변경

=== 6단계: 가추법 활용 ===
관찰된 현상에서 가능한 원인을 추론하고 다시 검증하라.

현상: "API 응답이 느리다"
가능한 원인들:
- 데이터베이스 쿼리가 느림 → 쿼리 실행 계획 확인
- 네트워크 지연 → 로컬 테스트로 비교
- 메모리 부족 → 메모리 사용량 모니터링

== Detailed Examples ==

=== 예시 1: 새로운 기능 구현 ===
'''상황:''' 사용자 프로필 페이지에 '좋아요' 기능 추가

'''잘못된 접근:'''
- 데이터베이스 스키마 변경
- API 엔드포인트 추가
- 프론트엔드 UI 구현
- 알림 기능 추가
- 권한 검증 로직 추가
모든 것을 한 번에 구현 → 3일 후 동작하지 않음

'''TinyExperiment 접근:'''
1. '''가설:''' "좋아요 데이터를 저장할 수 있다"
   - 임시 테이블 생성
   - 간단한 INSERT/SELECT 쿼리 테스트
   - 5분 만에 검증 완료

2. '''가설:''' "API로 좋아요 데이터를 주고받을 수 있다"
   - 기본적인 POST/GET 엔드포인트만 구현
   - Postman으로 테스트
   - 10분 만에 검증 완료

3. '''가설:''' "프론트엔드에서 API를 호출할 수 있다"
   - 단순한 버튼과 AJAX 호출만 구현
   - UI는 나중에 다듬기
   - 15분 만에 검증 완료

각 단계가 성공한 후 다음 단계로 진행. 실패하면 원인을 정확히 알 수 있음.

=== 예시 2: 버그 디버깅 ===
'''상황:''' "로그인 후 대시보드가 가끔 안 보인다"는 신고

'''잘못된 접근:'''
코드를 훑어보며 "아마 세션 문제일 것이다" 추측하고 세션 관련 코드를 모두 수정

'''TinyExperiment 접근:'''
1. '''가설:''' "재현 가능한 조건이 있다"
   - 다양한 브라우저, 사용자로 테스트
   - 재현 조건 발견: "IE에서만 발생"

2. '''가설:''' "특정 브라우저의 문제다"
   - 콘솔 로그 확인
   - 네트워크 탭에서 요청 확인
   - JavaScript 오류 발견

3. '''가설:''' "이 JavaScript 오류가 원인이다"
   - console.log 추가해서 실행 경로 추적
   - 특정 함수에서 오류 발생 확인

4. '''가설:''' "이 함수의 이 부분이 문제다"
   - 최소한의 수정으로 오류 해결
   - 테스트로 검증

=== 예시 3: 성능 개선 ===
'''상황:''' "페이지 로딩이 너무 느려요"

'''TinyExperiment 접근:'''
1. '''측정부터:''' 브라우저 개발자 도구로 정확한 로딩 시간 확인
2. '''가설 1:''' "이미지가 너무 크다" → 이미지 최적화 후 측정
3. '''가설 2:''' "JavaScript 파일이 크다" → 번들 크기 분석
4. '''가설 3:''' "데이터베이스 쿼리가 느리다" → 쿼리 실행 시간 측정

각 개선 후 실제 측정치로 효과 확인.

== 요리사의 지혜 (은유) ==
훌륭한 요리사는 새로운 요리를 개발할 때 이렇게 한다:

1. '''기본 레시피로 시작''' - 검증된 방법부터
2. '''한 번에 하나만 바꾸기''' - 설탕량만 조정, 다른 것은 그대로
3. '''맛을 보며 조정''' - 변경 후 즉시 결과 확인
4. '''기록 남기기''' - 무엇을 바꿨는지, 결과가 어땠는지
5. '''실패해도 괜찮은 환경''' - 작은 양으로 실험, 큰 파티 음식은 나중에

프로그래밍도 마찬가지다. 경험 많은 개발자는 작은 실험을 통해 점진적으로 시스템을 이해하고 개선한다.

== Common Anti-Patterns (피해야 할 것들) ==

=== "Big Bang" 개발 ===
- 한 번에 많은 기능을 구현하려고 함
- 실패 시 원인을 찾기 어려움
- 되돌리기 어려움

=== "추측 기반" 디버깅 ===
- 현상을 제대로 관찰하지 않고 추측으로 수정
- 한 번에 여러 부분을 수정
- 실제 원인과 다른 곳을 수정

=== "완벽주의" 함정 ===
- 실험 환경을 너무 완벽하게 만들려고 함
- 실험보다 환경 구축에 더 많은 시간 소모
- 간단한 실험으로도 충분한 상황에서 과도한 준비

== Tools and Techniques ==

=== 개발 환경 도구 ===
- '''Hot reload:''' 코드 변경 시 즉시 반영
- '''REPL:''' 작은 코드 조각을 즉시 실행
- '''디버거:''' 단계별 실행과 상태 확인
- '''로깅:''' 실행 경로와 변수 값 추적

=== 테스팅 도구 ===
- '''Unit tests:''' 작은 단위의 동작 검증
- '''Test fixtures:''' 일관된 테스트 환경
- '''Mock objects:''' 외부 의존성 제거
- '''Test doubles:''' 복잡한 객체의 간단한 대체재

=== 프로파일링 도구 ===
- '''성능 측정:''' 실행 시간, 메모리 사용량
- '''데이터베이스 쿼리 분석:''' 실행 계획, 인덱스 사용
- '''네트워크 모니터링:''' 요청/응답 시간, 데이터 크기
- 인터랙티브 디버거
- 성능 프로파일러
- 시각화 도구

하지만 가장 중요한 것은 도구 자체가 아니라 도구를 활용하는 마음가짐이다. "빠르게 확인하고, 빠르게 배우고, 빠르게 조정한다"는 체계적인 요리사의 철학이 핵심이다.
Line 185: Line 204:
TinyExperiment를 습관화하면:

- '''불확실성이 줄어든다''' - 추측 대신 확실한 지식 축적
- '''학습이 가속화된다''' - 빠른 피드백으로 경험 축적
- '''실패 비용이 낮아진다''' - 작은 실패로 큰 실패 방지
- '''자신감이 향상된다''' - 복잡한 문제도 작은 단위로 분해하여 해결
- '''시스템 이해가 깊어진다''' - 점진적 탐색으로 전체 그림 파악
- '''팀 협업이 개선된다''' - 명확한 가설과 결과로 소통
TinyExperiment를 숙련화하면 복잡한 문제 앞에서도 당황하지 않게 된다. 큰 규모 문제 해결도 이루어낼 수 있고, 복잡한 시스템도 작은 부분부터 차근차근 이해할 수 있다는 것을 안다. 실수를 두려워하지 않게 되고, 실험을 통한 학습이 매우 자연스러워진다.

가장 중요한 변화는 시스템과 대화하는 방법을 안다는 점이다. 코드에서 뭔가하고, 반응을 보고, 다시 실험하는 순환이 자연스러워진다. 이것이 진정한 프로그래머의 기본기다.
Line 195: Line 209:
* '''Enables:''' [[BabyStep]], [[UseAbduction]], [[HereAndNowDebugging]]
* '''Used by:''' [[TddIsDesignActivity]], [[PiecemealGrowthCenterFirst]]
* '''Complements:''' [[ShortFeedbackCycle]], [[MicroCommit]]
* '''Conflicts with:''' BigBangDevelopment, GuessBasedDebugging
* '''연관:''' [[BabySteps]], [[GreenRefuge]] - 작은 단계와 안전한 되돌림
* '''활용:''' [[DesignThroughTest]], [[DetectiveWork]] - 설계와 디버깅에서의 실험
* '''보완:''' [[TightLoop]], [[AtomicCommit]] - 빠른 피드백과 작은 변경
Line 201: Line 214:
* [[추법]] - 관찰에서 가설을 도출하는 사고 방
* Kent Beck의 "Test Driven Development" - 작은 테스트로 설계하기
* Gary Klein의 "Sources of Power" - 전문가의 직관과 가설 검증
* 과학
방법론 - 가설-실험-검증의 순환
* [[추법]] - 가설을 세우고 검증하는 사고 방
* Kent Beck의 "Test Driven Development" - 작은 테스트로 시작하기
* Gary Klein의 "Sources of Power" - 전문가의 직관적 사고 패턴

TinyExperiment

Context

복잡한 프로젝트에서 문제를 해결하거나 새로운 기능을 구현할 때, 또는 버그를 찾고자 할 때. 불확실성이 높고 여러 가능성이 존재하는 상황에서 무엇 먼저해야 할지 모르는 경우.

Problem

두 가지 요리사의 이야기

어떤 레스토랑에 두 명의 요리사가 있었다. 둘 다 새로운 파스타 요리를 만들라는 임무를 받았다.

첫 번째 요리사는 모험적인 성격이었다. "완벽한 새로운 파스타를 만들겠어!" 특별한 면도 새로 만들고, 소스도 완전 새롭게 하고, 치즈 특유의 맛도 새로 만들고, 향신료도 특별한 것들로만 쓰기로 했다. 3시간 동안 심혈을 기울여 요리를 완성했지만, 이상한 맛이 나와서 먹을 수가 없었다. 손님들은 고개를 절레절레 흔들었다. 무엇이 모험적인 요리사는 어디서 잘못되었는지 알 수 없었다. 면을 잘못했나? 소스를 잘못했나? 치즈를 잘못했나? 처음부터 다시 만들어야 했다.

두 번째 요리사는 체계적인 다른 방식을 선택했다. 먼저 기본적인 기본 파스타를 만들어 맛을 확인했다. 그 다음 소스를 조금 바꿔서 다시 맛을 봤다. "좋다, 이게 맞다." 그리고 치즈 한 종류만 바꿔서 또 맛을 봤다. 각 단계마다 맛이 좋은지 나쁜지 확인했다. 만약 한 변경사항이 맛이 없다면, 바로 이전 단계로 돌아갔다. 2시간 후, 손님들이 "이 파스타 정말 맛있네요!"라고 극찬하는 요리가 완성되었다.

프로그래머들도 마찬가지다. 복잡한 문제를 한 번에 모든 요소를 바꿔려다가 모험적인 요리사처럼 실패하거나, 체계적인 요리사처럼 작은 단계로 차근차근 실험하거나. 차이는 매우 명확히 있다.

Solution

실험 가능한 환경을 먼저 만들어라

체계적인 요리사의 핵심은 단순했다. 큰 변화를 한번에 시도하는 대신, 각 변경이 어떤 영향을 주는지 확인하고, 실패해도 쉽게 되돌아갈 수 있게 준비하는 것이었다.

프로그래밍에서도 이와 같은 "작은 실험"을 만들 수 있다. 새로운 기능을 구현할 때, 버그를 찾을 때, 코드를 개선하려 할 때 이 접근법을 사용하라.

울타리를 쳐서 실험 영역을 격리하라

큰 문제에서 특정한 부분만을 분리해내어 그 부분에만 집중할 수 있는 "울타리"를 만들어라. 울타리 안에서는 자유롭게, 빠르게, 안전하게 다양한 실험을 할 수 있어야 한다. 이것이 TinyExperiment의 핵심이다.

체계적인 요리사는 "전체 요리"가 아니라 "이 소스" 또는 "이 치즈"처럼 구체적인 부분에만 집중했다. 프로그래머도 "전체 시스템"이 아니라 "이 함수" 또는 "이 데이터 변환"처럼 구체적으로 범위를 한정하라.

실험 가능한 환경을 창의적으로 구축하라

이것이 TinyExperiment를 단순한 PDCA 사이클과 구별하는 핵심이다. **실험 가능한 환경을 만드는 것 자체가 훈련된 창의성을 요구한다.**

실제 사례: 구글 앱스 스크립트 테스팅 환경

한 페어 프로그래밍 세션에서 친구가 구글 폼 설문조사 데이터로부터 분석 보고서 PDF를 생성하는 구글 앱스 스크립트를 만들고 있었다. 하지만 스파게티 코드 때문에 속도가 느려지고 있었고 리팩토링이 필요했다.

문제는 구글 앱스 스크립트에는 제대로 된 유닛 테스트 환경이 없다는 것이었다. 물론 있을 수도 있지만, 1시간이라는 제한된 시간에는 그것을 찾고 설정할 여유가 없었다.

해결책: **매우 간단한 테스팅 환경을 직접 만들었다.** console.log를 사용해 true/false 또는 pass/fail을 테스트케이스 이름과 함께 출력하는 것이다.

많은 사람들이 print문을 사용한다. 하지만 소수만이 이것을 assertion 형태로 활용한다. 임의의 값을 출력하면 기준을 만족하는지 판단하기 어렵다. 하지만 단순히 true/false나 pass/fail을 테스트케이스 이름과 함께 출력하면, 어떤 테스트케이스가 성공했는지 실패했는지 즉시 알 수 있다.

function testDataTransformation() {
  console.log("테스트: 날짜 파싱", parseDate("2024-01-15") === "2024/01/15" ? "PASS" : "FAIL");
  console.log("테스트: 빈 데이터 처리", handleEmptyData("") === null ? "PASS" : "FAIL");
  console.log("테스트: 계산 로직", calculateScore([1,2,3]) === 6 ? "PASS" : "FAIL");
}

이렇게 해서 **거의 모든 작업에서, 어떤 상황에서도 TinyExperiment 환경을 만들 수 있다.** 핵심은 기존 도구의 한계에 굴복하지 않고, 창의적으로 실험 환경을 구축하는 것이다.

구체적인 가설을 세워라

체계적인 요리사는 "이 소스가 더 나을 것이다"라는 구체적인 가설을 세우고 실험했다. 프로그래머도 "이 함수가 느린 것이다"가 아니라 "이 반복문에서 데이터베이스를 N번 호출해서 느린 것이다"처럼 구체적으로 하라.

빠른 피드백 루프를 만들어라

체계적인 요리사는 각 단계마다 바로 맛을 봤다. 완성된 전체 요리가 아니라 작은 부분만 실험할 수 있게 했다. 프로그래머도 git 브랜치를 잘 활용한다. 새로운 시도를 위한 브랜치를 만들고, 실패하면 쉽게 되돌아간다. 테스트 코드도 전체 시스템을 실행하지 않아도 확인하는데 필요한 부분만 한다.

찔러보고 반응을 관찰하라

이것이 TinyExperiment의 핵심 리듬이다. 작은 변화를 주고(찔러보고), 시스템의 반응을 관찰하고, 그 결과에서 학습하고, 전략을 조정하고, 다시 찔러본다. 이는 일종의 PDCA 사이클이지만, **실험 가능한 환경을 만드는 데 훨씬 더 많은 에너지를 투입한다는 점**이 다르다.

한 번에 하나씩만 바꿔라

체계적인 요리사의 가장 중요한 원칙이다. 소스를 바꿀 때는 다른 것은 그대로 두고 소스만 바꿨다. 그래야 결과가 좋아졌는지 나빠졌는지 소스 때문이라는 걸 확실히 알 수 있었다. 프로그래머도 한 번에 하나의 변수만 바꿔야 한다. 함수를 최적화할 때 동시에 데이터베이스 스키마까지 바꾸면, 어떤 것이 진짜 효과가 있었는지 알 수 없다.

Examples

주니어 개발자 민호의 첫 번째 버그 해결

민호는 "로그인 후 메뉴버튼이 보이지 않는다"는 버그 리포트를 받았다. 처음엔 막막 했다. 로그인 시스템도 복잡하고, 메뉴버튼도 여러 컴포넌트와 연결되어 있었다.

선배개발자 수진이 조언을 해주었다. "체계적인 요리사처럼 해봐. 큰 문제 대신 작은 부분만 울타리 쳐서 보자."

민호는 먼저 "이 버그를 재현할 수 있나?"라는 질문부터 시작했다. 자신의 로컬 환경에서 직접 로그인해보니 문제없이 작동했다. 그런데다 다른 브라우저에서 시도해보니까 Internet Explorer에서만 문제가 발생했다.

"IE에서만 발생한다"는 새로운 정보로 민호는 범위를 더욱 좁혔다. "JavaScript 오류가 있을 것이다." 개발자 도구를 열어보니 실제로 오류 메시지가 있었다. 특정 함수에서 undefined 오류가 발생하고 있었다.

더욱 "이 함수가 왜 값이 undefined이다"라는 구체적인 질문을 만들었다. console.log를 몇 개 추가해서 값들을 확인해보니, IE에서는 특정 API가 지원되지 않아서 undefined가 되고 있었다.

문제를 정확히 파악한 민호는 간단한 브라우저 호환성 체크 코드를 추가했다. 테스트해보니 문제가 해결되었다. 전체 과정은 1시간밖에 걸리지 않았다. 만약 처음부터 "아마 전체 시스템 문제일 것이다"라고 추측하고 모든 연관 코드를 다 뒤졌다면, 하루가 걸릴 수도 있었다.

구글 앱스 스크립트 리팩토링 사례 (상세)

페어 프로그래밍 파트너는 300줄짜리 구글 앱스 스크립트를 가지고 있었다. 설문조사 데이터를 받아서 복잡한 분석을 하고 PDF 보고서를 생성하는 코드였다. 문제는 모든 로직이 하나의 거대한 함수에 뒤섞여 있어서 수정할 때마다 다른 부분이 깨졌다.

**1단계: 실험 환경 구축** 일반적인 유닛테스트 프레임워크를 사용할 수 없는 상황이었다. 대신 매우 간단한 테스트 환경을 만들었다:

function runTests() {
  console.log("=== 데이터 파싱 테스트 ===");
  testDataParsing();
  console.log("=== 계산 로직 테스트 ===");
  testCalculationLogic();
  console.log("=== PDF 생성 테스트 ===");
  testPdfGeneration();
}

function testDataParsing() {
  var sampleData = [["이름", "점수"], ["김철수", "85"], ["이영희", "92"]];
  var result = parseFormData(sampleData);
  console.log("파싱 결과 확인:", result.length === 2 ? "PASS" : "FAIL");
  console.log("첫 번째 항목:", result[0].name === "김철수" ? "PASS" : "FAIL");
}

**2단계: 울타리 치기** 전체 300줄을 한번에 리팩토링하는 대신, 가장 명확한 부분부터 분리했다: - 데이터 파싱 로직만 별도 함수로 분리 - 계산 로직만 별도 함수로 분리 - PDF 생성 로직만 별도 함수로 분리

**3단계: 하나씩 실험** 각 함수를 분리할 때마다 테스트를 실행해서 기존 기능이 깨지지 않았는지 확인했다. 문제가 발생하면 즉시 이전 상태로 되돌렸다.

**결과:** 1시간 만에 300줄의 스파게티 코드가 10개의 작은 함수로 깔끔하게 분리되었다. 각 함수는 10-20줄 정도였고, 각각 명확한 책임을 가지고 있었다. 가장 중요한 것은 **실험 환경을 먼저 구축했기 때문에 자신있게 리팩토링할 수 있었다**는 점이다.

The Art of Creating Experiment-able Environments

왜 실험 환경을 만드는 것이 그렇게 중요한가?

TinyExperiment와 일반적인 PDCA의 차이는 여기에 있다. PDCA는 "계획-실행-확인-조치"의 사이클에 집중한다. 하지만 TinyExperiment는 **"확인"을 쉽고 빠르고 안전하게 할 수 있는 환경을 구축하는 것**에 더 많은 에너지를 투입한다.

마치 과학자가 실험실을 먼저 세팅하고 실험을 시작하는 것처럼, 프로그래머도 "실험실"을 먼저 만들어야 한다.

훈련된 창의성의 필요

실험 환경을 만드는 것은 단순한 기술적 작업이 아니다. **훈련된 창의성**이 필요하다:

- 기존 도구의 한계를 인식하고 - 현재 상황에서 가능한 최선의 대안을 찾고 - 최소한의 노력으로 최대한의 실험 가능성을 만들어내는 것

구글 앱스 스크립트 사례에서 "console.log를 assertion으로 사용하기"는 이런 창의성의 예시다. 완벽하지 않지만, 주어진 제약 조건에서 가능한 최선의 실험 환경이었다.

다양한 실험 환경 구축 기법

**1. REPL 활용하기** - 언어별 인터랙티브 셸 사용 - 작은 코드 조각을 즉시실행하며 테스트

**2. 가벼운 테스트 헬퍼 만들기** - assert 같은 간단한 검증 함수 - 결과를 쉽게 확인할 수 있는 출력 형식

**3. 샌드박스 환경 구축** - 메인 코드와 분리된 실험 공간 - 안전하게 실패할 수 있는 환경

**4. 빠른 피드백 도구** - 핫 리로드, 와치 모드 - 즉시 결과를 볼 수 있는 시각화

실험 환경의 품질 지표

좋은 실험 환경은 다음 특성을 가진다: - **빠름**: 실험에서 결과까지 몇 초 이내 - **안전함**: 실패해도 전체 시스템에 영향 없음 - **격리됨**: 다른 요소들의 간섭 최소화 - **관찰 가능함**: 결과를 명확하게 확인 가능

Common Pitfalls

완벽한 실험 환경을 기다림

많은 프로그래머들이 완벽한 실험하기 위해 완벽한 환경을 구축하려고 한다. 모든 테스트를 작성하고, 모든 도구를 설정하고, 모든 문서를 읽고 나서야 실험을 시작하려 한다. 하지만 이런 완벽주의는 실제로는 시간 낭비다.

체계적인 요리사는 바로 맛을 볼 수 있는 기본 셋업만 가지고 바로 실험을 시작했다. 완벽한 주방 시설을 기다리지 않았다. 프로그래머도 print문이나 로그만 있어도 충분히 실험할 수 있다.

추측만 하기

"아마 이게 문제일 것이다"라는 추측만 하고 실제로는 확인하지 않는 경우들이 많다. 추측은 실험의 시작점이 될 수 있지만, 그 자체로는 의미가 없다. 체계적인 요리사도 "이 소스가 더 나을 것 같다"고 생각했지만, 반드시 실제로 맛을 확인했다.

너무 큰 실험

한번에 너무 많은 것을 바꾸려고 하는 것도 흔한 실수다. "이 함수와 이 데이터베이스 쿼리와 이 UI를 동시에 개선해보자"는 식으로 접근하면, 결과가 좋아져도 나빠져도 무엇 때문인지 알 수 없다.

Tools for Your Laboratory

실험을 위한 도구들이 필요하다. 체계적인 요리사에게 맛을 볼 수 있는 숟가락이 있었듯이, 프로그래머에게도 실험을 위한 도구들이 있다.

**기본 도구들:** - 코드를 즉석에서 실행해볼 수 있는 REPL - 중간 결과를 바로 확인할 수 있는 로그 출력 - 변화를 확인할 수 있는 버전 관리 - 빠른 실행이 가능한 테스트 코드

**고급 도구들:** - 핫 리로드 개발 환경 - 인터랙티브 디버거 - 성능 프로파일러 - 시각화 도구

하지만 가장 중요한 것은 도구 자체가 아니라 도구를 활용하는 마음가짐이다. "빠르게 확인하고, 빠르게 배우고, 빠르게 조정한다"는 체계적인 요리사의 철학이 핵심이다.

Resulting Context

TinyExperiment를 숙련화하면 복잡한 문제 앞에서도 당황하지 않게 된다. 큰 규모 문제 해결도 이루어낼 수 있고, 복잡한 시스템도 작은 부분부터 차근차근 이해할 수 있다는 것을 안다. 실수를 두려워하지 않게 되고, 실험을 통한 학습이 매우 자연스러워진다.

가장 중요한 변화는 시스템과 대화하는 방법을 안다는 점이다. 코드에서 뭔가하고, 반응을 보고, 다시 실험하는 순환이 자연스러워진다. 이것이 진정한 프로그래머의 기본기다.

* 연관: BabySteps, GreenRefuge - 작은 단계와 안전한 되돌림 * 활용: DesignThroughTest, DetectiveWork - 설계와 디버깅에서의 실험 * 보완: TightLoop, AtomicCommit - 빠른 피드백과 작은 변경

See Also

* 추론법 - 가설을 세우고 검증하는 사고 방법 * Kent Beck의 "Test Driven Development" - 작은 테스트로 시작하기 * Gary Klein의 "Sources of Power" - 전문가의 직관적 사고 패턴


CategoryPattern

TinyExperiment (last edited 2025-07-24 23:12:45 by 정수)