레이블이 추정심리학인 게시물을 표시합니다. 모든 게시물 표시
레이블이 추정심리학인 게시물을 표시합니다. 모든 게시물 표시

2025년 11월 19일 수요일

WBS의 과학: 왜 인간은 큰 작업을 못 맞추는가

"이 기능 개발하는데 얼마나 걸릴까요?"

"음... 한 3일?"

(5일 후)

"아직 좀 더 필요해요."

왜 우리의 추정은 항상 틀릴까요?

이 질문을 수없이 받아봤고, 수없이 틀려봤습니다. 그리고 깨달았죠. 우리가 놓치는 것들이 너무 많다는 걸.

시간 추정이 틀리는 5가지 이유

1. 코딩 시간만 생각한다

대부분의 개발자가 하는 실수입니다. "로그인 API? 코드는 2시간이면 충분해"라고 생각하죠. 하지만 실제로는:

# 개발자가 생각하는 시간
developer_estimate = {
    "코딩": 2  # 시간
}

# 실제 필요한 시간
actual_time = {
    "요구사항 파악": 0.5,
    "설계 고민": 0.5,
    "환경 설정": 0.5,
    "코딩": 2,
    "디버깅": 1,
    "테스트 작성": 1,
    "코드 리뷰": 0.5,
    "리뷰 반영": 0.5,
    "문서 작성": 0.5,
    "배포": 0.5,
    "모니터링 확인": 0.5
}
# 총 8시간 (4배!)

print(f"추정: {developer_estimate['코딩']}시간")
print(f"실제: {sum(actual_time.values())}시간")
print(f"오차: {sum(actual_time.values()) / developer_estimate['코딩']}배")

실제 프로젝트에서 "순수 코딩"은 전체 시간의 25-30%에 불과합니다.

2. 최선의 시나리오만 상상한다

우리 뇌는 긍정적입니다. 추정할 때 이런 것들을 가정하죠:

// 개발자의 상상 속 세계
const ideal_world = {
  버그: "당연히 없겠지",
  요구사항_변경: "확정됐으니 안 바뀌겠지",
  외부_API: "문서대로 잘 작동하겠지",
  코드_리뷰: "한 번에 통과하겠지",
  테스트: "모두 성공하겠지",
  배포: "문제없이 되겠지"
};

// 현실 세계
const real_world = {
  버그: "예상치 못한 엣지 케이스 5개 발견",
  요구사항_변경: "아, 이것도 추가해주세요",
  외부_API: "문서가 틀렸네? Rate limit이 있었네?",
  코드_리뷰: "이 부분 다시 작성해주세요",
  테스트: "CI에서만 실패하는 이상한 버그",
  배포: "롤백... 다시 수정... 재배포..."
};

3. 컨텍스트 스위칭을 무시한다

"하루 8시간 근무니까 8시간 작업 가능"이라고 생각하시나요?

def calculate_actual_productivity():
    """실제 생산적인 시간 계산"""

    total_hours = 8

    # 시간 도둑들
    time_thieves = {
        "일일 스탠드업": 0.5,
        "이메일/슬랙 확인": 1,
        "예상치 못한 질문/도움 요청": 0.5,
        "PR 리뷰 (남의 것)": 0.5,
        "회의 (갑자기 잡힌)": 1,
        "빌드/테스트 대기": 0.5,
        "집중력 회복 시간": 0.5,
        "커피/화장실/스트레칭": 0.5
    }

    productive_hours = total_hours - sum(time_thieves.values())

    print(f"근무 시간: {total_hours}시간")
    print(f"뺏기는 시간: {sum(time_thieves.values())}시간")
    print(f"실제 작업 가능: {productive_hours}시간")

    return productive_hours

# 결과: 8시간 중 실제 작업 가능 시간은 3시간

충격적인 사실: 대부분의 개발자는 하루에 2-4시간만 집중해서 코딩합니다.

4. 통합과 디버깅을 과소평가한다

"내 코드는 완벽해. 한 번에 될 거야"

// 단위 기능 개발 시간
const feature_time = {
  "결제 모듈": 16,  // 시간
  "장바구니": 12,
  "상품 목록": 8,
  "검색": 10
};

// 통합할 때 발생하는 추가 시간
const integration_overhead = {
  "결제 + 장바구니": 8,  // 데이터 동기화 이슈
  "장바구니 + 상품": 4,   // 재고 관리 충돌
  "검색 + 상품": 3,       // 인덱싱 문제
  "전체 통합 테스트": 12,
  "버그 수정": 16,
  "성능 최적화": 8
};

// 총 시간 = 46시간(기능) + 51시간(통합) = 97시간!
// 통합이 개발보다 오래 걸림

5. 남의 코드 이해 시간을 빼먹는다

"그냥 저 함수 호출하면 되겠지"

실제로는:

# 외부 의존성 파악 시간
dependency_overhead = {
    "문서 읽기": 2,
    "예제 코드 찾기": 1,
    "로컬에서 테스트": 2,
    "예상과 다른 동작 디버깅": 3,
    "우리 코드에 맞게 래핑": 2,
    "엣지 케이스 처리": 2
}

# "그냥 Stripe 연동하면 되겠지" → 실제 12시간

숨은 작업 체크리스트

개발 시간을 추정할 때 이 체크리스트를 사용하세요:

개발 전 작업 (전체의 20%)

□ 요구사항 명확화 미팅
□ 기술 스택 조사
□ 설계 문서 작성
□ 개발 환경 셋업
□ 더미 데이터 준비
□ API 스펙 정의

개발 중 작업 (전체의 40%)

□ 실제 코딩
□ 단위 테스트 작성
□ 로컬 테스트
□ 디버깅
□ 리팩토링
□ 주석 및 문서화

개발 후 작업 (전체의 40%)

□ 코드 리뷰 요청
□ 리뷰 피드백 반영
□ 통합 테스트
□ 버그 수정
□ 성능 최적화
□ 배포 준비
□ 스테이징 테스트
□ 프로덕션 배포
□ 모니터링 설정
□ 롤백 계획

현실적인 추정을 위한 곱셈 규칙

개발자 경험별 계수

const experience_multiplier = {
  "처음 하는 작업": {
    senior: 2.0,    // 예상 시간 x 2
    junior: 4.0     // 예상 시간 x 4
  },

  "유사 경험 있음": {
    senior: 1.5,
    junior: 2.5
  },

  "여러 번 해본 작업": {
    senior: 1.2,
    junior: 1.8
  }
};

// 사용 예
const estimated = 8;  // 시간
const actual = estimated * experience_multiplier["처음 하는 작업"]["junior"];
// 실제: 32시간

작업 복잡도별 계수

def get_complexity_multiplier(task):
    """작업 복잡도에 따른 추가 계수"""

    multipliers = {
        "단순 CRUD": 1.2,
        "비즈니스 로직": 1.8,
        "외부 API 연동": 2.5,
        "결제/보안": 3.0,
        "실시간 처리": 3.5,
        "분산 시스템": 4.0
    }

    base_estimate = task["hours"]
    complexity = task["type"]

    return base_estimate * multipliers[complexity]

# 예: "결제 시스템 8시간" → 실제 24시간

팀 차원의 숨은 작업

개인 작업 외에도 팀 차원의 오버헤드가 있습니다:

team_overhead = {
    "일일 스탠드업": "팀원 수 * 0.5시간",
    "주간 회의": "팀원 수 * 1시간",
    "코드 리뷰 (남의 것)": "PR 수 * 0.5시간",
    "지식 공유": "주 2시간",
    "문서 업데이트": "주 1시간",
    "긴급 이슈 대응": "주 3시간 (평균)",
    "배포 준비/모니터링": "주 2시간"
}

# 10명 팀 기준 주당 오버헤드
weekly_overhead_per_person = 12  # 시간
# 주 40시간 중 30%가 팀 활동

버퍼 전략: 현실적인 일정 만들기

PERT 기법으로 과학적 추정

def pert_estimation(optimistic, most_likely, pessimistic):
    """
    Program Evaluation and Review Technique
    NASA가 사용하는 추정 방법
    """

    # PERT 공식
    estimate = (optimistic + 4 * most_likely + pessimistic) / 6

    # 표준편차 (불확실성 측정)
    std_dev = (pessimistic - optimistic) / 6

    return {
        "expected": estimate,
        "std_dev": std_dev,
        "68%_confidence": (estimate - std_dev, estimate + std_dev),
        "95%_confidence": (estimate - 2*std_dev, estimate + 2*std_dev),
        "99%_confidence": (estimate - 3*std_dev, estimate + 3*std_dev)
    }

# 실제 사용 예
login_feature = pert_estimation(
    optimistic=8,     # 모든 게 완벽하면
    most_likely=12,   # 아마도
    pessimistic=24    # 최악의 경우
)

print(f"예상: {login_feature['expected']:.1f}시간")
print(f"95% 확률로 {login_feature['95%_confidence']}시간 내 완료")

리스크 기반 버퍼

const calculate_buffer = (task) => {
  let buffer_percentage = 10;  // 기본 10%

  // 리스크 요인들
  if (task.has_external_dependency) buffer_percentage += 20;
  if (task.uses_new_technology) buffer_percentage += 25;
  if (task.requires_integration) buffer_percentage += 15;
  if (task.has_unclear_requirements) buffer_percentage += 30;
  if (task.assigned_to_junior) buffer_percentage += 20;

  return task.estimated_hours * (1 + buffer_percentage / 100);
};

// 높은 리스크 작업 예
const risky_task = {
  name: "외부 결제 API 연동",
  estimated_hours: 16,
  has_external_dependency: true,  // +20%
  uses_new_technology: true,       // +25%
  requires_integration: true,      // +15%
  // 총 버퍼: 70%
};

const realistic_estimate = calculate_buffer(risky_task);
// 16시간 → 27.2시간

추정 개선을 위한 실전 팁

1. 추정 회고 하기

## Sprint 회고 템플릿

### 이번 스프린트 추정 정확도
- 작업 A: 예상 8h → 실제 12h (150%)
- 작업 B: 예상 16h → 실제 14h (87%)
- 작업 C: 예상 4h → 실제 10h (250%)

### 차이 발생 원인
- 작업 A: 예상못한 리팩토링 필요
- 작업 C: 외부 API 문서 부정확

### 다음 스프린트 개선 사항
- 외부 의존성 있는 작업: 기존 추정 x2
- 리팩토링 가능성 있는 레거시 코드: +50% 버퍼

2. 히스토리 데이터 활용

# 과거 데이터 기반 추정
historical_data = {
    "로그인 기능": [8, 12, 10, 14],  # 과거 4번의 실제 시간
    "CRUD API": [16, 20, 18, 22],
    "외부 API 연동": [24, 40, 32, 48]
}

def estimate_based_on_history(task_type):
    if task_type in historical_data:
        past_times = historical_data[task_type]
        avg = sum(past_times) / len(past_times)
        max_time = max(past_times)

        # 평균과 최악 사이의 중간값 사용
        return (avg + max_time) / 2
    else:
        return None  # 히스토리 없으면 PERT 사용

3. 플래닝 포커 활용

// 팀 추정 세션
const planning_poker = {
  task: "사용자 프로필 페이지 개발",

  estimates: {
    "개발자A": 8,   // 시니어, 유사 경험 많음
    "개발자B": 16,  // 주니어, 처음
    "개발자C": 12,  // 미드레벨
    "디자이너": 20  // 디자인 복잡도 고려
  },

  discussion: [
    "A: 기존 컴포넌트 재사용 가능",
    "B: 상태 관리가 복잡할 것 같음",
    "D: 반응형 대응에 시간 필요"
  ],

  final_estimate: 14  // 토론 후 합의
};

마무리: 정직한 추정이 최고의 추정

"개발자는 낙관주의자다"라는 말이 있습니다.

하지만 정직한 추정이 결국 모두를 살립니다:

  • PM은 현실적인 일정을 세울 수 있고
  • 개발자는 야근 없이 집에 갈 수 있고
  • 고객은 믿을 수 있는 약속을 받을 수 있습니다

다음에 추정할 때는 이렇게 해보세요:

  1. 순수 개발 시간 추정
  2. x3 (숨은 작업 고려)
  3. 리스크 버퍼 추가
  4. 팀과 검증

처음엔 "너무 보수적인 거 아니야?"라는 소리를 들을 수 있습니다.

하지만 3번만 정확한 추정을 해내면, 모두가 당신을 신뢰하게 될 것입니다.

추정은 예술이 아니라 과학입니다. 데이터를 모으고, 패턴을 찾고, 개선하세요.


정확한 프로젝트 추정과 관리가 필요하신가요? Plexo를 확인해보세요.

#시간추정 #숨은작업 #프로젝트관리 #개발일정 #PERT