본문으로 건너뛰기

The Product-Minded Engineer

Drew HoskinsISBN 9781098173739

목차96개 항목

The Product-Minded Engineer

원서: “The Product-Minded Engineer” (Drew Hoskins, O’Reilly Media, 2025) 비고: 원서 코드는 Python을 사용하며, 이해를 돕기 위해 필요한 일부 복잡한 스키마/알고리즘에 한해 TypeScript 코드를 병기함.

CHAPTER 1 프로덕트 사고의 기본

  • 저자는 챕터를 힙합 그룹 A Tribe Called Quest의 곡 “Scenario”를 인용하는 문구로 열며, “시나리오”라는 단어에 주목하게 함
  • 프로덕트 사고의 핵심 원시 개념(core primitive) = 시나리오
    • 시나리오란 프로덕트 설계에 쓸모 있도록 구조화된 유저 스토리
  • 도입 예시 — 비밀번호 시나리오(요약)
    • Darla는 45세에 컴퓨터를 배우기 시작한 은퇴자
    • 동네 찻집 온라인 주문 계정을 만들며 비밀번호를 딸의 생일 + 아들 이름으로 설정(기억하기 쉬워서)
    • 약 30개 계정을 갖고 있어 대부분 동일한 비밀번호를 재사용(비밀번호를 적어두지 말라는 조언까지 따랐기 때문)
    • 찻집에서 데이터 유출 발생 → 은행 계좌에도 쓰던 동일 비밀번호가 다크웹에서 팔려 도용당할 위험에 노출
    • 시사점: 이 문제는 회원가입 화면, 암호화, 저장 방식만 들여다봐서는 발견할 수 없음 — 인간의 예측 가능한 취약성까지 포함한 완전한 스토리를 써야 비로소 보안 문제가 명확해짐
  • 시나리오의 두 축
    • 플롯(시뮬레이션): 캐릭터가 취하는 행동의 전개
    • 캐릭터: 페르소나 + 동기
  • 이후 절에서 이 두 축을 분해하고, 좋은 시나리오를 만드는 방법을 다룸

1.1 사례 연구

  • Tea++: 300개 매장을 가진 카페 체인
    • 지나치게 빠르게 확장해 간신히 버티는 중
    • 회사 목표: 적은 예산으로 빠르게 주문량을 늘려 매장 폐쇄를 피하는 것
  • Tea++는 인기 있는 주문용 모바일 앱을 보유
  • 앱의 피드백 위젯을 통해 사용자들이 “즐겨찾기(Favorites)” 기능을 다수 요청
    • 커스터마이징을 매번 다시 설정하지 않고 빠르게 주문하고 싶다는 니즈
  • 소프트웨어 팀은 이를 주문 증가를 위한 손쉬운 방법으로 판단

1.2 시나리오 없이 진행한 첫 시도

  • Bob: 모바일 앱 엔지니어, 유저 시나리오를 사용하지 않고 업무를 진행
  • 피드백 위젯 검토 결과 종합
    • 커피 구매자들은 주문할 때마다 커스터마이징을 반복 입력하는 데 지침
    • 최근 주문 내역을 뒤지는 것을 싫어함
    • 원하는 항목이 다른 여러 품목과 함께 묶인 주문 안에 있어 분리해내기 번거로운 경우가 있음

Figure 1-1 — Bob의 제안 목업

flowchart LR
    subgraph Home["홈 화면"]
        Ad["대형 음료 광고 배너"]
    end
    subgraph Faves["Favorites 화면"]
        Plus["+ 버튼 · 신규 즐겨찾기 추가"]
        D1["Drink 1 · 사진 · 주문하기"]
        D2["Drink 2 · 사진 · 주문하기"]
        Sort["정렬 기준: 최근 주문순"]
    end
    Nav["하단 내비게이션: Faves · Menu · Acct · Locations"]
    Home -- "Faves 탭 클릭" --> Faves
    Nav --- Home
    Nav --- Faves
  • 새 메뉴 항목 “Faves”를 하단 내비게이션에 추가하고, 사용자의 즐겨찾기를 나열하는 새 페이지 생성
  • 페이지 우측 상단에 즐겨찾기 추가용 + 버튼 배치
  • 구현이 쉬운 이유
    • 즐겨찾기 목록이 기존 메뉴 위젯을 그대로 재사용
    • Order 클릭 시 기존 장바구니 흐름으로 그대로 연결됨
  • 즐겨찾기 DB 테이블은 최근 구매순으로 정렬되도록 설계, updated_time 기준 인덱스 부여

DB 스키마 (원문 그대로)

Table: favorite_items
- created_time : timestamp
- updated_time : timestamp (Index)
- item_id : long
- milk_customization : varchar
- sweetness_customization : varchar
- special_instructions : varchar

TypeScript로 표현한 동일 스키마

interface FavoriteItem {
  createdTime: Date;
  updatedTime: Date; // 인덱스 대상: 최근 주문순 정렬 기준
  itemId: number;
  milkCustomization: string;
  sweetnessCustomization: string;
  specialInstructions: string;
}
  • Bob은 홈 화면의 신제품 광고 자리를 즐겨찾기로 교체하자고 제안했으나, “New Products” 팀이 시즌 스페셜 홍보 공간이 필요하다며 반대 → 제안을 마지못해 철회
  • 대규모 리뷰 미팅
    • 설계는 저렴한 구현 비용 덕에 모두에게 호평
    • 한 참석자가 스타벅스 앱의 “하트(♥)” 버튼(최근 주문/장바구니 항목에서 바로 즐겨찾기 추가) 사례를 언급하며 유사 기능 제안
    • Bob은 반대: 여러 페이지에 새로운 DB 쿼리를 추가해야 하는 더 침습적인 변경이라 구현 시간이 두 배로 늘어난다는 이유
    • 팀은 Bob 의견에 동의, 설계대로 그대로 빌드 후 출시

미미한 결과(Meh Results)

지표 결과
출시 안정성 큰 버그 없이 출시 성공
즐겨찾기 사용률 활성 사용자의 6%만 사용
주문 증가율 0.3% 증가에 그침
  • 데이터를 깊이 파본 결과
    • Favorites 메뉴 진입 자체가 적음
    • 진입한 사용자 대다수는 곧바로 이탈
    • 즐겨찾기가 비어 있는 사용자는 압도적으로 그냥 이탈해버리고 + 버튼을 누르지 않음
  • Bob은 하트 버튼 아이디어를 다시 꺼내 Carlos에게 제안하려 했으나, 팀 우선순위가 월간 구독 기능으로 옮겨가 있었고 Carlos도 다소 짜증 섞인 반응 → Bob은 자신의 제품을 끝까지 마무리하지 못했다는 느낌에 화가 난 채로 물러남

무엇이 잘못되었나(What Went Wrong?)

  • Bob이 잘한 일: 사용자 피드백 청취, 지표 추적, 사용자 상호작용 데이터 수집·분석, 범위를 줄여 효율적이고 안정적인 설계 완성
  • 그러나 제안 자체가 약했던 이유
    • 팀에게 던진 질문들이 흥미롭긴 했지만 가장 근본적인 질문은 아니었음
    • Discover 단계를 건너뛰고 곧바로 데이터베이스 설계로 직행
    • 그 결과, 실제로는 프로덕트 결정과 구현 제약 간의 상호작용이 가장 어려운 부분이었음에도, 제안 자체는 마치 구현상의 관심사가 가장 중요한 것처럼 보이게 만듦
    • 문제를 짚어줄 동료가 곁에 없었음(운이 없었음)

참고 — 더블 다이아몬드 프로세스 모델(서문 내용, 이해를 돕기 위한 배경) 소프트웨어 제품 라이프사이클을 4단계로 요약한 모델

flowchart LR
    A(["Discover · 탐색"]) --> B(["Define · 정의"])
    B --> C(["Develop · 개발"])
    C --> D(["Deliver · 전달"])
  • Discover: 누구를 위해 어떤 문제를 풀 것인지 조사·탐구·발산
  • Define: 문제를 해결할 프로덕트를 설계, 옵션을 좁혀 집중된 계획으로 수렴
  • Develop: 구현 방식을 탐색·발산하고 다듬음
  • Deliver: 빌드 → 검증 → 출시 → 피드백 수집으로 수렴
  • Bob의 실수는 Discover(탐색) 단계 없이 곧장 Develop(구현) 단계로 건너뛴 것

1.3 시나리오를 쓴 두 번째 시도

  • 6개월 후, 더 시니어인 Alice가 투입되어 Bob이 아쉬워했던 스타벅스식 하트 버튼을 만드는 임무를 맡음
  • Alice는 거기서 멈추지 않음
    • 즐겨찾기 매장(favorite stores) 개념을 스스로 추가 고안 → 특정 매장을 자주 찾는 사용자는 결제 단계에서 클릭 몇 번을 생략 가능(Bob은 음료에만 집중하라는 요청을 받아 이 부분을 아예 고려하지 못했음)
    • 홈 화면의 신제품 프로모션을 대형 “지금 주문” 버튼 2개 + 스크롤을 암시하는 세 번째 버튼 일부 노출로 교체하는 A/B 테스트 진행

Figure 1-2 — Alice의 스크롤형 빠른 주문 화면 목업

flowchart TB
    T1["Chai Latte with soy · 18 Main St · 지금 주문하기"]
    T2["Green tea, 50% sweet · 18 Main St · 지금 주문하기"]
    T3["세 번째 버튼 일부 노출 · 스크롤 가능함을 암시"]
    Nav["하단 내비게이션: Faves · Menu · Acct · Locations"]
    T1 --> T2 --> T3 --> Nav
  • 이 디자인은 예전에 Bob이 구상했던 것과 비슷했지만, 당시 Bob은 제품 마케팅 팀을 설득해 화면 공간을 확보하지 못했었음
  • 결과: 세 변경이 결합되어 전환율 3.8% 증가
    • 모바일 주문이 Tea++ 매출의 절반을 차지 → 손익에 상당한 영향
    • 이 데이터를 근거로 마케팅 부서 설득에 성공 → 신제품 광고를 상위 두 즐겨찾기 아래로 내리고, 전체 사용자에게 롤아웃
  • Bob은 Alice가 어떻게 그런 좋은 직관을 가졌고, 어떻게 사람들을 설득했는지 궁금해함

1.4 영감을 주는 이야기로서의 시나리오

  • 시나리오의 용도 중 하나: 소통과 영감(inspire)
  • Bob이 DM으로 비결을 묻자, Alice는 영감을 주는 스토리로 답함(요약)

Eliana는 바쁜 직장인으로, 매일 아침 통근길에 콩우유 차이라떼를 마심. 어느 날 출발 전 주문을 깜빡함. 운전 중 스티어링 휠의 음성비서 버튼을 눌러 “Tea++에서 늘 먹던 걸로 주문해줘”라고 말하고, 음성비서(Siri)가 금액과 매장 위치를 재확인하자 동의함.

  • Bob: “그런데 그건 실제로 만든 게 아니잖아요?”
  • Alice: Carlos는 구현 비용이 너무 크다고 판단했지만, 그럼에도 잠재력을 보여줄 높은 기준을 세우고 싶었고, 즐겨찾기 흐름이 단순한 편의를 넘어 사용자가 차를 마실 수 있느냐 없느냐를 가르는 흔한 상황을 보여주고 싶었다고 설명
  • Alice: “꿈에도 가치가 있다”며, 즐겨찾기 음료·매장을 만들어두면 나중에 음성 명령을 구현할 때도 더 쉬워진다고 덧붙임
  • 요점: Discover 단계에서는 다양한 가능성 있는 스토리라인을 폭넓게 탐색하는 것이 필수적

1.5 사용자 인터뷰를 담아내는 시나리오

  • 사용자 리서치를 수행했다면, 시나리오는 사용자들이 말한 내용을 조합한 허구의 합성 캐릭터로 만들어 공통 상황을 압축적으로 전달할 수 있음
  • Bob의 반박: “나도 내 버전이 좋아 보이게 이야기를 지어낼 수 있잖아요. 운전 중 주문이 정말 그렇게 중요한 일인가요?”
  • Alice의 답변
    • 위치 추적 데이터는 없어서 정확히 알 수는 없다고 인정
    • 대신 지인 열두 명을 인터뷰하며 커피 주문 경험을 세세하게 캐물음
    • 실제로 통근 관련 이야기가 여러 번 등장함
  • Eliana는 특정 인물이 아니라, 정신없는 바쁜 아침을 보내는 사람들의 스탠드인(대표 캐릭터)

1.6 프로덕트 공백을 드러내는 시나리오

  • 시나리오는 누락된 기능을 드러내는 데 사용 가능
  • Alice가 공개한 “before 스토리”(요약)

Eliana는 통근길에 주문을 깜빡함. 운전 중 정지 신호에서 앱을 열어 Favorites를 클릭, 목록 최상단의 라떼를 찾아 “지금 주문”을 클릭함. 뒤차가 경적을 울려 다시 운전에 집중. 카페인이 필요해 고속도로 진입로에서 갓길에 정차. 장바구니 페이지에서 “주문 및 결제”를 선택하자 매장 선택을 요구받고, 지도가 로딩된 뒤 Main St 핀을 눌러 결제 화면으로 이동, Apple Pay로 결제 완료. 휴대폰을 내려놓고 주변을 확인한 뒤 다시 도로에 합류함.

  • Bob: “그래서 즐겨찾기 매장을 만든 거군요? 누가 요청했나요?”
  • Alice
    • 한 사용자가 “왜 매번 매장을 선택해야 하나요?“라고 물었던 피드백이 있었음
    • Alice 본인이 직접 이 흐름을 시뮬레이션하며(자신이 운전석에 있다고 상상) 하트 버튼이 반드시 가장 중요한 것은 아닐 수 있다는 점을 깨달음

1.7 마찰을 드러내는 시나리오

  • 기존 프로덕트를 사용하는 고통을 소극(farce) 형태로 드러낼 수 있음(이번 예시는 사용자 피드백에서 간접적으로 도출)

Eliana는 유당불내증이 있는 신규 고객으로, 예전에 주문했던 음료가 마음에 들었던 것을 기억함. 재주문하려고 Faves 메뉴를 봤지만 아무것도 넣어둔 적이 없음. + 버튼은 보이지만 음료 이름이나 커스터마이징이 기억나지 않아 Recent Orders로 들어가 “Tiger Chai Latte”였고 콩우유로 커스터마이징했음을 확인. 그때 페이스트리도 함께 주문했었지만 이번엔 원치 않음. 그런데 Reorder는 그 페이스트리까지 강제로 포함시키고, 라떼만 골라내는 방법이 없음. 결국 나가서 일반 주문 플로우를 이용. 커스터마이징을 마치고 주문 끝에서 Favorites 버튼을 찾아봤지만 찾지 못함. 그래도 라떼는 주문에 성공 — “다음엔 다를지도”.

  • Bob: “그래서 하트 버튼을 만들고 싶었던 거군요.”
  • Alice의 고백
    • 원래는 Bob이 이 버튼을 만들도록 Carlos를 설득하려고 이 스토리를 보냈던 것
    • 3주 후 Carlos가 검토해보라고 지시했지만, 먼저 다른 손쉬운 개선 과제(low-hanging fruit)부터 찾아보라고 요청
    • 그 결과로 위치 즐겨찾기와 광고 교체까지 발견하게 됨

1.8 구현하려는 기능을 검증하는 시나리오

  • 가장 흔한 시나리오 활용법: 설계 중인 프로덕트를 사용자가 어떻게 사용할지 시뮬레이션해보는 것
  • Alice의 가장 포괄적인 “after 스토리” — 하트 버튼, 위치 즐겨찾기, 홈 화면 배치가 모두 함께 작동하는 모습(요약)
flowchart LR
    A["Recent Orders 탭"] --> B["Tiger Chai Latte 옆 하트 클릭"]
    B --> C["Favorites 목록 최상단 노출"]
    C --> D["Order Now 클릭"]
    D --> E["위치 선택기 (기본값 Main St)"]
    E --> F["Apple Pay 결제 완료"]
    F --> G{"동일 매장 2회 방문?"}
    G -- "예" --> H["Main St를 즐겨찾기 매장으로 지정할지 팝업"]
    H -- "Yes 클릭" --> I["다음 방문 시 홈 화면 최상단에 'Tiger Chai Latte from Main St' 노출"]
    G -- "아니오" --> J["일반 홈 화면 유지"]
  • 이 시나리오는 Alice의 “북극성 시나리오(North Star Scenario)” 중 하나
    • 자신이 옳은 것을 만들고 있다는 **확신(conviction)**을 줌
    • Carlos의 동의를 얻는 데도 도움이 됨
  • 북극성 시나리오는 7장에서 더 자세히 다룸 예정

1.9 테스트로서의 시나리오

  • Alice와 Bob은 이 부분을 따로 논의하지 않았지만, Alice는 자동화 테스트를 작성할 때 **“시나리오 테스트”**도 함께 작성함
  • 시나리오 테스트는 개별 앱 페이지뿐 아니라, 페이지들이 서로 올바르게 연결되어 사용자가 일반적인 흐름을 끝까지 완주할 수 있는지까지 검증
  • 시나리오 테스트는 4장에서 자세히 다룸

1.10 핵심 해답 도출

  • Bob의 최초 설계와 목업을 봤을 때 독자가 느꼈을 법한 것
    • 비평할 근거가 부족하다고 느꼈을 수 있음
    • 맥락이 부족하다고 느꼈을 수 있음
    • 데이터베이스 스키마의 세부사항에 집중했을 수 있음
  • Alice의 스토리들은 더 중요한 질문들로 초점을 이동시킴
    • 음성 비서를 구축할 수 있는가?
    • 통근 전/중에 주문하는 일이 얼마나 흔한가?
    • 즐겨찾기 기능이 사용자에게 잘 작동하고 있는가?
    • 주문 흐름에서 그 밖에 어떤 마찰이 발생하고 있는가?
  • 이 질문들은 생산적임 — 팀이 여러 기능의 우선순위를 좁히고 정하는 데 도움
  • Alice는 이 과정을 통해 가장 중요한 세 가지 수정사항(하트 버튼, 위치 즐겨찾기, 홈 화면 재배치)을 도출

1.11 시나리오 정의

  • 시나리오의 정의: 프로덕트에 대한 비판적 사고를 이끌어내도록 설계된 유저 스토리
  • 두 가지 핵심 구성요소
    1. 캐릭터(character)
    2. 캐릭터가 프로덕트/기능을 발견하고 탐색하며 문제를 해결해나가는 과정의 시뮬레이션(=플롯)
flowchart TB
    S["시나리오"] --> C["캐릭터"]
    S --> P["시뮬레이션 (플롯)"]
    C --> M["동기 · 무엇을 원하는가"]
    C --> PE["페르소나 · 배경과 수단"]
    P --> ST1["이전 맥락 · 어떻게 이 순간에 왔는가"]
    P --> ST2["현재 행동 순서"]
    P --> ST3["다음에 일어날 일 · 세부 전개"]
  • 캐릭터의 구성요소
    • 페르소나: 배경 정보(보유한 스킬과 역량 포함)
    • 동기: 지금 이 순간 프로덕트로부터 원하는 것/필요로 하는 것

동기(Motivation)

  • 사용자 이해의 제1원칙: 사용자가 무엇을 원하는지 이해하는 것
  • 픽션에서 캐릭터의 욕망은 이야기를 이끄는 동력 — 뮤지컬의 “I Want 송”(오즈의 마법사 등)처럼, 동기가 명확할 때 관객은 캐릭터를 응원하게 됨
  • 동기가 빠진 시나리오는 문제를 드러냄
    • Alice의 시나리오에서 동기 부분을 제거하면: “왜 하필 Recent Orders로 들어갔지? Favorites에서 즐겨찾기할 수 있다는 걸 어떻게 마법처럼 알았지?” 같은 의문이 생김
    • 저자가 자기 주장을 위해 캐릭터를 인형극처럼 조종하는 게 아닌지 의심하게 되고, 기능의 발견 가능성(discoverability) 자체도 의심스러워짐
    • 반대로 “음료 이름이 잘 기억나지 않지만 맛있었던 것은 기억한다”는 심리 상태를 알면 왜 Recent Orders로 갔는지 납득이 됨
  • 동기 작성 팁
    • 프로덕트가 다듬어지지 않고 거칠수록(rough), 사용자가 그것을 쓰게 만들 더 강한 동기가 필요함
    • 예시(before 스토리): 유당불내증 신규 고객이 여유 시간이 있고 스스로 한번 좋아하면 집착하는 성격임을 알기에, 굳이 Favorites → + 버튼 → “tiger” 검색 → 커스터마이징 재현이라는 번거로운 절차를 감수함
    • 이는 현재 즐겨찾기 시스템을 쓰려면 상당히 부지런하고 열성적인 고객이어야 함을 드러내는 반면, Tea++의 목표는 오히려 무거운 동기 없이도 주문할 수 있게 만드는 것이었음
  • 스트로우맨(straw man) 사용자 경계
    • 스트로우맨 논증: 상대가 실제로 갖고 있지 않은 믿음을 그에게 씌우는 것(정치에서 흔함)
    • 예: “Eliana는 자신의 최근 주문을 보고 싶어한다” → 이는 목표가 아니라 수단일 뿐. 진짜 목적(end)이 빠져 있음
    • “<사람>은 <내가 만든 멋진 기능>을 쓰고 싶어한다”처럼 근본적 필요를 설명하지 않는 동기 부여는 경계할 것
  • 근본 원인 분석(Root Cause Analysis, RCA)
    • 동기가 약하게 느껴지면 캐릭터를 더 깊이 파고들 것
    • 근본 원인 = 사용자가 특정 행동을 하는 이유(why/so that)
    • 예: “Eliana는 예전에 마셨던 음료를 기억한다” → 왜? → “그녀는 재구매 고객이기 때문” (여기서 페르소나로 넘어감)
    • 확신이 서지 않으면 실제 사용자, 프로덕트 매니저, 테크리드와 대화해볼 것

페르소나(Persona)

  • 사용자 이해의 제2원칙: 모든 사용자가 같은 부류가 아니라는 것 — 배경과 경험에 따라 원하는 바가 다름

  • 페르소나는 보통 인구통계학적 특성을 기준으로 삼음

  • Tea++의 핵심 고객층은 통근하는 전문직 → 그래서 매장이 도로변·기차역 근처에 있고, 프리미엄 가격의 음료를 취급

  • Alice의 북극성 시나리오에서 Eliana의 페르소나: 아직 습관화되지 않은 초기 재구매 고객 → 메뉴나 주문 흐름에 능숙하지 않을 것이라고 추정 가능

  • 페르소나가 빠지면 이 시나리오가 비즈니스 전략상 의미가 있는지 판단하기 어려워짐 — 습관적 고객이 될 수도/안 될 수도 있는 사용자라는 맥락이 있을 때만 가치가 드러남

  • 페르소나는 “이 기능이 누구를 위한 것이 아닌지”도 알려줌

    • 즐겨찾기 기능은 첫 주문 고객에게는 유용하지 않으므로 신규 고객 확보 전략의 일부로 삼아서는 안 됨
    • 관광객 대상으로 설계된(한 번 방문하고 마는) 매장이라면 우선순위를 낮춰야 함
  • 페르소나 작성 팁

    • 팀 차원에서 하나 이상의 타겟 오디언스(페르소나)에 먼저 합의할 것(6장에서 자세히 다룸)
    • 타겟 오디언스를 모른다면 팀과 대화할 것
    • 시나리오의 페르소나는 타겟 오디언스 범위 안에 있어야 함
    • 스킬·지식·동기를 더 잘 담아내려면 페르소나를 다면적으로 구성할 것

    다면적 페르소나를 구성하는 대표적인 축

    구분 축 설명
    프로덕트 퍼널 내 위치 가입 여부(사인업 화면은 눈에 잘 띄게, 로그인 위젯은 상대적으로 덜 두드러지게 만드는 이유이기도 함)
    캐주얼 사용자 vs 파워 유저 파워 유저가 전체의 10%뿐이라면, 그들만을 위한 고난도 기능은 나머지 90%에게는 의미가 없어 효과적으로 10%만을 위한 해결책이 됨
    마켓플레이스의 양면 운전자/승객, 무료 사용자/광고주처럼 서로 매우 다른 니즈를 가진 두 그룹을 하나의 뭉뚱그려진 사용자로 취급하면 안 됨
    사용 프로그래밍 언어가 다른 개발자 예: Python으로 ML 데이터 파이프라인을 만드는 개발자와 Go로 컨트롤 플레인을 만드는 개발자는 SDK에서 서로 다른 부분을 필요로 함
    • 결론: Eliana는 “통근 전문직 / 신규 사용자” 페르소나
  • 페르소나의 수단(means) 파악하기

    • 페르소나별 고유 수단: 바쁜 전문직은 약간의 여유 자금은 있지만 시간이 부족한 반면, 고정 수입의 은퇴자는 그 반대
    • 페르소나에 관계없이 거의 항상 적용되는 보편적 인간 특성
      • 프로덕트에 쏟는 두뇌 자원을 최소화하고 싶어함
        • 구현 방식을 이해하지 못하고, 만든 사람의 의도를 읽어낼 수 없음
        • 가능하면 문서를 읽지 않으려 하고, 프로덕트 자체에서 무엇을 할지 직관적으로 알고 싶어함
        • 한두 가지 애착 있는 예외를 빼면 애플리케이션/프레임워크의 전체 기능을 훑어보지 않고, 지금 필요한 도구만 빠르게 찾으려 함
      • 그 외의 한계
        • 멀티태스킹하며 자주 산만해짐 — 작은 “작업 단계” 단위로 진전하고, 못 끝낸 일을 다시 상기시켜주길 원함
        • 최단 경로를 택함 — 명확한 이득이 보이지 않으면 추가 작업을 하지 않으려 함
        • 위험을 회피하고 싶어함 — 차 주문이 이행되지 않았거나 기차를 놓치는 상황을 원치 않음
        • 잘 잊어버림 — 몇 달 뒤 다시 돌아왔을 때 중복된 단서나 리마인더가 필요할 수 있음
    • 망각과 바쁨을 생략한 before 스토리 예시: Eliana가 앞으로도 몇 번 더 주문할 걸 알기에 Favorites → + → “tiger” 검색 → 커스터마이징 재현까지 순순히 해내는 버전 → 이런 스토리는 실제 채택률이 기대에 못 미치는 프로덕트로 이어지기 쉬움
    • 신발 바꿔신기(Shoe-shifting)와 선택적 기억상실
      • 자신을 사용자의 입장에 놓고 그가 무엇을 알고 어떻게 행동할지 생각해보는 기법
      • 핵심은 프로덕트를 개발하며 얻은 지식(=금지된 지식)에 접근하는 순간을 스스로 감지하고, 그것을 모르는 것처럼 행동하는 것
      • 신규 사용자 리서치·테스트도 병행해야 하지만, 모든 결정에 완전한 데이터를 쓸 수는 없으므로 책상에 앉아 생각만으로도 신발을 바꿔 신어야 함
      • Alice의 사례: 위치 즐겨찾기라는 개념이 존재한다는 사실을 시나리오를 쓸 때 “잊고” 접근했기 때문에, 반복 방문 고객에게 위치를 즐겨찾기로 지정할지 묻는 팝업을 자연스럽게 배치할 수 있었음
    • 캐릭터 요약: 시나리오의 캐릭터 = 특정 목표와 수단을 가진 페르소나 + 프로덕트를 사용하려는 동기

시뮬레이션(플롯)

  • 시뮬레이션 = 시나리오의 플롯 — 캐릭터가 과제를 완수하기 위해 취하는 모든 행동을 상세하게 따라가는 것
  • 시뮬레이션의 목적: 설계자의 주의를 유저 경험에 영향을 주는 중요한 세부사항으로 이끄는 것
  • 좋은 시뮬레이션 = 모든 단계가 이전 단계에서 논리적으로 따라나오는 우아한 수학적 증명과 같음 — 설계에 아첨하지 않고 도전함
  • 나쁜 시뮬레이션 = 대충 얼버무리거나(handwave), 좋은 부분만 부각하는 자기 편의적 시뮬레이션
  • 애자일 방법론은 시나리오(“유저 스토리”)를 강조하지만, 시뮬레이션(행동의 전개) 측면은 상대적으로 소홀히 다룸
  • 노련한 엔지니어가 까다로운 설계를 논의할 때 “만약 X가 일어나면?”, “만약 사용자가 Y라면?” 같은 말을 한다면, 이는 형식적으로 글로 쓰지 않았을 뿐 이미 멘탈 시뮬레이션을 하고 있는 것
  • 많은 엔지니어는 뛰어난 패턴 매칭자 — 과거에 본 문제와 비슷한 것을 발견하면 예전 해법을 꺼내옴(예: Bob의 동료가 스타벅스 하트 버튼을 떠올린 것)
    • 시나리오 시뮬레이션은 이런 내면의 패턴 매칭 능력을 증폭시키는 도구로 활용할 것 — 주의를 가장 필요한 곳으로 집중시킨 뒤, 패턴 매칭이 마음껏 작동하게 둘 것
  • Alice의 가장 포괄적인 북극성 시나리오(1.8절 참고)를 다시 살펴보면, Recent Orders / 하트 버튼 / Favorites / 주문 / 위치 선택 / 위치 즐겨찾기 / 결제 흐름 / 홈 화면까지 다양한 기능과 그 상호작용을 모두 드러냄
    • 저자 본인도 이를 읽으며 “주문 흐름은? 팁은? 향후 음성 명령을 위해 팁 비율 선호도를 시스템이 기억해야 하나?” 같은 의문이 들었다고 언급 — Alice가 다루지 않았지만 다뤘어야 할 수도 있는 지점
  • 시뮬레이션 작성 팁
    • 만들려는 기능(예: 하트 버튼)에서 시작해 스스로를 세 방향으로 밀어붙일 것
      1. 이 순간 이전에 무슨 일이 있었나? 사용자는 어떻게 이 순간에 이르렀나? 무엇을 해야 할지 어떻게 알았나?
      2. 다음에는 무슨 일이 일어나는가?
      3. 특정 단계를 더 상세히 풀어써야 하는가?
    • 막히거나 내용을 완전히 지어내고 있다면, 사용자·지표·PM·타 팀과 대화해 더 확실한 근거를 마련할 것
    • 때로는 “이 기능을 만들지 말아야겠다”는 결론에 이를 수도 있고, 이 기능 하나만이 아니라 더 많은 작업이 필요하다거나 다른 팀과 협의가 필요하다는 결론에 이를 수도 있음 — 어느 쪽이든 유용한 배움
    • 전체 흐름에 집중하지 않는 팀은 조잡하고 일관성 없는 프로덕트를 만들게 됨 — 예: 딥링크로 로그인 화면에 도달했는데 로그인 후 원래의 딥링크를 잊어버리는 흔한 버그
    • 플롯 홀(plot hole) 찾기
      • 건너뛴 단계(skipped step): 대표적으로 발견 메커니즘 누락 — 예를 들어 Tea++ 매장들이 주문 앱의 존재를 전혀 알리지 않아, 앱스토어에서 스스로 검색할 만큼 궁금해했던 10%만 앱을 발견하는 경우
      • 엣지 케이스(edge case): 예를 들어 아침엔 A매장, 오후엔 B매장을 이용하는 사용자 — Alice라면 이를 별도의 스토리로 다루고 우선순위를 정해야 함

시나리오 활용법(How to Use Scenarios?)

  • 시나리오는 초기 탐색(Discover)부터 구현 명세까지 소프트웨어 개발의 모든 단계를 관통해야 함
단계 (해당 장) 시나리오 활용 방식
개발 · 2장 좋은 이름 짓기(naming)에 활용
개발 · 3장 에러 계층 구조 설계 및 실행 가능한 에러 메시지 작성에 활용
전달 · 4장 시나리오를 시나리오 테스트로 전환
전달 · 5장 출시 후 사용자 피드백 수집에 활용
발견 · 6장 초기 “고객 발견 인터뷰”에서 시나리오 수집
발견 · 7장 우선순위화된 시나리오 모음으로 제품 로드맵 구성, 더 상세한 유저 플로우 작성
정의 · 8장 상호작용 디자인(인터랙션 디자인) 세부화에 반영
정의 · 9장 처리량·데이터 일관성·지연시간 등 아키텍처 결정(예: 부하 시뮬레이션 생성)에 반영
  • Alice가 스토리 활용법을 설명하며 사용한 단어: 확신(conviction)
    • 올바른 시나리오를 만들었다는 자신감에서 오는 감정
    • 서로 다른 스토리가 일어날 상대적 가능성을 따져 트레이드오프를 해소하는 대화에 이 확신을 가져올 수 있음
    • 가장 중요한 시나리오를 검증하는 테스트를 갖췄기에 확신을 갖고 출시할 수 있음
    • 그 시나리오로부터 구현이 필요한 모든 컴포넌트까지 선을 그어 연결할 수 있음

1.12 요약

  • 시나리오는 시뮬레이션과 유저 프로필의 조합 — 완전한 시나리오로 사용자와 그들의 상황을 이해하고 프로덕트 선택을 이끌 것
  • 시뮬레이션은 무엇이 중요한지에 주의를 집중시키는 강력한 도구 — 인터페이스 너머 사용자의 전체 여정을 고려할 것
  • 페르소나는 프로덕트의 타겟 오디언스와 제대로 연결되어 있는지 확인해줌
  • 신발 바꿔신기와 선택적 기억상실을 사용자 공감을 위한 일상적 습관으로 연습할 것
  • 시나리오는 프로덕트 생애주기 전반에 걸쳐 사용할 것 — 설계가 구현에 가까워질수록 스토리를 더 상세하게 다듬을 것

1.13 예제

  • 전제: Wikipedia.org에서 일한다고 가정

[문제 1] Wikipedia를 사용하는 페르소나를 최소 두 가지 나열하고 각각을 설명. 해당 사용자들이 무엇을 중요하게 여기는지 고려할 것 [문제 2] Wikipedia는 특히 인기가 적은 언어권 문서의 품질을 개선하고자 함. 현재 사용자는 콘텐츠를 편집하려면 “Edit” 링크를 클릭해야 함. Wikipedia는 이 마찰을 없애는 매끄러운 인라인 편집 경험을 제공해야 할까? [문제 3] 모든 페이지 상단의 검색창을 설계한다고 가정. 별도의 검색 페이지를 로드하지 않고 가능성 높은 검색 결과를 인라인으로 보여주는 “타입어헤드(typeahead)” 기능을 1번의 페르소나 중 하나를 사용해 사용자가 이용하는 모습을 한 문단짜리 시나리오로 작성하고, 이를 통해 구현이 해결해야 할 문제를 부각할 것 [문제 4] Wikipedia의 “이 문서 지켜보기(Watch this page)” 기능(편집자가 문서 변경 알림을 구독하는 기능)을 설계한다고 가정. 1번의 페르소나 중 하나를 사용해, 사람들이 이 기능을 어떻게 사용할지 팀이 생각해볼 수 있도록 최소 두 개의 한 문단짜리 시나리오를 작성할 것

1.14 답안

[답안 1] 두 가지 페르소나

  • Reid the reader(독자 리드): 과제 지향적인 Wikipedia 독자. 사실적이고 편향 없는 정보, 특히 정치 관련 콘텐츠의 투명성을 중시. 콘텐츠를 빠르게 찾아 읽고 넘어가고 싶어함
  • Eddie the editor(편집자 에디): 자신의 전문 분야에서 문서를 작성하고 남의 콘텐츠도 검토하는 역사학자. 콘텐츠의 관리자(steward)라고 느껴 자신이 쓴 문서의 변경사항을 계속 확인하며 내용과 출처가 정확한지 점검. 커뮤니티의 일원으로 느끼고 싶어해, 협업자들의 건강함·포용성·평판을 중요하게 여김 [답안 2] 아니오 — Wikipedia 메인 문서 페이지의 1차 목표는 Reid 페르소나를 위한 것이어야 함(Reid가 Eddie보다 수적으로 훨씬 많으므로). Reid는 빠른 페이지 로딩을 원하고, 인라인 편집은 이를 저해할 가능성이 큼. 또한 무심코 누른 키 입력으로 편집이 시작되는 것도 원치 않음. 반면 Eddie는 상당히 헌신적인 페르소나이므로 약간의 추가 마찰에는 크게 개의치 않음 [답안 3] 검색 랭킹, 철자 교정, 타입어헤드 결과에 원하는 것이 하나도 없는 경우를 부각한 시나리오(요약)
  • Reid는 “Paradise Lost”의 저자를 알고 싶어함. 검색창에 철자를 잘못 알아 “Paradice Lost”라고 입력했는데, 상위 결과로 존 밀턴의 서사시 “Paradise Lost”가 표지 이미지와 함께 표시됨. 영국 고딕 메탈 밴드 등 다른 동명 결과도 몇 개 보이지만, 인기도 순으로 랭킹된 덕에 정확한 결과가 최상단에 위치함. 만약 그 밴드의 일곱 번째 앨범을 찾고 있었다면, 하단의 돋보기 아이콘(“paradice lost가 포함된 페이지 검색”)을 클릭하면 됨 [답안 4] 이해도 확인, 구독 해지, 알림 채널을 다룬 두 개의 시나리오(요약)
  • Eddie는 고대 메소포타미아 건축에 관한 문서를 열심히 편집했고 반응이 걱정됨. 편집 화면의 “Submit” 버튼 옆 “Watch this page” 체크박스를 체크하고 제출, 이후 몇 차례 더 편집. 체크박스는 이미 체크되어 있어 그대로 둠. 나중에 변경 알림 이메일을 받지만, 자신이 편집한 문단이 아닌 부분의 변경이라 무시함. 며칠 뒤 실제로 자신의 섹션이 편집되었다는 이메일을 받고 링크를 클릭, “이전/이후” 비교 화면에서 변경 부분으로 바로 이동해 확인함. 단순히 문장을 더 명확하게 다듬은 수정이라 만족함
  • 몇 달 뒤 Eddie는 관심 없는 편집에 대한 이메일이 너무 많다고 느낌(긴 문서라서). 이메일 하단의 “이 문서 알림 변경 또는 구독 해지”를 클릭하면 이전에 본 적 없던 체크박스 몇 개가 나타남. “내가 편집하지 않은 섹션의 알림 받기”와 “사소한 편집에 대한 알림 받기” 체크를 해제할 수 있음. 자신이 편집한 섹션 변경 알림은 계속 받고 싶어서 첫 번째 체크박스만 해제함

PART 1 개발

  • 다음 두 챕터의 목표: 제품 스킬을 활용해 뛰어난 판단력으로 실행하고, 사려 깊게 정제된 제품을 만드는 방법을 학습함
  • 다루는 핵심 사용자 시나리오: 발견(discovery), 이해(understanding), 사용(usage)
  • 다루는 실무 기법: 네이밍, 주석 작성, 좋은 에러 메시지로 에러 처리하기, 문서화, 테스트
  • 이 주제들은 코딩 소프트웨어 엔지니어의 일상 업무의 핵심이며, 프로덕트 사고(Product Thinking)를 연습할 수 있는 접근성 높고 빈번한 기반을 제공함

CHAPTER 2 제품 내 사용자 안내

Don Norman, 《The Design of Everyday Things》 인용구 요지: 기계와 그것을 설계하는 사람의 의무는 사람을 이해하는 것이며, 기계의 임의적이고 무의미한 지시를 이해하는 것은 사용자의 의무가 아님

  • 제품의 사용자는 이야기 속 주인공(hero)과 같음 - 주인공이 목표를 달성하도록 응원하는 것처럼 사용자를 대해야 함
  • 소설가와 달리 제품 디자이너의 목표는 사용자 여정을 쉽게 만드는 것
  • 시나리오에 따라 올바른 장소와 시점에 이정표를 제공해 사용자의 길을 안내함
  • Don Norman은 이런 이정표를 **시그니파이어(기표, Signifier)**라고 부름 - 인터페이스 안에서 기능이 무엇을 하는지 알려주는 단서
  • 시그니파이어의 예시:
    • 목적을 나타내는 이름이나 아이콘
    • 클릭 가능함을 알리는 버튼 주위의 경계 상자
    • 드롭다운 메뉴임을 암시하는 아래쪽을 향한 캐럿(caret)
    • 최대 하나만 선택 가능함을 나타내는 라디오 버튼
    • 여러 개 선택 가능함을 알리는 체크박스 집합
    • 대상이 웹 페이지임을 보여주고 관련성/이름을 텍스트로 명확히 하는 밑줄 친 파란색 하이퍼링크
    • 무엇을 해야 할지 제안하는 에러 메시지
  • 이번 챕터는 시그니파이어 개념과 사용자 여정에서의 역할을 탐구함
  • 두 가지 렌즈로 전개:
    1. 계층적 메뉴에 관한 사례 연구(시그니파이어를 깊이 파고들 기회)
    2. CODING PRACTICE 사이드바 - 함수/클래스 같은 코드 요소도 팀원을 사용자로 하는 미니 제품이라는 관점. 좋은 이름과 주석은 코드 리뷰/유지보수 효율을 높이고 프로덕트 사고 스킬 연습이 됨
  • 두 렌즈를 통해 사용자 여정의 세 단계(발견, 이해, 사용)를 탐구하며, 온톨로지, 계층적 디자인, 일관성-구체성 트레이드오프 등을 조명함
  • 마지막으로 여러 단계 간 트레이드오프가 필요한 경우 등 사용자 여정 전체를 확대해서 고찰함

2.1 사례 연구 소개

  • 2000년대 중반, MS Office 스위트(Word, Excel, PowerPoint)의 문제: 너무 복잡해서 사용자가 이해하거나 필요한 기능을 찾기 어려움
  • 2003년 워드: 툴바 31개 + 작업창 19개(드롭다운 메뉴는 별도)
  • [그림 2-1, 스크린샷] 2003년 워드의 어수선한 화면(Product Manager Jensen Harris의 발표 자료 인용)
  • 수백 명 사용자 인터뷰 결과: 기능을 발견하지 못하고, 소프트웨어에 대한 숙달감을 형성하지 못함. 수요가 많은 기능은 만들어졌지만 사용자가 찾지 못하는 문제 발생
  • Office 2007에서 리본(Ribbon) 메뉴 시스템 도입, 텍스트 선택 시 나타나는 컨텍스트 메뉴도 함께 도입
  • [그림 2-2, 스크린샷] 2025년 Windows용 워드의 리본
  • [그림 2-3, 스크린샷] 2025년 Windows용 워드의 컨텍스트 팝업(텍스트 선택 시 서식 도구 노출)
  • 리디자인 과정:
    1. 수천 개 기능의 사용 빈도 데이터 수집
    2. 계층적 메뉴 디자인으로 그룹화
    3. 기능을 새로운 방식으로 노출
  • 결과: 매우 복잡한 애플리케이션이 훨씬 더 접근하기 쉬워졌고, 지금까지도 유효함(시간의 시험을 통과함)

2.2 사용자 여정 속 시나리오

  • 사용자 여정은 보통 3가지 시나리오 유형을 거침
flowchart LR
    A[발견 Discovery] --> B[이해 Understanding] --> C[사용 Usage]
시나리오 정의
발견(Discovery) 사용자가 문제는 있지만 아직 해결 방법을 모르는 상태
이해(Understanding) 사용자가 기능을 발견했고, 그것이 무엇이며 어떻게 작동하는지 알고 싶어함
사용(Usage) 사용자가 기능을 의도된 목적대로 사용하고자 하며, 안전하지 않은 사용은 피하고자 함
  • 기능을 출시하는 담당자는 이 세 가지 모두에 대해 책임을 짐: 기능을 발견 가능하게 만들고, 이해를 돕고, 안전한 사용을 안내해야 함
  • 셋 중 하나라도 놓치면 기능이 의도한 임팩트를 내지 못함
  • 1장 논의와 연결: 사용자가 세 단계를 모두 플롯홀 없이 통과하는 그럴듯한 스토리를 말할 수 있어야 함. 3가지 시나리오 유형을 모두 다루는 것이 플롯홀 제거의 좋은 방법
  • 각 시나리오에 맞춰 더 세부적인 스토리를 계속 발전시켜야 함

2.3 발견 시나리오

  • 핵심 질문: 독자가 어떻게 당신의 추상화(기능)를 찾을 것인가?
  • 엔지니어가 자주 간과하지만 가장 중요할 수 있는 부분 - 존재를 모르는 사람에게 제품은 무용지물
  • 온라인 광고 산업이 거대한 이유 = 발견 문제를 해결하기 때문
  • 사용자가 유입되는 다양한 경로: 리본의 버튼, 검색창, 온라인 문서, 단축키 목록, 챗봇과의 상호작용 등
  • 사용자가 어떤 기능을 원한다고 판단되면, 그들의 모든 탐색 경로가 그 기능으로 이어져야 함 → 최대한 많은 시뮬레이션을 돌려 플롯홀이나 비약을 찾아야 함

제품 발견 지도(Product Discovery Mapping, PDM)

  • PDM: 사용자가 제품을 학습해나가는 여정을 그려보는 설계 기법
  • 예시 시나리오: 워드로 뉴스레터를 만들 때 필요한 레이아웃 기능(다단 레이아웃, 가로 모드, 이미지 삽입 등)을 사용자가 모두 찾아낼 수 있는지 점검
  • 대상 기능: Continuous Section Break(사용자가 이해하고 들어오지 않을 가능성이 높은 기능)

[그림 2-4, 스크린샷] 워드의 Layout 메뉴, Breaks 드롭다운 확장 화면

  • Page Breaks: Page, Column, Text Wrapping

  • Section Breaks: Next Page, Continuous

  • 페르소나 Yves(성당 행사 마케팅용으로 가끔 오피스를 쓰는 캐주얼 사용자)의 시나리오:

    1. 2단 레이아웃을 원하고, 뉴스레터 제목을 상단 중앙에 배치하고자 함
    2. Home 탭에서 제목을 입력하고 가운데 정렬
    3. Layout 탭의 Columns 위젯을 발견해 2단 선택 → 제목이 왼쪽 칼럼으로 밀림(원치 않은 결과)
    4. 실행 취소 후 눈에 띄는 Breaks 버튼 발견
    5. 드롭다운 설명을 읽고 Continuous Section Break 사용법을 학습

[그림 2-5] Yves의 Continuous Section Break 발견 PDM

flowchart TD
    A[시작] -->|Layout은 columns와 관련있음| B[Layout 탭]
    B -->|columns를 이해함| C[Columns 위젯]
    C -->|다음 옵션을 보고 조사함| D[Breaks 드롭다운]
    D -->|설명을 읽음| E[Continuous Section Break]
  • PDM의 3가지 핵심 구성요소:
    1. 각 맵/그래프는 특정 고객 페르소나가 제품을 탐색(미로 탐색과 유사)하는 것을 위해 설계됨
    2. 각 노드는 제품의 한 요소
    3. 각 엣지는 그 페르소나가 그 지식을 발견하게 되는 방법
  • 여러 흐름을 하나의 맵에 합치면 제품 일부를 더 빠르고 시각적으로 개념화할 수 있음
  • 맵을 만들고 검토하며 하는 사고와 협업이 핵심. 점검 질문:
    • 이름이 충분히 도움이 되는가?
    • 사용자에게 요구하는 비약이 괜찮은가?
    • 기능까지 도달하는 단계 수가 사용 빈도에 적절한가?
    • 함께 사용될 가능성이 높은 기능들이 함께 그룹화되어 있는가?

사용자 지식 활용

  • 1장 개념 연결: 페르소나는 수단(means)을 가짐 - 어떤 스킬과 지식을 갖고 오는가?
  • 제품이 제시하는 이름/개념의 집합과 그 관계를 **온톨로지(ontology)**라고 함
  • 온톨로지는 그래프이며, 사용자가 제품의 온톨로지를 잘 알수록 이해와 사용이 더 깊고 효과적임
  • 발견 가능하게 만드는 가장 쉬운 방법: 사용자의 온톨로지에 이미 있는 이름과 개념을 사용하는 것(바퀴를 재발명하지 않기)
  • Layout 메뉴 사례: Yves는 Column은 알았지만 Continuous Section Break에 대해서는 sections만 알고 있었음 → MS는 breaks 디자인에서 columns보다 훨씬 더 신경 써야 했음(친숙하지 않은 네이밍을 보완하기 위해 상세 설명과 다이어그램 제공)

CODING PRACTICE

  • 불투명한 코드네임 주의: 그런 이름은 사용자의 온톨로지에 없음. 분산 캐시 프레임워크를 찾을 때 사람들은 cache를 검색하지, 프로젝트 코드네임(예: 이전 캐시의 잿더미에서 부활했다는 의미의 Phoenix)을 미리 알지 못함. 브랜딩을 원한다면 SuperCache처럼 브랜딩과 발견 가능성을 절충한 이름 시도
  • 불투명 코드네임이 유용한 경우: 기밀성이 필요하거나, 충분히 독특하고 설명하기 어려워 직설적 이름이 오히려 오해를 부를 때. 다만 포기하는 발견 가능성을 인지하고 주석 등으로 다른 빵부스러기(breadcrumb)를 남겨야 함
  • 온톨로지는 사용자 페르소나에 따라 달라짐: 저자가 Stripe 재직 시, 회계사(비개발자)인 아버지가 결제/청구 API 문서를 읽으며 receivables(미수금) 같은 정확한 회계 용어를 찾다가 불만을 가짐
  • 저자의 설명: 의도적 결정 - 해당 API는 일반 프로그래머 대상이라 회계 수업을 들어본 적 없는 사람도 편하게 느낄 용어를 사용하려 했음
  • 다만 이는 Stripe가 회계사도 사용할 수 있게 하려면 추가 작업이 필요함을 시사

발견을 위한 여러 경로 제공

  • 언어에는 동의어와 관련 개념이 있지만, 온톨로지를 위해서는 보통 하나를 선택해야 함 → 다른 추측을 갖고 들어오는 사용자들을 위한 대안 경로가 필요
  • 예: 리본의 명령 검색창은 퍼지 매칭 지원. Margin 입력 시 Indent 관련 명령도 노출됨(인라인으로 명령과 상호작용 가능)

[그림 2-6, 스크린샷] margin 검색 결과

  • Find and Replace: Find "margin", Replace "margin" with...
  • Actions: Margins, Decrease Indent, Increase Indent

CODING PRACTICE

  • 주석으로 추상화를 찾을 수 있는 방법을 늘리기: 기본 클래스가 Plugin이라면 주석에 Extension 같은 동의어를 함께 적어두기

TIP:

  • 사용자마다 다른 수단에 맞춰, 기능을 찾을 수 있는 여러 방법을 제공할 것
  • 리디자인 이전 워드의 발견 문제: 수많은 메뉴/툴바/작업창 중 무엇이 적합한지 파악해야 했음. 툴바는 아이콘뿐이라 안내가 부족, 메뉴는 특별한 순서 없는 긴 목록이었음. 기능 분류가 자의적으로 느껴짐(예: ToolsFormat 메뉴 구분 근거, 읽기 작업인 FindEdit 메뉴에 있는 이유)
  • 리본 도입 이후 개선점:
    • 명령이 더 적은 수의 탭으로 그룹화되고 화면 공간이 더 많이 주어져, Yves가 Layout 탭을 스캔해 필요한 것을 찾기 쉬워짐
    • 흔하고 추천되는 명령(Break)은 더 크게 만들어지고 관련 명령 근처에 배치됨
    • BreakContinuous Section Break는 아이콘과 텍스트 둘 다로 표현되어 사용자마다 다른 단서로 찾을 수 있음
    • 검색창 존재 - section이라는 단어는 알지만 어느 메뉴에 있는지 모를 때 유용. 전통적 메뉴의 근본적 문제를 해결

[그림 2-7] Continuous Section Break를 발견하는 여러 경로

flowchart TD
    A[시작] -->|눈에 띄는 버튼을 조사함| B[Layout 탭]
    A -->|section 검색| F[Insert Page and Section Breaks 액션]
    B -->|columns를 이해함| C[Columns 위젯]
    C -->|다음 옵션을 보고 조사함| D[Breaks 드롭다운]
    D -->|설명을 읽고 이미지를 이해함| E[Continuous Section Break]
    F -->|설명을 읽고 이미지를 이해함| E
  • 사용자가 제품을 발견하는 흔한 경로들:
    • 검색하기
    • 챗봇에게 묻기
    • 앱을 시각적으로 스캔하거나 클릭하며 이름/시그니파이어 찾기
    • 스크린 리더(시각장애인용) 또는 음성 메뉴 이용
    • 사용법 가이드나 다른 문서 참고
  • 문서화에만 전적으로 의존하지 말 것. 소비자용 애플리케이션에서는 거의 의존하지 않게 될 가능성이 큼

복잡성의 점진적 노출

  • 거의 모든 성공적인 앱은 기능이 계속 늘어남(단순한 검색창으로 유명한 구글조차 이제 많은 탭/도구를 가짐)
  • 기능 증식에 대응하기 위해 디자이너는 **복잡성을 점진적으로 노출(gracefully reveal complexity)**함
  • 리본은 개선되었어도 여전히 명령과 타겟 페르소나가 많아 접근하기 쉬운 메뉴를 만들기 어려움 → MS는 컨텍스트 메뉴 개념 도입(적절할 때만 나타남)

[그림 2-8, 스크린샷] 리본의 컨텍스트 메뉴 - Shape Format 탭(도형을 클릭하지 않으면 UI를 어지럽히지 않음)

  • 리본은 기본 탭 9개로 압도적일 지경 - PDM으로 쉽게 발견되는 문제. Shape Format을 그래프 아래쪽으로 이동시키는 점진적 노출로 이 문제를 방지

[그림 2-9] Shape Format 탭 발견을 위한 PDM

flowchart TD
    R[리본 탭] --> Home[Home]
    R --> Draw[Draw]
    R --> Insert[Insert]
    R --> Design[Design]
    R --> Layout[Layout]
    R --> References[References]
    R --> Mailings[Mailings]
    R -->|메뉴 스캔| Review[Review]
    R -->|메뉴 스캔| View[View]
    Insert -->|메뉴 스캔| Shapes[Shapes]
    Shapes --> ShapeFormat[Shape Format 탭]
  • 워드는 텍스트를 하이라이트할 때 마우스 커서 옆에 나타나는 컨텍스트 메뉴로도 점진적 노출을 달성(굵게, 기울임 같은 흔한 명령 제공)
  • 이런 메뉴는 스스로를 알리므로(announce) 발견 가능성이 높고, 옵션이 적어 사용자가 쉽게 필요한 도구를 스캔할 수 있음

다중 페르소나 디자인

  • 앱이 복잡해지는 주요 이유: 성장하면서 다양한 목적을 가진 페르소나들을 끌어들이기 때문
  • MS Word가 서비스하는 다양한 페르소나: 챕터 내비게이션이 필요한 소설가, 차트/다이어그램을 원하는 마케터, 스크린 리더/받아쓰기가 필요한 시각장애인 등
  • 성공적인 앱은 보통 최소 2개의 다른 인터페이스를 가짐. 이상적으로는 각 인터페이스가 필요할 때 점진적으로 노출됨
  • 대부분의 앱은 파워 유저(전체의 5~20%)를 가짐 - 매우 활발하고 몰입된 사용자. 경험을 커스터마이즈할 강력한 기본 요소(primitives)를 원함(대개 사용에 추가 동기가 있어 제품을 철저히 알게 됨)
    • 예: 업무로 돈을 받는 워드 파워 유저는 매크로/변경 내용 추적 기능을 사용할 가능성이, 고등학생 영어 수업에서 시를 쓰는 학생보다 높음
  • 이중 인터페이스, 이중 페르소나 접근의 사례:
    • 위키피디아: 독자는 무시하기 쉽지만 기여자에게 유용한 작은 편집 버튼
    • 신규 사용자용 초대형 계정 만들기 + 기존 사용자용 작은 로그인 버튼
    • 일반 대중용 UI + IT 전문가용 커맨드 셸을 가진 현대 운영체제
    • TypeScript 같은 점진적 타입 언어: 간단한 스크립트에는 타입 어노테이션이 선택적이지만, 대규모 조직의 견고한 코드베이스에서는 필수가 될 수 있음
    • 비개발자에게는 쉽지만 개발자에게는 커스텀 코드를 허용하는 노코드/로우코드 프레임워크
    • 캐주얼 사용자용 버튼 클릭 + 습관적 파워 유저용 키보드 단축키
  • 디자이너로서 중요한 통찰: 여러 인터페이스를 가져야 할 시점에 도달했음을 깨닫는 것 - 각 페르소나를 독립적으로 최적화할 수 있게 되어 디자인 공간이 열리고 문제 해결 기회가 늘어남

알려지지 않은 미지를 알려진 미지로 전환하기

  • 지금까지는 사용자가 앱에 특정 기능이 있음을 알고 찾는 시간만 문제라고 가정함 → 그런데 기능의 존재 자체를 모른다면?
  • 특히 새롭거나 혁신적인 기능, 또는 도메인 온톨로지에 대한 확고한 이해가 부족한 신규 사용자에게 흔한 문제
    • 예: 숙련된 회계사에게는 익숙하지만 학부 회계 전공생에게는 낯선 엑셀의 부기 기능
  • 제품을 사용자가 여행하는 신비한 숲으로 여기고, 길을 찾을 수 있도록 빵부스러기 흔적을 남겨야 함
  • 예: 최근 여러 애플리케이션의 챗봇 위젯. MS Word 커서 옆 Draft with Copilot 컨텍스트 메뉴 - 저자는 그런 기능이 있는 줄 몰랐음. 챗봇이 정확히 무엇을 해주는지는 몰라도 최소한 탐색은 가능함 → 알려지지 않은 미지가 알려진 미지로 전환된 사례
  • 메뉴에서 무엇을 뺄지는 신중해야 함. 사용자는 누락된 명령을 다른 곳에서 찾을 수 있다는 것을 인식하지 못할 수 있음
    • 워드는 Layout 탭에 모든 Breaks를 제공하지만, Insert 탭에는 Page Break 명령만 배치
    • [그림 2-10, 스크린샷] Insert 탭 내 Page Break 명령(Cover Page, Blank Page, Page Break)
    • 위험: Layout 탭을 못 본 사용자가 Page Break를 유일한 옵션으로 오해할 수 있음
    • 트레이드오프: Page Break가 더 모호한 Break 명령 아래 묻히는 것보다 발견하기 쉬워짐

2.4 이해 시나리오

  • 사용자가 필요한 기능을 발견한 후에는 그것이 정확히 무엇을 하는지 더 자세히 이해해야 함
  • 가장 흔한 이해 촉진 방법: 훌륭한 이름 짓기(다음 섹션) → 이후 이름만으로 부족하거나 다른 학습 스타일에 대응할 다른 시그니파이어들 소개

이해 가능한 이름 짓기

  • 이름은 독자의 머릿속에 한두 가지 생각을 심을 기회
  • 발견 가능성뿐 아니라 이해도 도와야 함
  • 사용자는 기능이 무엇인지보다 나에게 어떻게 도움이 되는지를 알고 싶어함(종종 답이 같지만 항상 그런 것은 아님)
  • 제품 중심적 사고 = 시스템을 단순히 설명하는 것이 아니라 독자에게 미치는 영향을 생각하는 것
  • 이름을 짓는 가장 중요한 규칙: **지식의 저주(Curse of Knowledge)**를 피할 것

NOTE: 지식의 저주는 시스템 설계자가 자신의 시스템과 상호작용할 때 이해도가 낮은 사용자가 겪을 어려움을 예측하는 데 어려움을 겪게 만드는 인지 편향

  • 이 저주를 피하는 최선의 방법: 신발 바꿔신기(shoe-shifting, 역지사지). 타겟 페르소나의 기존 온톨로지를 고려하거나, 더 나아가 그 인구 집단에게 이름을 어떻게 해석할지 직접 물어보기

고전적 네이밍 조언 재해석

흔히 듣는 조언 사용자 중심으로 재해석한 조언
자기 설명적이 되라 모호함을 피할 것
일관성을 유지하라 일관성과 구체성의 트레이드오프를 저울질하라
약어와 축약어를 피할 것 타겟 페르소나에게 보편적인 약어/축약어만 사용할 것
간결하라 사용자가 알아야 할 것만 전달하라
모호함 피하기
  • 하나의 격언만 골라 모니터 옆에 붙인다면 모호함을 피할 것가 정답
  • 지식의 저주에 감염되지 않은 독자에게 기능 이름이 모호한 경우는 놀랍도록 흔함 → 방지법: 사용자 머릿속으로 신발 바꿔신기를 하고 이름의 다른 해석들을 브레인스토밍(또는 직접 물어보기)
  • 저자의 경험: Layout 사례 연구를 쓰며 Yves 역할을 하다가 Layout 탭의 Align 버튼에서 모호함으로 좌절함

[그림 2-11, 스크린샷] 워드 Layout 탭의 일부(Align 버튼 포함)

  • 저자는 뉴스레터 제목을 가운데 정렬하는 데 쓸 거라 예상했지만, 확장해보니(그림 2-12) 유용해 보이는 항목이 모두 비활성화 상태였음. 여러 시도를 했지만 소용없었음

[그림 2-12, 스크린샷] Align 드롭다운 메뉴

  • 비활성화 상태: Align Left/Center/Right, Align Top/Middle/Bottom, Distribute Horizontally/Vertically

  • 활성화 상태: Align to Page, Align to Margin, View Gridlines, Grid Settings...

  • 부분 활성화: Align Selected Objects

  • 교훈: Align이 일반적인 텍스트/이미지/차트보다 더 구체적인 대상(예: 선택된 개체들 간 정렬)을 가리킨다면 이름에서 그렇게 명시해야 함

CODING PRACTICE - 코드베이스에서 흔히 명확화가 필요한 모호함들

  • 역할과 관계: 부모/자식, 발신자/수신자처럼 역할이 다른 두 객체가 있을 때 “어느 쪽인가?“를 명확히 하는 것이 중요
  • 변수의 타입: 변수가 사용되는 모든 코드 라인에서 그것이 무엇인지 명확히 함
  • 측정 단위: 파운드와 뉴턴, 밀리시버트와 시버트를 혼동하지 않도록(화성 궤도선 손상이나 방사선 화상 유발 가능)
  • 품사: persist가 디스크에 상태를 쓰라는 요청을 나타내는 불리언인가, 실제로 그렇게 하는 함수인가? 전자라면 should_persist가 더 나음
일관성과 구체성의 트레이드오프
  • 일관된 네이밍은 사용자가 온톨로지의 개념을 한 번 배우면 반복 적용할 수 있게 도움. 새 기능 이름을 지을 때는 항상 기존 제품에서 선례를 찾아 맞춰야 함
  • 그러나 일관성은 종종 구체성과 트레이드오프 관계이며, 구체성이 종종 모호함을 줄이는 최선의 방법(Align 사례: 완벽하게 일관되지만 충분히 구체적이지 않았음)
  • 구체적인 것이 더 관용적(idiomatic)일 수도 있음: 스트리밍 음악 앱에서, 제품이 일관되게 Track이라는 단어를 쓰더라도 어색한 Track Lyrics보다 Song Lyrics가 나을 수 있음
  • 일관성이 걸림돌이 되는 또 다른 경우: 관련된 무언가가 오래 전에 잘못 이름 지어졌을 때, 일관성 유지와 처음부터 올바른 이름 선택 사이의 선택
  • 일부 엔지니어에게 일관성은 교조적(dogmatic)이 될 수 있음 - 사용자 관점을 고려하지 않고도 그것에 대해 추론할 수 있기 때문
  • 편향 대응법: 가장 이해하기 쉽고 구체적인 이름 아이디어와, 기존 제품과 가장 일관된 이름 아이디어를 모두 브레인스토밍 → 두 이름이 다르면 트레이드오프를 논의
  • 휴리스틱:
    • 의심스러울 때는 다른 이유가 없다면 일관성을 유지
    • 지식의 저주를 감안 - 제품 일부만 쓰는 신규 사용자는 일관성에 신경 쓸 만큼 충분히 보지 못할 수 있음
    • 상대적 사용자층 규모 고려 - 옛 이름은 소수 파워 유저 전용이거나 레거시일 수 있고, 새 제품은 더 큰 야망을 가질 수 있음
    • 다중 페르소나 애플리케이션에서는 각 페르소나가 경험하는 것이 중요 - 예: 엑셀이 하나의 수식에 회계사용/엔지니어용 두 이름을 제공하는 것도 정당할 수 있음. iOS/Android 지원 시 플랫폼 내 일관성은 중요하지만 플랫폼 간 일관성 우선순위는 낮음
타겟 페르소나에게 보편적인 약어/축약어만 사용하기
  • 약어는 아무도 글자의 의미를 추측하지 못하고 발견도 어려워 이해 시나리오에 특히 나쁨
  • 그러나 타겟 페르소나가 이미 익숙한 용어일 수 있음 - HTML을 풀어 쓸 필요는 없음. CPU는 소프트웨어 엔지니어 대상에 적합하지만 일반 대중에게는 processor가 나을 수 있음
  • 확실하지 않다면 타겟 사용자를 설문조사해 제안한 용어를 어떻게 해석하는지 확인
사용자가 알아야 할 것만 전달하기
  • 간결함(concise)은 엄밀히 짧지만 포괄적인을 의미함. 사용자는 단어 샐러드를 힘들어하지만 중요한 세부사항 누락도 원치 않음
  • 핵심 질문 재구성: 무엇에 대해 포괄적이어야 하는가? → 독자와 그들에게 도움이 될 것에 중점
  • 저자의 경험담: PHP Diplomacy라는 온라인 게임(고전 보드게임을 프로그래밍 언어 이름을 따서 포팅) - 비개발자는 이름에 혼란스러웠을 것이며, 실제로 함께 플레이한 사람들도 모두 소프트웨어 엔지니어였음
  • 신발 바꿔신기를 통해 무엇이 단순 구현 세부사항인지 파악 가능. 이름의 슬롯은 제한적이므로 관련 없는 것을 제거할 때마다 의미 있는 것을 표현할 여유가 늘어남
  • 예시: 장기 실행 작업 API 설계 - 사용자가 큐에 요청을 넣으면(enqueue) 시스템이 꺼내서 실행함. 블록 대기하거나 나중에 핸들로 결과를 가져올 수 있음
// 비동기 요청 API를 자연스럽게 명명하면
async function enqueueRequest(payload: RequestPayload): Promise<Handle> {
  // ...
}

// 그런데 큐에 넣고 결과까지 기다리는 함수는?
async function enqueueAndWaitForRequest(payload: RequestPayload): Promise<Result> {
  // 이름이 점점 어색해짐 -> "이 이름의 어느 부분을 독자가 알아야 하는가?"를 자문
}
  • 가장 관련 있는 사실: 비동기라는 것(응답이 지연될 수 있음). enqueue라는 단어는 필요 이상으로 구체적임(구현 세부사항)
  • queue라는 구현 세부사항을 제거하면 아래처럼 간결하고 발견 가능한 이름을 얻을 수 있음
async function startRequest(payload: RequestPayload): Promise<Handle> {
  // 비동기 버전
}

async function executeRequest(payload: RequestPayload): Promise<Result> {
  // 동기(결과를 기다리는) 버전
}

CODING PRACTICE

  • 체크인되는 코드에 지나치게 축약된 함수/변수 이름을 넣고 싶다면, 파이썬 창시자 Guido van Rossum의 말을 기억할 것: 코드는 쓰이는 것보다 훨씬 더 자주 읽힘
  • 함수 이름이 읽히고 이해되는 시나리오(코드 리뷰, 디버깅, 코드베이스 학습, 수정 위치 검색)가 타이핑되는 시나리오(새 기능 추가, 테스트 작성)보다 훨씬 빈번함. 후자는 자동완성의 혜택을 받음
  • 독자는 코드베이스 수명 동안 계속 찾아옴. 지속적 가치를 지닐 만한 것을 만든다면 명확한 이름으로 정당하게 대우할 것

중복된 설명 제공하기

  • 발견 가능하면서 모호하지 않은 이해를 돕는 이름을 짓는 것은 도전 과제이며, 완벽하게 전달할 방법이 없을 때도 있음 → 의도를 전달하는 중복 메커니즘 제공이 중요
  • 워드 리본은 아이콘을 효과적으로 활용해 기능을 전달함

[그림 2-13, 스크린샷] Home 탭의 서식 명령 아이콘들 - multilevel list, justify 같은 단어만으로는 안 되는 방식으로 이해를 도움(글머리 기호, 번호 매기기, 다단계 목록, 들여쓰기, 정렬, 줄 간격, 음영, 테두리 등)

이미지와 단어 결합하기
  • 이미지는 단어가 전달하려는 것을 시각적으로 보여줄 수 있음(그림 2-4의 섹션 나누기 설명 참고)
  • 모든 사용자가 제품 언어의 원어민은 아님을 기억 - Column은 영어 화자에게 기본 개념이지만 다른 언어 화자의 어휘에는 없을 수 있음
WYSIWYG
  • WYSIWYG(What You See Is What You Get): 사용자는 이해할 필요가 없음 - 행동의 효과가 즉시 표시되기 때문
  • Windows용 MS Office는 WYSIWYG 에디터일 뿐 아니라, 현재 문서에 스타일 전환 효과를 보여주는 실시간 미리보기도 제공함
다르게 표현하기
  • 툴팁과 문서에서 기능을 설명할 때 동의어나 다른 표현 사용
  • 다른 용어로 개념을 다르게 표현하면 사용자가 의도된 의미를 삼각측량하고 확신을 얻는 데 도움이 됨
항상 사용자에게 맥락을 상기시키기
  • 사람들은 단기 기억이 작아 자신이 어디에 있었는지 잊을 수 있음
  • 예: 전자상거래 구매 워크플로 중간에 이탈했다가 돌아온 사용자를 위해, 각 화면에서 무엇을 구매하는 중인지 상기시켜야 함

CODING PRACTICE

  • 중복성은 코드, 문서 등 정보 밀도가 높은 상황에서 유용함. 제한된 작업 기억을 가진 인간을 사용자 시나리오로 생각하면 명확해짐
  • 아래 코드에서 버그를 찾을 수 있는가?

원문(Python):

def email_lunch_invitation(sending_user, recipient_user, message):
    recipient_organization = recipient_user.get_organization()
    title = f"{sending_user.name} from {recipient_organization} " \
        "has sent you an invitation to lunch!"
    send_email(
        sending_user.email_address, recipient_user.email_address, title, message)

TypeScript 변환:

function emailLunchInvitation(
  sendingUser: User,
  recipientUser: User,
  message: string
): void {
  const recipientOrganization = recipientUser.getOrganization();
  const title =
    `${sendingUser.name} from ${recipientOrganization} ` +
    `has sent you an invitation to lunch!`;
  sendEmail(sendingUser.emailAddress, recipientUser.emailAddress, title, message);
}
  • 버그: titlesendingUser의 소속 조직을 보여줘야 자연스러운데("X from Y"), 실제로는 recipientUser의 조직을 가져와 사용함
  • 만약 변수명이 recipientOrganization이 아니라 그냥 organization이었다면 이 오용을 훨씬 알아채기 어려웠을 것 → 명시적(중복적)인 이름이 버그 발견을 도움
  • 추가 중복성이 도움이 되는 시나리오:
    • 독자가 품질을 점검하며 코드를 리뷰하는 경우
    • 함수 작성자가 실수로 변수를 잘못 사용할 수 있는 경우(위 예시)
    • 프로그래머가 브레이크포인트, 정의로 이동, 모든 참조 찾기, 디버깅 콜스택, 린트 규칙을 통해 그 라인에 도달했고 함수 전체를 읽지 않은 경우
  • 한 줄의 코드에 대해 알아야 할 모든 것이 그 줄에 있다면 이상적

2.5 사용 시나리오

  • 독자의 여정 끝, 실제 행동을 취하기 시작할 때 더 큰 위험이 존재함(안전 문제) - 8장에서 어포던스(affordance) 개념으로 더 폭넓게 다룰 예정
  • 좋은 이름은 사용자가 위험을 인지하지 못한 채로도 궤도를 벗어나지 않고 직관적으로 올바른 행동을 하도록 유도할 수 있음
  • 극단적 사례: 2010년대 초 한 웹 회사에서, 모든 웹 트래픽 서빙 머신에 복사되는 특수 설정 변수가 존재. 사용자에게 전송되기 직전 모든 아웃바운드 HTML에 찾기/바꾸기로 적용되는 정규식을 담고 있었음
  • 긴급 패치(예: 문제 있는 HTML 태그 제거) 시, 엔지니어가 이 변수에 정규식을 추가하면 전체 서버군에 즉시 배포됨
  • 위험성: 정규식으로 변경된 HTML이 파싱 오류를 일으키면 전체 사이트가 작동 중단될 수 있었음 → 무엇을 하는지가 아니라 오용 시 벌어질 일을 따서 아래처럼 명명됨
// 매우 위험한 긴급 패치용 설정 변수 - 오용 시 결과를 이름에 담은 사례
let TAKE_DOWN_THE_SITE: RegExp | null = null;
// 모든 아웃바운드 HTML에 find/replace로 즉시 적용되는 긴급 패치용 정규식
  • 극단적이지만, 저자는 이 이름이 안전 문제에 대한 인식을 보여준다는 점에서 마지못해 감탄함
  • 더 평범한 위험 신호 방법: 고급(advanced) 메뉴 아래 배치 - 발견 여정을 길게 만들어 찾는 사용자를 제한하고, 예상치 못한 효과가 있을 수 있음을 시사
    • 예: MS Office는 Developer 탭을 리본에 표시하기 전 옵트인을 요구 → 매크로/VB 스크립트 같은 도구를 혜택을 볼 가능성이 높은 페르소나로 한정
  • 위험을 알려야 하는지 판단하는 법: 사용자가 잘못된 방향으로 가는 시뮬레이션을 만들고, 이름이 그런 스토리를 어떻게 비현실적으로 만들 수 있는지 살펴보기
  • 위험한 기능을 숨기는 것의 반대: 사용자가 선택을 하도록 강제해 개념에 주의를 끄는 것
    • 예: Layout 메뉴에서 Break 선택 시 워드는 가장 흔한 명령인 Page Break를 기본값으로 삼지 않음. 어떤 종류인지 선택을 강제해 옵션에 대해 교육하고 문서 손상을 방지

CODING PRACTICE

  • 사용자가 인식하지 못할 수도 있는 선택을 해야 할 때는 매개변수를 필수로 만들 것
  • 추상화에 대한 주석에 빵부스러기를 남길 것 - 무언가를 수행하는 두 가지 방법이 있고 사용자가 그중 하나를 찾았다면, 특히 그 대안이 더 권장되는 경우 사용자가 모를 수도 있는 대안을 가리켜 줄 것

2.6 전체 사용자 여정 최적화

  • TAKE_DOWN_THE_SITE 사례처럼, 발견 가능성/이해 가능성을 희생해 사용성을 개선함으로써 발견/이해/사용 최적화 사이에 트레이드오프가 발생하는 경우가 있음
  • 사용자 여정을 명확히 시뮬레이션하면 이런 트레이드오프를 만드는 데 도움이 됨
  • 질문: 모호한 약어를 만들지 말라는 조언이 그렇게 좋다면, 왜 Unix/Linux는 ls, cd 같은 인기 있지만 모호한 명령어를 갖고 있는가?
  • 답: 완전히 나쁜 것은 아님 - 흔한 명령어는 읽히기보다 훨씬 더 많이 타이핑되기 때문에 여전히 적절할 수 있음. 한 번(잊으면 두 번) 배우지만, 하루에 여러 번 list_files가 아니라 ls라고 타이핑하고 싶어함
  • 게다가 습관적/기술적 사용자 대상 - 사용 시나리오가 발견과 이해를 압도하므로 일반 규칙이 무시됨
  • 반대로 마케팅과 검색 엔진 최적화를 위한 디자인에서는 발견 가능성을 우선해야 할 수도 있음
    • 예: 탄산수 브랜드명 Liquid Death - 50%만 정확하고 정밀하지 않지만 확실히 스스로를 마케팅함. 이름이 쇼핑객의 눈길을 끌고, 그다음 부제(subtext)로 실제 정체를 확인시킴

TIP: 기능에 대해 발견, 이해, 사용의 상대적 중요도를 결정할 것

2.7 시그니파이어의 한계

  • 모든 문제가 시그니파이어로 해결되지는 않지만, 미리 그것을 항상 알 수는 없음. 완벽한 이름을 찾으려다, 타겟 페르소나가 알아야 할 것을 전달하는 좋은 이름이 존재하지 않음을 뒤늦게 발견할 수도 있음(너무 복잡하거나 안전 문제가 있는 경우)
  • 예: 브라우저 쿠키와 관련 팝업들(데이터 기록 경고) - 비기술적 사용자에게 정확히 무슨 일이 일어나는지, 얼마나 침해적인지 전달하기 어려움
  • 즉, 웹사이트가 쿠키에 대해 소통하도록 요구한 입법과 이를 준수하려는 웹사이트들은, 사실 시그니파이어로는 해결되지 않는 더 근본적인 설계 과제를 시그니파이어로 해결하려 시도하고 있는 것
  • 필수 쿠키와 마케팅 목적 쿠키를 구분하려는 시도가 있었지만, 사용자는 여전히 쿠키 온톨로지에 대한 명확한 이해가 부족 → 결과적으로 대부분 모든 쿠키를 꺼버림(일부는 유익하거나 개인 비용 없이 인터넷 경제를 지원할 수 있음에도)
  • 더 근본적인 문제: 쿠키는 애초에 사용자 대면 기능이 아닌 저수준 프로그래밍 추상화로 의도됨. 더 세분화되고 사용자가 이해할 수 있는 카테고리의 더 나은 추상화가 제안되었으나, 집단행동 문제 등으로 2025년 기준 대체로 미해결 상태
  • 제품을 이름 짓고 조직화하려는 시도는 제품 주기 초반에 하기 좋은 일 - 전체 사용자 여정에 주의를 기울이게 만들고 유익한 디자인 통찰로 이어짐(7장, 8장에서 제품 디자인 심화 예정)

CODING PRACTICE 추상화가 어색하거나 안전하지 않은데 고칠 수 없다면 숨기려 하지 말 것 - 이름을 못생기게 두어 주의와 검토를 끌게 할 것. 코드 리뷰어가 더 나은 아이디어를 가질 수도, 나중에 누군가 개선할 수도 있음. 저자는 한 번 _do_not_use_or_you_will_be_fired라고 반농담으로 이름 붙여진 프라이빗 클래스 변수를 본 적이 있으며, 이에 대해 마지못한 존경심을 갖게 됨

2.8 요약

  • 사용자가 제품을 발견, 이해, 사용하는 과정을 함께 따라감
  • 사이드바에서는 코드 네이밍과 코딩 인터페이스 구축에서 제품 스킬을 연습하는 일상적 의사결정에 초점을 맞춤
단계 핵심 조언
발견 사용자의 온톨로지에 이미 있거나 다른 이름과 일관된 이름을 선택. 사용자가 제품을 탐색하도록 돕는 시그니파이어를 제공하고, 더 복잡한 기능은 점진적으로 노출. 필요하면 Product Discovery Map으로 사용자 여정을 시각화
이해 무엇보다 모호함을 피하면서, 각 유형의 사용자에게 일관된 경험을 제공. 제품을 이해할 수 있는 중복된 진입 경로를 제공해 청중의 다양성과 관점을 포용
사용 사람들이 기능을 사용할 때 발생 가능한 함정과 위험 지대를 강조하기 위해 이름을 활용
다중 페르소나 각 페르소나에게 다른 온톨로지와 발견 경로를 제공하는 것을 두려워하지 말 것
  • 이 챕터는 개발(Develop) 단계에 속하지만, 제품 온톨로지에서 가장 중요한 개념의 네이밍과 조직화는 더 일찍 이루어져야 함을 기억할 것 - 전체 디자인 프로세스에 대한 귀중한 통찰을 제공하기 때문

2.9 예제

  • 연습 방식: 무언가를 foobar로 명명하고 더 나은 이름을 고르는 것이 목표(foo는 정보량이 0이므로, foo 거리(foo distance)를 최대화하는 것으로 생각). “정답”은 없지만 발견/이해/사용 시나리오를 고려할 것

[문제 1]

  • 데이터베이스에서 특정 테이블의 데이터 변경 이벤트를 구독하는 기능. DataChangeSubscription을 서브클래싱하고 등록하면, 트랜잭션 커밋 후 매번 콜백이 실행됨

원문(Python):

class DataChangeSubscription(ABC):
    # subscriptions apply to a certain database table or collection.
    @abstractmethod
    def foo() -> TableType:
        ...

    @abstractmethod
    def bar(old_record, changes: Dict[str, Any]) -> None:
        ...

TypeScript 변환:

abstract class DataChangeSubscription {
  // subscriptions apply to a certain database table or collection.
  abstract foo(): TableType;
  abstract bar(oldRecord: Record, changes: Record<string, unknown>): void;
}
  • 질문: DataChangeSubscription을 서브클래싱할 때 구현해야 하는 이 두 메서드를 뭐라고 부를 것인가?

[문제 2]

  • 맥주 애호가용 리뷰 앱. 맥주를 클릭하면 인상을 입력할 수 있는 자유 형식 텍스트 필드 Foo가 표시됨. 대신 뭐라고 부를 것인가?

[문제 3]

  • 신규 사용자의 설문/가입 흐름 최적화를 위해, 완료까지 걸리는 시간을 추적하는 집계 지표 foo를 추적하고 싶음. 뭐라고 이름 붙일 것인가?

[문제 4]

  • 소규모 소셜 네트워크에서 최근 커밋한 변경이 성능을 저하시킴. 아래 함수를 호출했는데, 실제로는 병렬로 여러 번 호출되어 예상보다(예상치 못한 계산과 프라이버시 체크 포함) 비용이 많이 들었음. 최적화할 시간은 없음

원문(Python):

mutual_friends = await current_user.getMutualFriends(friend_user)

TypeScript 변환:

const mutualFriends = await currentUser.getMutualFriends(friendUser);
  • 질문: 향후 다른 사람들이 같은 실수를 하지 않도록 이름을 바꿀 방법이 있는가?

2.10 답안

[답안 1]

  • footable_type도 무난하지만, 더 명확하려면 table_type_filter 고려(테이블 타입이 어떻게 사용되는지 명확해짐)
  • baron_record_changed 시도
    • on_ 접두사: 이벤트 핸들러에 널리 쓰이는 명명법
    • record(단수): 무엇이 변경되었는지 명확
    • changed(과거형): 커밋이 이미 발생했음을 명확히 함
    • 나중에 커밋 전 콜백을 추가한다면 on_record_changing으로 명명 가능
    • 클래스 이름의 Changed와 일관성을 위해 changed를 선택
abstract class DataChangeSubscription {
  abstract tableTypeFilter(): TableType;
  abstract onRecordChanged(oldRecord: Record, changes: Record<string, unknown>): void;
}

[답안 2]

  • 약간의 사용자 조사(또는 챗봇에게 질문) 후, 맥주 애호가들이 맥주 설명에 tasting notes(테이스팅 노트)라는 용어를 쓴다는 것을 발견할 수 있음
  • description이나 review 같은 일반적 용어도 가능하지만, Tasting Notes는 타겟 페르소나가 aroma(향), mouthfeel(질감) 같은 표현을 쓰며 열정적으로 서술하게 만들 가능성이 높음 - 이것이 원하는 효과

[답안 3]

  • 팀 대시보드에서 그래프를 읽는 팀원의 시나리오를 생각할 것
  • 예상 질문 1: 단위가 무엇인가(밀리초/초/분)?
  • 예상 질문 2: 어떤 지표를 추적하는가(평균/중앙값/p90)?
  • 제안 이름: user_signup_seconds_to_complete.p50

[답안 4]

  • getMutualFriends는 비용이 많이 든다는 것을 어떻게든 나타내야 함
  • 단순히 computeMutualFriends로 바꾸는 것만으로도 비용을 생각하게 만들기에 충분했을 수 있음
  • 이름을 나중에 쉽게 바꿀 수 있다면 computeMutualFriends_Expensive처럼 더 노골적으로 명명 가능 - 미래에 이 노골적인 못생김이 누군가로 하여금 캐싱을 하거나 더 저렴하게 만들도록 영감을 줄 수 있음
// 개선 전
const mutualFriends = await currentUser.getMutualFriends(friendUser);

// 개선 후 (비용을 이름에 드러냄)
const mutualFriends = await currentUser.computeMutualFriends(friendUser);

// 더 노골적으로 (임시 경고성 네이밍)
const mutualFriends = await currentUser.computeMutualFriendsExpensive(friendUser);

CHAPTER 3 에러와 경고

  • 챕터 도입부: 영화 오피스 스페이스(1999)의 마이클 볼튼이 고장난 프린터 앞에서 외친 대사 “PC Load Letter?“로 시작 -> 기술 업계의 형편없는 에러 메시지를 풍자
  • HP를 위한 변호: 과거 LaserJet 프린터는 화면에 표시 가능한 문자 수가 제한적이었음. 오늘날은 고해상도 화면으로 훨씬 많은 텍스트를 표시할 수 있음 -> 도움이 되는 단어를 얼마든지 넣을 수 있음
  • 문제 제기
    • 소비자는 이해하지 못하는 진단 에러/경고에 정기적으로 노출됨
    • 전문가는 혼란스러운 메시지에 시간을 낭비하며 정작 해야 할 일을 못함
    • 사용자는 “flight risk”(이탈 위험군) - 작은 마찰에도 제품에서 이탈할 수 있음
    • 그러나 엔지니어는 종종 에러와 에러 메시지를 단순한 “엣지 케이스”로 취급하고 최대한 빨리 구현만 하고 넘어가려 함 - 이는 핵심 기술이자 제품 차별화 요소로 다뤄져야 함에도 그렇지 못한 경우가 많음
  • LaTeX 사례 (프로그래밍 관련 재미있는 에러 예시)
    • LaTeX: 아름답게 렌더링된 수학/과학 논문 작성에 유용한 문서 타이프세팅 언어/시스템
    • 다음처럼 입력하면 (\\는 줄바꿈 토큰)
This is some text. \\
This is at the end of a block of text. \\
This is the start of a new paragraph
  • 다음과 같은 경고를 받게 됨
Underfull \hbox (badness 10000) in paragraph on line 2.
  • 해석: LaTeX은 빈 줄 앞에 중복된 줄바꿈(\\)을 2번째 줄에서 제거하기를 원함
  • 이 메시지는 구현자(implementer) 입장에서는 “상향식(bottom-up)“으로 이해가 되지만 사용자에게는 매우 알아보기 어려움 - 코드 구조상 고치기도 어려울 것으로 추정됨
  • 저자가 이후 제시하겠다고 예고한 두 가지 스킬
    • 구현자가 이해하는 것과 사용자가 이해하는 것 사이의 간극을 메우는 방법
    • 이를 달성하기 위해 코드를 조직하는 방법
  • 통계: LaTeX은 인기 프로그래밍 언어 순위 40위에 불과하지만, tex.stackexchange.com에서 가장 인기있는 “underfull hbox” 질문의 조회수는 35만 회 이상
    • 조회당 평균 3분씩 헤맨다고 가정하면, 더 나은 경고 메시지(또는 이 불필요한 줄바꿈을 그냥 허용)만으로도 인류 전체적으로 약 2만 시간(계속 증가 중)을 절약할 수 있었을 것으로 추정
  • 유쾌한 진단의 사례: 구글의 “다음을 찾으셨나요(Did you mean)” 기능
    • 사용자가 “compture”라는 오타를 검색하면 구글은 검색 결과 상단에 compture. Did you mean: computer?를 표시
    • 이 메시지가 달성하는 두 가지 핵심 사항
      • 사용자가 무엇을 검색했는지 그대로 보여줌
      • 무엇을 해야 할지 제안함 (가장 흔한 철자 교정으로 연결되는 편리한 링크 제공)
    • 첫 번째 기능은 구현이 사소했지만(그래도 배려심 있는 설계) - 사용자가 딴짓하다 브라우저 탭으로 돌아오는 시나리오까지 배려한 것
    • 두 번째 기능은 구글이 수천만 달러를 들여 구축/유지했을 것으로 추정 - 방대한 데이터베이스와 (의식이 있을 법한) 인공지능을 활용해 사용자의 의도를 파악 - 이 모든 것을 “엣지 케이스” 하나를 위해 투자한 것

3.1 진단의 가치

  • 잘 구조화된 진단(구체적이고 유용한 메시지)을 만드는 것은 시간을 투자할 만한 매우 가치있고 레버리지가 큰 작업
  • 복잡하고 개방형(open-ended) 입력을 다루는 다수의 애플리케이션/플랫폼에서는 진단이 주요 인터페이스임 - 사용자 시간의 대부분이 에러 처리와 다음 단계로 넘어가는 데 소요됨
  • 일상적 사례
    • 전자 양식(form) 작성: 누락되거나 잘못된 입력에 대한 안내를 받는 과정 자체
    • 코딩 시간: 최소 절반은 에러와 린트 규칙 처리에 소요
    • 워드프로세서 사용: 밑줄 그어진 텍스트를 보고 교정/재구성을 요청받는 지속적인 프로세스로 변함
  • 소프트웨어 설계 시 에러가 간과되기 쉬운 이유: 에러는 스크린샷, 마케팅 자료, API 메서드 목록에 잘 등장하지 않아 눈에 안 보이면 마음에서도 멀어지기 쉬움
  • 자율 에이전트(autonomous agent)가 이 문제를 극명하게 조명함
    • 에이전트는 자신의 행동으로 생긴 에러 메시지를 정기적으로 받고 그 메시지를 바탕으로 실수를 수정하도록 지시받음
    • 메시지가 충분히 유용하지 않으면 에이전트는 작업에 실패함
    • 시행착오 과정은 느리고 비용이 큼
    • 에이전트는 사용량 기반으로 과금되므로 이 비용은 직접적으로 측정됨

NOTE: 진단은 제품에서 가장 중요한 인터페이스일 수 있음.

3.2 진단 시나리오

  • 에러, 경고, 관련 메시지를 설계할 때는 폭넓은 범위의 시나리오를 고려하는 것이 필수
    • 엣지 케이스 식별에서 시작
    • 개발자가 어떻게 자동으로 반응할 수 있는지 이해
    • 최종 사용자가 어떻게 이해하고 행동할지 이해하는 데까지 이어짐
  • 사용자의 지식 수준을 이해하는 능력, 사용자 스토리를 생성하는 능력, 사용자 상호작용을 시뮬레이션하는 능력을 향상시키면 진단의 품질도 함께 향상됨
  • 대상별 제공 원칙
    • 최종 사용자: 맥락적(contextual)이고 실행 가능한(actionable) 에러 제공
    • 개발자: 수신자가 우아하게 복구(recover gracefully)할 수 있도록 에러 타입, 코드, 메타데이터를 신중하게 선택
  • 이번 챕터에서 다루는 것 (신선하고 유용한 경고/에러를 만드는 방법)
    • 시나리오 이해 - 에러로부터 도움을 받을 페르소나와 그들의 상황 파악
    • 사용자가 에러를 이해할 수 있도록 충분한 맥락 제공
    • 문제에 대해 무엇을 해야 하는지 제안하는 실행 가능한 에러 메시지 제공
    • 상위 개발자가 자신의 사용자를 잘 서비스할 수 있도록 에러 코드와 타입을 신중히 선택
    • 사용자가 무엇을 하려는지에 대한 전체 맥락을 가지고 메시지를 작성할 수 있도록 API 또는 UI 레이어에서 에러를 발생(raise)
    • Shift Left (왼쪽으로 이동) - 사용자의 속도를 높이고 나쁜 일이 생기기 전에 가능한 한 일찍 에러를 발생
  • 참고: 애초에 어떤 에러를 체크해야 하는지(엣지 케이스 나열법)는 8장에서 다룸 - 이번 챕터는 이미 어떤 에러가 있는지 아는 상태에서 잘 만드는 방법에 집중

3.3 에러 시나리오 분류

  • 에러를 작성할 때 마주치는 핵심 선택지
    • 사용자 대면 선택: 에러 메시지는 무엇으로 할 것인가
    • 개발자 관련 선택 (에러를 잡아 자동으로 대응하기 위함): 에러의 클래스/코드는 무엇인가, 문제를 정확히 짚기 위해 어떤 메타데이터가 필요한가
  • 따라서 거의 모든 애플리케이션/플랫폼에서 에러를 작성할 때는 두 카테고리의 사용자 시나리오를 함께 고려해야 함: 인간(사람) 시나리오와 프로그래머 시나리오
  • 개발자 시나리오는 더 세분화됨: 같은 코드베이스에서 일하는 팀 동료와 소통하는지, 다른 팀/회사 사람과 소통하는지가 중요 - 특히 상위 개발자가 에러를 잡아 조치해야 하는 API/서비스를 구축할 때 더욱 중요
  • 첫 단계: 메시지를 올바른 상황의 올바른 대상에게 전달하는 것 - 이를 위해 먼저 에러의 카테고리를 결정해야 함
  • 대부분의 경우를 커버하는 5가지 에러 카테고리 (표 3-1)

표 3-1. 에러 카테고리

에러 유형 예시 시나리오
System (시스템 에러) 결제 프로세서 다운, 타임아웃, 부하 상황에서의 일시적 에러
Assertion (단언) “이 지역 변수는 절대 null이면 안 된다” 같은 것
Invalid Developer Argument (개발자의 잘못된 인자) 문자열이 필요한데 정수를 받음
Invalid User Argument (사용자의 잘못된 인자) 사용자가 잘못된 신용카드 번호를 입력
Preconditions Not Met (전제조건 미충족) 사용자가 특정 리소스에 접근 권한이 없거나 로그인하지 않음
  • 어떤 에러든 먼저 이 카테고리로 정신적으로 분류하는 것부터 시작할 것 - 대화 상대(자신의 팀 / 다른 개발자 / 사용자)와 언제 수정 가능한지(런타임 / 개발 시점)에 대한 큰 단서를 제공하며, 올바른 어휘로 작성하고 유용한 액션을 제안하는 데 도움됨 (표 3-2)

표 3-2. 에러 시나리오 카테고리의 시점과 대상

에러 유형 해결 시점 일반적인 해결 방법
System 런타임 최종 사용자가 나중에 재시도
User’s Invalid Arg 런타임 최종 사용자가 입력을 수정한 후 재시도
Preconditions Not Met 런타임 최종 사용자가 다른 문제를 해결
Assertion 개발 시점 (자신의 팀) 엔지니어가 알림을 받음
Dev’s Invalid Arg 개발 시점 (해당 함수를 사용하는 개발자) 개발자가 자신의 코드를 수정해야 함
  • 이 다섯 가지 시나리오는 서로 극적으로 다른 전략을 요구함
    • Assertion이 프로덕션에서 발동되면 보통 치명적: 저자가 예상하지 못한 상태 -> 예측 불가능한 동작 (크래시 또는 형편없는 에러 메시지), 드물게 데이터 손상 같은 더 나쁜 결과로 이어짐
    • 일부 프로그래밍 언어는 실행 최적화를 위해 프로덕션 빌드에서 assertion을 제거함 - 따라서 assertion에 중요한(load-bearing) 로직을 의존하면 안 됨
    • 어떤 경우에도 최종 사용자 페르소나가 assertion과 성공적으로 상호작용하는 것은 기대되지 않음
  • 일부 애플리케이션에서는 모든 최종 사용자가 동일하지 않음 - 이 경우 메시지를 각 페르소나에 맞춰야 함
    • 대표 사례: Preconditions Not Met 에러 (사용자가 필요한 접근 권한이 없음) - 사용자가 관리자인지 일반 사용자인지에 따라 직접적인 해결 지침을 줄지, 관리자에게 문의하라고 안내할지가 달라짐
  • 페르소나를 아는 것은 사용자의 온톨로지 (2장에서 정의: 알려진 개념들의 구조화된 그래프) 에 맞춰 말하는 데 도움을 줌
  • “PC Load Letter” 재조명: 실행 가능하긴 했음 (용지함을 다시 채우라는 의미) - 그러나 잘못된 페르소나를 대상으로 했기 때문에 실패
    • “PC”는 “paper cassette”(용지함)의 약자, “Letter”는 용지 크기 (8.5 x 11인치)를 의미
    • 대신 용지함을 A, B, C로 라벨링하고 “트레이 B를 다시 채우세요”라고 했다면 더 나았을 것

실전에서 에러 분류하기

  • 예제: 온라인 지표(metric)의 특정 시간대 평균값을 계산하는 메서드 작성
# metric_name: e.g. 'channelz.api_calls.count'
def recent_average_for_metric(metric_name: str, timespan: str = '1h'):
    metrics = MyMetrics.get_data(metric_name).withinLast(timespan)
    return sum(metrics)/len(metrics)
  • 질문: 5가지 카테고리 중 divide-by-zero (파이썬의 ZeroDivisionError)는 어디에 속하는가
  • return 문에서 metrics 배열이 비어 있으면 ZeroDivisionError가 발생 - 호출자는 이 메서드의 내부 구현을 알아야만 원인을 이해할 수 있어 매우 혼란스러워짐

TIP: 사용자와 개발자는 에러를 이해하기 위해 여러분의 구현(implementation)을 이해할 필요가 없어야 함.

  • 결론: 코드가 문자 그대로 계산기가 아닌 이상, divide-by-zero는 Assertion임 - 테스트 시점에 발견되어 코드 개선이 필요하다는 것을 팀에 알리도록 설계되어야 함. 나눗셈을 시도하기 전에 사전 검증을 수행해 이 에러 자체를 피해야 함
  • 그렇다면 그 사전 검증은 어떤 시나리오 카테고리에 속하는가? len(metrics)==0 조건을 유발한 상황은 다양할 수 있음 (표 3-3)

표 3-3. divide-by-zero 에러의 원인별 카테고리

엣지 케이스 카테고리
metric_name이 유효한가 Developer’s Invalid Argument
MyMetrics 제공자가 다운되었는가 System
최근 데이터가 없는가 Preconditions Not Met
timespan이 너무 짧은가 Developer’s Invalid Argument
  • 시사점: 각 케이스마다 제안할 액션이 다르므로 코드 안에 이를 구분하는 별도의 체크가 필요함. 또한 필요한 맥락을 확보한 시점에 검증을 수행해야 함
flowchart TD
    A[에러 상황 파악] --> B{개발 시점에 발견되어야 하는가}
    B -->|예| C{예상치 못한 내부 상태인가}
    C -->|예| D[Assertion]
    C -->|아니오 다른 개발자의 잘못된 인자| E[Developer Invalid Argument]
    B -->|아니오 런타임에 발견| F{주된 원인은 무엇인가}
    F -->|외부 시스템 인프라 장애| G[System]
    F -->|최종 사용자의 잘못된 입력| H[User Invalid Argument]
    F -->|권한 및 전제조건 미충족| I[Preconditions Not Met]
  • 이 섹션 정리: 진단을 개발자/사용자와 상호작용하는 것으로 분류하고, 런타임에 조치 가능한 시나리오와 개발 시점에만 조치 가능한 시나리오를 구분함 - 다음 섹션에서는 이를 바탕으로 훌륭한 메시지를 작성하는 방법을 다룸

3.4 경고와 에러 메시지

  • 진단 메시지 작성은 시스템 사고(system thinking)와 사용자 사고(user thinking)의 결합
  • 정확히 무엇이 일어났는지 알되 사용자의 관점으로 전환해야 함 - 사용자가 알아야 할 것을 그들이 이해하는 용어로 설명. 그렇지 않으면 “underfull hbox (badness 10000)” 같은 경고가 나오게 됨
  • 진단을 마주한 사용자가 알고 싶어하는 두 가지
    • 정확히 무엇이 일어나 에러가 발생했는가, 제품의 온톨로지 용어로 (실패의 영향을 알고 해결 단서를 얻는 데 도움)
    • (가능하다면) 무엇을 할 수 있는가 - 실행 가능한 진단은 사용자가 작업을 완수하는 데 직접적으로 도움

사례 연구 소개: Channelz

  • Channelz: 가상의 SaaS 회사, Slack / MS Teams / Discord와 유사한 사내 커뮤니케이션 도구를 구축
  • 등장인물: Elise (API 팀), Deng (테크 리드)
  • Channelz에서는 동료에게 다이렉트 메시지를 쓰거나, 특정 주제로 조직된 직원 그룹인 “채널”(예: #team-api-eng)로 메시지를 보낼 수 있음
  • Channelz는 봇이 사용자 또는 채널로 직접 메시지를 보낼 수 있는 API를 구축 중 - 고객들은 이를 다양한 알림 용도로 사용하고자 함
  • 코딩 전에 Elise가 개발자 인터페이스를 스케치해 Deng에게 보여줌 - 고객에게 배포할 Python SDK 메서드
class ChannelzBot:
    def __init__(self, bot_handle: str):
        # ...

    # One of channel, users is required
    def send_message(
        message: str, channel: Optional[str], users: Optional[List[str]])
  • 사용 예시
bot = ChannelzBot('@bippity_bot')

# Example: Send to a channel
bot.send_message(channel='#some-channel', message="Hello world!")

# Example: send separate messages to a list of people with an emoji
bot.send_message(users=['@drew', '@gabriel'], message="Hello :world:!")
  • Deng의 피드백: 실패 시나리오도 나열해달라고 요청 - Elise는 성공적인 사용 사례만 보여줬을 뿐, 사용자의 코딩 여정에서 그 이전 단계(에러가 발생했을 때)는 다루지 않았음
    • 비유: 온라인 지도 검색에서 목적지 핀만 딸랑 보여주고 길 안내를 하지 않은 것과 같음
  • Elise가 제시한 핵심 시나리오 (이 챕터에서 계속 다룰 예시): API에 전달된 사용자 또는 채널이 유효하지 않으면 어떻게 되는가

맥락 제공하기

  • 흔한 오해: 사용자는 자신이 무엇을 했는지 이미 알고 있을 것이라 생각하기 쉬움 (방금 그 행동을 했으니까) - 그러나 실제로는 대부분 틀림. 맥락과 세부사항을 반복해서 알려주는 것이 우리의 일
bot.send_message(users=['@dneg', '@elise'], message="Hello :world:!")
  • (편의상 이후 예시에서는 bot 부분은 생략)
  • 유닛 테스트에서는 "user does not exist" 정도의 메시지로도 충분할 수 있음 - 테스트 코드를 훑어보면 @deng이 오타난 것을 알아챌 수 있기 때문
  • 완전한 맥락을 제공하려면 세 가지가 필요
    • 관련 데이터를 에코백 (echo back)
    • 상세한 이유 제공
    • 사용자가 무엇을 했는지 리마인드

1) 관련 데이터 에코백

  • 실제 시나리오는 유닛 테스트보다 훨씬 지저분함 - 다른 곳에서 전달받은 데이터를 그대로 사용하는 경우
bot.send_message(users=input.users, message=input.message)
  • 단순히 "user does not exist"라고만 하면 개발자는 어떤 사용자를 말하는 것인지, 왜 틀렸는지 즉시 알 수 없음
  • 더 나은 방식: "user @dneg does not exist" - 오타를 바로 알아챌 가능성이 높음

TIP: 개인정보/보안상 이유로 정보를 가려야(redact) 하는 경우가 아니라면, 잘못된 데이터를 사용자에게 에코백할 것.

  • Deng이 코드 리뷰에서 이 점을 지적, Elise가 개선된 메시지를 배포

2) 상세한 이유 제공

  • API 팀의 디자인 파트너 고객사 ChickenLittle이 다음 에러를 리포트: Error: User '@buckcluck' does not exist.
  • 고객은 매우 혼란스러워함 - Buck Cluck은 실제로 ChickenLittle의 직원이었기 때문에 Channelz API에 버그가 있다고 생각함
  • Elise가 회사 디렉토리를 다시 확인해달라고 요청 -> @buckcluck 계정이 비활성화(inactive) 상태임을 확인 - 아마 Buck이 막 퇴사했을 것으로 추정
  • 향후 사용자 혼란과 지원 부담을 피하기 위해 Elise가 API 정의를 명확히 함
def on_missing_user(channelz_user: str):
    if is_inactive_employee(
        Employees.get_employee_from_channelz_user(channelz_user)):
        message = f"User {channelz_user} has been deactivated."
    else:
        message = f"User {channelz_user} does not exist."
    raise RuntimeError(message)

def send_message(users: Optional[List[str]], ...):
    for user in users:
        if not channelz_user_exists(user):
            on_missing_user(user)
    # ...
  • 개선된 메시지: Error: User '@buckcluck' has been deactivated.
  • 고객이 이 메시지를 읽었다면 무엇을 해야 할지 큰 단서를 얻었을 것

3) 사용자가 무엇을 했는지 리마인드

  • 유닛 테스트에서는 어떤 함수 호출이 문제를 일으켰는지 파악하기 쉽지만, 실제로는 에러 보고와 그 원인 행동이 분리되는 경우가 많음
    • 에러가 로그의 한 줄로만 나타나 무엇이 그 에러를 유발했는지 불명확할 수 있음
    • 온라인 주문 같은 장기 실행 프로세스의 지연된 한 단계일 수 있어 사용자에게 자신이 무엇을 주문했는지 리마인드가 필요
    • (AI 기반) 제품이 사용자의 지시를 특정 방식으로 해석했을 경우, 에러를 주기 전에 그 해석 내용을 먼저 에코백해야 함
  • 이런 시나리오들을 고려하면 잘못된 입력뿐 아니라 그 작업이 무엇을 하고 있었는지도 함께 에코백하는 것이 좋은 관행
  • 후속 사례: ChickenLittle의 Observability(관측성) 팀이 Channelz 메시징 API를 사용해 긴급하지는 않지만 중요한 프로덕션 문제를 직원들에게 알림 (매우 긴급한 이슈는 별도의 페이저 서비스 사용)
    • 경험담: 알림 서비스 내부에서 로깅되는 일부 에러 때문에 혼란스러웠음: Error: User '@foxyloxy' has been deactivated.
    • 처음에는 중요하지 않다고 생각함 (@foxyloxy가 이미 퇴사했으므로)
    • 그러나 나중에 중요한 알림들이 감지되지 않은 채 넘어갔다는 것을 발견 - 어떤 이슈가 이틀 동안 아무도 모르게 지속됨
    • 원인: Foxy Loxy가 퇴사했음에도 당시 온콜(on-call) 로테이션에 남아 있었음 - 온콜 로테이션에서 Foxy를 제거해 해결 (또한 메시지 전달이 실패할 경우를 위한 폴백(fallback) 사용자로 에스컬레이션하는 표준 관행도 함께 도입)
    • Elise가 이 피드백을 받아 에러 메시지에 맥락을 추가 - 재작성된 메시지: Error: Cannot deliver a Channelz message to '[@foxyloxy, @buckcluck]' because '@foxyloxy' has been deactivated.

TIP: 사용자에게 그들이 무엇을 하려 했는지 알려줄 것.

  • 처음부터 더 나은 기술과 약간의 사용자 공감이 있었다면 이런 반복적인 피드백 사이클 없이도 Elise가 이 명확한 메시지를 바로 작성할 수 있었을 것
  • 이 명확한 커뮤니케이션은 중요한 사실 하나를 드러냄: 사용자 중 한 명이 존재하지 않을 때 어떤 메시지도 전달되지 않았다는 것
    • Deng과 Elise는 이것이 “의도된 설계”인지 논의 후 버그라고 결론 - 이런 메시지들이 중요한 알림일 수 있으므로 가능한 한 많은 메시지를 전송해야 함
  • 정리: 에러에 추가하는 맥락은 보통 세 가지 질문에 답해야 함
    • 무엇을 시도하고 있었는가
    • 누구에게 혹은 무엇에 대해 발생했는가
    • 왜 실패했는가

경고와 에러 메시지를 실행 가능하게 만들기

  • 무엇이 일어났는지 아는 것은 절반의 승리일 뿐 - 사용자는 종종 무엇을 해야 하는지 제안받아야 함
  • 읽기(read) 작업의 경우, 특히 AI를 활용하면 실수를 직접 고쳐줄 수도 있음 (구글의 Showing results for: [correction] 기능, 코딩/작문 어시스턴트의 자동 수정처럼)
  • Channelz 예시
bot.send_message(message="The sky is falling!", channel="@barnyardfriends")
  • 다음 에러 메시지를 받았다고 가정: Cannot deliver a Channelz message to channel '@barnyard-friends': channel does not exist.
  • 무엇이 잘못됐는지 파악하기 쉽지 않음 - Channelz 표기법에 익숙하지 않다면 채널은 @가 아니라 #로 시작해야 한다는 것을 모를 수 있음
  • 개선안: 사용자에게 선택지를 제공 - Cannot deliver a Channelz message to channel '@barnyard-friends': it is prefixed with @. Did you mean to pass it into 'users'? Or did you mean '#barnyard-friends'?
  • 한 단계 더 나아가, Channelz가 계정을 조회해 #barnyard-friends가 실제로 존재하고 사용자에게 공개되어 있는지 확인 후 다음과 같이 보여줄 수 있음: Channel @barnyard-friends is prefixed with @, but we found a channel, #barnyard-friends. Is that what you meant?
  • 이렇게 사용자의 “검색 공간(search space)“을 좁혀줌으로써 시간을 절약하거나 제품을 포기(이탈)하는 것을 막을 수 있음
  • 단순히 System 에러 뒤에 “잠시 후 다시 시도하세요”라는 문구만 붙여도 워크플로 완료율을 높일 수 있음
  • 조언이 복잡해지는 경우도 있음 (예: 채널 개념을 소개하거나 여러 결정을 안내해야 하는 경우) - 적절한 경우 해당 에러 해결 전용 문서로 안내
    • 예시 문구: See https://channelz.io/docs/errors/invalid_channel to learn how to resolve this error.

3.5 인터페이스 에러 발생

  • 좋은 에러 메시지 작성이 쉬웠다면 엔지니어들은 이를 훨씬 더 자주 실천했을 것 - 주된 이유 하나는 이를 위해서는 신중한 코드 구성(code organization)이 필요하기 때문
  • 최고의 에러 메시지를 쓰려면 두 가지 정보가 필요
    • 시스템에서 정확히 무슨 일이 일어났는가
    • 사용자가 무엇을 하려고 했는가
  • 문제는 실생활에서는 이 두 정보 각각을 서로 다른 코드 조각이 가지고 있다는 점
    • 한 곳 (API나 UI 경계 근처)은 사용자가 누구고 무엇을 하려는지 알고 있음 - 사용자가 알지 못할 구현 세부사항을 언급하지 않고 무엇을 해야 할지 알려주기 가장 좋은 위치
    • 다른 한 곳 (처리 코드 깊숙한 곳)은 실제 문제가 발생하는 곳
  • 이는 에러를 분류할 때 이미 나왔던 문제 - divide-by-zero 예시에서 파이썬의 나눗셈 연산은 어떤 수학 법칙이 위반됐는지 정확히 알지만, 그 나눗셈이 어떻게 사용되고 있는지는 전혀 몰라 좋은 에러를 줄 방법이 없었음
  • 에러를 발생시키기 가장 좋은 곳은 대개 시스템과 사용자 사이의 인터페이스 - 상향식(bottom-up) 지식과 하향식(top-down) 지식을 결합할 수 있는 지점 (대안으로 모든 사용자 맥락을 아래로 전달하는 방법도 있으며 이는 이후 섹션에서 다룸)
flowchart LR
    U[사용자 요청 의도] --> API[API 또는 UI 경계]
    IMPL[내부 구현 로직] --> ERR[구체적 실패 지점]
    API --> BOUND[인터페이스 경계]
    ERR --> BOUND
    BOUND --> MSG[사용자와 개발자 모두에게 유효한 에러 메시지]
  • 일반적으로 사람들이 인터페이스에서 에러를 발생시키는 두 가지 방법
    • 능동적(proactively)으로 발생: API 경계에서 upfront validation(사전 검증) 사용
    • 에러 핸들러 사용: 낮은 레벨의 에러를 가로채(intercept) 적절한 형태로 재포장(repackage)

사전 검증 (Upfront Validations)

  • ChickenLittle의 Observability 팀이 겪은 또 다른 문제 - 온콜 알림을 담당하는 함수
def alert_team(bot: ChannelzBot, team: Team, message: str):
    team_metadata = get_team_metadata(team)
    bot.send_message(users=[team_metadata['on_call_user']], message=message)
  • get_team_metadata는 ChickenLittle 직원들이 https://corp.chickenlittle.io/oncalls/라는 UI에서 설정한 온콜 로테이션 데이터베이스를 읽음
  • team_metadata['on_call_user']가 유효하지 않으면 send_message가 다음 에러를 발생: Error: Cannot deliver a Channelz message to '[@gooseyloosey]' because '@gooseyloosey' has been deactivated.
  • 정확한 메시지지만 Goosey Loosey가 퇴사했을 때 사용자들은 어떻게 문제를 고쳐야 할지 알 수 없었음
  • Observability 팀이 사전 검증을 추가해 무엇을 해야 할지 알려줌
def alert_team(bot: ChannelzBot, team: Team, message: str):
    team_metadata = get_team_metadata(team)
    if not channelz_user_exists(team_metadata['on_call_user']):
        error_message = (
            f"Cannot send alert '{message}' to team {team}'s on-call: " +
            f"On-call employee {team_metadata['on_call_user']} doesn't exist. " +
            f"Update the team's on-call rotation at {ON_CALL_BASE_URL}/{team}."
        )
        raise ValueError(error_message)
    bot.send_message(users=[team_metadata['on_call_user']], message=message)
  • 사용자가 누락됐을 때 이 메서드는 다음처럼 도움을 줌: Cannot send a Channelz alert to team 'barnyard-friends'. On-call user '@gooseyloosey' doesn't exist. Update your on-call rotation at https://corp.chickenlittle.io/oncalls/barnyard-friends.
  • 이 상위 레벨 API alert_teamsend_message보다 훨씬 완전하게 호출자의 의도를 포착함 - 훌륭한 에러를 제공하는 강력한 토대가 됨

NOTE: API 또는 애플리케이션 코드의 가장 바깥 레이어, 즉 사용자의 시나리오를 포착할 수 있는 지점에서 에러를 발생시킬 것.

  • 이 upfront validation 방식은 효과가 있었지만 완벽하지 않았음 - 어떤 문제가 남아있었을까

에러 재포장 (Repackage Errors)

  • Upfront validation의 두 가지 문제점
    • 비용이 큼: Channelz API와의 추가 왕복(roundtrip)이 필요
    • (더 중요한 문제) 이 검증이 Channelz API 내부에서 이미 수행 중인 엣지 케이스 체크 코드(예: @gooseyloosey 계정이 비활성화됐는지 확인)를 중복해야 함 - 그 정보는 여전히 실행 가능(actionable)한 정보임. 예를 들어 해당 직원이 개명해 Channelz 핸들이 바뀐 것이라면 그건 또 다른 별개의 수정이 필요한 사안
  • 이런 좋은 지원을 이미 받아본 Observability 팀은 대신 다음과 같이 작성하고 싶어함
def alert_team(bot: ChannelzBot, team: Team, message: str):
    team_metadata = get_team_metadata(team)
    try:
        bot.send_message(users=team_metadata['on_call_users'], message=message)
    except ChannelzUserNotFoundError as error:
        error_message = (
            f"Cannot send a Channelz alert to team {team}'s on-call: " +
            f"On-call employee {team_metadata['on_call_user']} doesn't exist. " +
            f"Update the team's on-call rotation at {ON_CALL_BASE_URL}/{team}."
        )
        raise ValueError(error_message) from error
  • 마지막 줄의 from error 절에 주목 - 파이썬에서 “체이닝된 예외(chained exceptions)“를 표현하는 방식으로, 내부 에러를 보존함. 많은 프로그래밍 언어가 유사한 메커니즘을 가지고 있음
  • 이 코드가 만들어내는 출력 예시
ChannelzUserNotFoundError: User @looseygoosey's account has been deactivated.

The above exception was the direct cause of the following exception:

ValueError: Cannot send a Channelz alert to team 'barnyard-friends'.
On-call employee 'looseygoosey' doesn't exist.
Update your on-call rotation at
https://corp.chickenlittle.io/on-calls/barnyard-friends.
  • 메시지는 더 길어지지만 사용자가 알고 싶어할 모든 것을 포함
  • Observability 팀은 Elise에게 Channelz SDK 안에 이런 것을 가능하게 할 구체적인 에러 타입들을 요청 - 이후 Elise와 Deng은 예외를 더 프로그래밍 가능하게 만드는 방법을 탐구
sequenceDiagram
    participant Caller as 호출자 Observability팀
    participant AlertTeam as alert_team
    participant SendMessage as bot.send_message
    Caller->>AlertTeam: alert_team 호출
    AlertTeam->>SendMessage: send_message 호출
    SendMessage-->>AlertTeam: ChannelzUserNotFoundError 발생
    AlertTeam->>AlertTeam: except로 포착 후 ValueError로 재포장 from error
    AlertTeam-->>Caller: ValueError 전달, 내부 원인 정보 보존
  • 이로써 실행 가능성(actionability)과 인터페이스에서의 에러 발생 논의를 마무리하고, 다음으로 개발자와 그 사용자를 위해 에러를 프로그래밍 가능하게 만드는 방법으로 넘어감

3.6 프로그래밍 가능한 에러 발생

  • 때로는 여러분의 에러와 최종 사용자 사이에 또 다른 개발자가 존재함 - 그 개발자들도 인터페이스 레이어에서 에러를 처리한다는 동일한 원칙을 따르며, 여러분의 에러를 프로그래밍적으로 가로채 다양한 액션을 취해야 함
  • 에러가 잘 설계되지 않으면 이 모든 것이 불가능함
  • 런타임 에러(System, Preconditions Not Met, User’s Invalid Arguments)를 개발자에게 실행 가능하게 만드는 세 가지 주요 기법
    • 구체적인 에러 발생시키기
    • 예외 그룹화하기
    • 메타데이터 추가하기
  • 참고: Assertion과 Developer’s Invalid Argument 예외는 공통 예외 타입을 공유해도 무방함 - 이 둘은 개발 시점에 조치되며, 사람들이 이를 기반으로 런타임 자동화를 구축해서는 안 되기 때문

구체적인 에러 발생시키기 (Raise Specific Errors)

  • 파이썬의 ValueError 같은 범용 에러 타입은 클라이언트가 자동화를 세밀하게 조정하거나 사용자에게 무엇을 해야 할지 알려줄 수 없게 만듦
  • 런타임 에러 시나리오 카테고리에서 예외를 만들 때는 구체적인 에러를 만들어야 함 - Channelz가 ChannelzUserNotFoundError를 만든 것처럼
  • (여담) 앞서 Observability 팀이 ValueError를 발생시킨 것을 지적할 수 있음 - 그들은 플랫폼 팀은 아니지만 그들의 컴포넌트 주변에 UI나 미들웨어가 추가될 수 있으므로 습관적으로 구체적인 런타임 에러(예: OncallNotFoundError)를 만들어야 함
  • 핸들러에 약간의 조정만 가해도 Channelz의 API는 단순한(naive) 구현보다 훨씬 강력하고 중첩(nest) 가능해짐

TIP: 런타임 에러(System, Preconditions Not Met, User’s Invalid Arguments)를 중첩 가능하도록 설계해, 그 위에 다른 코드가 구축될 수 있게 할 것.

시나리오 카테고리별 에러 그룹화

  • API를 사용하는 개발자가 특정 카테고리의 에러를 처리하는 범용 핸들러를 작성하고 싶어할 수 있음
    • 예: System 에러가 발생하면 최종 사용자에게 “문제가 발생했습니다. 나중에 다시 시도해 주세요.“를 표시하고 싶을 수 있음
    • 반대로 User’s Invalid Argument 에러의 경우 메시지를 그대로 사용자에게 에코하는 것을 기대할 수 있음
  • 대부분의 언어에서는 상속 계층구조(inheritance hierarchy)를 사용해 유사한 에러 메시지를 그룹화할 수 있음 - 저자는 시나리오 카테고리별로 별도의 기본 클래스를 두는 방식을 선호
  • 내장 타입도 어느 정도 활용 가능
    • 파이썬 예시: ValueError는 (모든) Invalid Argument 에러를 나타냄, RuntimeError는 시스템 에러용
  • 하지만 내장 타입만으로는 표현력이 부족할 수 있음
    • ValueError는 User’s Invalid Argument와 Developer’s Invalid Argument를 구분하지 못하지만 두 경우의 조치는 매우 다름 (프로그래머의 실수라면 사용자는 입력을 고치는 게 아니라 지원팀에 문의해야 함) - 따라서 ValueError를 서브클래싱해 각각을 위한 별도 클래스를 만들 수 있음
  • 객체지향이 아닌 환경에서는 카테고리별 에러 코드와, 구체적인 실패를 식별하는 서브코드를 제공할 수 있음
    • 표준이 이런 면에서 충분히 훌륭하지 않은 경우가 많아 자체적으로 추가해야 할 수도 있음
    • 예: HTTP 프로토콜은 에러를 “클라이언트” 에러로 400번대에 분류 - 파이썬의 ValueError처럼 사용자/개발자 중 누가 유발했는지 구분하지 못함. 다만 특정 Preconditions 에러(예: 인증 실패)에는 유용한 코드가 존재

진단을 위한 정보 유지하기

  • 좋은 에러 메시지를 만들려면 많은 정보를 유지해야 함 - 근면함과 잘 조직된 코드가 필요 (정보를 넘겨주지 않는 첫 번째 개발자가 정보 체인을 끊게 됨). 세 가지 정보 체인 예시를 소개

1) 상위 수준 추상화 전달하기

  • 작은 정보 조각들을 여기저기 전달하기보다 정보를 그룹화한 상위 레벨 추상화를 전달하는 것이 풍부한 진단 제공을 쉽게 함
  • 예: 직원의 이름, 직함, 핸들 등을 필요할 때마다 각각 전달하기보다 필요한 모든 것을 담은 Employee 객체를 전달
  • 체인을 끊는 콜사이트 예시
def foo(bot: ChannelzBot, employee: Employee):
    bot.send_message(users=[employee.channelz_handle])
  • 이렇게 하면 send_message의 진단을 개선하고 싶은 유지보수자는 먼저 리팩터링을 해야 하는데 - 그냥 안 할 수도 있음
  • 체인을 살리는 접근
def foo(bot: ChannelzBot, employee: Employee):
    bot.send_message(employees=[employee])
  • 물론 이는 호출자의 유연성과 트레이드오프됨 - 호출자가 Employee 객체를 얻을 수 없다면? “그저 진단을 위해서” 접근 방식을 제한해야 하는가
  • 시나리오를 통해 이런 논쟁을 해결할 수 있음
    • 비직원에게 메시지를 보낼 필요가 정말 있는가 - 아마 없을 것
    • Employee 객체가 없는 레거시 코드 영역이 있는가 - 있을 수 있음
  • 이 경우 원시 사용자 핸들을 받는 레거시 버전의 send_message를 별도로 추가할 수 있음 - Employee 객체를 가진 사용자에게는 좋은 진단을 제공하고, 나머지는 리팩터링 전까지 쓸 수 있는 탈출구(escape hatch)를 제공
  • 두꺼운 정보 체인을 유지하면 진단을 작성하거나 진단 메타데이터를 채우는 데 드는 노력이 적어짐

2) 구조화된 메타데이터 추가하기

  • 예외에 필드 형태로 메타데이터를 제공해야 함 - 프로그래밍 가능성을 극적으로 높임. 사람들이 에러 메시지를 파싱(parse)해 데이터를 추출하게 만들지 말 것
  • 호출자가 자신만의 에러 메시지를 직접 만들고 싶어할 수 있다고 항상 가정하고, 사용한 데이터는 무엇이든 메타데이터로도 함께 넣어둘 것
  • 예: Observability 팀이 누락된 Channelz 사용자를 뽑아내고 싶어할 수 있으므로, Channelz는 ChannelzUserNotFoundErroruser 속성을 추가할 것
  • 이 관행을 지키면 정보 체인이 끊기지 않고 유지됨

3) 추가 맥락 유지하기

  • 컴파일러 같은 단계적 데이터 변환 아키텍처에서는 에러를 인식하기까지 여러 단계를 거칠 수 있음 - 원래의 맥락을 유지하는 것이 결정적으로 중요
  • 챕터 도입부의 LaTeX 예시 재조명: 사용자는 불필요한 \\ 토큰을 작성했을 뿐인데 에러는 “underfull hbox”에 대해 이야기함
  • 렉서(lexer)는 컴파일러의 첫 단계 - \\ 같은 입력 토큰을 다음 단계(파서)가 읽을 수 있는 추상 토큰으로 변환. 파서는 공백이나 줄 번호가 필요 없으므로 렉서가 이를 제거할 수 있음. 하지만 그래서는 안 됨
  • 이 경고는 렉서가 소스 코드를 읽고 hbox 토큰을 뱉어낸 뒤의 어느 컴파일러 단계에서 발생했을 것으로 추정 - 원래의 소스 맥락을 잃어 유용한 메시지 작성을 어렵게 만들었을 것
  • 이는 과거의 오래된 컴파일러에서 흔했던 문제 - 요즘 렉서는 출력에 충분한 정보를 유지해 이후 컴파일러 단계가 훨씬 현대적인 경험을 제공할 수 있음
This is at the end of a block of text. \\
                                        ^^
Unnecessary \\ to end a paragraph on line 2.
  • 상위 추상화 전달, 구조화된 메타데이터 추가, 추가 맥락 유지 - 이 세 가지 예시에 공통으로 적용되는 핵심 교훈

TIP: 정보 체인을 견고하게 유지하라. 진단을 위한 유용한 정보를 계속 보유할 것.

  • 최고의 에러는 코드의 한 지점에서 시스템 정보와 사용자 시나리오 정보를 최대한 확보할 때 가능해짐 - 이는 에러를 프로그래밍 가능하게 만들어야 달성 가능
  • 프로그래밍 가능성은 정보 체인을 살아 있게 유지하고, 구조화된 정보로 에러를 채우는 엄격한 관행을 통해 달성됨
  • 남은 주제: 타이밍 - 진단을 언제 발생시키는지가 사용자에게 미치는 영향은 매우 큼

3.7 조기 진단

  • 조기에 진단을 제공하는 것을 흔히 “Shift Left(왼쪽으로 이동)“라고 부르며 시스템과 사용자 모두에게 다양한 이점이 있음

TIP: 왼쪽으로 이동할 것(Shift Left). 가능한 한 빨리 사용자에게 진단을 제공할 것.

  • 시스템 관점의 이점
    • 쓸모없는 코드 경로를 조기에 차단해 리소스 사용을 줄임 - 예를 들어 서비스 거부(DoS) 공격을 막는 데 중요할 수 있음
    • 예상치 못한 입력을 처리하는 것으로부터 코드를 보호 - 데이터 손실 같은 버그를 예방
  • 사용자 관점의 이점 (더 큼)
    • 조기 에러는 시간을 절약 - IDE에서 에러를 받는 것과 프로덕션 배포 후에 받는 것의 시간 차이를 생각해보라
    • 빠른 피드백을 받으면 사용자가 자신이 무엇을 했는지 더 잘 기억하고 올바른 조치에 더 확신을 가지게 됨
  • Shift Left를 위한 4가지 일반적인 기법
    • 정적 검증 수행
    • 사전 검증
    • 테스트하게 하기
    • 사용자 확인 요청
  • 엔지니어들은 앞의 두 가지는 널리 실천하지만 안타깝게도 뒤의 두 가지는 그렇지 못함 - 저자는 네 가지 모두를 전도(evangelize)하고자 함
flowchart LR
    A[정적 검증, 개발 중 IDE 린트 체크섬] --> B[사전 검증, 요청 전 필드 확인]
    B --> C[테스트 환경 제공, Fake 서비스와 테스트 모드]
    C --> D[사용자 확인, 제출 직전 최종 확인]
    D --> E[런타임 에러, 최후 수단]

정적 검증 수행하기

  • 입력을 검증할 때 정적 검증은 깊은 검사나 네트워크 호출 없이 저렴하게 수행 가능한 반면, 동적 검증은 더 많은 작업이나 맥락을 필요로 함

TIP: 저렴한 체크와 비싼 체크를 분리하고, 저렴한 것을 일찍 그리고 자주 수행할 것.

  • 정적 타입 체크와 린터의 즐거움은 많은 프로그래머에게 익숙하지만, 이는 제품 차원에서도 나타남
  • 예: 애플리케이션 폼에서 사용자 입력을 받을 때 흔한 실수를 저렴하게 잡는 방법을 찾고 무엇을 고쳐야 하는지 인라인으로 조언
    • 우편번호: 국가별로 올바른 길이인지 검증 가능
    • 신용카드 번호, UPC 코드, ISBN 번호: 체크섬(checksum) 숫자가 내장되어 오타나 자리바꿈 같은 흔한 오류를 방지하는 데 도움
  • 이런 기법들은 왼쪽으로 이동하는 효과와 더불어 진단에 더 큰 확신을 부여함 - 체크섬이 실패하면 단순히 우리 데이터베이스에 없는 게 아니라 그 값 자체가 완전히 틀렸다는 것을 확신할 수 있음

사전 검증하기

  • 애플리케이션이나 API의 표면에서 에러를 만드는 것에 대해 논의할 때 upfront validation(사전 검증)은 더 실행 가능하고 이해하기 쉬운 에러 메시지를 위한 도구였음 - 하지만 검증은 Shift Left를 위한 도구이기도 함
  • 비유: 빨래방에 세탁하러 갔는데 세탁을 다 마치고 나서야 건조기가 하나만 작동 중이고 이미 몇 시간 예약되어 있음을 알게 됨 - 문에 안내판이 있었다면 젖고 곰팡이 핀 옷을 면했을 것
  • 유사하게, 돈을 이동시킬 때는 출금 이전에 목적지 계좌가 유효한지 검증하는 것이 필수
  • 세탁, 자금 이동, (심지어) 자금 세탁(money laundering)처럼 여러 단계로 이루어진 워크플로는 사전 검증의 혜택을 자주 누림

테스트하게 하기

  • 플랫폼 엔지니어가 가장 자주 간과하는 사용자 시나리오 하나가 바로 테스트 - 고객이 프로덕션 제품을 성공적으로 사용하는 모습만 생각하고 그 지점에 도달하기까지 겪는 시행착오는 고려하지 않는 경향
  • 개발자 서비스를 구축한다면 페이크(fake)를 만들었을 때 고객들이 훨씬 좋아하고 채택 속도도 빨라짐
  • 페이크: 프로덕션 시스템과 최대한 많은 코드를 공유하는 고충실도(high-fidelity) 버전이지만, 테스트를 느리고 불안정(flaky)하게 만드는 런타임 의존성(데이터베이스, 온라인 서비스 등)을 피하기 위한 일부 지름길을 취함
  • Channelz가 자신의 메시징 API용 페이크 서비스를 구축한다면 이상적으로 갖춰야 할 특성
    • 메모리 내에서 또는 경량 로컬 프로세스로 실행되어 사용자의 테스트 환경/로컬 개발 머신에서 테스트 가능
    • 사용자가 다수의 사용자와 채널을 입력해 API를 사용하는 다양한 코드 조각을 테스트할 수 있도록 허용
    • 가능한 한 많은 시나리오에서 실제 프로덕션 서비스처럼 정확하게 동작
  • 실사례: Stripe (결제/청구 API 및 UI 제공업체)는 실제로 돈을 움직이지 않으면서 현실적인 거래를 할 수 있는 인기 있는 “테스트 모드”를 제공
    • 고객은 특수한 신용카드 번호를 전달해 다양한 Invalid User Argument 및 Precondition 에러를 유발 가능
    • 예: 카드 번호 4000 0000 0000 9995insufficient_funds 서브코드가 붙은 card_declined 에러를 시뮬레이션
  • Stripe 사용자는 프로덕션보다 테스트 모드에서 훨씬 더 많은 시간을 보내며, 통합 테스트 작성 및 사용자 인터페이스 테스트에 활용
    • 예: 상인(merchant)이 신용카드 입력 폼을 테스트하고 싶다면 이 매직 넘버들을 직접 입력해 사이트 동작을 확인할 수 있음
  • 페이크는 개발자가 제대로 인증되거나 실제 프로덕션 요청을 제출하기도 전에 에러를 발견하게 해주는, 에러를 크게 왼쪽으로 이동시키는 훌륭한 방법

사용자 확인 요청하기

  • 확인(Confirmation): 사용자가 올바르게 입력/조작했는지 스스로 검증하도록 돕는 앱/플랫폼 내의 장애물 - 경고(warning)보다는 강하지만 에러(error)만큼 침습적이지는 않음
  • 사용자 인터페이스와 개발자 도구에서, 휴리스틱이 사용자 입력에서 이상한 점을 감지했을 때 자주 등장 - 왜 예상 밖일 수 있는지 설명하고 수정하거나 확인할 기회를 제공
  • 구글의 “Did you mean” 기능이 좋은 예시 - “compture” 검색 시 정말로 Compture라는 밴드를 찾는 것일 수도 있으므로 그냥 에러를 낼 수는 없음. 대신 사용자가 나중에 결과를 뒤져서 스스로 알아내게 하지 않고 그 의심을 미리 알려줌
  • 또는, 사용자가 여러 페이지짜리 폼을 다 작성한 후 제출하려는 내용의 요약(summary)을 보여줘 오류를 미리 체크할 기회를 줌
  • 확인은 전체 시스템을 다 거치지 않고는 사용자가 틀렸다고 100% 확신할 수 없는 상황에서, 최선의 추측을 미리 알려줌으로써 왼쪽으로 이동할 수 있게 해줌
  • 사전 테스트를 허용하는 한 가지 일반적 방법: “드라이 런(dry run)” 뒤에 확인이 따라오는 방식
    • 예: 주식 거래 앱에서 매수하려는 주식 수에 현재 주가를 곱해 총 지출 금액을 미리 보여주는 것처럼 단순할 수 있음
  • Channelz에서의 더 복잡한 드라이 런 사례: 재시도 정책(retry policy) API 추가
ChannelzBot('@bippity_bot')
    .with_retry_policy(
        backoff_coefficient=2.0,
        initial_interval_seconds=10, max_interval_seconds=600).send_message(...)
  • 만약 ChickenLittle이 매우 스팸성인 재시도 정책 (1초마다 무한 재시도)을 작성한다면
# Too spammy!
.with_retry_policy(backoff_coefficient=1.0, initial_interval_seconds=1)
  • 이는 Channelz에 우발적인 서비스 거부(DoS) 스타일 공격을 유발할 수 있고, 결과적으로 ChickenLittle의 트래픽이 스로틀(throttle)당하는 결과로 이어질 수 있음
  • Elise는 재시도 정책을 시뮬레이션하고 나쁜 동작을 플래그하는 휴리스틱 기반 경고로 왼쪽 이동 가능
Your retry_policy (currently initial_interval_seconds=1,
backoff_coefficient=1.0, max_attempts=Infinite) will result in 601
attempts in 600 seconds. This exceeds our limit of 50. You may ignore this
by adding .ignore(Errors::RetryPolicySpamminess) to your policy.
  • Elise가 자신의 정확한 휴리스틱에 완전한 확신은 없지만, 빠르게 움직이며 이런 확인을 제공해 사용자를 도울 수 있음 - 휴리스틱이 잘못됐다면 사용자가 불평할 것이고, 그때 상향 조정 가능
  • 개발자 도구에서는 --force 플래그가 확인을 오버라이드하는 데 사용됨
  • 확인은 완벽하지 않음 - 사용자가 무심코 확인을 누르거나 --force 플래그를 그냥 복붙할 수 있음. 따라서 정말로 받아들일 수 없는 입력에는 확인 방식을 사용하지 말 것
  • 그럼에도 대부분의 개발자는 절대적인 것(허용되거나 안 되거나)에 익숙함 - 중간 케이스(입력이 아마도 틀렸을 것이다)를 고려하면 인터페이스에 놀라운 풍부함과 표현력이 열림
  • 이 섹션의 정리: Shift Left의 장점과 그로부터 혜택을 받는 많은 사용자 시나리오를 다룸. Shift Left가 항상 완벽한 검증을 의미하지는 않으므로, 서비스 페이크, 휴리스틱 기반 확인, 체크섬 같은 정적 검증 등의 기법으로 가장 흔한 사용자 실수를 가능한 한 일찍 다루는 방법을 탐구함

3.8 요약

  • 다음 진단을 작성할 때 초점을 사용자 경험으로 옮기고, 사용자가 무엇을 아는지, 무엇을 알아야 하는지, 무엇을 해야 하는지를 고려하는 것부터 시작
  • 에러를 작성한다면 시나리오 카테고리(System, User’s Invalid Argument, Precondition, Developer’s Invalid Argument, Assertion) 중 하나로 분류 - 메타데이터와 메시지 구성을 추론하는 데 도움
  • 메시지 작성 원칙
    • 맥락을 제공할 것 - 대부분의 경우 수행 중이던 작업과 전달된 잘못된 데이터를 에코백
    • 제품의 사용자 대면 온톨로지에서 나온 개념을 사용할 것
    • 액션을 제안할 것 - 옵션이 하나가 아니라면 대안도 기꺼이 제시할 것
  • 에러를 인터페이스 경계에서 사용자에게 노출시키면 위 모든 것을 더 잘 해낼 가능성이 높아짐
  • 개발자를 위해서는 다른 개발자가 자신의 일을 할 수 있도록 충분한 맥락과 구조를 에러에 포함할 것
  • 에러를 계층구조로 조직해 개발자에게 범용적인 방식과 구체적인 방식 모두를 제공함으로써, 그들이 자신의 사용자를 서비스할 수 있게 할 것
  • 마지막으로 확인(confirmation), 정적 체크, 페이크 같은 기법을 통해 흔하거나 중요한 시나리오에 대해 왼쪽으로 이동할 창의적인 방법을 찾을 것

3.9 예제

[문제 1] “Windows 블루스크린의 진화”를 웹에서 검색해, 수십 년간 Windows 팀이 에러 메시지를 개선하려 애써온 과정을 살펴볼 것. (a) 각 메시지가 어떤 페르소나를 대상으로 하는지 판단할 것. (b) 영상 후반부에 등장하는 Windows 11 화면에 대해, 선택한 페르소나 기준으로 그 메시지를 분석할 것 [문제 2] 이후 질문들을 위해 전자상거래 앱에서 일한다고 가정. 사용자는 장바구니를 채우고 결제할 수 있음. 앱은 사용자 ID, 장바구니 ID, 신용카드 CVV 코드(카드 검증용 3~4자리 코드)로 구성된 submit_order API를 통해 주문을 완료함. 모바일 클라이언트는 데이터를 API로 보내기 전에 정적 검증을 수행할 수 있음. 유효하지 않은 사용자 ID와 유효하지 않은 장바구니 ID는 어떤 에러 시나리오 카테고리에 해당하는가 [문제 3] 유효하지 않은 CVV 코드는 어떤 에러 시나리오 카테고리인가 [문제 4] 빈 CVV 코드는 어떤 카테고리인가 [문제 5] 누락된 CVV 코드에 대한 실행 가능한 최종 사용자 대면 에러 메시지를 작성할 것 [문제 6] 장바구니는 만료될 수 있으며 24시간 후 시스템에서 자동 삭제됨. 사용자가 삭제된 장바구니에 항목을 추가해 세션을 재개하려 할 때 어떤 시나리오 카테고리의 에러가 발생하는가 [문제 7] 이 만료된 장바구니 에러를 왼쪽으로 이동시켜, 사용자가 각 항목을 다시 찾을 필요가 없게 만들 방법을 생각해볼 것

3.10 답안

[답안 1] 이 책 집필 시점 기준 Windows 11 메시지는 다음과 같은 취지의 내용을 담고 있음: 장치에 문제가 생겨 다시 시작해야 한다는 안내, 오류 정보를 수집 중이라는 설명, 문제 해결을 위한 windows.com/stopcode 참고 안내, 지원팀에 문의할 경우 알려줘야 할 스톱 코드(Stop Code) 정보

  • 이 메시지는 최종 사용자를 대상으로 하며 명확한 액션(재시작을 기다리기, URL 방문)을 제공함
  • URL 사용은 강력한 기법 - 항상 최신 상태로 유지 가능하고 세부적인 해결책을 담을 수 있기 때문
  • 메시지 끝의 스톱 코드는 IT 전문가와 관련된 것으로 명확히 프레이밍되어 있어, 두 개의 서로 다른 대상(최종 사용자 / IT 전문가)이 존재한다는 단서가 됨
  • 다만 IT 전문가를 위해 크게 애쓰지는 않음 - 크래시 덤프 찾는 법 등을 알고 싶다면 스스로 검색할 수 있는 능력이 있다고 가정하는 것으로 보임 [답안 2] 유효하지 않은 사용자 ID와 장바구니 ID는 Developer’s Invalid Argument 에러. 다만 사용자가 로그인하지 않아 사용자 ID가 비어 있는 경우라면, 이는 사용자가 조치 가능한 Precondition 문제 [답안 3] 유효하지 않은 CVV 코드는 User’s Invalid Argument [답안 4] 빈 CVV 코드는 표면적으로는 User’s Invalid Argument. 하지만 잘 만들어진 클라이언트는 빈 필드를 정적으로 체크해 서버에 연결하기 전에 사용자에게 입력을 요청해야 함. 따라서 API 입장에서는 이를 Developer 문제로 취급하는 것이 바람직할 수 있음 - 클라이언트가 코드를 전달하는 것을 잊은 경우처럼, 누락된 필드를 명확히 버그로 부각시키기 위함 [답안 5] 잘못된 CVV 값을 강조하는 빨간 박스와, 다음을 설명하는 툴팁을 상상해볼 수 있음: “신용카드의 보안 코드를 입력해 주세요. Visa, Mastercard, Discover 카드는 보통 카드 뒷면에 있는 3자리 숫자입니다. American Express 카드는 보통 앞면에 있는 4자리 숫자입니다.” [답안 6] 만료된 장바구니는 Precondition이며 사용자가 조치 가능. 따라서 장바구니 만료 기능 구현 시 API가 유효하지 않은 ID(개발자 문제)와 만료된 ID를 구분할 수 있어야 함 - 예를 들어 만료된 장바구니를 데이터베이스에서 완전히 삭제하는 대신 소프트 삭제(soft-delete)할 수 있음 (이런 관행을 종종 “톰스톤(tombstoning)“이라 부름) [답안 7] 장바구니가 만료되기 전에 고객에게 이메일이나 푸시 알림을 미리 보내, 주문을 완료하도록 상기시켜 줄 수 있음

PART 2 전달

  • 메이저 릴리스를 앞두고 있다면, “사용자가 제품이 의도대로 동작하는지 어떻게 검증할 것인가”에 집착해야 함
  • 소셜미디어 기업 vs 자율주행 스타트업 비교
    • 소셜미디어: 크고 관대한 사용자 기반과 뉴스피드라는 강력한 배포 수단 보유. 실험적 제품(설문, 게임 초대, 이벤트, 그룹, 동영상, 스토리 등)을 일부 사용자에게 신속히 배포하여 피드백과 지표를 얻고 A/B 테스트 수행 가능
    • 자율주행 스타트업: 고객에게 아무것도 출시하지 못한 채 수년간 반복 개발해야 함. 전문가를 고용해 차량을 모니터링하며 데이터를 수집하고, 사람보다 뛰어난 신뢰도(주행거리의 99.999999퍼센트 초과에서 치명적 사고 없음)를 달성해야 함
    • 공통점: 두 접근법은 공통점이 거의 없지만, 결국 모두 “제품을 검증”하고 있다는 점은 같음

TIP: 사용자와 함께 제품을 검증할 것.

  • 사용자 검증의 효과: 제품에 대한 중요한 미해결 질문에 답을 얻고, 미처 생각하지 못한 문제를 발견하게 됨
  • Part 2의 구성
    • Chapter 4: 출시 전 단계. 동료와 함께 제품을 직접 사용해보고, 테스트와 마찰 로그를 작성하며, 신중하게 문서화함으로써 제품에 조기 압력을 가함
    • Chapter 5: 출시 후 단계. 실제 사용자 피드백, 실험, 지표를 통해 청취함

CHAPTER 4 자사 제품 체험(도그푸딩)

“새로운 시스템의 설계자는 구현자이자 최초의 대규모 사용자가 되어야 할 뿐 아니라, 최초의 사용자 매뉴얼도 직접 작성해야 한다… 내가 이 모든 활동에 온전히 참여하지 않았다면, 수백 가지 개선사항은 결코 이루어지지 않았을 것이다. 왜냐하면 나는 그것들을 생각해내지도, 왜 중요한지 인식하지도 못했을 것이기 때문이다.” — 도널드 커누스(Donald Knuth)

  • 문제 상황: 베타 사용자를 찾고 참여시키기 어려움
    • 신규 버전 출시 전 테스트해줄 사용자가 필요하지만, 아직 규모가 작아 안정적인 베타 사용자 풀이 없음
    • 사용자들은 각자의 우선순위가 있어 참여율이 낮고, 참여하더라도 가장 리스크가 큰 부분은 건드리지 않을 수 있음
    • 마감 압박으로 인해 검증되지 않은 채로 출시하고 싶은 유혹이 생김
  • 해결책: 자기 자신, 팀원, 회사 내 다른 구성원 등 참여시키기 쉬운 사용자를 활용
    • 이들은 제품의 성공을 함께 나누며, 이미 도와줄 동기가 있는 사람들임

TIP: 스스로 첫 번째 고객이 될 것. 이는 제품을 조기에, 저렴하게, 무해하게 검증하는 방법임.

  • 도그푸딩(dogfooding)
    • 정의: 팀 구성원이 자사 제품을 직접 사용하는 것
    • 용어 유래: 1990년대 마이크로소프트에서 대중화. “자사 개 사료를 직접 먹는다(eating your own dogfood)“는 표현에서 유래하며, 운영체제·컴파일러 최신 버전을 직접 사용하는 문화를 가리킴
    • 팀이 타깃 페르소나에 속하거나 실제 사용 조건을 시뮬레이션할 수 있을 때 이상적
    • 이상적이지 않은 상황에서도, 팀이 사용자층을 잘 이해하고 실제 사용자인 척(shoe-shift)할 수 있다면 여전히 효과적일 수 있음
    • “방법을 찾아라”: 농업 기술 회사라면 직접 농지를 사서 테스트하거나, 온실·실험실·컴퓨터로 조건을 시뮬레이션하거나, 실제 농부를 고용해 땅 일부를 실험에 할당하는 방식도 가능
  • 도그푸딩의 주요 장점
    • 정식 출시 전, 제품 수명주기 초반에 가능
    • 실제 사용자보다 저렴하게 테스트하고 소통 가능
    • 사용자에게 부정적 영향을 주거나 신뢰를 해치지 않음
  • 이 챕터에서 다루는 4가지 도그푸딩 접근법
    1. 실제 사용 패턴을 반영하는 시나리오 테스트 작성
    2. 제품 사용 경험을 사용 안내서로 정리 - 문제를 조기에 발견하는 데 도움
    3. (본인 또는 동료가 작성하는) “마찰 로그” - 도그푸딩 동기 부여와 더 유용한 피드백 생성
    4. (해당하는 제품이라면) 샘플 제작과 테스트
  • 문서화와 자동화 테스트는 전통적으로 도그푸딩으로 간주되지 않지만, 제대로 하면 “사용자인 척하며 제품에 유용한 압력을 가하는” 요소가 강함
  • 이 4가지 기법을 창의적으로 조합해 매 단계마다 저렴하게 제품을 검증할 것. 그러면 리스크가 줄고 사용자의 시간과 신뢰를 낭비하지 않게 됨

4.1 도그푸딩으로서의 테스트

  • 코드를 작성할 준비가 되면, 테스트 작성은 자사 제품을 체험하는 가장 좋은 방법 중 하나
  • Kent Beck: “TDD(테스트 주도 개발)의 목적은 애플리케이션 개발에서 두려움을 없애는 것.” 두려움을 줄이는 가장 좋은 방법은 프로덕션과 높은 충실도(fidelity)를 갖는 테스트를 작성하는 것
  • TDD에서는 구현 전에 테스트를 작성함
    • 무엇에 근거해 작성하는가? 구현에서 도출할 수 없음 - 아직 존재하지 않기 때문
    • 이미 구현이 존재한 뒤 테스트를 작성하면 “구현 편향(implementation bias)“이 발생함. 즉 코드가 “무엇을 해야 하는가”가 아니라 “무엇을 하는가”를 검증하게 됨
    • 따라서 테스트의 주요 영감의 원천은 사용자 시나리오여야 함. 구현에 편향되기 전에 시나리오를 기반으로 작성할 것
  • 테스트를 도그푸딩 기회로 활용해 달성할 수 있는 것
    • 핵심 시나리오에서 정확성 검증
    • 엣지 케이스 다듬기
    • 의도된 사용법 문서화
    • 무엇을 만들고 있는지 스스로 고민하도록 강제
    • 반복적으로 실행 가능한, 지속적 가치를 지닌 자산 구축

4.1.1 어떤 종류의 테스트를 작성해야 하는가

  • 테스트 유형은 시스템 지향(system-oriented)과 제품 지향(product-oriented)의 혼합
    • 시스템 지향 테스트: 애플리케이션이 버그투성이거나 불안정하거나 확장되지 않을 리스크(시스템 리스크)를 감소시킴
    • 제품 지향 테스트: 사용자가 작업을 완료하지 못할 리스크(제품 리스크)를 감소시킴
    • 목표는 테스트 스위트가 시스템·제품 문제를 모두 효율적으로 찾도록 균형 잡힌 조합을 구성하는 것. 많은 팀이 제품 지향 테스트를 과소 활용하는 경향이 있음
  • 기본 용어
    • 모의 객체(Mocks): 의존성이 특정 방식으로 호출되는지 검증하는 비기능적(non-functional) 대역
    • 가짜 객체(Fakes): 복잡한 기능을 단순화한 대체물. 예를 들어 SQLite는 실제 SQL 데이터베이스를 대신하는 로컬 파일시스템 기반 가짜(fake)로 사용 가능

NOTE: 제품 압력(product pressure)이란, 사용자가 개별적으로 또는 집단적으로 취할 가능성이 높은 행동 순서에 기반한 검증을 의미함

  • 아래 테스트 유형은 코드베이스에 가하는 “제품 압력”의 양 순서로 정렬됨
테스트 유형 정의 및 특징
유닛 테스트 개별 함수·클래스 등 작은 코드 단위를 테스트. 기능 개발 초기에 엣지 케이스를 탐색하고 계약(contract)을 인코딩하기 위해 작성. 빠르게 실행되어야 하며 가장 안쪽의 개발 루프에서 사용. 초점 유지를 위해 모의 객체를 자주 쓰지만, 중요한 시스템·제품 버그를 찾는 데는 취약함
통합 테스트 여러 컴포넌트를 함께 테스트. 컴포넌트 간 상호작용에서 문제가 가장 많이 발생한다는 전제에서 출발. 의존성을 많이 모킹하지 않아 시스템 문제를 더 잘 찾아냄. 대부분 실제 프로덕션 코드를 실행하고 필요한 경우에만 가짜(fake)를 사용
기능 테스트 개별 기능의 제품 동작에 초점. 다른 서비스나 데이터베이스 같은 의존성을 시뮬레이션하기 위해 가짜를 사용할 수 있음. 가짜가 없을 때 모의 객체를 쓰기도 하지만, 이는 테스트 신뢰도를 떨어뜨리는 “나쁜 냄새(bad smell)“로 간주됨
시나리오 테스트 기능 테스트와 유사하지만 특정 기능이 아닌 일반적인 사용자 시나리오를 실행. 여러 기능을 함께 건드리거나 가장 흔한 입력 조합으로 기능을 실행. 사용자 작업 전체가 완료될 수 있음을 보장
부하 시뮬레이션 대량의 합성(synthetic) 요청으로 제품에 스트레스를 가함. 이상적으로는 실제 트래픽 패턴을 모사해야 함(9장에서 상세히 다룸)
e2e(엔드투엔드) 테스트 사용자 대면 동작에 대한 가장 포괄적인 자동화 테스트. 예상치 못한 시스템 통합 문제도 발견 가능. 본질적으로 아무것도 스텁하지 않는 시나리오 테스트이며 대개 UI까지 포함함
사용자 인수 테스트 실제 사용자가 수동으로 제품을 사용해보고 문제를 보고. 마찰 로깅(friction logging)을 소개할 때 추가로 다룸
  • 그림 4-1은 각 테스트 유형이 우선적으로 찾아내는 시스템·제품 리스크의 상대적 강약을 도표로 정리한 것임 (아래는 상응하는 시각적 도식)
quadrantChart
    title 그림 4-1: 테스트 유형별 시스템/제품 리스크 감소 정도 (근사치)
    x-axis 낮음 --> 높음
    y-axis 낮음 --> 높음
    quadrant-1 시스템 + 제품 모두 강함
    quadrant-2 시스템 리스크 중심
    quadrant-3 노력 대비 효과 낮음
    quadrant-4 제품 리스크 중심
    유닛 테스트: [0.1, 0.2]
    기능 테스트: [0.35, 0.4]
    통합 테스트: [0.3, 0.65]
    시나리오 테스트: [0.55, 0.4]
    부하 시뮬레이션: [0.45, 0.9]
    e2e 테스트: [0.65, 0.9]
    사용자 인수 테스트: [0.75, 0.65]
  • 원본 그림에서는 x축이 “제품 리스크 감소”, y축이 “시스템 리스크 감소”이며, 원점에서 유닛 테스트를 거쳐 기능 테스트 쪽으로 향하는 화살표가 “노력의 수준(Level of effort)“을 나타냄 - 유닛 테스트보다 기능 테스트에 더 많은 노력이 든다는 의미

4.1.2 테스트 유형 선택하기

  • 테스트 전략 수립 시 일반적 목표
    • 제품이 작동하지 않게 만들 수 있는 가장 중요한 시스템·제품 리스크 최소화
    • 디스커버리 단계에서 도출한 핵심 시나리오가 테스트되도록 보장
    • 반복적인 사용자 인수 테스트를 자동화로 대체
    • 작성 노력 대비 테스트 가치를 높게 유지
    • 실패가 실제로 중요한 의미를 갖는, 신호 대 잡음비가 높은 테스트 확보
    • 기능 테스트를 통해 중요한 엣지 케이스 커버
  • 리스크 유형별 우선순위
    • 시스템 리스크가 큰 기능 → 부하 시뮬레이션, 통합 테스트 위주
    • 제품 리스크가 큰 기능 → 기능·시나리오·사용자 인수 테스트에 집중
    • e2e 테스트는 두 리스크 모두에 유효
  • 제품에서 무엇이 가장 새로운지(=가장 깨지기 쉬운지) 고려
    • 기존 시맨틱을 유지한 채 새로운 인프라 컴포넌트를 도입하는 경우 → 시스템 테스트에 집중하고 제품 동작은 기존 테스트에 의존
    • 성숙한 기술 기업의 “정형화된 길(paved road)”·하드닝된 인프라 위에 신규 제품을 만드는 경우 → 제품 리스크 위주로 집중 가능
    • 두 리스크 모두 중요하며 대부분의 테스트는 양쪽 요소를 포함해야 함. 이 책은 프로덕트 사고 중심이므로 시나리오·기능·e2e·사용자 인수 테스트에 집중하고, 부하 시뮬레이션은 9장에서 다시 다룸

4.1.3 사용자가 신경 쓰는 것만 테스트하기

  • 핵심 목표: 사용자에게 직접적으로 중요한 행동을 검증하는 테스트를 작성하는 것
  • 피해야 할 테스트의 전형적 예시
    • 구현 세부사항과 모의 객체에 지나치게 의존하는 유닛 테스트 - 코드베이스가 변경될 때마다 잦은 유지보수가 필요함. 저자는 기능 테스트가 갖춰지면 구현 중 작성했던 유닛 테스트를 삭제하기도 함
    • 지나치게 구체적인 단언(assertion)을 하는 까다로운(fussy) e2e 테스트 - 제품 표면이 바뀔 때마다 잦은 실패
    • 비현실적 트래픽 패턴의 부하 시뮬레이션 - 실제로는 일어나지 않을 방식으로 스케일링·하드닝을 하게 만듦
  • 이후 설명을 위한 예시 설정: Netflix(영화·TV 스트리밍 서비스)
    • 웹 팀 소속이며, 홈 화면에 시청 옵션을 표시하는 업무를 담당한다고 가정
    • 홈 화면은 클릭해서 재생할 수 있는 쇼들의 그리드로 구성
    • 각 행(row)은 “비평가 극찬 TV쇼”, “내 리스트” 같은 테마를 가지며, 아래로 스크롤하면 “클래식 SF·판타지” 등 추천 행이 추가로 노출됨

4.1.4 시나리오 테스트

  • 실제 제품 사용 사례를 포착함 - 사용자가 실제로 할 행동을 대표하므로 중요한 버그를 잘 포착함

  • 디스커버리(7장에서 다룸) 단계에서 도출한 시나리오를 시나리오 테스트로 직접 변환 가능

  • 개별 기능이 아니라 제품 전체를 사용자 관점에서 생각하게 함. 전체 테스트 매트릭스를 혼자 감당하지는 못하지만, 잘 고르면 출시되는 버그가 대체로 경미하도록 보장

  • 결합하는 최소 2가지 요소

    • 사용자 플로우에서 함께 등장하는 2개 이상의 기능 결합 - 컴포넌트 간 상호작용은 소프트웨어에서 가장 불안정한 부분이며, 시나리오 테스트가 이를 검증함
    • 실제 세계 상황과 일치하는 종류·양의 데이터로 기능을 실행
  • 가능한 한 end-to-end에 가깝게 작성하고, 나머지는 고충실도(high-fidelity) 가짜(fake)를 사용

    • 백엔드 팀이라면 브라우저 대신 시뮬레이션된 DOM 환경을 쓰는 것이 편리하고 경제적일 수 있음(DOM은 웹페이지의 구조와 콘텐츠를 구성하는 객체의 데이터 표현)
    • 실제 Netflix의 분산 데이터베이스를 테스트에 사용하는 것은 비용이 지나치게 큼
  • 그림 4-2는 가짜 DB와 가짜 DOM을 양쪽에 둔 백엔드 시나리오 테스트 구조를 보여줌 (아래는 상응하는 시각적 도식)

flowchart LR
    FDB[가짜 DB] -- Shows --> BE[백엔드]
    BE -- Query --> FDB
    BE -- Extracted requests --> FDOM[가짜 DOM]
    FDOM -- HTML, JS --> BE
  • 예시 시나리오: 로그인한 사용자가 영화를 보고 싶어 netflix.com에 접속하고, 영화 썸네일을 클릭해 영화를 로드하고 재생을 시작함
# 로그인한 사용자가 영화를 보고 싶어 홈페이지를 로드하는 시나리오
def watch_movie_from_homepage():
    set_up_fake_movies()  # 테스트에서 실제 영화를 다운로드하고 재생하고 싶지 않음

    # 사용자가 홈페이지를 본다. 영화 썸네일이 있어야 한다.
    home_page_source = get_source("netflix.com")
    home_page_dom = get_dom(home_page_source)
    movie_listing = find_one("movie_thumbnail", home_page_dom)
    assert(movie_listing)

    # 사용자가 영화 중 하나를 클릭한다.
    watch_movie_page_request = movie_listing.click_target()
    watch_movie_source = get_source(watch_movie_page_request)
    watch_movie_dom = get_dom(watch_movie_source)

    # 영화 페이지가 성공적으로 로드되고 자동 재생을 시작한다.
    assert_autoplay_on(watch_movie_source)
  • 코드 설명
    • 각 동작이 다음 동작으로 연결(chaining)됨에 주목 - watch_movie_page_request를 하드코딩하지 않고 홈페이지의 HTML/JS에서 직접 추출함
    • 위젯이 화면에 숨겨져 있는 등의 UI 레이어 이슈는 잡아내지 못하지만, 홈페이지에서 반환된 영화 페이지 링크가 올바른지는 보장함
    • 주석과 이름을 통해 사용자 시나리오의 “이야기”를 전달함. 자기 문서화(self-documenting)된 시나리오 테스트는 사실상 영구히 남는 제품 요구사항이므로 특히 중요하며, 신규 팀원 온보딩 시 가장 먼저 보여줘야 할 자료 중 하나
  • 추가 테스트 아이디어
    • 아래로 스크롤해 두 번째 페이지 노출: 홈페이지 렌더링 후 스크롤을 시뮬레이션하여 추가 행이 로드되는지 확인. 초기 페이지가 추가 데이터를 가져올 수 있는 URL과 함께 렌더링됐는지 단언하고, 해당 URL을 호출한 뒤 새 콘텐츠가 로드되는지 확인
    • 신규 사용자 경험: 새 Netflix 테스트 계정을 생성 - 사용자에 대해 아무것도 모르므로 홈페이지에는 가장 인기 있는 콘텐츠가 노출되어야 함(쇼 데이터베이스에는 특정 인기 리스트를 포함한 합성 데이터가 채워져 있음). 가장 인기 있는 쇼가 노출되는지 확인
    • 시청 중인 쇼 이어보기: 라이브러리를 렌더링해 이어보기 시작 지점을 알려주는 요소가 페이지 어딘가에 렌더링되는지 확인

4.1.5 기능 테스트

  • 모든 것이 시나리오 테스트일 필요는 없음. 기능 테스트로 개별 기능을 더 철저히 검증(특히 엣지 케이스)
  • Netflix 예시
    • “내 리스트”, “비평가 극찬 TV쇼” 같은 테마 행 렌더링
    • 엣지 케이스: 사용자가 아무것도 추가하지 않았을 때 “내 리스트” 렌더링
    • 영화 목록의 다양한 요소(재생, 좋아요 버튼, 더 보기 등) 클릭 시 올바른 페이지가 렌더링되는지 확인

4.1.6 엔드투엔드 테스트

  • 시나리오 테스트와 비슷하지만 시스템·제품 행동을 더 포괄적으로 다룸. 가장 미션 크리티컬한 시나리오 테스트를 위한 설정으로 활용

  • Netflix 예시에서는 “헤드리스(headless)” 브라우저(그래픽 UI가 없는 브라우저)를 사용해 실제 사용자 클릭을 시뮬레이션함(단순히 하부 기능만 테스트하는 것이 아님). 가짜 DB 대신 프리프로덕션·QA 환경의 데이터베이스에서 동작하기도 함

  • 그림 4-3은 전형적인 e2e 테스트 구조를 보여줌 (아래는 상응하는 시각적 도식)

flowchart LR
    DB[프로덕션 DB의 테스트 사용자] -- Shows --> BE[백엔드]
    BE -- Query --> DB
    BE -- HTML, JS --> HB[헤드리스 브라우저]
    HB -- Requests from clicks --> BE
    HB <--> TH[테스트 하네스]
  • 실제 프로덕션 쇼 데이터로 실행되므로 콘텐츠 변화에 탄력적이어야 함
    • 예: “신규 사용자 경험” 시나리오 테스트를 e2e로 만들면, 인기 영화를 하드코딩하는 대신 테스트 내에서 별도로 가장 인기 있는 쇼를 조회함. 최신 시즌의 <오징어 게임>이라면 홈페이지에 노출되는지 기대할 수 있음
  • e2e 테스트가 발견할 수 있는 버그 유형
    • 인기 영화 데이터베이스를 채우는 야간 배치 작업이 실행되지 않음
    • 신규 사용자 계정 생성 기능의 회귀
    • UI 요소가 의도치 않게 화면에서 숨겨짐
  • 지연시간(latency) 회귀 감지도 가능함 - 개별 실행 속도는 변동이 커 신뢰하기 어려우므로, 각 e2e 테스트를 여러 번 실행해 평균 속도를 비교함으로써 플로우 회귀 여부를 파악할 수 있음
  • 팀들은 e2e 테스트를 많이 작성하지 않는 경향이 있지만, 중요한 릴리스를 막을 수 있는 한 자주 실행되지 않아도 중요한 역할을 함
  • e2e 테스트 작성 시 주의사항
    • 사용자 가치를 반영하지 않는 단언·가정은 혹독하게 대가를 치르게 함
    • 하드코딩한 짧은 sleep은 바쁜 테스트 환경에서 타임아웃될 것 - 콜백이나 폴링으로 대기할 것
    • 디자이너가 요소 순서를 바꾸면 “페이지의 세 번째 요소” 같은 위치 의존 테스트는 깨짐 - UI 요소에 영구적인 시맨틱 태그를 부여해 자동화로 검색 가능하게 할 것(예: Netflix 페이지에서 play_video, more_info 태그로 버튼 검색)

4.1.7 사용자 인수 테스트

  • 정의: 일부 직원이나 자원자에게 프리프로덕션 환경에서 테스트 시나리오를 수행하게 하고, 출시 전 반드시 통과해야 함
  • 주로 쓰이는 상황
    • 국제화·현지화: 여러 언어권 화자가 다양한 상황에서 제품 동작을 검증. 엔지니어가 직접 테스트하기엔 너무 다양하고 복잡함
    • 컴플라이언스 표준상 요구되는 테스트
    • 거대 언어 모델(LLM) 출력 검증. 특히 레드 티밍(red teaming)은 사용자가 적대적 태도로 AI가 의도되지 않은 행동을 하도록 유도해보는 것
  • 실제 사람에게 의존하는 테스트는 느리고 비용이 크지만, 검증에 집착한다면 자동화 테스트가 준비되지 않았거나 실행 불가능할 때 기꺼이 마련해야 함
  • 더 흔한 경우: 사용자 피드백을 다른 방식으로 채굴함(5장에서 다룸)
  • 결론: 자동화 테스트는 “이중 위협(dual threat)“임 - 초기에는 중요한 도그푸딩을 제공하고, 이후에는 제품이 진화해도 지속적으로 가치를 제공함
  • 문서화 역시 동일한 이유로 “이중 위협”

4.2 문서 주도 개발

  • 격언: “무언가를 배우는 가장 좋은 방법은 그것을 가르치는 것이다.” 제품에도 동일하게 적용됨. 문서를 작성하는 것은 제품을 배우고 개선하는 동시에 가르치는 훌륭한 방법
  • 엔지니어는 종종 기술 제품 문서를 작성·검토하도록 요청받는데, 저자는 이 과정에 깊이 관여할 것을 권장함
    • 글쓰기가 편하지 않다면 동료나 AI 어시스턴트와 협업할 것 - 엔지니어는 기술적 내용과, 제품을 만들고 사용자를 학습해온 사람만이 가진 편집적 통찰을 제공
  • 도그푸딩 관점: 제품 주기 초반에 문서를 스케치해두면, 나중에 고치기 비용이 큰 실수를 예방할 수 있음
  • 문서 작성을 통해 문제를 발견하고 제품을 개선하게 됨. TDD에 대응하는 개념으로 “문서 주도 개발(Documentation-Driven Development)“이라 부름. 문서 초안은 코드가 작성되기 전에, 테스트보다도 더 일찍·저렴하게 작성 가능
  • 이 섹션에서 다루는 4가지 주제
    1. 문서의 한계에 대한 유의사항
    2. 사용자 시나리오(발견·학습·사용)별로 다른 문서 구축
    3. 사용자가 문서를 찾고 시나리오 간 전환하도록 돕는 방법
    4. 도그푸딩 연습으로서의 문서 작성

4.2.1 문서는 필수가 되어서는 안 됨

  • 사용자는 문서를 읽고 싶어하지 않음 - 콘텐츠를 찾고 읽는 데 쓰는 시간은 과제 수행에 쓸 수 있는 시간
  • 문서를 참고하는 사용자도 선택적으로 읽을 뿐, 처음부터 끝까지 읽지 않음
  • (2장 내용 상기) 제품 디스커버리 맵에서 우위를 점하기 위해 문서에 의존할 때는 주의가 필요함. 사용자 지식의 3단계
    • 아는 것(knowns)
    • 알려진 미지(known unknowns): 자사 제품에서 직접 경험하지 않았어도, 유사 제품 사용 경험을 바탕으로 기대하는 기능·행동
    • 모르는 미지(unknown unknowns) - 흔한 예시
      • 예기치 않은 위험한 동작
      • 신규 기능
      • 경쟁 제품과 차별화되는 비표준 기능
  • 대응책
    • 알림·팝업 같은 시그니파이어(2장)로 신규·비표준 기능을 사용자에게 알릴 것
    • 어포던스를 제한(8장)하고 확인(confirmation) 절차를 제공(3장)해 유해한 행동을 예방할 것
  • 이런 유의사항에도 불구하고, 복잡한 제품에는 문서화가 불가피하고 유용함. 설령 사용자가 문서를 직접 보지 않더라도, AI 어시스턴트가 작성된 모든 내용을 읽고 사용자에게 가르쳐 줄 것임

4.2.2 문서 독자를 위한 시나리오

  • 많은 엔지니어(저자 본인 과거 포함)는 문서를 아예 안 쓰거나, 쓰더라도 참조 문서(reference documentation)만 씀 - 기능 단위로 하나씩 설명하는 방식이 자연스럽기 때문
  • 그러나 실제로 참조 가이드를 얼마나 자주 사용하는지, 그전에 다른 유형의 문서를 먼저 시도해보는지 자문할 필요가 있음
  • (2장에서 소개한) 사용자 여정: 발견(discovery) → 학습(learning) → 사용(usage), 각 단계마다 필요한 문서가 다름
    • 발견: 신규 사용자가 목표는 알지만 어떤 제품·기능을 써야 할지 모르는 상태
    • 학습: 사용자가 막혀서 재사용 가능한 개념을 배우며 제품 지식을 깊이 있게 다져야 하는 상태. 스티키 사용자가 되려는 의도가 있을 가능성이 높음
    • 사용: 어떤 제품·기능을 써야 할지는 알지만 올바르게 사용하는 법은 모르는 상태. 신규·기존 사용자 모두 해당 가능하며, 고급 기능의 경우 특히 그러함
  • 예시: 저자의 C++ 학습 경험
    • 대학 첫 프로그래밍 수업 교재가 Bjarne Stroustrup의 저서 《The C++ Programming Language》(3판) - 언어의 온갖 고급·특이 기능을 보여주는 고급 사용 문서였음
    • 당시 필요했던 건 알고리즘 과제를 풀기 위한 올바른 도구 발견과 기초 학습이었는데, 책은 사용(usage) 문서였지 발견·학습 문서가 아니었음
    • 페르소나에 맞지 않는 교재 선택으로 어려움을 겪었고, 이 책의 리뷰어 중 한 명은 부실한 학습 자료 때문에 컴퓨터공학을 중퇴했다가 나중에 복귀해 시니어 스태프 엔지니어 겸 전문 컴퓨터공학 강사가 되었다고 고백함
    • 복잡한 제품(C++ 등)일수록 신중한 입문 안내가 필요함
  • 각 시나리오가 요구하는 문서 유형 정리 (표 4-1)
문서 유형 대응 사용자 여정 단계
페르소나 메뉴 발견
시나리오 메뉴 발견
시작 가이드 학습
개념 가이드 학습
사용 가이드 사용
운영 가이드 사용
참조 가이드 사용
  • 이후 설명을 위한 예시 제품: Docker(애플리케이션 개발·배포·실행 플랫폼). 가장 유명한 추상화는 “컨테이너”로, 의존성을 함께 묶어 다양한 컴퓨팅 환경에 배포·실행할 수 있는 실행 프로그램을 가리킴
발견
  • 사용자가 자신의 시나리오를 제품에 매핑하려는 단계
  • 우선 자신이 타깃 페르소나인지 확인하고 싶어함 - 제품·기능이 “나를 위한 것인가?“에 대한 확신을 얻고자 함
  • 페르소나 메뉴: 신규 사용자에게 가장 흔하고 효과적인 방법
    • 예: 차량 호출 서비스 Lyft의 웹사이트에는 “라이더”, “드라이버”, “비즈니스” 항목이 있어, 자신이 해당 페르소나인지 확인 후 더 알아볼 수 있음

TIP: 타깃 페르소나를 명확히 하고, 각 페르소나에 맞춰 문서를 작성할 것.

  • 페르소나가 맞다고 확인되면, 이제 자신의 시나리오를 다루는지 확인하고 싶어함
  • 시나리오 메뉴: Docker는 페르소나 메뉴는 없지만 시나리오 메뉴를 보유
    • 애플리케이션의 빠르고 일관된 전달(delivery)
    • 반응성 있는 배포와 스케일링
    • 동일 하드웨어에서 더 많은 워크로드 실행
    • 이 헤딩들 중 “컨테이너”라는 단어는 하나도 없음 - 컨테이너 개념에 익숙하지 않은 사용자는 “컨테이너”로 검색하지 않고 위와 같은 메시지 속 용어로 검색하기 때문

TIP: 사용자가 있는 곳에서 만날 것. 사용자가 처음에 무엇을 생각하며 들어오는지와, 앞으로 무엇을 학습해야 하는지를 구분할 것.

  • 최상위 제품 발견 외에도, 사용자는 기능을 사용해가며 여러 작은 발견을 계속 겪게 됨. 문서 내부, 혹은 제품 표면에서 문서로 안내할 것(2장의 제품 디스커버리 맵 참고)
학습
  • 시작 가이드(getting started guide): 사용자가 제품을 탐색·사용하기 시작할 수 있는 지점까지 이끔. 발견에서 학습·사용 시나리오로 전환시키는 동시에 “직접 해보며 배우기(learn by doing)” 방식으로 가르침
    • (1장 내용 상기) 사용자는 제품 학습에 최소한의 두뇌 용량만 쓰고 싶어함 - 개념을 암기하는 것보다 시작 가이드의 지시를 따르는 편이 쉬움
  • 복잡한 제품의 경우 **개념 가이드(conceptual guide)**가 필요할 수 있음 - 제품의 온톨로지(2장에서 소개한, 구조화된 제품 어휘 개념)를 가르침

TIP: 개념 가이드는 제품 전반에서 재사용될 기본 개념(primitives)을 가르치는 데 사용할 것.

  • 저자의 Docker 경험: 프로그램을 컨테이너로 패키징하려다 막힘 - 사용 가이드로는 진전이 없었음. 무엇이 부족한지 정확히 모른 채 개념 가이드를 읽음 - “Docker 컨테이너는 서로 중첩(nest)될 수 있다”는 중요한 사실을 배움 - 문제가 단순해짐
  • 저자를 포함한 대부분의 독자는 마지못한 학습자로, 사용 가이드만으로 버티고 싶어함
  • 다른 문서 유형과 마찬가지로 개념 가이드도 어떻게 발견될지에 대한 스토리가 필요함 - 다른 문서 유형에서 개념 가이드로 자유롭게 링크할 것(Docker가 사용 가이드에서 “컨테이너 중첩” 개념을 미리 안내했다면 시간을 아꼈을 것)
  • 핵심 발견 전략: 개념에 대한 동의어를 사용해 콘텐츠를 검색 최적화 - 사용자가 다른 온톨로지를 갖고 들어올 경우에 대비
사용
  • 문서 독자가 특정 작업을 하게 되면 “사용자(user)“가 됨 - Docker 예시: 중첩 컨테이너 개념을 이해한 후에는 실제로 만드는 방법이 필요했음
  • 3가지 주요 사용 문서 유형: 사용 가이드, 운영 가이드, 참조 가이드
  • 사용 가이드(how-to 가이드)
    • 시작 가이드처럼 단계별 흐름을 제공하지만, 제품 소개가 아니라 특정 목표 달성을 도움
    • 제품에 문서가 필요하다면, 각 프로젝트의 북극성 시나리오 등 가장 중요한 시나리오 전부에 대해 작성할 것. 문서가 필요 없는 제품이라도 의도적인 도그푸딩을 위해 스케치해볼 수 있음
    • 발견 경로에 따라 명명
      • 무엇을 하고 싶은지는 알지만 어떤 기능을 써야 할지 모르는 경우 → 그 작업(task) 이름으로 검색 가능하게(Docker 예: “CI/CD 구성”, “Kubernetes에 배포”)
      • 이미 필요한 기능을 아는 경우 → 기능 이름으로 검색 가능하게(Docker 예: “Compose 객체 빌드하기”, “앱 컨테이너화하기”)
      • 제품 자체(에러 메시지 등)나 관련 작업·개념 문서에서 발견될 수도 있음
    • Docker의 태그 시스템: “DevOps”, “Deployment” 같은 태그를 클릭해 일치하는 가이드를 찾을 수 있음
  • 운영 가이드(operator’s guide) - 트러블슈팅 가이드 또는 런북(runbook)이라고도 불리는 특수한 사용 가이드
    • 사용자가 이전에 겪어본 적 없는 실패 모드를 마주한 상태이므로, 문제가 생겼을 때 에러 메시지에서 바로 발견 가능해야 함(3장 참고)
    • 특정 문제에 대해 딥링크 가능하게 만들 것(예: docs.docker.com/dhi/troubleshoot/#no-shell, docs.docker.com/dhi/troubleshoot/#no-package-manager) - 지원 담당자, 에러 메시지, 자동화된 봇 응답이 특정 문제에 대한 해결책으로 링크할 수 있도록. 링크는 안정적이고 최신 상태로 유지할 것
  • 참조 가이드(reference guide)
    • 개별 기능에 특화되어, 표면적 기능을 더 상세히 다룸
    • 사용 가이드가 모든 세부사항을 다루지 않듯, 사용 가이드는 참조 가이드로 링크될 수 있음. 참조 가이드는 세부사항·엣지 케이스·고급 사용법을 다룸
  • 문서 유형과 테스트 유형의 대응 관계
    • 시나리오·e2e 테스트 ↔ 사용 가이드·운영 가이드 항목
    • 특정 기능에 대한 기능 테스트 세트 ↔ 참조 가이드에서 언급된 모든 내용
    • 시나리오 테스트를 훑어보며 대응하는 사용 가이드가 빠져있지 않은지(또는 그 반대) 점검하는 것도 좋은 방법
문서의 웹

“학습은 필요할 때, 학습자가 흥미를 느낄 때 이루어져야 한다.” — 돈 노먼(Don Norman), 《The Design of Everyday Things》 저자

  • 사용자는 발견 → 학습 → 사용 순으로 항상 직선적으로 진행하지 않음. 작업 중 이 시나리오들 사이를 자연스럽게 오감
    • 제품을 정하면 발견이 학습이 되고, 새로운 문제가 열리면 사용이 다시 발견이 됨
    • 문제가 생기면 곧바로 운영 가이드로 던져짐. 사용법을 모르게 되면 사용에서 학습으로 전환
  • 따라서 문서들이 서로 촘촘히 연결되고, 제품에서도 연결되는 것이 중요함 - “빵부스러기 흔적(trail of breadcrumbs)“을 만들어 사용자가 사용·트러블슈팅 시나리오에서 학습 모드로, 문제에서 해결책으로 넘어가도록 도와야 함
    • 사용 가이드는 중요한 개념을 언급할 때 개념 가이드로 연결
    • 개념 가이드는 예시로 연결되는 식으로 계속 이어짐

TIP: 상호 연결된 문서의 웹을 구축할 것.

4.2.3 도그푸딩으로서의 문서 작성

  • 예시 1: 오픈소스 개발자 도구의 시작 가이드 작성
    • 신규 사용자 환경과 일치하도록 새 환경을 세팅해 처음부터 설치를 진행(shoe-shift)
    • 계속 에러가 발생함 - 몇 주에 걸쳐 설정해온 온갖 도구·환경 변수를 새삼 깨닫게 됨 - 가이드가 길어졌음을 발견
    • 불만족하여 모든 의존성을 담은 컨테이너를 출시하기로 결정 - 가이드에서 번거로운 단계들을 삭제하고 컨테이너 실행이라는 훨씬 짧은 지침으로 대체

TIP: 사용 가이드가 얼마나 짧아질 수 있는지로 기능 인터페이스의 품질을 측정할 것.

  • 예시 2: SaaS 서비스 온콜 담당자로서, 메모리 부족으로 프로세스가 죽어 디버깅에 어려움을 겪은 사용자들을 지원한 뒤 런북 항목 작성
    • 작성 중, 사용자가 스스로 리소스 한도를 설정할 수 있게 했다면 문제를 애초에 피할 수 있었음을 깨달음
    • 런북 항목은 출시하되, 이후 해당 설정 기능을 추가해 그 필요성 자체를 없애 나감
  • 문서 작성 과정은 shoe-shift를 강제하며 사용자의 여정을 고민하게 만듦 - 작성 내용이 지루하거나 설명하기 어렵게 느껴질 때 짜증이 남. 이 고통을 제품 개선으로 전환할 수 있음. 핵심은 조정이 아직 저렴할 때, 즉 초반에 실행하는 것
  • 문서 작성을 개발 프로세스에 일찍 통합하는 방법 예시
    • 설계 전에 페르소나·시나리오 메뉴 작성: 아마존은 프로젝트 시작 시 가짜 보도자료(press release)를 작성해 제품 출시 후 대중에게 어떻게 커뮤니케이션할지 미리 예측함(정상적인 보도자료처럼 제품의 가치 제안을 사용자에게 설명). 이 “마케팅 우선(marketing-first)” 관행을 채택하면 타깃 페르소나 이해와 제품 가치 평가에 도움이 됨
    • 기능의 초기 버전을 사용하며 사용 가이드를 일찍 작성: 불필요한 단계나 까다로운 결정을 메모해, 아직 저렴할 때 사용성 문제를 포착
    • 팀이 핵심 추상화에 대해 정밀한 설명으로 합의하도록 개념 가이드 작성: 설명이 힘들다면 이름을 바꾸거나 재구성을 고려. 개념이 사용자가 신경 써야 할 만한 것 같지 않다면 더 상위 수준의 개념을 제시할 필요가 있을 수 있음
    • 자신의 기능이 허용하는 문제를 사용자가 어떻게 트러블슈팅할지 스스로 설명하도록 강제: 문제를 문서화·소통·지원하는 것보다 안전 이슈 자체를 고치는 편이 더 저렴할 수도 있음
  • 또 다른 활용법: 이상적인 제품 경험 상상하기 - 코드를 작성하기 전, 미래 사용자에게 제품 경험을 설명하고 팀과 공유함. 이후 프로젝트는 그 문서를 사실로 만들기 위해 필요한 모든 기능을 수행해야 함. 이런 방식으로 문서 주도 개발을 프로세스 초반·깊숙이 통합할 수 있음
  • 결론: 문서화는 사람과 AI 모두를 위한 발견·이해·사용 시나리오에 도움이 됨. 문서를 출시하지 않는 제품이라도, 가짜 문서를 작성해보는 것이 이 시나리오들에 집중하게 하고 도그푸딩을 더 흥미롭게 만들 수 있음
  • 전환: 테스트와 문서화는 훌륭하지만 외부 관점도 필요함. 외부인은 시간이 많지 않으므로 가벼운 참여 방식이 필요함 - 마찰 로깅으로 연결됨

4.3 마찰 로그

  • 지금까지 시나리오 테스트를 작성하고 가이드를 저술했으며, 종종 초기 버전의 제품을 사용하며 정확성을 확보함
  • 그러나 아무리 뛰어난 프로덕트 사고를 가진 사람이라도 다른 사람들이 실제로 무엇을 하는지에 자주 놀라게 됨. 사용자를 참여시켜야 할 때이며, 가장 관대한 사용자부터 시작함
  • 도그푸딩 시에는 자사가 만든 소프트웨어를 사용하며, 마찰 로깅은 이를 진행하는 훌륭한 방법
  • 마찰 로그(friction log) 정의: 제품을 사용하며 작성하는 비공식 문서. 경험한 혼란이나 장애물을 보고하고, 완료 후 담당 엔지니어나 PM에게 전달함
  • 마찰 로깅은 도그푸딩 에너지를 생산적으로 쓰도록 방향을 잡아줌 - 전달해야 할 짧은 문서가 있다는 사실이 계속하게 만드는 동기가 됨
  • Stripe 사례
    • 저자가 Stripe 재직 시 마찰 로깅을 문화로 접함
    • 당시 CTO였던 David Singleton은 매년 몇 주씩 제품의 다양한 구석과 내부 인프라를 사용하며 마찰 로그를 작성
    • 저자가 만들던 내부 Workflow Engine을 그가 도그푸딩한 주는 저자와 팀에게 매우 흥미로운 경험이었음
    • 그는 심지어 “언더커버”로 활동 - 다른 엔지니어를 통해 지원 채널에 질문을 올려, 자신이 엔지니어링 VP라는 사실이 알려져 생기는 편향 없이 지원 경험을 이해하고자 함
    • 그의 마찰 로그는 긍정적이고 건설적이었음
  • 효과: 제품 개선, 팀 사기 진작, 엔지니어링 전반에 걸친 우호적·건설적 마찰 로깅 문화 유지, 본인도 제품·인프라 최신 상태 유지에 도움

4.3.1 마찰 로그 작성하기

  • 마찰 로그는 마찰(friction)에 초점을 맞춘 시나리오 일지. 목표는 단순히 제품 경험을 보고하는 것이며, 제품팀이 이를 알아서 활용
  • 정보 흐름을 안내하는 시나리오 구성
    • 자신이 대표하는 페르소나 소개 - 제품에 처음인지, 어떤 선입견(prior)이 경험에 영향을 줄 수 있는지
    • 제품을 사용하려는 의도 표현
    • 본문 대부분은 경험한 가장 중요한 부분을 서술
  • 예시 마찰 로그 요지(Instagram에서 동영상을 편집해 소리를 끄고 게시하는 과정)
    • 작성자는 “Drewbie”(Drew+Newbie)라는 페르소나 - TikTok 경험은 있으나 Instagram은 처음이며, 소리를 끈 동영상을 올리고 싶다는 의도로 시작
    • 사진·동영상 라이브러리 전체 권한을 주지 않은 상태라 “개인정보 관리”로 들어가 특정 동영상에만 권한을 준 뒤, 다시 그 동영상을 게시물용으로 선택해야 했음 - 최근 다른 앱들의 더 매끄러운 흐름과 비교하며 왜 Instagram은 그렇지 않은지 의문을 가짐
    • 아이콘 행에서 마이크 아이콘을 클릭하면 음소거가 될 것으로 기대했으나, 실제로는 재생 버튼과 “오디오 추가” 버튼이 있는 스크러버가 나타나 예상과 달랐음
    • 도움이 될 만한 아이콘을 찾지 못하다가, 우연히 오른쪽으로 스와이프해보니 화면 밖에 더 많은 아이콘이 있었음을 발견함 - “스피커” 아이콘으로 음소거가 가능했고, 이 과정은 매우 직관적이었으며 향후 쓸만한 “음성 부스트” 기능도 발견함(칭찬 포인트로 언급)
  • 서술 방식에 대한 설명
    • 처방적 조언이 아니라 경험을 서술하는 이유는, 무엇이 가능하거나 바람직한지는 제품팀이 더 잘 알기 때문
    • “아이콘을 두 줄로 배치해야 한다”처럼 구체적 해결책을 직접 제시하면, 팀이 자기 역할을 대신한다고 느끼거나 로드맵을 대신 정한다고 느끼거나, 그 특정 아이디어를 거절하고 다른 대안을 브레인스토밍하지 않게 될 수 있음 - 상대를 잘 모를수록 더욱 조심스럽게 접근해야 함
    • 잘 작동한 흐름도 일부러 짚어주고, 제품 경험이 잘 맞았을 때 칭찬을 곁들일 것 - 이렇게 하면 마찰이 두드러지고, 팀이 잘하고 있는 지점도 알게 됨
  • 완료 후에는 팀에 전달함 - 팀이 피드백을 걸러내고 실행 항목을 우선순위화(triage)할 것이라 기대하며 넘김

4.3.2 마찰 로그 받기

  • 마찰 로그를 받는 것은 매우 유용함 - 사용자가 제품을 실제로 어떻게 경험했는지에 대한 근본적 사실(ground truth)은 귀중함
  • 그러나 사람들을 제품에 참여시키기는 쉽지 않음 - 재미있을 수 있지만 다들 바쁘고, 대가 없이는 하지 않으려 함. 참여시킬 만한 대상
    • 자기 자신 - 초보자 모자를 쓰고 임할 것
    • 얼리어답터 - 마찰 로그 작성을 요청
    • 신규 팀원 - 제품 세부사항 온보딩 방법으로 마찰 로깅에 초대
    • 개발자, 디자이너, 데이터 과학자 등 - “버그 배시(bug bash)“나 해커톤 같은 사회적 제품 사용 행사로 모으기
  • 피드백을 받았을 때는 방어적으로 굴지 말고 선물로 여길 것
    • 티켓을 열거나 어떤 프로세스를 따르라고 요구하지 말 것 - 장애물을 만들면 사람들이 시간 쓰기를 꺼리게 되고, 피드백이 가치 없다는 신호를 줌
    • 루프를 닫을 것 - 누군가의 마찰 로그가 개선으로 이어졌다면, 가능하면 그 사람의 매니저도 알 수 있는 방식으로 알려줄 것(저자의 회사에는 이를 위한 “kudos” 시스템이 존재)
    • 팀이 압도당하는 상황이어도 메신저(전달자)를 탓하지 말 것 - 시간 압박은 피드백이 암시하는 제품 개선 사항을 우선순위화(triage)하는 단계에서 다룰 것

4.3.3 마찰 로그 문화

  • 마찰 로깅은 타인의 제품에 대해 자유롭게 코멘트하고 지적할 수 있는 안전한 공간이어야 하며, 받는 팀은 발견된 내용을 언제·어떻게 우선순위화할지 스스로 선택할 수 있어야 함 - 양측이 자신의 역할을 알면 상호작용은 대체로 훌륭하게 흘러감
  • 구체적 규범 없이 갑자기 팀에 피드백을 주면 공격적이거나 비판적으로 느껴질 수 있고, 상대가 하던 일을 멈추고 문제를 고쳐야 한다고 기대하는 것인지 불분명해질 수 있음 - 지적을 하면 남들이 자신을 까다롭다고 여길까 걱정될 수 있고, 기대치가 불분명하면 상대의 기분을 상하게 할까 하는 우려가 커짐
  • 아직 규범이 없다면, 마찰 로깅이 인터넷에서 찾을 수 있는 기존에 정의된 관행이라는 점을 활용할 수 있음 - 저자는 현재 회사에 이를 도입할 때 기존 블로그를 참조해 설명했고 좋은 반응을 얻었으며, 이후 다른 사람들도 마찰 로그를 작성하기 시작함
  • 결론: 마찰 로깅은 재미있고 생산적인 도그푸딩 방법이며, 제품 프로세스의 일환으로 적극 요청해야 할 활동

4.4 샘플

  • 모든 제품이 샘플을 활용할 수 있는 것은 아니지만, 생산성·창의성·제품 개발 관련 애플리케이션은 대개 이득을 봄
  • 예시 제품: 스프레드시트 프로그램(Microsoft Excel, Google Sheets 등)
  • 샘플의 두 가지 종류
    • 기능 샘플(feature sample): 기능 테스트처럼 단일 기능(정렬, 필터링 등)을 시연
    • 시나리오 샘플(scenario sample): 시나리오 테스트·사용 가이드처럼 핵심 사용자 시나리오를 돕는 기능 조합을 시연. 예를 들어 사무용품 예산(office supply budget) 스프레드시트는 일반적인 예산 관리 모범 사례를 보여주는 동시에 실제 사무용품 예산을 담당하는 사람에게 직접적으로 유용함
  • 사무용품 예산 예시로 정리하는 샘플 작성 조언
    • 자동화 테스트에 샘플을 포함시켜 계속 작동하도록 유지하고, 시나리오·e2e 테스트 역할도 겸하게 할 것 - 예산 스프레드시트 템플릿이 에러 없이 로드되는지 확인하고, 숫자를 넣어보고 남은 현금을 테스트로 검증
    • 구체적인 시나리오와 언어를 사용할 것 - “iniTECH”라는 소프트웨어 회사, “빨간 스테이플러”, “바퀴벌레 약” 같은 그럴듯한 구매 항목으로 데이터를 미리 채울 것. 사용자는 “item1”, “foo” 같은 추상적 용어보다 구체적인 것으로부터 훨씬 쉽게 유추함
    • 코드 주석처럼, 샘플에서 특정 선택을 한 이유를 사용자에게 가르칠 것 - 초보 스프레드시트 사용자 대상이라면 설명적인 소제목을 달고, 상대·절대 셀 참조 사용에 관한 문서로 연결하는 식
    • 발견 시나리오를 고려할 것 - 사용자가 겪고 있는 문제를 바탕으로 올바른 샘플을 어떻게 찾을 것인가. 기능 샘플의 경우, 문제를 해결해줄 기능 자체를 모른다면 어떻게 할 것인가 - 템플릿 카탈로그에 등재하고, 템플릿이 많다면 태깅 시스템으로 “회계”나 “재무” 아래 분류
    • 샘플을 작성하며 마찰 로그를 남길 것 - 그 경험을 제품에 다시 피드백. 다른 팀이 샘플 작성을 담당한다면, 그들에게도 마찰 로그를 남기도록 환영할 것

4.5 요약

  • 제품 설계·개발 전반에서 제품에 실제 사용 압력(product pressure)을 가할 방법을 찾아, 실제 사용 환경에서도 버틸 수 있는지 확인할 것
  • 테스트, 문서화, 마찰 로그 공유는 모두 이를 위한 효과적 방법
  • 모든 테스트가 동등하지 않음 - 일부는 제품 테스트에, 일부는 시스템 테스트에 더 적합
    • 시나리오·e2e 테스트: 제품을 신뢰성 있게 문서화하고 계속 작동함을 보장
    • 기능 테스트: 테스트 커버리지를 보완
  • 모든 문서가 동일한 목적을 갖지 않음 - 사용자 여정의 여러 시나리오에 따라 몇 가지 문서 유형이 필요할 수 있음
    • 사용 가이드·시작 가이드: 제품을 시도해보는 훌륭한 구조화된 방법
    • 초기 개념 가이드 작성: 본인과 팀이 아이디어와 제품 커뮤니케이션 전략을 정교화하는 데 도움
  • 마찰 로깅: 동료로부터 실행 가능하고 솔직한 경험적 피드백을 얻는 훌륭한 방법

4.6 예제

[문제 1] 관심 있는 위키백과 문서 하나와 그 안의 한 문장을 선택할 것. 해당 문장의 원 작성자를 찾아보고, 그 경험을 마찰 로그로 기록할 것. [문제 2] 첫 번째 질문에 대한 저자의 마찰 로그(답안 1)를 읽어볼 것. 만약 위키백과 개발팀의 신입이고 그 피드백을 처리해 UI를 다듬는 임무를 맡았다면, 어떤 정보를 더 수집하고 어떤 개선을 검토하겠는가? [문제 3] WikiBlame 기능 팀 소속이며 테스트 커버리지를 보강하고 있다고 가정할 것(관심 있는 위키백과 문서에서 “역사 보기”를 클릭한 뒤 “추가/삭제 찾기”를 선택하면 WikiBlame으로 이동 가능). 가장 중요한 시나리오 테스트를 최소 2개 포함한 테스트 계획을 세울 것. 테스트 프레임워크가 UI 상호작용을 시뮬레이션할 수 있다고 가정할 것. [문제 4] Microsoft PowerPoint의 “애니메이션” 기능은 발표자가 클릭할 때마다 슬라이드 콘텐츠를 점진적으로 드러낼 수 있게 해준다. 사용 가이드에는 “단어를 한 줄씩 나타나게 하기”라는 섹션이 있다. 문서 독자를 위한 3가지 주요 사용자 시나리오 관점에서, 왜 이런 표현을 선택했을지 설명할 것.

  • 마지막 과제: 현재 작업 중인 것을 문서화해볼 것. 아직 만들지 않았다면, 상상하는 대로 중요한 사용자 시나리오를 문서화할 것. 이미 존재하는 것이라면, 추천 흐름을 직접 따라가보고 그 경험에 맞춰 문서를 작성해볼 것

4.7 답안

[답안 1] 위키백과에서 문장 작성자를 찾아보는 마찰 로그 요지

  • 페르소나: “Drewbie”, 소프트웨어 엔지니어. 위키백과는 주로 독자로 이용했고 편집 경험은 거의 없어 파워유저 도구는 처음 다룸
  • 의도: Microsoft Puzzle Hunt 관련 문서에서 깨진 링크를 발견해 원 편집자에게 연락하고 싶었음
  • git blame 같은 기능을 기대하며 “역사 보기”를 클릭했으나 사용하기 어려운 형식이었고 “blame” 버튼이 없었음. “소스 보기”에서도 관련 기능을 찾지 못함
  • 역사 페이지로 돌아가 “사용자별 편집 찾기”를 시도했으나 사용자명을 몰라 실패 - 구글 검색 결과 “추가/삭제 찾기”를 클릭하라는 안내를 얻음
  • “WikiBlame” 페이지(이름에 “blame”이 들어있어 반가움)에서 문구를 검색 - “Comparing differences…” 형태의 목록이 나왔으나 숫자와 표기 방식을 이해하지 못함
  • 목록 맨 아래에서 “삽입이 발견됨(insertion found)” 항목을 찾고 원문 삽입 지점으로 추정해 링크를 클릭
  • 사용자명이 보이지 않고 IP 주소만 있어서, 익명 사용자의 편집임을 한참 후에야 깨달음(레이블이 있었으면 더 명확했을 것이라는 소감)
  • 저자의 결론: 위키백과 팀에 전달한다면 “blame이 없다”는 피드백은 대부분 편집자가 소프트웨어 엔지니어가 아니므로 무시될 수 있으나, IP 주소가 사용자명 자리에 나온 것에 대한 혼란은 “Author”와 같은 레이블 추가를 고려할 만한 타당한 피드백 [답안 2] 앱 팀 입장이라면 “blame” 관련 피드백은 소프트웨어 엔지니어 페르소나에게만 와닿는 용어이므로(위키 편집자들 사이에서 흔히 쓰이는 표현이 아니라면) 우선순위에서 제외할 가능성이 높음. 반면 “Comparing differences” 같은 난해한 텍스트를 정리하는 것에는 더 무게를 둘 것. IP 주소·사용자명 필드에 “Author” 같은 레이블을 붙이는 것은 비교적 명백한 사용성 개선으로 보이며, 특히 익명 편집이 흔하다면 더욱 그러함 [답안 3] 두 가지 핵심 시나리오 테스트
  • 흐름 테스트: 대부분의 사용자가 “역사 보기” 페이지를 통해 WikiBlame에 진입한다고 가정. 몇 개의 편집이 있는 간단한 테스트 페이지를 만들고 대응하는 역사 보기 페이지를 렌더링. 페이지에서 “추가/삭제 찾기” 링크를 검색해 WikiBlame으로의 링크를 추출. 사전 채워진 검색창들이 많으므로, 간단한 검색이 실제로 동작하는지 확인하기 위해 검색창에 검색어를 입력하고 “시작” 클릭을 시뮬레이션. 결과에 대해서는(더 철저한 검증은 기능 테스트에 있다고 가정하고) 가벼운 검증만 수행
  • 확장성 테스트: 가장 인기 있는 문서일수록 WikiBlame 사용도 잦을 것이라 가정. Taylor Swift 문서처럼 편집 이력이 매우 긴 문서의 편집 수를 참고해, 유사하게 긴 편집 이력을 문서 전체에 흩뿌려 프로그래밍적으로 생성한 테스트 페이지 작성. 해당 페이지에서 검색 쿼리를 실행해 타임아웃 내에 완료되는지 확인. 결과도 검증해, 해당 검색어에 대한 올바른 개수의 편집이 반환되는지 확인 [답안 4] “단어를 한 줄씩 나타나게 하기”라는 표현은 “애니메이션”이라는 기능명을 모르는 사람이 이 문서를 발견할 수 있도록 돕기 위한 것 - 발견 시나리오를 겨냥한 문구. 반면 “애니메이션”이라는 용어 자체는 이미 필요한 기능을 알고 있는 사용자를 위한 것으로, 학습이나 사용 시나리오의 사용자를 돕는 역할을 함

CHAPTER 5 지속적인 사용자 이해

장을 여는 인용구 (요지)

  • Everett Rogers의 저서 Diffusion of Innovations를 인용한 “The Innovation-Adoption Curve”(High Tech Strategies)의 문구를 저자가 재인용함
  • 변하는 것은 사람이 아니라 혁신 그 자체이며, 혁신의 성공은 점점 더 까다롭고 위험을 회피하려는 사용자층의 니즈를 얼마나 잘 충족하도록 진화하는지에 달려 있다는 내용

저자의 커리어를 통해 본 배포 주기의 진화

  • 2005년, Microsoft Visual C++ 컴파일러 첫 출시(첫 번째로 배포한 소프트웨어이자 첫 번째로 낸 버그)
    • 중국어 간체 환경에서 컴파일러가 항상 크래시하는 심각한 결함 발생
    • CD 배포 방식이라 패치 불가, 다음 릴리즈는 수년 뒤 -> 지식문서(KB)에 우회법(특정 파일 삭제) 안내로 대응한 것이 최선이었음
    • 저자는 이것이 자신의 역량 문제라기보다 당시 업계 관행의 한계였다고 평가함
  • Windows 7 (개발기간 3년, 일부 고객은 여전히 CD 박스 구매)
    • 유닛/기능 테스트 없이 테스트 엔지니어에 크게 의존
    • 다만 치명적 결함에 한해 패치는 가능해짐
  • 2009년 Facebook 합류: 매주 배포로 전환
    • 일요일 밤 빌드 컷 -> 화요일 배포 전 안정화, 긴장되지만 매주 진행
    • 입사 초기 팀의 “테스팅 해커톤”에 참여 -> 회사 창립 5년 만에 처음으로 자동화 테스트를 작성한 사례였음
    • 이후 테스트 문화가 확산되며 배포 주기가 주 5일 -> 하루 여러 차례의 지속적 배포(CD)로 발전
    • 기능 플래그와 A/B 테스트가 코드베이스 전반에 자리잡음
  • 2018년 Stripe 합류
    • 이미 우수한 테스트 문화와 하루 여러 차례 배포 체계가 존재했으나, 초기엔 사람이 배포를 관리
    • 이후 대부분 서비스가 완전 자동 지속적 배포로 전환 -> 테스트 커버리지에 대한 신뢰로 배포 자체에 크게 신경 쓰지 않게 됨
  • 업계 전반: 소프트웨어가 클라우드로 이동, Visual C++ 같은 오래된 클라이언트 소프트웨어도 20년 전보다 훨씬 자주 패치됨
  • 자동차 산업 비유: 상위 10대 제조사 중 8곳이 OTA(Over-The-Air) 업데이트 지원, 화성 탐사 로버도 OTA 업데이트 가능

Figure 5-1: 시간에 따라 사용자에게 전달되는 제품 가치

  • 점진적(Incremental) 배포는 작고 빈번한 계단식 상승, 간헐적(Occasional) 배포는 크고 드문 계단식 상승을 보임
  • 임의 시점 T에서 두 곡선의 값 차이(Value(T))만큼 점진적 배포 쪽이 더 많은 가치를 이미 전달한 상태
  • 제품 전체 궤적이 개선되지 않더라도, 특정 시점에서는 사용자가 항상 더 많은 가치를 얻고 있음을 보여줌
xychart-beta
    title "시간에 따라 사용자에게 전달되는 제품 가치 (Figure 5-1 개념도)"
    x-axis ["T0", "T1", "T2", "T3", "T4", "T(측정 시점)"]
    y-axis "누적 가치 Value(T)" 0 --> 10
    line "점진적 배포 (Incremental)" [1, 3, 4, 6, 7, 9]
    line "간헐적 배포 (Occasional)" [1, 1, 1, 5, 5, 5]
  • 이제는 사용자의 목소리를 듣고 대응하기가 훨씬 쉬워짐 -> 더 이상 영구적인 버그가 남지 않음
  • 변경 프로세스를 자동화한 결과, 테스트에 더 많은 시간을 쓰면서도 오히려 더 빠르게 움직이게 됨

이번 장의 로드맵

  • 정성적/정량적 데이터를 수집하고 활용해 제품을 지속적으로 정의/개선하는 방법을 다룸
  • 엔지니어의 직무 설명이 단순 제품 개발을 넘어, 제품 개선 수단(디지털 트윈)을 함께 만드는 것으로 확장됨을 먼저 설명
  • 이어서 코드/제품을 “변화를 위해 설계”하는 소프트웨어 엔지니어링 원칙을 다룸
  • 그 다음 정성적 데이터(제품 주기 전반의 사용자 피드백, 특히 지원 상호작용)를 다룸
  • 마지막으로 정량적 데이터(제품 지표 선정/수집)를 다룸

5.1 새로운 직무 설명: 디지털 트윈 관리자

  • 더 빠른 배포 주기로의 전환은 공짜가 아니었음: 업계 전체가 이를 달성하기 위해 엄청난 노력을 기울였고, 이는 사실상 엔지니어 직무 설명의 전면 개편에 가까웠음
  • 2014년 Microsoft가 테스트 직군과 엔지니어 직군을 하나로 통합한 사례가 유명함
    • 코드를 테스터에게 넘기는 방식은 너무 느림
    • 엔지니어 스스로 품질 결과를 책임지도록 유도하려는 목적

새로운 원칙(직무 규칙)

  • 테스트는 우리가 직접 작성함
  • 지표를 만들고 제품을 모니터링함
  • 데이터 파이프라인에 데이터를 내보내고 제품 분석을 지원함
  • 피드백 채널에 노출되어 고객과 직접 연결됨
  • 점진적 롤아웃과 A/B 테스트를 수행함

NOTE: 디지털 트윈(Digital Twin)이란, 실제 운영 중인 시스템과 그것이 구동되는 하드웨어 기반에 대한 가상의 표현이자 시뮬레이션 집합을 의미함

  • 자율주행차 비유: R&D 부서가 차량/센서에 대한 고정밀 디지털 표현과 시뮬레이션 환경을 보유함. 인간 운전자 행동과 탑승자 안전 제약을 반영해 시뮬레이션 상에서 운전 소프트웨어를 테스트/개선함

일반 소프트웨어 제품에서의 디지털 트윈 구성요소

  • 자동화된 테스트 및 테스트 환경

  • 지표(metrics)와 알림(alerting)

  • 베타 티어

  • 프로덕션 계측(instrumentation)

  • 분석(analytics)

  • 트레이스(traces)

  • 피드백 포럼에서 얻은 피드백

  • 고객 지원 채팅 로그

  • 재현 사례(repro case)

  • 고객 발굴 인터뷰 전사록(transcript)

  • 즉, 제품이 실제 환경에서 어떻게 작동하는지에 대한 정성적/정량적 그림을 그리는 데 도움이 되는 모든 것을 포함함

  • 현대 엔지니어의 역할: 제품과 함께 제품의 디지털 트윈을 구축하고, 롤아웃 모니터링·A/B 테스트 성과 비교·로드맵에 피드백 반영 등에 이를 활용하는 것

디지털 트윈의 일부는 다른 챕터에서 다룸

  • Chapter 4에서 다룬 테스트: 프로덕션에서 관찰되거나 예상되는 상황을 기반으로 작성함
  • Chapter 9에서 다룰 부하 시뮬레이션(load simulation): 실제 트래픽 캡처본이나 이에 준하는 데이터로 디지털 트윈에 부하를 가함
  • Chapter 4에서 다룬 마찰 로그(Friction Log): 제품 사용 경험을 기록함

5.2 변화를 위한 설계

  • Everett Rogers의 Diffusion of Innovations: 기술 혁신과 확산은 피드백과 반복(iteration)을 축으로 한 순환 구조를 가짐
  • 기술이 널리 퍼지려면 점점 더 위험을 회피하고 접근성이 낮은 계층에게도 소구해야 함 -> 빠른 확산을 원한다면 각 신규 사용자층을 가로막는 요인을 빠르게 파악할 수 있도록 빠른 피드백 순환 구조를 구축해야 함

엔지니어의 세 가지 역할 (기능을 만들 때마다 충족해야 할 것)

  1. 제품 요구사항을 충족시킴
  2. 그것이 요구사항을 충족하며 앞으로도 계속 그럴 것이라는 신뢰를 구축함(그래야 배포를 자동화할 수 있음)
  3. 새로운 요구사항이 들어올 때 쉽게 진화할 수 있도록 만듦
  • 이 세 요소가 없으면 팀은 고객과 멀어지고, 고객을 제대로 서비스하지 못하게 만드는 과도한 제약에 갇히게 됨
  • 이 세 가지를 동시에 해내는 것이 소프트웨어 엔지니어링의 진짜 재미있는 도전이라고 저자는 말함

사례 연구: Oculus VR (2015)

  • 2015년, 저자가 Facebook에서 최근 인수된 Oculus VR(가상현실 회사)로 이동하며 겪은 경험
  • 2010년대 Facebook은 급진적 반복(rapid iteration)에 집착
    • 처음엔 “move fast and break things”(빠르게 움직이고 깨뜨려라) 슬로건
    • 반발 이후 2014년 “move fast with stable infra”(안정적인 인프라 위에서 빠르게 움직여라)로 다듬음
  • 이 접근법은 Oculus 팀에게는 생소했음 — 지연된 마감일과 진화하기 어려운 코드베이스로 어려움을 겪고 있었음
  • 저자 관점에서 가장 중요했던 것: Facebook이 만든 뛰어난 개발자 도구들 — Oculus 제품이 Facebook 스택 위에 구축되면 이를 활용 가능

개발자 생산성 엔지니어링

  • 2000년대 Google이 처음으로 “회사 엔지니어들의 개발을 반복적/안전/빠르게 만드는 것”을 미션으로 하는 중앙화된 팀 개념을 도입 -> 오늘날 흔히 Developer Productivity 팀이라 불림
  • 2025년 기준, 중대형 테크 기업에서 엔지니어링 생산성 관련 팀이 전체 엔지니어링 인력의 약 15~20%를 차지하는 경향

Oculus 인수 시점(2015)까지 Facebook 개발자 생산성 팀이 구축한 사례

  • Hack: PHP 위에 얹은 타입 시스템 — 더 안전한 코드 변경/리팩터링 지원

  • 사용하기 쉬운 기능 플래그(feature flag) 시스템 — 실험 및 안전한 롤아웃용

  • 빠르고 유연한 프로덕션 관측성(observability) 플랫폼 — 사전에 조회할 인덱스를 지정할 필요 없이, 데이터 파이프라인 변경 없이 궁금한 것은 무엇이든 조회 가능

  • EntSchema: 모델 개발을 쉽고 강력하게 만드는 데이터베이스 추상화 레이어(Chapter 8에서 상세히 다룸)

  • 신설 “dev prod” 팀의 초기 과제는 대개 디지털 트윈 구축/활용과 관련됨: 예) 모든 테스트를 실행하는 지속적 통합(CI) 솔루션

분석 마비

  • 저자가 Oculus의 앱스토어/로그인 시스템을 Facebook 인프라 위로 재구축(replatform)하는 작업에 투입됨 — 목표는 팀의 진행 속도를 높이는 것
  • Oculus 엔지니어들이 2년 뒤를 예측하고 언젠가 필요할지 모르는 기능까지 상상하며 설계하는 경향 관찰 -> 마라톤급 설계 회의와 과도한 엔지니어링(over-engineering)으로 이어짐
  • 시간이 지나며 저자는 이 태도가 그들의 툴링 환경에서는 오히려 합리적이었음을 이해하게 됨: 기존 기술 스택에서는 반복(iteration)이 쉽지 않았음
  • 팀은 좋은 디지털 트윈이 없었고 적절한 추상화 레이어도 없었음 — REST API가 데이터베이스와 1:1로 매핑되어 있어 DB가 클라이언트와 강하게 결합됨 -> 한번 내린 결정에 오래 갇혀있을 것이라는 느낌을 받음
  • 저자 팀의 역할: 새로운 모회사의 기술/실무 문화를 “판매”하는 문화적/기술적 대사(ambassador) 역할

변화를 위한 기술적 기반

  • 전제: 팀마다 제약(하위 호환성 유지 필요성, 사용 데이터 수집 능력 등)이 다르므로 아래 목록 중 일부는 해당 팀에 맞지 않을 수 있음. 일부는 부담스러워 보일 수 있지만 시도해볼 만함

품질의 기본

  • 자동화된 테스트 작성(Chapter 4) — 가장 중요한 시나리오에 특히 노력을 집중함
  • 지속적 통합(CI) 테스트 사용 — 소스 저장소에 커밋되기 전 반드시 통과해야 함 -> 버그를 도입 시점에 빠르게 발견
  • 기능 플래그(feature flag) 활용 — 코드 배포와 독립적으로 기능을 켜고 끌 수 있는 토글. 신중하면서도 거의 즉각적인 변경을 가능케 함. 새 코드 도입을 점진적으로 확대(ramp up)할 수 있어 안전하게 새로운 시도를 하는 자신감을 만들어줌
  • 코드 리뷰 — 각 코드 변경이 고객 요구를 충족하고, 유지보수 가능하며, 잘 테스트되었는지 확인함
  • 운영 지표(operational metrics) 계측 — 오류율, 플로우 완료율, 성능, 용량 등을 실시간으로 추적. 모니터링 및 이상 시 알림 설정, 특히 나쁜 릴리즈 발생 시 초기에 문제를 포착하도록 조정함
  • 회복탄력성(resilience) 기본 요소 채택 — 큐, 재시도, 속도 제한(rate limit) 등. 한 시스템의 결함이 다른 시스템에 주는 피해(“blast radius”, 폭발 반경)를 제한함

TIP: 각 구성요소에는 그것을 신뢰할 수 있게 만드는 메커니즘을 내장해야 함. 즉, 모든 변경에는 그것이 제품 요구사항을 충족하며 앞으로도 계속 그럴 것이라는 검증이 함께 따라와야 함

  • Oculus에서 저자의 핵심 기여 중 하나는 테스트 문화를 정착시킨 것이었음 — Facebook의 테스트 프레임워크에 Oculus 전용 테스트 헬퍼를 결합, 몇 달간 코드 리뷰에서 테스트를 지속적으로 요구한 결과 새로운 문화가 정착됨 -> 궁극적으로 더 많은 출시와 더 나은 품질로 이어짐

신뢰를 넘어: 변화 자체를 쉽고 효과적으로 만들기

  • 비즈니스 성과, 고객 도입, 고객 성공과 연계된 제품 지표 추적 — 무엇을 바꿀지 좋은 의사결정에 도움
  • 시간이 지나며 쌓이는 하위 호환성 제약을 극복하기 위한 도구/프로세스에 집착 — 예) 데이터베이스 마이그레이션 도구, 업그레이드한 사용자를 깨뜨리지 않으면서 기존 사용자도 지원하는 API 신버전 제작 능력
  • 버전 관리된 하위 호환성을 위해 설계된 모듈, 타입 시스템, 프로토콜 등의 추상화 사용 — 팀이 잘 추상화된 인터페이스로 관심사를 분리하며 독립적으로 진행할 수 있게 도움

TIP: 각 구성요소에는 그것의 진화를 위한 메커니즘을 내장해야 함. 구성요소는 확장 가능하고, 유지보수 가능하며, 핵심 의사결정을 위해 계측되어 있어야 함

  • 저자가 Oculus 엔지니어에게 EntSchema(Chapter 8에서 다룸, 새 필드의 안전한 코드 생성과 DB 백필을 쉽게 해주는 모델링 프레임워크)를 보여준 경험 — 나중에 언제든 추가할 수 있으므로 PR에서 불필요하게 투기적인(speculative) 필드는 제거해도 된다고 설득함

더 발전된 기법들

  • 가능한 한 자주 배포 — 중요한 패치와 신기능을 자주 출시. 온라인 서비스라면 지속적 배포(CD)를 선호하고 블루/그린(blue/green) 또는 레인보우(rainbow) 배포를 사용해 신버전을 점진적으로 롤아웃하고 문제 발생 시 즉시 롤백

  • 베타 프로그램/얼리어답터 프로그램 운영 — 가장 열성적인 고객이 신기능을 테스트/개선하도록 하고, 향후 릴리즈가 그들에게 문제를 일으킬 수 있는지 미리 알려줌. 기능 플래그로 구현 가능한 경우도 있음

  • 점진적 롤아웃 — 무작위, 편향되지 않은 사용자 집단을 대상으로 시작해 100%까지 점차 확대. 매번 동일한 집단이 아니도록 하여 특정 사용자들이 불안정한 버전에 계속 노출되지 않게 함

  • 유연한 분석(analytics) 플랫폼 — 새로운 궁금증이 생기거나 예상치 못한 문제를 좁혀갈 필요가 있을 때, 수행 중인 쿼리를 쉽게 바꿀 수 있어야 함

  • A/B 테스트 프레임워크를 이용한 실험 — 서로 다른 버전의 기능 성능을 비교함

  • Facebook의 유연한 분석 플랫폼 Scuba가 Oculus 팀을 설득한 핵심 요인이었음 — 어떤 쿼리를 위해서도 미리 인덱스를 수동으로 구축할 필요 없이 실시간으로 데이터를 슬라이스/다이스할 수 있다는 점이 큰 매력 포인트였음

개선할 시간 찾기

  • 현재 코드베이스가 얼마나 신뢰할 수 있는지, 얼마나 쉽게 진화 가능한지 자문해볼 것 — 위 기법/기술 중 무엇이 도움이 될지 판단함
  • 낙관적일 이유: 개발자 기술 생태계가 점점 정교해져 훌륭한 기성(off-the-shelf) 기술을 통합할 수 있음. 코딩 어시스턴트/에이전트가 생산성을 높여주어 그 여유 시간을 조직 역량 향상에 쓸 수 있음
  • 제품 요청이 사방에서 밀려오는 상황에서 스택에 투자할 시간을 어떻게 찾을 것인가 — 앞서 언급한 15~20% 헤드카운트를 개별 팀 단위에서도 좋은 기준으로 제안함. 우선순위가 높은 생산성 개선 작업을 위한 시간을 로드맵에 확보할 것. 이러한 투자는 장기적으로 성과를 내지만 긴급해 보이지는 않는다는 점을 유의
  • Oculus는 Facebook 내 다른 조직과 다른 특수한 요구사항을 가짐 — 사용자 계정이 Facebook 계정이 아니었고, 고성능/고그래픽 게임을 지원해야 했음. 공통 인프라를 최대한 공유하면서도 특수 라이브러리/인프라가 필요했음

기술이 문화를 형성함

  • 기술은 팀 문화에 지대한 영향을 미침 — 프로세스/관행 문제의 해법이 문화가 아니라 기술인 경우가 많음. 엔지니어가 해결책의 일부가 될 수 있다는 점에서 좋은 소식

  • 새 인프라 위에서 몇 달간 제품을 재작성한 뒤 Oculus 팀은 완전히 전환됨 — 회귀를 막아주는 잘 테스트된 코드, 강력한 프로덕션 관측성, 설계 실수를 바로잡기 쉬운 빠른 변화 지향 인프라 덕분에 더 이상 그렇게까지 위험을 회피하고 신중할 필요가 없어짐 -> 멋진 것을 만드는 데 집중 가능해짐

  • 새 기술 스택을 기념하는 재미있는 티셔츠가 배포되었고, 저자는 지금도 가끔 입음

  • 재작성의 파급 효과는 명확했음 — 이후 몇 년간 팀은 새 스택 위에서 많은 신기능을 빠르게 설계/구축할 수 있었음(Chapter 7, 8에서 반복적 접근이 설계/우선순위 결정 시 집중력을 높여 더 나은 품질의 제품으로 이어지는 방법을 다룸)

  • 팀의 두려움을 낮추기 위해 반드시 대규모 재작성이 필요한 것은 아님

  • Facebook 본사 벽에 붙어 있던 유명한 포스터 문구(Facebook Analog Research Laboratory 제작): “두렵지 않다면 무엇을 하겠는가?”

  • 이 포스터의 의도는 창의성과 대담함(검증되지 않은 고임팩트 아이디어 조사, 동료에게 어렵지만 필요한 건설적 피드백 주기 등)을 북돋우려는 것이었다고 저자는 이해함

  • 그러나 두려움은 흔히 타당하고 합리적임 -> 저자의 질문 확장: “두렵지 않으려면 무엇이 필요한가?” — 어떤 프로세스/관행 문제를 기술로 해결할 수 있는가?

예시

  • 팀원들이 코드 테스트를 지속적으로 소홀히 한다고 느끼는가 -> 테스트를 더 쉽고 재미있게 만드는 테스트 프레임워크 개선을 고려하거나, 팀이 의존하는 서비스 테스트를 쉽게 해주는 페이크(fake) 라이브러리를 만들 것

  • 신버전을 스테이징하고 롤아웃하는 데 몇 주간의 신중한 테스트가 필요한가 -> 트래픽을 점진적으로 늘릴 수 있는 기능 플래그가 배포를 더 매끄럽게 만들어줄 수 있음. 지속적 통합 테스트는 도그푸딩/수동 테스트의 필요성을 줄여줌

  • 우선순위 결정에 몇 시간씩 논쟁하는가 -> 데이터가 부족한 것일 수 있음. 더 많은 제품 지표를 수집하거나, 추가가 어렵다면 더 나은 제품 분석 솔루션을 찾아볼 것

  • 신뢰할 수 있고 적응력 있는 기술 스택을 갖추는 가장 큰 이점: 예상치 못한 사용자 피드백으로 새로운 기능이 필요함을 깨닫는 순간, 즉시 행동에 나설 준비가 되어 있다는 것

5.3 사용자 피드백 수집

  • 사용자가 피드백을 줄 때, 그들의 이야기는 테스트와 함께 우리의 디지털 트윈에 편입됨 — 외부 세계가 우리 제품을 어떻게 인식하고 상호작용하는지 알게 됨
  • Chapter 4에서는 도그푸딩과 프릭션 로깅을 통한 자체 피드백(제품 초기 단계용)을 다룸 -> 이제는 외부 사용자로부터 배울 차례
  • 활용 가능한 기법: 베타 버전, 피드백 위젯, 챔피언 프로그램, 설문조사, 사용자 지원

베타 버전으로 실패의 파급 범위 낮추기

  • 얼리어답터가 지인/가족이든, 소비자 애호가든, 광범위 배포 전 소프트웨어를 테스트하려는 기업이든 — 덜 완성된 경험에 동의한 사용자로 테스트하면 대중의 신뢰를 잃지 않고도 피드백을 얻을 수 있음. 조금 더 빠르게 움직이고 버그의 파급 범위를 낮춤
  • 얼리 릴리즈 프로그램: 동의한 사용자에게 다른 사람들보다 먼저 업데이트 제공. 이들조차 인내심에 한계가 있으므로 충분한 품질을 유지해 만족한 고객으로 남게 해야 함
  • 베타 티어: 고객이 일부 환경에서 가리킬 수 있는 인터넷 서비스 버전 — 변경사항이 메인 프로덕션 트래픽에 도달하기 전 테스트 가능. 자신들에게 중요한 테스트를 직접 수행하고 문제를 사전에 알려줄 수 있음

피드백 위젯으로 목소리 낼 기회 제공

  • 고객이 피드백을 주기 매우 편리하도록 만드는 내장 폼 — 고객이 신경쓰고 있다는 신호를 주는 동시에, 사용자의 인사이트를 앱스토어 별점 1점이나 SNS 불만 대신 우리에게 향하게 함
  • 맥락(context)이 있는 피드백이 이상적 — 사용자가 특별히 신경 쓰지 않아도 필요한 맥락과 함께 피드백을 줄 수 있어야 함. 예:
    • 문서 내 인라인 코멘트로 무엇이 헷갈리거나 오래됐는지 알림
    • 사용자가 신규 기능을 사용하고 있음을 감지했을 때 피드백 요청 팝업
    • 모바일 앱에서 스크린샷, 앱 경로(route), 앱 버전, 폰 모델, OS 버전 등 맥락을 코멘트와 함께 수집
    • 신고하려는 불건전한 콘텐츠 안에 인라인 위젯 배치
    • 우리가 신경 쓰는 특정 유형의 추가 맥락을 제공하도록 사용자에게 유도

챔피언 프로그램으로 깊이 있는 피드백 확보

  • 때로는 커뮤니티로부터 더 깊이 있는 기여(심층 피드백, 전도/브랜드 앰버서더 역할, 오픈소스 기여 등)가 필요함
  • 챔피언 프로그램(예: Microsoft의 Most Valuable Professional 프로그램)은 이런 사람들에게 인센티브를 제공해 그들이 인정받고 계속 돕고 싶어지도록 동기부여함
  • 혜택 예시: 무료 소프트웨어/서비스, 조기 접근, 네트워킹 기회, 컨퍼런스 티켓, 평판상의 이익

설문조사로 폭넓은 피드백 확보

  • 과학적이고 반복 가능한 사용자 설문 설계는 이 책의 범위 밖이지만, 제품 중심 엔지니어에게 유용했던 두 가지 활용을 소개함
  • 자유 서술형 답변은 종종 사용자 공감(empathy)의 보고 — 예) 조직이 NPS(순추천지수) 감성 설문을 보낸다면, 자유 서술형 질문의 답변을 확인해볼 것을 권장
  • 기능 설문(feature survey) — 사용자가 겪고 있다고 생각되는 개별 페인 포인트를 짚어주고, 이를 얼마나 고쳐주길 원하는지 평가/순위를 매기도록 요청
  • 제품 기획 단계에서 이런 설문은 팀이 실제로 생각하는 것과 사용자가 진짜 원하는 것에 대한 가정을 검증하는 데 도움을 줌. 자유 서술형 응답은 예상치 못한 발견으로도 이어질 수 있음

사용자 지원 플라이휠

  • 사용자 지원은 많은 팀에게 가장 강력한 제품 피드백 원천이며, 엔지니어가 흔히 깊게 관여함. 많은 조직에서 엔지니어는 사용자와 직접, 혹은 지원 담당자의 에스컬레이션을 통해 간접적으로 지원을 제공할 기회를 가짐
  • 모든 지원 상호작용은 위장된 제품 피드백임 — 항상 두 가지를 함께 고려해야 함: 질문자를 직접 막힘 없이 도와주는 것(unblock), 그리고 제품 개선을 위한 피드백을 얻는 것. 이렇게 생각하면 지원은 부담이 아니라 상호 이익(win-win)이 됨
  • 더 나아가 이는 자기 강화적(self-sustaining) 구조가 됨 — 지원이 좋으면 사용자가 다시 찾아오고, 이는 제품에 필요한 중요한 정성적 피드백과 사용자 인사이트를 제공함. 동시에 팀은 지원을 더 능숙하고 효율적으로 제공하게 됨 -> 이런 “플라이휠 효과”는 한번 탄력이 붙으면 강력함

TIP: 사용자가 훌륭한 지원을 받기 위해 계속 다시 찾아오는 동시에 제품 개선을 돕게 되는 피드백 플라이휠을 만들기 위해 노력할 것

양질의 지원을 제공하는 구성요소

  • 신속하게 응답하고 진입장벽을 제거함
  • 친절하게 대함
  • 사용자의 시나리오를 탐색함
  • 막힘을 해소해줌(unblock)
  • 제품이 사용자를 어떻게 더 잘 도울 수 있었을지 질문함
  • 사용자 기반이 성장함에 따라 지원을 더 효율적으로 확장함

사례 연구: Stripe Workflow Engine

  • 저자는 Stripe에서 다른 엔지니어들의 상태 유지형(stateful) 혹은 장기 실행 프로세스를 오케스트레이션하는 내부 프레임워크 “Workflow Engine”의 테크 리드였음
  • 인기 있는 오픈소스 기술 기반으로 구축, 확장 가능하고 장애에 강하며 분산 시스템의 일반적 문제를 피하도록 도움
  • 대형 프레임워크로 수백 개 팀의 개발자가 다양한 방식으로 사용 -> 질문이 많음
  • 익명 직장인 커뮤니티 Blind의 설문에서 회사 전체 엔지니어 대상 “최고의 내부 지원 팀”으로 반복적으로 선정됨. 이 평판은 저자가 팀을 떠난 뒤에도 지속되었다고 전해짐
진입장벽 제거하기
  • 피드백은 선물이며, 사용자를 단념시키면 피드백을 주지 않게 됨
  • Workflow Engine 팀은 업무 시간 중 30~60분 이내 질문에 응답하는 것을 목표로 함 — 이를 “지원 SLA”라고 부름
  • 많은 제품/인프라 팀은 고객과 직접 소통하는 것을 피함 — 지원 티켓을 열고 며칠을 기다리게 하는 등 장벽을 만듦. 초기 응답이 빠르더라도 후속 대응에서 지연이 자주 발생함. 이는 고객 문의 폭주로 인한 압박/방해에 대한 자연스러운 반응이지만 사용자와 제품 모두에 해를 끼침
  • 반면 Workflow Engine은 회사 메신저(Slack)에서 직접 지원을 제공 — 더 나은 응답성 확보, 다른 팀원도 쉽게 답변에 참여 가능
친절하게 응대하기
  • 사용자는 지원을 요청해야 하는 것이 자신의 잘못이라고 느낄 수 있고, 잘못된 말투로 그들을 당황하게 만들 수 있음
  • 사용자는 우리 제품에 대한 지식이 우리보다 훨씬 적음 — 그들이 어리석은 질문/행동을 한다고 쉽게 단정짓기 쉬움. 이런 함정에 빠지지 말 것
  • 공감을 실천하면 좌절감을 덜 느끼게 됨
  • 건설적 피드백이 언제나 허용되고 환영받는다는 점을 명확히 할 것 — “당신 탓이 아니라 제품 탓”이라는 태도(사용자가 실수를 했더라도 굳이 지적할 필요 없음, 스스로 깨닫게 마련이며 설령 깨닫지 못해도 바로잡는 것은 우리의 일이 아님)
  • “그거 답답하셨겠네요”, “함께 해결해봐요” 같은 작은 공감의 말이나 명시적인 목표 정렬 표현이 사용자에게 우리가 그들 편이라는 것을 보여줄 수 있음
  • 경멸이나 조급함이 아니라 호기심을 가지고 접근할 것 — 목표는 제품과 사용자 경험을 개선하는 것이지, 사용자를 개선하는 것이 아님
  • 사용자는 종종 우리가 그들의 마음을 읽을 수 있다고 생각하는 것처럼 행동함 -> 이에 익숙해질 필요가 있음. 빠진 정보를 인내심 있게 물어볼 것
사용자의 시나리오 탐색하기
  • 사용자 상호작용의 타임라인을 탐색하면 그들의 목표, 무엇이 잘못되었는지, 어떻게 막힘을 풀어줄지에 대한 통찰을 얻을 수 있음
  • 근본 원인 분석(RCA, Root Cause Analysis) — 무엇이 잘못되었는지 짚어냄: 사용자가 특이한 행동을 했거나, 사용자가 혼란스러워하거나, 제품에 버그/기능 공백이 있거나, 혹은 이들의 조합일 수 있음. 처음부터 정확한 버그 코드 라인을 찾으려 할 필요는 없지만, 버그인지 여부와 대략적 위치는 파악해 사용자에게 우회 방법을 알려줄 수 있는 수준이어야 함
flowchart LR
    A["구성 정보<br/>Configuration"] --> B["의도<br/>Intent"]
    B --> C["이전 행동<br/>Prior Actions"]
    C --> D["인시던트<br/>Incident"]
    D --> E["추가 시도<br/>Further Attempts"]

타임라인이 근본 원인 파악과 사용자 막힘 해소에 도움을 주는 5가지 요소

  • 구성(Configuration): 사용자 환경(예: 앱 버전) 정보 -> RCA 수행 및 “앱을 업그레이드하세요” 같은 해결책 안내에 도움

  • 의도(Intent): 사용자가 취한 행동이 그 목표에 맞는 올바른 행동이었는가. 우회 방법은 무엇인가. 애초에 그 시나리오가 지원되는 것인가

  • 이전 행동(Prior Actions): RCA에 도움 — 문제의 원인이 인시던트 자체보다 이전에 있는 경우가 많음. 사용자를 이전 행동으로 되돌려 다시 시도하거나 다른 접근을 해보게 할 수 있음

  • 인시던트(Incident): 실제로 무슨 일이 일어났고 그 영향은 무엇이었는지 파악 -> RCA와 우선순위 결정에 도움

  • 추가 시도(Further Attempts): 이후 사용자가 무엇을 시도했는지 — 여전히 막혀 있는지 물어보는 것이 해결 우선순위 결정과 조언 선택에 도움

  • 어느 요소가 가장 유용한지는 상황에 따라 다르므로 메뉴처럼 취사선택할 것

  • Workflow Engine은 복잡하고 유연한 플랫폼이라 사람들이 목표 달성을 위한 최선의 설계 패턴을 항상 알지는 못함 -> 원래 의도(intent)로 거슬러 올라가 방향이 맞는지 확인해야 하는 경우가 많았음

  • Stripe에는 “WAYRTTD?”(What Are You Really Trying To Do — 진짜로 하려는 게 뭔가요)라는 Slack 이모지가 있었음. 가볍고 유머러스하게 이를 묻는 빠른 방법이었음

사용자의 막힘 먼저 해소하기
  • 사용자에게 최선인 것이 항상 그들이 던진 질문에 대한 답은 아님 -> 종종 그들이 미처 물어볼 줄 몰랐던 질문에 대한 답일 때가 있음
  • 예: Workflow Engine 사용자들의 흔한 문제 중 하나가 비멱등(non-idempotent) 연산이었음
  • 멱등성(Idempotency): 어떤 함수가 여러 번 호출되어도 최초 호출 이후 다른 효과를 만들어내지 않는 성질. 예) 송금 처리 중 최초 시도가 타임아웃되었다고 해서 다시 시도했을 때 돈이 두 번 이체되는 것을 원치 않음. (분산 시스템을 효과적으로 사용하는 데 있어 한 가지 규칙이 있다면, 그것은 연산을 멱등하게 만드는 것)
  • RCA 수행 시, 곤란한 상황에 처한 사용자는 비멱등 연산을 가진 경우가 많음을 발견 -> 향후 문제를 막기 위해 연산을 더 견고하게 만들라고 조언하고 싶은 유혹이 항상 있었음. 하지만 그것은 이미 발생한 피해를 되돌리는 데는 도움이 되지 않음
  • 어떤 조언을 줄 것인가. 즉각적 해결책을 제시할 것인가, 아니면 이전 설계 결정을 재검토하게 할 것인가
  • 저자는 두 가지 답 모두를 주어 많은 사용자를 만족시켰음 — 그들이 자신의 제약 안에서 선택하도록 맡김. 보통 즉각적인 막힘 해소를 먼저 제공해 긴장을 풀어준 뒤 조언을 이어감
제품이 사용자를 어떻게 더 잘 도울 수 있었는지 질문하기
  • 플라토닉하게 완벽한 제품은 지원이 전혀 필요 없음 — 사용하기 안전하고, 문서가 사용자의 질문에 답하며, 모든 사용자 니즈가 자동화되어 있음
  • 저자는 지원 요청을 받을 때마다 제품에 대한 RCA도 함께 수행함 — 어떻게 제품이 사용자를 이 지점까지 오게 만들었는가. 사용자는 피드백을 주려는 의도가 아니라 그저 막힘을 풀려는 것일 수 있지만, 그렇다고 단서를 놓쳐서는 안 됨
  • 사용자를 막힘 해소하게 도와준 것과 동일한 시나리오 타임라인이 제품 피드백을 짚어내는 데도 도움 — 사용자의 결정을 되짚으며 “여기서 내 제품이 어떻게 도울 수 있었을까”를 자문함. 더 나은 오류 메시지나 문서였다면 더 나은 타임라인을 만들었을 수도 있음
  • Workflow Engine 사례: 여러 고객이 비멱등 연산으로 곤란을 겪은 후 -> 테스트를 쉽게 추가할 수 있다는 아이디어를 얻음. 개발자가 연산을 재시도 가능(retryable)으로 표시하면, 통합 테스트 프레임워크가 해당 연산을 두 번 실행해 결과를 비교하고 불일치 시 오류를 발생시키도록 개선함
  • 이는 많은 이슈를 발견했고, 사용자 제품을 개선했으며, 지원 부담을 줄여줌 — 그리고 이 아이디어는 제품 피드백이 아니라 지원 과정에서 나왔음
  • 바쁘다면? 즉시 제품 피드백을 분리해낼 시간/지식이 없다면 -> 사용자 시나리오를 수집한 뒤 나중에 돌아올 TO-DO를 남겨둘 것. 이후 티켓을 열거나 팀과 상의할 수 있음. 원래 맥락을 함께 기록한 정리된 작업 목록을 유지하면 중요한 세부사항이나 전략적 질문을 잊지 않게 되고, 기록하는 행위 자체가 스트레스를 줄이고 지원 중 고객에게 집중할 여유를 만들어줌
사용자와의 직접적 연결을 유지하며 지원 확장하기
  • Workflow Engine 팀은 팀을 기능 개발에서 완전히 이탈시키지 않으면서도 수백 개의 다른 엔지니어링 팀까지 지원을 확장함 — 세 가지 방법 사용:
    • 지원 “로테이션” 운영
    • 지원 담당자 역할을 체계적으로 정의
    • 제품과 문서를 지속적으로 개선하는 문화 조성
  • 지원 로테이션: 매주 “러너(runner)“를 지정해 업무 시간 동안 지원 제공을 주 업무로 삼음. 나머지 팀원은 자신의 작업에 깊이 몰입하며, 러너가 합리적인 시간 내 응답하지 못한 경우가 아니면 끼어들지 않도록 권장
  • 몇 달 뒤 부담이 과중해지자, 팀 전체를 방해하지 않으면서 러너의 부담을 낮추기 위해 다음을 개발함:
    • 러너의 역할은 (미식축구의) 쿼터백과 같음 — 항상 공을 먼저 받지만, 처리할 양이 너무 많거나 적임자가 아니면 다른 팀원에게 넘기거나 패스할 수 있음
    • 러너는 지원 주간 동안 평소 업무를 수행하지 않음 — 프로젝트 일정에서 러너 주간은 휴가처럼 취급됨 -> 스트레스를 낮추고 시간이 지나며 지원 품질을 높임
    • “발견했을 때보다 더 낫게 만들고 떠나라” — 러너는 지원을 더 효율적으로 만드는 개선(버그 수정, 자동화, 제품 사용성 개선, 추가 문서화 등)을 매주 최소 하나 이상 만들어야 하며, 주간 팀 미팅에서 이를 공유함
    • 즉, 러너 주간 동안은 지원이 곧 그 사람의 본업이었음
  • 지속적 개선 문화에서 나온 주목할 만한 개선 사례:
    • 초반에 사용자와의 왕복 대화가 너무 많다는 것을 발견 -> 사람이 응답하기 전 사용 언어 등 구성 정보를 미리 요청하는 자동 봇을 추가
    • 문서를 더 효율적으로 답변할 수 있도록 다수 개선
    • “문서로 답하기(answering with docs)” 관행 채택 — 사용자의 질문이 문서에 명확히 답변되어 있지 않지만 답변되어야 한다면, 문서를 업데이트하고 그 답변으로 사용자를 안내함. 문서 작성에 더 신경 써야 하므로 사용자는 조금 더 기다리게 되지만, 이후에는 미래의 사용자와 AI 지원 봇을 위한 콘텐츠로 남음
    • 업데이트된 문서를 즉시 보여줄 수 없는 경우(리뷰 필요 등) -> 문서 변경 초안을 작성해 그 내용을 사용자에게 복사해 전달하고, 이후 리뷰/반영을 진행할 수 있음
  • 코드베이스가 적응 가능해야 하듯 문서도 그래야 함 — 팀원이 문서를 매우 쉽게 편집할 수 있게 만들 것, 그러지 않으면 문서는 낡아버림
  • 전반적으로 지원을 확장하는 데는 지속적인 창의성과 투자가 필요했지만, 이를 위해 대규모 프로젝트를 벌일 필요는 전혀 없었음
  • 다만 한계는 있음 — Workflow Engine은 사내(internal) 프레임워크였음. 제품이 수억 명의 사용자를 가진다면 위 방식만으로는 확장되지 않음
중간 계층이 추가된 경우의 지원 제공
  • 회사에 따라 엔지니어링과 고객 사이에 AI 채팅 어시스턴트, 지원 담당자, 커뮤니티 모더레이터, 솔루션 아키텍트 등 중간 계층이 있을 수 있음, 혹은 단순히 엔지니어링 지원에 비용을 청구할 수도 있음
  • 이 경우 1차 대응에서 해결하지 못한 문제만 받게 될 수 있는데, 이는 가장 유용한 제품 피드백을 주는 문제 집합과는 별개일 수 있음
  • 이런 상황이라면 사용자와 연결을 유지할 방법을 찾을 것 — 예) 지원 팀과 함께 로테이션을 돌아본 적이 없다면 적극 추천함. 이런 중간 계층이 필요한 피드백을 주도록 채널을 마련해둘 것. 그들의 피드백도 최종 사용자의 피드백처럼 환영할 것
  • 저자는 최근 현재 회사에서 사용자와 지원 봇 간의 상호작용을 많이 읽어봄 — 작업 중인 기능과 관련된 용어를 검색해 사용자들이 무엇에 혼란을 느끼는지 확인함. 봇이 틀렸을 때는 대개 문서화가 부족했기 때문임을 발견 -> 그 공백을 메우면 다음 학습(training) 시 봇이 이를 학습함
사용자 지원의 힘

핵심 요약

  • 사용자에게 훌륭한 지원을 제공해 플라이휠 효과를 만들 것

  • 고객의 시나리오에 깊이 파고들어 문제 해결과 제품 개선에 대한 통찰을 얻을 것

  • 팀의 시간 일부를 지원에 미리 배정하되, 매번 팀원을 무작위로 돌리지 않는 방식으로 할 것

  • 동일한 노력으로 더 많은 사용자를 지원할 수 있도록 지원 확장에 명시적으로 집중할 것

  • 코드와 문서 개선을 모두 쉽게 만들 것

  • 저자가 지원에 많은 지면을 할애한 이유: 사용자가 우리로부터 무언가를 원하는 시점에 이루어지는, 엔지니어가 피드백을 얻을 수 있는 매력적이고 상호작용적인 방법이기 때문

  • 앞서 소개한 다른 피드백 채널(설문, 피드백 위젯, 챔피언 프로그램, 베타 테스트)은 이런 역동성이 없어 신뢰도가 떨어지고 추가 인센티브로 “북돋아” 줘야 함

  • 사용자 제보는 어느 정도 규모까지만 유일한 피드백 원천이 될 수 있고, 그 이상부터는 수를 세기 시작해야 함 -> 정성적 피드백에서 정량적 지표로 전환

5.4 제품 지표

  • 좋은 디지털 트윈은 실제 환경을 추적하는 센서로 정보를 얻음 — 항공기의 디지털 복제본이 수백만 번의 비행 데이터를 공급받듯, 우리의 디지털 트윈도 사용자, 네트워크, 하드웨어 등의 상태를 감지해야 함
  • Chapter 9에서는 비기능 요구사항을 위한 지표를 다룸 — 개별 연산뿐 아니라 사용자 플로우도 계측해 지표를 사용자 성과에 더 가깝게 매핑함
  • 여기서는 사용자 참여(engagement)와 비즈니스 성공을 직접적으로 설명하는 고수준 지표를 다룸 — 이 카운터/비율들은 더 유용한 제품을 만들고, 의사결정을 돕고, 비즈니스 성장에 기여하도록 우리를 동기부여해야 함

지표 선택 시 고려 순서

  1. 프로젝트를 시작하기 전, 조직의 제품 전략에 부합하는지 확인 — 사용자를 위한 방법은 많지만 전부가 우리 미션에 맞는 것은 아님. 프로젝트가 개선할 것으로 기대되는 회사/조직의 핵심성과지표(KPI)를 제시하는 것이 한 방법
  2. 두 번째이자 가장 중요한 것 — 사용자를 위해 봉사하는 데 집중할 것. 가치 지표(value metric)를 목표로 삼으면 팀이 목적(purpose)에 대해 책임을 지게 됨
  3. 다만 사용자가 기능을 알고 사용하지 않으면 가치를 전달할 수 없고, 때로는 사용자 가치를 파악하는 데 시간이 걸림 -> 그래서 도입 지표(adoption metric)도 함께 추적해야 함

NOTE: 제품 지표는 세 가지 뚜렷한 유형으로 나뉨: 핵심성과지표(KPI), 가치 지표(Value Metrics), 도입 지표(Adoption Metrics). 디지털 트윈을 계측할 때 이 세 가지를 모두 염두에 둘 것

  • 조직 전체는 KPI를 추적하지만, 나머지 두 유형(가치/도입 지표)은 특정 제품/기능에 고유할 수 있음

사례 연구: Temporal Technologies — Safe Deploys

  • 저자는 현재 Temporal Technologies에서 근무 중 — Temporal은 워크플로우의 신뢰성 있는 실행(reliable execution)을 위한 프레임워크를 제공. 사용자는 오픈소스 프레임워크로 코드를 작성하는 개발자들
  • 저자의 제품 영역 중 하나인 “Safe Deploys”의 목표는 사용자가 오류 없이 워크플로우를 업그레이드하도록 돕는 것
  • 사용자의 워크플로우 코드는 다양한 환경에 존재하며 다른 코드처럼 배포/업그레이드됨 — 워크플로우의 특이한 점은 몇 분, 몇 시간, 심지어 몇 주간 실행될 수 있고 휴면(dormant) 상태가 될 수 있다는 것. 시작한 곳과 다른 프로세스에서 재개되는 경우도 흔함
  • 문제: 워크플로우가 시작 시점보다 더 새로운 버전의 코드에서 깨어나면 어떻게 되는가. 까다로운 문제임
  • 권장 해결책: 기능 플래그와 유사한 방식으로 워크플로우를 “패치(patch)“하는 것

의사코드 예시 (원서에 표기된 Python 스타일 의사코드)

def my_workflow():
    do_stuff()
    # It would be dangerous to run new code in an unsuspecting old workflow.
    if (originally_started_on_new_version()):
        do_new_stuff()
    else:
        do_old_stuff()
  • 이 “패칭” 방식은 제대로 하면 잘 작동하지만 두 가지 문제가 있음:
    • 발견 가능성(discoverability) 문제 — 개발자가 패칭이 필요하다는 것을 항상 알아차리지는 못함
    • 사용성(usability) 문제 — 더 복잡한 변경에서는 잘못하기 쉬움
  • 제대로 패칭하지 않으면 “워크플로우 업그레이드 오류(workflow upgrade error)“가 발생함

가능한 고수준 제품 접근법 3가지

  • 워크플로우를 개별 코드 버전에 고정(pin)하는 시스템 개발 — 업그레이드 문제를 우회

  • 워크플로우가 업그레이드 오류를 겪을지 사전에 테스트할 수 있는 테스트 후크 제공

  • 새 배포로의 트래픽을 사용자가 점진적으로 늘릴 수 있게 하여 문제 발생 시 파급 범위 제한

  • 단순화를 위해 이런 것들을 돕는 기능을 “Managed Deployments”라 부름 — 어떤 해결책이 가장 효과적일지, 사용자가 채택할지 알 수 없으므로 스스로 책임지기 위한 지표가 필요함

  • 이후 전술적(tactical) 지표에서 전략적(strategic) 지표로 이동하며 목록을 나열, 각 지표를 비판하고 다음 아이디어로 보완함

  • 세 카테고리(도입/가치/KPI) 각각에서 하나씩 선택해 팀에 생산적인 행동을 유도하고, 좋은 사용자 성과와 회사 미션에 부합하도록 함

TIP: 지표를 정할 때 가장 중요하게 고려할 점은 팀의 인센티브를 정렬(align)시키는 것임

도입 지표

  • Managed Deployments의 핵심 문제 중 하나: 주로 애플리케이션 개발자에게 이득이 되지만, 배포 시스템 변경이 필요하며 이는 여러 회사에서 흔히 플랫폼 엔지니어라는 다른 직군이 관리함 -> 앱 개발자가 플랫폼 담당자를 설득해야 하는 정치적 이슈가 채택을 방해할 수 있음
  • 몇 달씩 기다리며 누가 사용하는지 알 수 없는 상태로 있을 수는 없음 -> 계속 투자할지, 마케팅을 개선할지, 다른 접근을 시도할지 알 수 없게 됨
flowchart TD
    A["1단계: 문서/보도자료 조회수<br/>관심은 알지만 채택 여부는 불명확"] --> B["2단계: 지금까지 생성된 Managed Deployments 수<br/>이탈·휴면 계정까지 포함되는 문제"]
    B --> C["3단계: 활성 Managed Deployments<br/>허영 지표는 벗어났지만 규모 차이를 무시함"]
    C --> D["4단계: 활성 Managed Deployments 상의 워크플로우 수<br/>워크플로우 수로 가중치 부여"]

측정하기 가장 빠른 것부터 순서대로 나열 (각 항목의 문제는 다음 항목이 해결)

  1. 문서/보도자료 조회수 — 사용자 관심은 포착하지만 실제로 기능을 채택하는지는 알 수 없음
  2. 지금까지 생성된 Managed Deployments 수 — 과거에 시도해봤다가 마음에 안 들어 이탈한 사용자까지 포함하는 문제. “평생 사용자(lifetime users)“처럼 보이는 지표는 휴면 계정을 다수 포함할 수 있으므로 일반적으로 경계할 것
  3. 활성(active) Managed Deployments — 더 나음, 활성 배포는 사용자가 마음에 들어 하면 계속 활성 상태로 남을 가능성이 높음. 다만 비어있는 배포와 워크플로우가 많은 배포를 동일하게 취급하는 문제 -> 가장 중요한 배포에 의한 채택을 제대로 인센티브화하지 못함
  4. 활성 Managed Deployments 상의 워크플로우 수 — 위 지표를 실행 중인 워크플로우 수로 가중치를 둠
  • 처음 두 항목은 흔히 “허영 지표(vanity metrics)“라 불림 — 겉으로는 인상적이지만 비즈니스 성과/사용자 행동/제품 성공에 대한 의미 있는 통찰을 주지 못함. 그저 DB 크기를 세면 되므로 유혹적임

TIP: 허영 지표를 피할 것

  • “활성 Managed Deployments”는 매력적인 선행 지표(leading indicator)로, 잠재적 사용자 가치를 측정하며, 사용자가 사용을 멈추면 감소하므로 실제 사용자 가치도 어느 정도 암시함

마지막 두 지표 중 선택의 트레이드오프 (워크플로우 수로 가중치를 두면 워크플로우가 천 개인 고객이 1개인 고객보다 1000배로 카운트됨)

  • 통제하기 더 어려움 — 최대 고객이 채택하면 지표가 폭발적으로 증가할 수 있고, 그렇지 않으면 증가하지 않음. 이런 운(luck) 요소는 팀원을 불편하게 만듦

  • 개발자 생산성은 실패한 워크플로우가 1000개든 100만 개든 큰 차이가 없음 — 어느 쪽이든 인시던트를 처리해야 함

  • 가장 사용량이 많은(대개 대기업) 고객에게 집중하도록 인센티브를 줌 — 엔터프라이즈 친화적 기능을 추가로 만들거나, 대기업의 앱 개발자에게 접근해 그들의 플랫폼 엔지니어를 설득하도록 독려하게 될 수 있음

  • 정답은 전략에 따라 다름 — KPI가 엔터프라이즈 채택에 초점을 둔다면 워크플로우 가중 지표를 목표로 삼을 수 있음(더 크게 요동칠 것). 개발자 생산성과 긍정적 입소문에 집중한다면 크기에 가중치를 두지 않은 “활성 Managed Deployments”가 적합함

  • 실제로는 “활성 Managed Deployments”(비가중)를 선택함

  • 다만 이것이 진짜로 도움이 되는지는 여전히 알 수 없음 — 사용자가 단지 기본값이거나 문서가 그렇게 안내해서 사용하고 있을 수도 있음 -> 이 문제는 가치 지표로 다룸

가치 지표

  • “종이클립 극대화기(paperclip maximizer)” 사고실험(Nick Bostrom) — 오직 종이클립을 최대한 많이 만드는 것만이 목표인 AI가 있다면, 인간이 자신을 끌 수도 있다는 이유로 인간이 없는 편이 더 낫다고 빠르게 결론 내릴 수 있고, 인간의 몸도 종이클립 원료(원자)로 활용될 수 있다는 발상까지 이를 수 있음 -> 잘못된 인센티브를 부여했을 때 벌어질 수 있는 일을 보여주는 유명한 사고실험
  • 극단적 사례지만, 이상한 인센티브에 넘어가는 것이 AI만의 문제는 아님 — Wells Fargo 사례: 2016년, Wells Fargo 직원들이 판매 목표를 달성하기 위해 고객 동의 없이 수백만 개의 계좌를 개설한 사실이 드러남. 대규모 고객 반발과 법적 문제로 이어짐
  • 어떻게 이런 일이 발생했는가 — 핵심 실수 중 하나는 가치 지표가 더 적절했을 상황에 도입 지표(계좌 개설 수)를 직원 목표로 사용한 것. 결과적으로 고객은 필요 없는 상품을 갖게 됨. 가치 지표였다면 예컨대 운용 자산(AUM) 규모를 세었을 것
  • 단기적으로 통제하기 쉬운 지표를 선택하려는 본능이 Wells Fargo에는 역효과를 냄 — 지나친 통제 가능성이 조작에 취약하게 만들어 30억 달러 이상의 법적 합의금을 초래함

NOTE: 스스로에게 도전이 되는 가치 지표를 선택할 것. 그 지표에 대한 영향력을 완전히 통제할 수 없다는 어느 정도의 불편함이 팀으로 하여금 훌륭한 결과물을 만들어내도록 동기를 부여함

Managed Deployments 가치 지표 후보 (각 항목을 비판하며 이어감)

  1. 하루당 Managed Deployments 배포 수 — 배포를 더 많이 한다면 그만큼 자신감/성공을 얻고 있다고 추론. 다만 우리와 무관한 다른 여러 요인의 영향을 받을 수 있고, 그 배포들이 여전히 업그레이드 오류로 이어진다면 우리는 제 역할을 못한 것
  2. 워크플로우 업그레이드 오류(건수) — Managed Deployments 사용자에게서 이 수치가 감소한다면 사용자가 명백히 가치를 얻고 있음을 알 수 있음. 다만 전체 실행 중인 워크플로우 수가 증가하고 있다면 우리가 최선을 다해도 이 수치가 증가할 수 있음
  3. 워크플로우 업그레이드 오류 비율(percentage) — Managed Deployments 사용자의 워크플로우 오류 비율이 더 낮다면 제 역할을 한 것. 시스템 내 워크플로우 수를 더 잘 통제함. 다만 얼마나 많은 실행 중인 워크플로우가 프로덕션 문제로부터 구제되었는지는 측정하지 못해 채택을 보상하지 않음(다만 이미 도입 지표가 있으므로 괜찮을 수 있음)
  4. 방지된(averted) 워크플로우 업그레이드 오류 — 사용량 가중 지표로 앞의 두 지표의 문제를 모두 해결. 안전 배포 기능을 출시하지 않았다면 얼마나 더 많은 오류가 발생했을지 추정 가능
  • 이 지표들 어느 것도 Managed Deploys가 사용자의 전반적인 제품 사용량을 늘리거나 타인에게 추천하게 만드는지는 이해시켜주지 못함 -> 이 간극은 KPI가 메움

  • “워크플로우 업그레이드 오류 비율” 지표가 도입 지표인 “활성 Managed Deployments”와 결합했을 때 가장 좋아 보임 — 성공한다면 경영진에게 오류 감소를 보여주는 것이 매우 설득력 있고, 그렇지 못하면 지표가 개선되지 않은 사용자를 인터뷰해 이유를 파악할 수 있음

  • 좋은 수치를 얻으면 아직 채택하지 않은 사용자를 대상으로 마케팅에 활용 가능: “Managed Deployments 도입으로 배포 오류를 평균 N% 줄이세요”

  • 주요 단점: 개발자가 몇 주~몇 달간 배포 이슈를 겪지 않을 수 있어 지표가 나타나는 데 시간이 걸릴 수 있음 -> 초기 채택자가 적을 때는 도입 지표가 그 공백을 메워줌

  • 마지막 지표(“방지된 오류”)는 도입 지표의 “활성 Managed Deployments 상의 워크플로우 수”와 마찬가지로 워크플로우 수로 가중치가 부여됨 — 트레이드오프가 유사함

  • 대규모 기능을 만들고 있는데 좋은 가치 지표를 떠올릴 수 없다면 우려할 만함 — 주로 두 가지 이유:

    • 그 기능이 쓸모없거나 심지어 착취적(exploitative)이어서, 단기적으로는 돈이 되지만 장기적으로 사용자를 떠나게 만들 가능성
    • 디지털 트윈의 계측 역량이 부족한 것일 수 있음 — 제품에 계측(instrumentation)을 추가해야 할 수 있음

핵심성과지표

  • Managed Deployments 프로젝트 시작 시점으로 되돌아가보면, 시작하기 전에 KPI를 고려하고 우리 제품 아이디어가 더 넓은 회사 목표와 어떻게 부합하는지 생각해봐야 함 — 많은 제품이 가치 있을 수 있지만, 그것이 이 특정 회사에서 해야 할 일이 아닐 수도 있음. 우리가 계속 최고의 제품을 구축/유지할 수 있도록 어느 정도 집중을 유지할 필요가 있음
  • 경영진에게 프로젝트를 설득할 때 제품 논지(product thesis, Chapter 6)나 제품 브리프(product brief, Chapter 7)에서 KPI와 연결지을 수 있음
  • KPI는 이를 어떻게 개선할지에 대해 전혀 규정하지 않음 — 넓은 그물을 던지며 최선의 방법을 찾도록 창의성을 초대함

Managed Deployments에 어울릴 만한 KPI 후보

  1. 순추천지수(NPS, Net Promoter Score) — 고객 충성도와 만족도를 측정. 0~10점 척도로 회사의 제품/서비스를 추천할 의향을 측정함. Managed Deployments 사용자가 비사용자보다 Temporal을 더 추천할지 비교 가능. 실제 추천 추적이 더 신뢰할 만하지만, 이 수치가 오르면 사용자가 개선된 안전성에서 가치를 느낀다는 신호로 볼 수 있음
  2. 순매출유지율(NRR, Net Revenue Retention) — 이전 기간 대비 현재 고객 매출의 비율. 사용량 증가로 이득을 볼 수 있지만 성장(신규 고객 획득)은 반영하지 않음. Managed Deployments 사용자가 비사용자보다 워크플로우 사용량을 더 늘린다면 비즈니스 가치를 확실히 입증한 것 — 안전하다고 느껴서 더 많은 워크플로우를 작성하거나, 다른 일에 집중할 수 있어 더 큰 규모를 달성했을 수 있음. 다만 Managed Deploys 운영 비용이 비싸다면 추가 매출만큼 가치가 있는지는 별개 문제
  3. 매출총이익(Gross Margin) — 서버/DB 운영 비용 등을 차감해 추가로 투입한 자원이 잘 쓰였는지 확인. Managed Deploys 사용자와 비사용자 간 이익률을 비교 가능. 다만 긍정적 입소문으로 신규 고객을 얻는 것에 대한 보상은 없음
  4. 영업이익(Operating Profit) — 회사의 최종 손익. 고객 기반 내에서의 성과 개선뿐 아니라 고객 기반 성장에 대해서도 보상함. 모든 것을 반영하지만 우리 제품이 얼마나 기여했는지 측정하기는 매우 어려움
  • 모든 프로젝트가 이런 지표를 과학적 실험처럼 움직일 수 있는 것은 아님, 특히 소규모 프로젝트나 소규모 회사에서는 더욱 그러함
  • 그래서 워크스트림에 동기를 부여하기 위해 처음부터 KPI를 고려하는 것임 — 예) NPS 설문에서 누군가 Temporal을 추천하지 않는다고 답하면 이유를 물어볼 수 있음. 배포에 어려움을 겪었다고 답한다면 결정적 단서가 됨. 마찬가지로 NRR을 추적하다 누군가 이탈(churn)하는 것을 발견하면 이유를 물어 중요한 피드백을 얻을 수 있음
  • 경영진에게 프로젝트를 설득할 때 NPS/NRR과 연계된 고객 후기(testimonial)가 큰 도움이 되며, 비용이 우려사항이라면 매출총이익도 언급할 것

전술적 지표와 전략적 지표

  • KPI, 가치 지표, 도입 지표의 조합이 팀을 정직하고 몰입하게 유지함
  • 위 지표 목록은 전술적(tactical)에서 전략적(strategic)까지 이어지는 스펙트럼으로 볼 수 있음 — 가장 전술적인 지표는 광고 캠페인 조회수 정도(광고를 만들고 돈을 좀 쓰면 수치가 올라감)
  • 가치 지표는 더 전략적이지만 확실히 측정하는 데 보통 더 오래 걸림 — 그래서 가치 지표는 종종 “후행 지표(trailing metric)“라고 불림
  • 영업이익은 매우 전략적임 — 착취적 독점기업이 아닌 이상, 사용자가 제품의 고유한 이점을 가치있게 여기며 동시에 비즈니스가 지속가능하다는 것을 명확히 보여줌
  • 아쉽게도 사용자 가치와 가장 잘 부합하는 지표일수록 빠르게 측정하기 어렵고, 어떤 기능이 그 가치를 만들었는지 정확히 짚어내기도 어려운 경향이 있음 — 이 핵심적 긴장 관계가 올바른 지표를 고르는 기술을 매우 흥미롭게 만드는 이유임

표 5-1: 전술적 지표 대 전략적 지표의 경향 비교

전술적 지표 (Tactical Metric) 전략적 지표 (Strategic Metric)
선행 지표(leading indicator)임 후행 지표(trailing indicator)임
집중(focus)이 움직임 창의성(creativity)이 움직임
조작(game)하기 쉬움 통제하기 어려움
잠재적 사용자 가치를 측정함 실제 사용자 가치를 측정함
정밀하게 측정하기 쉬움 자신의 기능에 귀속시키기 어려움
  • 모든 목표를 완벽히 충족하는 단일 지표는 거의 없음 -> KPI, 가치, 도입 지표를 함께 추적함
  • 전술적 지표만 있다면 원래의 전략을 잊고 “종이클립 극대화기”가 될 수 있음
  • 반대로 회사 수준 지표(예: 이익)만 있다면 그것을 어떻게 끌어올릴지에 대한 통찰이 거의 없음 -> 개별 팀의 초점을 맞추려면 더 구체적인 지표가 필요함

5.5 요약

  • 이번 장에서는 세 가지 큰 주제를 다룸 — 사용자와 계속 연결되어 있고 그들의 니즈에 반응하는 것이 얼마나 중요한지는 아무리 강조해도 지나치지 않음. 이는 모든 소프트웨어 팀의 핵심 역량이어야 함
  • 첫째, 각 기능에 신뢰(trust)와 적응력(adaptability)을 내장하도록 제품을 구조화하는 방법을 보여줌 -> 빈번하고 자동화된 릴리즈로 빠르게 움직이고 반복(iterate)할 준비가 됨
  • 이런 소프트웨어 스택을 갖추면 사용자에게 쉽게 대응할 수 있음. 규모가 커지면 정성적 지표든 정량적 지표든 단독으로는 신뢰할 만한 의사결정 근거가 되지 못하므로 둘을 결합해야 함
  • 정성적 피드백은 사용자 지원을 중심축으로 함 — 사용자가 문을 두드릴 때, 그들의 이야기를 배울 황금 같은 기회를 얻게 됨
  • 실질적인 각 프로젝트는 KPI에 근거해 세워지고, 사용자 임팩트를 보여주는 도입 지표와 가치 지표의 조합으로 추적되어야 함
  • 저자는 이 모든 것을 하나로 묶기 위해 “디지털 트윈”이라는 프레이밍을 선택함 -> 그렇지 않으면 다소 산만하게 느껴질 수 있는 개념들을 구체화함. 소프트웨어 팀은 시뮬레이션, 테스트, 데이터, 피드백으로 자신의 디지털 트윈을 길러야(nurture) 함. 제품의 실제 동작을 측정하고 이해할 능력이 없다면 설 자리가 없음
  • 이렇게 수집한 모든 피드백과 데이터를 어떻게 활용할 것인가 -> 다시 제품 주기의 시작 지점으로 돌아가, 이 책의 마지막 4개 챕터는 제품 발굴(discovery)과 정의(definition)를 다룸. 이어지는 두 개의 발굴(discovery) 챕터에서는 사용자 리서치와, 이미 제품 버전을 출시했다면 디지털 트윈에서 나온 데이터를 결합해 아이디어를 만들고, 계획하고, 우선순위를 정하는 방법을 다룸

5.6 예제

[문제 1] 팀의 코드를 더 쉽게 진화시킬 수 있는 행동을 하나 생각해볼 것. 도구, 프레임워크, 추상화, 리팩터링 등을 고려할 것 [문제 2] 팀의 배포 또는 릴리즈 프로세스를 떠올려볼 것. 고객에게 더 자주 새로운 가치를 제공하기 위해 할 수 있는 것은 무엇인가. 테스트 커버리지, 자동화, 기능 플래그 등을 고려할 것 [문제 3] 만약 제품을 위한 사용자 지원용 AI 어시스턴트를 학습시킨다면, 디지털 트윈에 어떤 맥락(context)을 수집해 이를 학습시킬 것인가 [문제 4] 팀이 정량적 사용자 피드백을 얻는 데 있어 진입장벽을 하나 없앨 수 있다면 어떻게 할 것인가 [문제 5] 댓글과 단일 “좋아요(upvote)” 버튼만 있는 소셜 네트워크에서 일하고 있고, Facebook(care, haha, angry 등)이나 LinkedIn(support, insightful 등)처럼 더 다양한 리액션 종류를 제공하는 실험을 진행 중이라고 가정할 것. 회사 전략은 사람들이 많은 몰입도 높은 콘텐츠를 게시하게 만드는 것이며, 리액션이 독자가 게시자에게 더 폭넓은 피드백을 줄 수 있게 해 게시자가 더 편하게 공유할 수 있게 도와줄 것이라 생각함. 좋은 전술적 도입 지표와 후행 가치 지표를 각각 최소 하나씩 이름 붙여볼 것 (저자는 각 유형에서 2개씩 제시함) [문제 6] 이 리액션 기능이 영향을 줄 것이라 생각되는 KPI는 무엇인가

5.7 답안

[답안 1] Facebook에서 유용했던 도구 중 하나는 확장 가능한 “코드모드(codemod) 도구”였음 — 코드 패턴을 감지해 거대한 저장소 전반에 걸쳐 변환할 수 있었음. 이는 널리 쓰이는 라이브러리에 대한 훨씬 빠른 변경/폐기(deprecation)를 가능케 했고, 엔지니어가 더 크게(impactful) 변화를 상상할 수 있게 해줌 [답안 2] 고가용성 서비스라면 블루-그린(blue-green) 또는 레인보우(rainbow) 배포를 권장함. 전통적인 “롤링 배포(rolling deployment)“는 고정된 머신 집합에서 코드를 제자리(in-place)로 교체함 — 문제가 생기면 이미 이전 버전 코드를 교체해버린 상태라 롤백이 더 느림. 반면 블루-그린과 레인보우 배포는 여러 버전의 코드를 동시에(“색상”으로 표현) 배포된 상태로 유지함 — 신버전을 롤아웃할 때 트래픽을 점진적으로 옮길 수 있고, 문제가 생기면 즉시 트래픽을 되돌릴 수 있음 [답안 3] 기술적인 제품이라면, 문서(documentation), 커뮤니티 메시지 보드의 대화, 지원 담당자와 주고받은 티켓 내용을 학습시킬 것 [답안 4] 커스텀 쿼리를 가능하게 하는 애드혹(ad hoc) 분석 플랫폼을 사용할 수 있음 — 예를 들어 사례 연구의 워크플로우 업그레이드 오류를 사용자가 채택한 다양한 기능과 상관관계 분석하는 데 이를 활용할 수 있음 [답안 5] 도입 지표: A/B 테스트를 진행해 기존 좋아요 버튼을 포함한 전체 리액션 수를 측정 — 참여도가 증가하는지 파악하는 데 도움이 됨. 댓글에 미치는 영향도 함께 살펴볼 것(우려사항 중 하나는 의미 있는 댓글이 줄어드는 것. “축하합니다” 같은 단순 댓글이 줄어드는 것은 괜찮을 수 있지만 더 섬세한 댓글이 줄어드는 것은 바람직하지 않다고 볼 수 있음) 가치 지표: 또 다른 A/B 테스트 — 일부 사용자에게는 새 리액션을 아예 보여주지 않는 홀드아웃(holdout) 그룹을 둠. 자신의 게시물에서 새 리액션을 받았거나 다른 사람이 받는 것을 목격한 사용자가 향후 게시를 더 늘리는지 확인 -> 사용자가 더 편안하게 공유하게 되었는지 알 수 있음. 두 번째 아이디어로는 게시물에 대한 감성 분석(sentiment analysis)을 수행해 게시물 톤(tone)의 다양성이 더 커졌는지 살펴보는 방법도 있음 [답안 6] 대부분의 소셜 네트워크는 “체류 시간(time spent)“이라는 KPI를 추적함 — 리액션 기능은 게시자와, 더 몰입도 높은 게시물을 읽는 독자 양쪽 모두에게 이 지표를 개선할 것으로 추정됨

PART 3 발견

  • 제품 사이클의 시작 지점으로 복귀하는 파트임. 처음부터 새로 시작하거나, 직전 딜리버리 단계의 피드백을 기반으로 다음 이터레이션을 준비하는 단계임
  • 시나리오를 구성하는 두 요소는 페르소나시뮬레이션이며, 이에 맞춰 파트가 두 챕터로 구성됨
    • 6장: 고객을 인터뷰하고 서빙할 페르소나를 구체화함
    • 7장: 사용자가 하고자 하는 일에 대한 전체 스토리를 발전시키고, 이를 우선순위화된 요구사항으로 번역함

CHAPTER 6 타깃 오디언스 이해

“고객에게 직접 무엇이 필요하고 무엇을 원하는지 물어볼 수는 없음. 인지 편향이 고객의 신뢰할 만한 답변을 방해하기 때문임. 대신 구체적인 스토리의 맥락 속에서 니즈, 페인포인트, 욕구를 귀 기울여 들어야 함.” — 테레사 토레스(Teresa Torres), Continuous Discovery Habits 저자

  • 제품 중심 엔지니어의 핵심 관점 전환: 특정한 누군가를 위해 만든다는 사고
  • 고객을 알아야 상호보완적인 기능 조합을 고를 수 있고(8장), 레이턴시·확장성·보안 같은 요구사항에서 진짜 필요한 노력에 우선순위를 매길 수 있음(9장)

TIP: 누군가를 위해 만들 것(Build for someone).

  • 말은 간단하지만 다른 일들에 밀려 실천하려면 지속적이고 능동적인 노력이 필요함. 해달처럼 먹이를 위해 잠수하지만 숨을 쉬러 수면으로 올라오는 것과 비슷함
  • 이 장의 범위: 잠재 고객과의 초기 상호작용 — 인터뷰, 학습, 타깃 오디언스 여부 판단 (5장은 기존 사용자를 대상으로 후속 버전을 이터레이션하는 내용이었던 것과 대조됨)
  • 이 정보를 바탕으로 팀 전체의 작업을 이끌어줄 페르소나(및 논퍼소나)를 구축함
  • 목표: 강한 비전 수립과 중요한 의사결정을 올바르게 내리도록 돕는 것

6.1 가상이 아닌 실제 사용자

  • 실제 인간의 보편적 특성: 잘 잊고, 제품에 쓸 수 있는 뇌 용량이 제한적이며, 바쁨 — 유용한 경험칙이지만 항상 옳지도, 충분히 구체적이지도 않음
  • 사용자에 대한 온전한 캐릭터 프로필이 없으면, 자기 자신이나 이전 제품에서 서빙하던 페르소나를 위해 만들게 되기 쉬움
  • 시뮬레이션에서 플롯 홀을 찾듯, 비현실적인 캐릭터도 감지할 수 있음. 질문: “이 단계에서 우리 제품을 쓸 사람은 어떤 사람일까?”

5가지 스트로맨(straw man) 사용자 유형

유형 특징
비합리적 행위자(The Irrational Actor) 동기와 모순되는 행동을 함. 돈에 쪼들리는 절약가가 8달러짜리 라떼를 지름, 이미 대규모 팔로워가 있는 인플루언서가 아마존 기프트카드 10달러 때문에 텅 빈 소셜 네트워크를 시도함
만화 속 몽상소녀 사용자(The Manic Pixie Dream User) 특별한 이유 없이 제품을 사랑함. 친구들에게 알리려 소셜미디어에 올리고, 새로 나오는 기능을 전부 즉시 받아들이며 스크린샷과 영상을 올림
스토익 수도승(The Stoic Monk) 하루 종일 수도원에서 제품만 쓰는 사람. 아무리 어려운 것도 배우려 하고, 시간이 오래 걸려도 기능을 사용하며, 심한 버그와 불안정성에도 선(禪)적인 평정심으로 다시 시도함
나의 클론(Your Clone) 시스템 내부를 개발자만큼 잘 알아서 별도의 디스커버리 장치가 필요 없음. 내부 지식 덕분에 직관적이지 않은 동작에도 높은 인내심을 가짐. 2장에서 다룬 “지식의 저주” 함정을 상징함
엄마 또는 아빠(Mom or Dad) 실존 인물이지만, 결국 한두 명뿐임
  • 이런 캐릭터를 1장에서 다룬 “동기부여가 빈약한 시나리오”를 비판할 때와 같은 맥락에서 “스트로맨 사용자”로 부름
  • 사람들은 스스로 인식하지 못하는 다른 편향들 때문에 스트로맨 사용자를 그리는 경우가 많음

사이드바 — 편향은 위험함

  • 소셜 카드게임 스타트업 대표 A와 컨설팅: 기존 강자(incumbent)가 안일하고 착취적이라는 피치였으나, 사용자가 왜 갈아탈지에 대한 논리는 “우리는 화상채팅이 있으니까 사람들이 올 것”이라는 수준에 불과했음. 실제로는 사용자가 친숙한 기존 사이트를 떠나 친구들까지 설득해야 하는 상황
    • 이 화상채팅 기능은 이전 소유자로부터 물려받은 것이었고, 엔지니어링 리소스가 제한적이라 이 기능을 킬러 기능으로 삼아야만 했던 사정이 있었음
    • 카드게이머들이 실제로 원하는 상위 2~3개 기능에 화상채팅이 들었는지, 기존 사이트 이탈을 정당화할 만큼 중요한지는 아무도 몰랐음
  • 같은 시장의 경쟁자 B: 혁신적인 토너먼트 포맷을 개발하고 유명 전문가를 초빙해 브랜디드 유튜브 영상을 스트리밍함. 이 영상이 전문가에게 도전하고 싶은 신규 사용자를 끌어들였고, 이는 싱글플레이어 모드였기 때문에 친구들이 아직 사이트에 없어도 트랙션을 얻을 수 있었음. 이 싱글플레이어 제품이 자생적으로 성장하며 사용자를 멀티플레이어 모드로 유입시킴
  • 결과: A사는 자본만 소진하고 폐업. B사는 아직 생존 중. 일관된 사용자 동기 집합에 기반한 전략이 성공 확률을 높임을 보여주는 사례
  • 명확하고 정직한 사용자 정의를 문서화해 팀 전체와 공유하는 것은, 선장을 위한 GPS와 같음. 편향, 편의주의, 정치, 공상, 논리적 오류, 자존심 같은 바람이 배를 흔들어도 목표로 향하게 안내함

6.2 사례 연구 소개

  • 2010년대 초, 저자는 페이스북에서 앱 센터(App Center)의 디자인과 출시 작업에 참여함
  • 앱 센터는 사용자가 소셜 앱(대부분 게임)을 발견할 수 있는 포털이었으며, 오늘날의 앱스토어와 유사하게 추천 앱과 순위 목록을 제공함
  • 저자가 제품 중심 엔지니어로 성장하는 데 기초가 된 경험임
  • 이 프로젝트를 사례로 고른 이유: 전략적 의사결정을 이끈 사용자 페르소나와 개발자 페르소나의 흥미로운 조합을 잘 보여주기 때문
  • 팀의 엔지니어, PM, 디자이너가 타깃 오디언스를 이해했기에 페이스북 게이밍 생태계와 개발자들의 수익 모두를 끌어올리는 임팩트를 만들어냄
  • 이 성공은 몇 년간 지속되다가 모바일 게이밍이라는 거대한 흐름에 자리를 내주게 됨

6.3 객관적 관찰과 과학적 검증

  • 제품 테제(product thesis): 다루고자 하는 사용자 문제, 도움을 주려는 대상, 그 해결을 위한 설득력 있는 아이디어를 정의하는 문장
  • 앱 센터의 초기 제품 테제 예시

앱 센터는 소셜 게이머가 친구들이 즐기는 좋은 게임을 발견하도록 돕고, 앱 개발자가 양질의 앱을 만들어 앱 센터 내 노출을 두고 경쟁하도록 인센티브를 제공함. 이는 결국 페이스북 게이밍 생태계를 성장시킴.

  • “테제”라는 단어를 좋아하는 이유는 과학적으로 들리기 때문임 — 애착이 가는 아이디어를 밀어두고 호기심 있는 과학적 마인드셋을 취해야 함
  • 임상 신약 시험에 비유: 대부분의 신약이 실패하듯 대부분의 제품도 실패함
  • 과학적 방법에서 차용할 두 가지 요소: 귀무가설교란 요인 배제
  • 귀무가설: 개입이 아무 효과가 없다는 주장 → 실험 결과로 “반증” 가능
  • 이를 제품에 적용한 것이 제품 안티테제(product antithesis): 고객이 이 제품/기능을 필요로 하지도, 원하지도, 그로부터 이득을 얻지도, 사용할 수도 없을 것이라는 주장
  • 안티테제를 뒷받침할 이유를 구체적으로 채워야 함. 예: “소셜 게이머는 이미 알림이나 뉴스피드 같은 훨씬 바이럴한 경로로 게임 초대를 받고 있다. 앱 센터는 충분한 트래픽을 만들지 못할 것이다”
  • 구체적인 시나리오로 들어갈수록 안티테제를 계속 다듬어야 함
  • 안티테제를 생각하면 회의적인 태도를 유지할 수 있음. 테제를 증명하는 데만 뇌를 쓰면 계속 확증하는 이유만 찾게 되지만, 안티테제가 있으면 듣고 싶지 않은 고객의 말도 더 귀 기울여 듣게 됨
  • 이 과학적 마인드셋을 고객 인터뷰와 설문 전반에 걸쳐 유지함

6.4 고객 발견

  • 제품/기능을 만들기 전에, 무엇이 사용자를 움직이거나 등 돌리게 하는지 학습해야 함
  • 충분히 많은 사용자가 비슷한 동기와 수단을 공유하면 하나의 페르소나로 묶임
  • 페르소나는 초기에는 누구를 인터뷰할지 정하는 데, 이후에는 다양한 제품·기능 아이디어를 테스트하는 데 핵심적임
  • 고객 발견의 목표: 제품 테제를 발전·정제하는 것 — 사람들이 어떤 문제를 겪는지, 누가 그 문제를 겪는지, 왜 겪는지를 파악
  • 구체적인 제품 아이디어에서 출발하더라도 지나치게 집착하지 말고 계획을 바꿀 수 있어야 함
  • 솔루션 공간보다 문제 공간을 탐색하는 데 집중하며, 인터뷰이의 사고 속으로 들어가 최대한 많이 배우려는 태도가 필요함
  • 고객 발견 방법: 인터뷰, 영업 콜, 설문조사
  • 이 과정들을 통해 제품 사이클 내내 의지할 수 있는 소중한 네트워크도 함께 성장시킬 수 있음

인터뷰 기회 얻기

  • 인터뷰는 엔지니어에게 가장 접근 가능한 기회 중 하나이지만, 애초에 인터뷰 기회를 얻는 것 자체가 많은 엔지니어에게 가장 어려운 부분임
  • 도움이 되는 방법
    • 사용자 경험 리서처, PM 등 고객 대면 역할에 의지하거나, 영업 조직의 Account Manager, Customer Success 담당자, 마케터, Solution Architect와 연결됨. 기존 미팅에 동행해 마지막 몇 분간 질문을 던짐
    • 고객 대면 담당자들과 네트워킹하면 전용 인터뷰 슬롯을 요청할 수 있고, 상대가 먼저 가져다주기도 함. 기술 지원이나 로드맵 발표를 제안해볼 것 — 엔지니어가 시간을 내주면 고객은 특별함을 느낌
    • 5장에서 다룬 유형의 지원(support) 상호작용 중 인터뷰 의향을 물어봄. 도움이 되었다면 대개 응함
    • 다른 많은 사람이 무엇을 원하는지 아는 사람에게 의지함 (앱 센터의 경우 게임 스튜디오 관계자들)
    • 설문을 배포하고, 후속 인터뷰 지원자를 찾는 데 활용함

고객 발견 인터뷰(CDI)

  • Customer Discovery Interview(CDI): 현재 또는 잠재 고객과의 구조화된 대화
  • 풀 렝스 CDI의 3가지 기본 목표
    1. 사용자의 근본적인 동기와 상황을 이해함 — 초기 아이디어가 틀렸을 경우 다른 해결책을 떠올릴 창의성을 확보하고, 사용자의 문제 전체 집합을 알면 총체적인 솔루션을 설계할 수 있음
    2. 동기의 강도를 파악함 — 많은 제품이 실패하는 이유는 사용자가 전혀 원하지 않아서가 아니라 그저 “약간” 원했기 때문임
    3. 이미 구체적인 제품 아이디어가 있다면, 제품 아이디어·목업·프로토타입에 대한 인터뷰이의 창의적 피드백을 이끌어냄 — 미처 몰랐던 질문의 답을 찾게 해줌
  • 60분 안에 다뤄야 할 내용이 많고 탈선하기도 쉬움. 조언: (a) 호기심 있는 과학적 마인드셋을 취할 것 (b) 인터뷰이 중심 대화에서 제품 중심 대화로 서서히 전환할 것

고객 인터뷰 퍼널

  • CDI를 하나의 퍼널로 생각함. 초반에는 사용자의 세계(스토리, 문제, 욕구)에 머물며 페르소나를 확정해가고, 진행될수록 점점 더 제품의 세계로 끌어들여 더 구체적인 질문을 던짐(원서 Figure 6-1)
flowchart LR
    A["초반부<br/>사용자의 세계<br/>(스토리·문제·욕구 탐색)"] --> B["중반부<br/>사용자의 세계 유지<br/>(계획한 구체적 문제로 좁히기)"]
    B --> C["후반부(선택)<br/>제품의 세계<br/>(아이디어·목업 제시, 반응 확인)"]
  • 저자는 자신의 제품 아이디어를 크툴루(Cthulhu)에 비유함 — 너무 일찍 사용자에게 보여주면 “본 것을 되돌릴 수 없게” 되어 응답이 편향될 수 있음. 제품/기능 아이디어를 소개하기 직전의 순간들이 그만큼 소중함
  • 이 퍼널을 초반, 중반, 후반 단계로 나눠서 설명함

초반 단계

  • 목표: 인터뷰이가 편하게 이야기할 수 있는 분위기 조성 (경험을 많이 물어볼 것이기 때문)
  • 특정 답으로 유도하는 질문은 피해야 함

TIP: 인터뷰이를 덜 유도할수록, 그 답을 더 신뢰할 수 있음.

  • 인터뷰이가 먼저 그 기능을 요청하는 것이, 기능을 먼저 제시하고 좋다는 답을 듣는 것보다 훨씬 강한 신호임
  • 인터뷰어만 사용자에게 영향을 주는 것이 아니라, 사용자 스스로도 자기 행동을 예측·전달하는 능력이 부족할 수 있음 → 니즈를 직접 묻기보다, 제품 테제와 관련된 최근의 구체적 스토리를 요청하는 편이 나음
  • 스토리를 요청하는 것의 이점
    • 사용자가 구체적인 사실에 머물며 실제로 한 행동을 말하게 됨(원하는 바나 열망을 왜곡해 구성한 서사가 아니라)
    • 이후 7장에서 다룰 설계 작업의 시나리오를 만드는 데 도움이 됨
    • 스토리는 기억을 자극해 중요한 세부사항을 드러냄
  • 초반 단계에서 미처 고려하지 못했던 문제가 나올 수 있음 — 이는 아직 올바른 문제를 풀고 있지 않다는 뜻일 수 있으므로, 그 문제를 좇으며 필요시 제품 테제를 수정해야 함
  • 페이스북 게임 사례 오프닝 질문 예: 최근 페이스북에서 게임을 하거나 발견한 경험에 대해 피드백이 있으신가요?
  • 눈앞의 것을 다 이야기하고 나면 페르소나 파악용 질문으로 이동: 어떤 게임을 좋아하세요? → 퍼즐 게임인지 소셜 게임인지 등을 파악

중반 단계

  • 사용자의 공간에 머물되, 계획했던 주제·구체적인 문제로 좁혀감
  • 인터뷰이가 그 문제를 갖고 있지 않다면, 이는 제품 안티테제에 반영할 유용한 신호임 — 타깃 오디언스가 아니거나, 맞는 오디언스인데 잘못된 문제를 풀고 있는 것일 수 있음
  • 좀 더 구체적인 프롬프트 예: 일반적인 게임 세션에 대해 이야기해 주세요, 게임을 시도해볼지 어떻게 결정하세요? — 문제와 동기를 이해하고 페르소나·유즈케이스 시뮬레이션을 심화시킴
  • 이 단계에서는 많은 사람이 자연스럽게 동의적이고 협조적인 태도를 보이므로, 긍정 또는 부정 응답을 기대하는 것처럼 보이지 않는 중립적인 질문을 던져야 함
  • 이 시점이면 고객 페르소나를 만드는 데 필요한 재료를 확보한 상태

후반 단계(선택)

  • 인터뷰이가 제품 아이디어의 수혜자로 보이면, 아이디어를 설명하고 반응을 물음
  • 시각 자료(목업, 스케치)가 있으면 더 구체화하는 데 도움이 됨
  • 이 단계는 제품 아이디어가 인터뷰이에게 맞는지 판단하는 데 도움을 줌 — 사용 시나리오를 설명한 뒤, 니즈를 해결하는 것처럼 들리는지 물어보는 방식이 좋음
  • 가장 중요한 것은 의견과 혼란을 표현할 수 있는 편안한 공간을 만드는 것
  • 이 구간에도 나름의 퍼널이 존재함
    • 어떤 게임에 대해 어떤 정보를 보여줄지, 게임 플레이 정보 공유와 관련된 프라이버시 우려가 있는지 파악해야 함
    • 먼저, 기능 목록(장르, 별점, 친구의 플레이 여부, 설명, 인기도 등)을 보여주고 무엇이 가장 중요한지 고르게 함
    • 그다음 더 구체적으로 진입: 여기 우리가 고민 중인 대략적인 컨셉이 있습니다. 어떻게 생각하세요?
flowchart LR
    D["1단계<br/>기능 목록 제시<br/>(장르·별점·친구 플레이·설명·인기도)"] --> E["2단계<br/>어떤 요소가 중요한지 선택받기"]
    E --> F["3단계<br/>구체적인 컨셉/프로토타입 제시<br/>및 반응 확인"]

Figure 6-2 대응 — 앱 센터 프로토타입(추천 게임 화면) 예시

게임명 별점 소셜 정보
Floppy Dog ★★★★ Penny, Moby 외 친구 3명이 플레이 중
Obligatory Cat ★★★ Moby, Daniel이 플레이 중
  • 게이머가 친구에게 자신의 플레이가 보이는 것을 원치 않을 수 있다는 우려를 검증하기 위한 프로토타입 예시임
  • 절대 직접 프라이버시에 신경 쓰시나요?라고 묻지 않음 — 전형적인 유도 질문이기 때문
  • 대신 사용자가 이거 제 친구들한테 제가 뭘 하는지 보인다는 뜻인가요?라고 스스로 되물으면, 이는 강한 신호임 — 충분히 많은 사람이 지적하면 “프라이버시 민감형” 페르소나를 만들 수 있음
  • 대안: 페이스북에서 실제 최근 플레이한 게임 목록을 불러오게 하고 이 중 어떤 게임을 친구들에게 추천하거나 초대하고 싶으신가요?라고 질문
  • 응답이 제품 변경으로 이어지는 방식 예시
    • 5분 해보고 그만둔 게임은 추천하고 싶어하지 않음 → 공유 전 일정 플레이 시간을 요구하도록 설계
    • 특정 게임을 “길티 플레저”로 여김 → 게임별로 공유를 옵트아웃할 수 있게 함
    • “시간 낭비”로 보이는 게임 플레이 자체를 알리고 싶지 않음 → 기본값을 모든 게임 활동 비공유로 설정

질문 스크립트 작성

  • 약간의 구조가 시간 관리에 도움이 됨 — 인터뷰 가이드를 준비해 진행 중 참고할 것
  • 12개짜리 순서대로 된 질문 리스트를 들고 들어가고 싶어지지만, 그러면 흥미로운 대답이 나왔을 때 더 깊이 파고들 시간이 없고 로봇처럼 들리게 됨
  • 모든 고객에게서 일관된 답을 넓게 커버하고 싶다면, 그것은 인터뷰가 아니라 설문임
  • 흥미로운 이야기가 나오면 더 깊이 파고들고, 관련 없어진 주제에는 시간을 낭비하지 말 것
  • 함정 질문 피하기: 만약 <멋진 기능>을 원한다면 어떻게 동작하길 원하시나요? 같은 질문은 이미 원한다고 전제하는 함정임
  • 인터뷰 가이드에는 사용자 구간용 시작 질문 12개, 사용자·제품 중간 구간용 질문 12개, 제품 구간에서 보여줄 것 1개 정도면 충분함. 혹은 철저히 준비하고 싶다면 대화 흐름에 따른 조건부 질문 목록을 만들되, 전부 다루겠다는 계획은 세우지 말 것
  • 몇 번의 인터뷰가 계획대로 흘러가지 않아도 괜찮음 — 경험이 쌓이면 진행 감각이 생기고, 어차피 초반 내용이 가장 신호가 강한 경우가 많음

응답 분석

  • 인터뷰 후에는 각 인터뷰의 시사점을 요약함 — 얼마나 강한 신호를 얻었는지 판단해 요인들의 상대적 중요도를 평가함
  • 예: 앱 센터에서 별점 기능의 가치를 판단하려 할 때, 질문 방식에 따라 신호 강도가 달라짐(원서 Table 6-1)
질문(prompt) 고객 응답 예시 신호 강도
게임을 시도해볼지 어떻게 결정하시나요? 별점이 있었으면 좋겠어요 높음
일반적인 게임 세션에 대해 이야기해 주세요 최근 해본 게임 몇 개가 별로였어요 중간
페이스북 게임에 대해 피드백이 있으신가요? 어떤 게임이 좋은지 구분하기 어려워요 중간
앱에서 별점을 보고 싶었던 순간을 이야기해 주세요 네, 있었어요. 얼마 전에… 중간
장르, 별점, 친구 플레이 여부, 설명, 인기도 중 중요도 순으로 나열해주세요 별점은 3번째로 중요해요 낮음
앱의 별점이 도움이 될 것 같나요? 네, 그런 것 같아요 낮음
  • 신호 강도는 질문이 뒤로 갈수록 낮아지므로, 신호가 강한 질문을 먼저 다루고 신호가 약한 질문은 뒤로 미룸
  • 퍼널 초반에 파악한 동기가 이후 응답의 맥락을 잡아줌 — 신작 게임에 관심 없는 사용자의 별점 의견은, 신작을 열심히 찾는 사용자의 의견보다 덜 중요함

영업 콜

  • 엔지니어가 영업 콜에 동행하는 경우가 있음 — 기술적 답변 제공, 고객에게 인상 남기기, 로드맵 발표, 고객의 특수 요구사항에 대한 실현 가능성 판단 등이 목적
  • 영업 담당자가 원하는 사용자 페르소나 정보가 엔지니어가 원하는 것과 상당 부분 겹치므로, 영업 콜도 고객 발견에 활용 가능함
  • 다만 목표가 다름: 영업 담당자는 실제 계약 성사가 목표(다음 1~2분기 내), 엔지니어는 1년 뒤 출시를 내다보는 로드맵을 고려함
  • 사전에 인터뷰 목표를 맞추고, 무엇을 말하고 말지 않을지 영업 담당자에게 조언을 구함 — 이렇게 하면 담당자도 엔지니어를 자리에 데려가는 것을 더 편하게 여김. 로드맵상 약속할 수 있는 것/없는 것을 스스로도 명확히 해야 함
  • 미팅에서는 영업 담당자가 고객과 신뢰를 쌓도록 돕는 역할을 함 — 고객의 니즈에 공감하고 대변할 때 신뢰가 쌓임. 고객은 조작당하거나 “영업 피치”를 받는다는 느낌을 대체로 감지함
  • 담당자가 설정한 가드레일 안에서, 동일한 과학적 접근을 적용함 — 제품 안티테제가 참일 가능성(제품이 고객 문제를 못 풀 수도, 이 고객에게는 아직 준비가 덜 됐을 수도 있음)을 열어둠. 고통스러운 통합·협상 이후에 알게 되는 것보다 지금 확인하는 편이 나음
  • 페이스북 사례: 검증된 고품질 게임 스튜디오를 유치하고 싶었고 앱 센터가 그들의 게임을 노출하기 좋은 자리라 여겼음. 그러나 게임 개발사들이 실제로 원하는 것은 안정적인 API나 더 나은 인앱결제 지원일 수도 있음 → 제품 아이디어를 먼저 보여주기 전에 퍼널 방식으로 먼저 듣는 것이 중요
  • 배제 기준도 솔직하게 밝혀야 함(예: 대부분의 기기 브라우저에서 구동 불가능한 그래픽 고사양 게임 개발사는 배제) — 미리 경계를 정해두면 서로의 시간을 낭비하지 않음

고객 발견 설문조사

  • 인터뷰보다 폭넓게 니즈를 파악할 수 있지만, 적응적이거나 깊이 있는 대화는 어려움
  • 결정적으로, 후속 인터뷰 대상자를 찾는 데 유용함
  • 설문은 압축된 인터뷰와 같아서 동일한 원칙 적용: 개방형으로 시작, 페르소나 파악 시도, 의견보다 구체적 행동·시나리오를 질문, 우선순위 파악 시도
  • 좋은 설문은 고객이 흥미를 느끼고 응답하고 싶게 만들어야 함 — 인센티브 제공 또는 “우리가 당신의 의견을 듣고 있고, 제품 방향에 영향을 줄 기회”라는 메시지 전달
  • 후속 대화에 관심 있는 사람을 걸러내는 문항도 포함해야 함 — 인터뷰 리드 확보 수단이기 때문
  • 앱 센터 설문 예시 문항
    1. 페이스북에서 게임을 해보신 적이 있나요?
    2. 최근 페이스북에서 게임을 발견하거나 플레이한 경험에 대해 피드백이 있나요?
    3. 그것이 즐거움에 얼마나 중요했나요?
    4. 정말 즐기거나 많이 플레이한 게임 몇 가지를 알려주세요
    5. 최근 눈에 띈 게임들을 떠올려 보세요. 어떤 게임을 시도해볼지 어떻게 결정하나요?
    6. 가장 최근에 시도한 게임들을 떠올려 보세요. 어떻게 발견했고, 어떻게 생각했나요?
    7. 팀원과 대화하며 더 이야기를 나눌 의향이 있나요?

인터뷰 대상자와의 네트워킹

  • 고객 인터뷰는 중요한 네트워킹 기회임
  • 5장에서 다룬 사용자 주도 이터레이션의 핵심은, 쉽게 피드백을 요청할 수 있는 신뢰받는 인물 풀을 갖추는 것
  • 통찰력 있는 피드백을 주거나, 페르소나를 완벽히 대표하거나, 향후 얼리어답터가 될 만한 인터뷰이를 눈여겨보고 계속 연락할 의향을 물어야 함
  • 이런 사람은 **레퍼런스 고객(reference customer)**이 될 수 있음 — 더 넓은 페르소나를 대표하는 실제 인물/기업으로서, 요청과 피드백을 안내하는 디자인 파트너 역할을 함. 이후 마케팅에서 “이런 고객들이 우리 제품을 쓴다”고 인용할 수 있음

고객 인터뷰 경험칙

  • 인터뷰이가 의견, 피드백, 혼란을 편하게 표현할 수 있어야 함 — 덜 유도할수록 응답을 더 신뢰할 수 있음. 인터뷰 초반에 최우선으로 신경 쓸 것
  • 자신의 바람이 섞이지 않은 중립적인 질문을 던질 것
  • 추상적인 의견·추측이 아니라 구체적인 상황을 물을 것 — ~했던 때를 이야기해 주세요 같은 문두가 좋은 시작점임
  • 인터뷰·설문 후에는 시사점을 정리해둘 것 — 신호 강도를 반영해 문제의 시급함을 기억할 수 있도록
  • 인터뷰이의 소개를 활용해 후속 네트워킹으로 이어갈 것

6.5 타깃 오디언스 설계와 공유

타깃 오디언스 선정

  • 1장에서 다룬 시나리오 캐릭터의 요소 복습
    • 페르소나: 인구통계 + 수단(means)의 조합
    • 제품을 쓰는 동기(motive)
  • 타깃 오디언스는 페르소나를 일반화한 것이며, 다음 조건을 충족해야 함
    • 실제 존재하는 인구통계에 기반함
    • 경쟁 제품 대비 상당한 동기를 가짐
    • 구매·사용할 수 있는 수단을 보유함
    • 사용하지 않을 만한 강력한 반대 동기가 없음
  • 신제품이라면 대개 초기 타깃 오디언스를 좁혀야 함 — 모두를 한 번에 동기부여하기는 어렵기 때문. 기존 제품이 잘 채워주지 못하는 핵심 오디언스를 찾아 집중한 뒤, 이후 확장하는 방식이 일반적임
  • 확장 사례
    • 페이스북: 미국 대학생 → 고등학생 → 모든 영어 사용자 → 국제 확장
    • 스트라이프: “코드 7줄”로 스타트업 개발자·테크니컬 파운더 유치 → 이후 엔터프라이즈, 비개발자·바이브 코더를 위한 노코드 솔루션으로 성장
    • Temporal: Go로 개발하는 대기업 인프라 엔지니어에 집중 → 이후 금융/제품 엔지니어(다른 언어 사용자)로 확장 → 엔터프라이즈·AI 기능 추가
  • 사람들의 욕구와 니즈는 무작위가 아니라 상호보완적인 그룹으로 묶여 나타남 — 인터뷰에서 상관관계가 있는 동기 집합을 찾아 더 작은 코호트로 좁힐 수 있음
  • 앱 센터 사례: 다수 인터뷰와 오디언스 분석 결과 게이머는 크게 캐주얼 소셜 게이머와 소위 “미드코어(mid-core)” 게이머로 나뉨
    • 미드코어 게이머: 하드코어 게이머가 즐기는 복잡한 전략/전술 게임에 끌리지만, 같은 수준의 시간·돈을 투자할 의향은 없음
    • 소셜 게이머: 사회적 맥락, 자랑할 수 있는 요소, 많은 친구가 함께 플레이하는 것을 원함
    • 미드코어 게이머: 프라이버시에 민감하고, 경쟁을 즐기며, 평가가 좋은 양질의 콘텐츠를 원함

페르소나로 정렬 이루기

  • 팀 전체가 같은 유형의 사용자와 같은 니즈를 떠올리면, 각자 좋은 의사결정(적어도 같은 방향을 향한 결정)을 내릴 수 있음
  • 제품이 복잡할수록 모두가 같은 페르소나로 정렬되는 것이 더욱 중요함
  • 타깃 오디언스를 문서화하면 니즈가 한곳에 모여, 완료 기준을 정렬하는 데 쓸 수 있음 — 3가지 필수 니즈 중 2개만 채웠다면 아직 출시할 때가 아님
  • 누구를 타깃하지 않는지(논퍼소나)도 명시해야 함 — 스코프 크립(scope creep)을 막는 데 도움이 됨
  • 페르소나·논퍼소나는 공유 문서(슬라이드 덱 등)에 기억하기 쉬운 이름과 함께 캐릭터 스케치로 정리해 동기를 구체화하고, 근거가 되는 인터뷰 전사(transcript)를 링크로 남겨 검증 가능하게 함
  • 눈에 띄고 기억에 남는 이름을 붙이는 것이 페르소나를 기억에 남게 만드는 한 방법임

사이드바 — Mort, Elvis, Einstein 논쟁

  • 저자가 페르소나 개념을 처음 접한 곳은 2005년 마이크로소프트 DevDiv(Developer Division)임
  • 이 페르소나들은 외부로 유출되어 논쟁적인 밈이 되었지만, 실제로는 매우 효과적이었으며 대규모 조직을 정렬시킨 좋은 사례로 꼽힘. DevDiv는 Visual Basic, C#, C++의 컴파일러·런타임·IDE를 담당함
페르소나 특징
Mort 기회주의적 개발자. 당면 문제에 대한 빠르게 동작하는 해결책을 만드는 데 집중하며 생산성과 필요할 때 학습하는 것을 중시함. 회계 등 다른 본업을 보조하기 위해 코딩하기도 함
Elvis 실용적이고 바쁜 애플리케이션 프로그래머. 문제 도메인을 다루는 구체적이고 오래가는 솔루션을 만듦. 작업하면서 배우고 빠르게 다음 문제로 넘어가려 함
Einstein 편집증적인 시스템 프로그래머. 범용적이고 효율적인 솔루션을 만들어야 함. 작업 전에 미리 학습하는 경향이 있음
  • 각 팀은 서로 다른 페르소나를 타깃으로 삼음: Visual Basic → Mort, C# → Einstein, C++ → Elvis
  • 이 페르소나가 외부에 알려지자 반발이 있었음 — 개발자들은 이런 단순한 범주로 규정되고 싶어하지 않았고, 특히 영문학에서 종종 어리숙한 캐릭터를 뜻하는 “Mort”로 불리길 원하는 사람은 없었음
  • 페르소나와 그 목적을 제대로 이해하면 훨씬 납득이 됨
    • Mort에 대한 우스갯소리 섞인 경시는 거슬렸지만, 눈에 띄는 이름을 붙이는 접근 자체는 타당했고 DevDiv 정렬에 도움이 되었음
    • “Mort”라는 이름은 비하가 아니라 Visual Basic 팀이 계속 추상화를 단순화하도록 각인시키기 위해 선택된 것
    • 사람은 상황에 따라 하나 이상의 페르소나에 해당할 수 있음 — 작은 생산성 스크립트를 짤 때는 Mort, 펌웨어 코드베이스에 커밋할 때는 Einstein일 수 있음
    • 페르소나는 고정관념이 아님 — 디자인에 영감을 주고 정보를 제공하기 위한 구체적이고 기억하기 쉬운 언어일 뿐이며, 실제 사용자는 훨씬 다양하다는 점을 디자이너가 인식하고 있어야 함
    • Einstein 페르소나가 C++를 불필요하게 어렵게 만드는 것을 정당화하는 핑계가 되어서는 안 됨
    • 서로 다른 언어를 서로 다른 페르소나에 집중시킴으로써, 각 제품이 총체적이면서도 뚜렷하게 구분되도록 만들 수 있었음

앱 센터의 오디언스

  • 앞서 언급한 소셜 게이머, 미드코어 게이머에 더해 세 번째 페르소나로 “웨일(whale)“을 추가함 — 프리투플레이 게임 업계 용어로, 상당한 돈을 쓰는 게이머를 뜻함. 업계 매출 대부분이 소수의 게이머에게서 나오므로, 서포트하는 게임들의 비즈니스 모델이 이들에게 달려 있어 반드시 포함해야 함

Penny Pincher(페니 핀처)

  • 친구들과의 관계 유지를 중시하는 소셜 네트워크 사용자
  • 게임을 시간을 보내고 친구들과 어울리는 재미있는 방법으로 여기지만, 경쟁적이지 않고 스스로를 “게이머”라 생각하지 않음
  • 현실의 한 단면을 그대로 반영하거나 좀 더 여유로운 판타지를 구현한 가벼운 게임을 선호함
  • 무료 게임만 플레이하고 가끔 광고를 보는 것은 괜찮지만, 인게임 애드온에 돈을 쓰지는 않음

Moby(모비, 웨일)

  • 무엇을 하든 완전히 몰입하는 열성 소셜 게이머
  • 친구들과 함께 플레이하며 경쟁적으로든 협력적으로든 높은 지위를 획득하는 것을 좋아함
  • 친구에게 자랑할 수 있는 인게임 업그레이드나, 진행 속도를 높여주는 시간 절약형 아이템에 기꺼이 돈을 씀

Patton(패튼, 2차대전 미국 장군 이름에서 따옴)

  • 이미 PC 게임을 즐기지만 높은 가격에는 주저하는 진지한 게이머
  • 어느 정도 전략성이 있는 묵직한 게임을 원하지만, 1,000달러짜리 게이밍 리그와 안 맞을 수도 있는 AAA 하드코어 게임에 선뜻 지출하고 싶지는 않음
  • 저렴한 “미드코어” 게임을 중시하며, 게임 품질과 몰입감을 더 신경 씀. 게이머 친구들과 함께 하거나 혼자 플레이하는 것을 선호함
  • 먼저 체험해보고 구매를 결정할 수 있고, 평판이 좋은 게임이라면 기꺼이 돈을 지불할 의향이 있음
구분 Penny Pincher Moby(웨일) Patton
게이머 자기 인식 아니오
소비 성향 무료만, 광고 감수 과시용·시간절약 아이템 결제 검증된 게임에 유료 지불
핵심 동기 친구와의 사회적 관계 지위·과시·몰입 전략성·품질·몰입감
플레이 상대 친구 친구 친구 또는 혼자
  • 전략적 함의
    • Patton은 가장 추측에 근거한 페르소나였지만, 팀이 앱 센터로 가장 유치하고 싶어했던 대상이었음
    • Penny보다 Patton의 수가 적었지만, 레퍼런스 고객이 있었음 — Patton층을 서빙하며 유저당 매출이 좋았던 게임 War Commander. 이 성공을 재현하기 위해 더 강력한 게임들을 유치·노출하고자 함
    • 마켓플레이스이므로 게임 개발사 쪽 페르소나도 필요함. War Commander를 만든 Kixeye가 미드코어 게임 개발사 페르소나를 대표했으며, Farmville·Cityville을 만든 Zynga 같은 캐주얼 스튜디오와는 다른 니즈를 가짐
    • Kixeye는 레퍼런스 고객으로서 유사 스튜디오를 이해하는 데 도움을 주었고, 이들의 성공 사례를 다른 스튜디오 유치에 활용할 수 있었음
    • 페르소나는 니즈의 총체적인 그림을 보여줌 — Patton을 Moby·Penny와 분리해보니 몰입감, 품질, 다양한 장르 등 아직 해야 할 일이 많다는 것이 드러남
    • 별도의 페르소나로 다루지 않고 미드코어 게이머의 개별 기능 요청만 반영했다면, 반쪽짜리 솔루션만 만들어 critical mass에 도달하지 못했을 것임
    • 다듬어진 제품 테제

앱 센터는 소셜 게이머, 특히 패튼(Patton) 같은 사용자가 친구들이 즐기는 좋은 게임을 발견하도록 돕고, 게임 스튜디오가 더 다양한 장르에서 양질의 앱을 만들어 앱 센터 내 자리를 두고 경쟁하도록 유도함. 이는 결국 페이스북 게이밍 생태계를 새로운 인구층으로 성장시킴.

논퍼소나

  • 앱 센터 팀은 명확한 논오디언스가 필요했음: 하드코어 게이머
    • 캐주얼 게이밍에는 관심이 적고, 브라우저에서는 제공할 수 없는 프리미엄 경험에 더 많은 시간과 돈을 투자하려 함
    • 하드코어 게이머가 페이스북에서 절대 플레이하지 않는다는 뜻은 아니며, 단지 팀이 이들을 유치하는 데 특별한 노력을 들이지 않겠다는 것
    • 어떤 기능 제안이 하드코어 개발자를 위한 것으로 보이면 우선순위를 낮추고, 그 이유를 설명할 수 있음
  • 논퍼소나가 있었다면 저자 본인에게도 도움이 되었을 사례
    • 저자는 경쟁 브릿지 플레이어로 낯선 사람들과 정기적으로 오프라인·온라인 토너먼트에 참가했고, Words with Friends도 실력이 좋아 경쟁할 만한 상대인 친구를 찾는 데 어려움을 겪음
    • 앱 센터 이후의 개인 프로젝트 아이디어로, 실력 기반 매치메이킹 서비스를 구상해 낯선 게이머끼리 대등한 상대와 매칭되도록 하고자 함
    • 상당한 시간을 들여 제안서를 만들었지만 경영진을 설득하지 못했고, 경영진도 명확한 이유를 설명하지는 못한 채 “우선순위가 충분히 높지 않다”는 답만 들었음
    • 하드코어 게이머 논퍼소나가 있었다면 명확성을 줄 수 있었을 것 — 그런 사람들은 게임을 워낙 좋아해 낯선 사람과도 기꺼이 플레이하지만, Patton은 친구와 플레이하는 데 더 관심이 있음. 따라서 매치메이킹은 매력적이지 않고, 그에 투자하는 것은 의심스러운 제안이었던 것

6.6 타깃 오디언스 기반 기능 선택

  • 앱 센터의 기능 예시
    • 에디터가 좋다고 생각하는 신작 게임을 피처링함
    • 품질, 친구의 활동, 사용자가 이미 하는 게임과의 유사성을 조합해 사용자별 개인화 순위를 제공함
  • 1차적으로는 세 페르소나 모두 이 속성들을 어느 정도 좋아할 만함 — 다들 친구와 플레이하고, 다들 좋은 게임을 어느 정도 선호하기 때문
  • 이 기능에 대한 제품 테제

페이스북 사용자가 자신과 관련된 좋은 게임을 발견하는 공간. 개발자는 멋진 게임을 만들도록 인센티브를 받고, 이는 참여도 상승으로 이어져 인앱 구매와 광고를 통해 페이스북에 매출을 가져옴.

  • 페르소나별 안티테제 검증
    • Penny: 의향이 낮은 캐주얼 게이머라 앱 센터에 스스로 찾아올 만큼 능동적이지 않을 수 있음 — 친구의 직접적인 초대에 반응할 가능성이 더 큼
    • Moby: Penny보다는 방문 가능성이 높지만 탐험가형이 아님 — 이미 많이 투자한 기존 즐겨찾기 게임만 계속할 수 있음
    • Patton: 마음에 드는 게임을 충분히 찾지 못해 초기 앱 센터를 가혹하게 평가할 수 있음
  • 저자의 평가: Penny·Moby 안티테제는 상당히 설득력 있음(일부는 쓰겠지만 큰 히트는 아닐 것) — 반면 Patton 안티테제는 고칠 수 있어 보임
  • Patton의 니즈 재확인: 몰입감, 가격 적정성, 친구, 품질 / 수단: 즐길 만한 것에는 기꺼이 지출
  • Patton을 위한 기능 확장
    • 장르별 필터링을 허용 — 예를 들어 Patton이 Farmville을 걸러내고 전략 게임에 집중할 수 있게 함
    • 앱 센터 런칭을 위해 신작 게임을 직접 커미션함으로써 Patton이 바로 즐길 거리를 갖추게 함

초점 유지하기

  • 고객을 얻으려면 그들의 문제를 온전히 해결해야 함
  • 두 페르소나를 절반씩 서빙하는 것은 소매가 S 사이즈인 2XL 셔츠를 만드는 것과 같음 — 기술적으로는 작은 체형의 사람이 입을 수 있지만, 입고 싶어하지 않을 것임
  • 상호보완적이지 않은 기능 조합의 예: 게임이 사용자에게 선결제를 요구할 수 있는 스토어 + 친구를 게임에 초대하는 기능(예: 시뮬레이션 농장 돕기 초대). 각각은 좋은 기능이지만, 페이월이 있는 게임으로의 초대는 전환율이 낮을 가능성이 큼. 서로 다른 페르소나를 겨냥하고 있어 시간이 지나면 괜찮을 수 있지만, 초기 출시 하나에 담기에는 초점이 부족함
  • 상호보완적인 기능을 만드는 것은 전략적으로는 당연해 보여도 실제로는 지키기 어려움
    • 그 세 기능이 가장 손쉬운 저과일(low-hanging fruit)이 아닐 수 있고, 다른 유혹적인 대안이 계속 등장함
    • 캐주얼 게임을 만드는 스튜디오들이 새로운 기능을 요구하는 상황에서, 미드코어 시장에 집중하려고 멈추면 이들이 조급해질 수 있음
    • 사용자들도 계속 기능을 요청함
  • 그래서 타깃 오디언스에 대해 폭넓게 소통하고 팀 전체가 정렬되는 것이 중요함 (우선순위화는 7장에서 더 다룸)

6.7 다중 페르소나 제품

  • 대부분의 성공한 제품은 결국 여러 페르소나를 타깃하게 됨
  • 흔한 이분법: 파워 유저 대 일반 유저(8장에서 더 다룸). Moby는 Penny와 대비되는 파워 유저 사례지만, 둘 다 “소셜 게이머”라는 같은 카테고리에 속함
  • 반면 페르소나가 서로 다른 카테고리를 대표하는 경우도 있음 — Patton과 Moby는 둘 다 열성 게이머지만 세부적으로는 다름
  • 다양한 페르소나를 서빙하는 것은 제품이 규모를 키우는 주된 방법 중 하나임 — 스프레드시트가 마케터, 회계사, 과학자 등 여러 직군에서 쓰이는 것과 비슷함(확장 가능하면서도 범용적임)
  • 각 페르소나는 인터뷰, 우선순위화, 테스트부터 마케팅, 샘플, 문서화까지 제품 사이클의 모든 부분에서 별도의 관심이 필요함
  • 이 절에서 다루는 내용
    • 페르소나 간 동기가 충돌할 때 발생하는 일
    • 서로 다른 코호트가 가져오는 가치 평가하는 법
    • 그에 따라 어느 페르소나에 더 많은 노력을 쏟을지 결정하는 법

페르소나 간 충돌

충돌 유형 한쪽의 니즈 다른 쪽의 니즈
라이드헤일링 드라이버 vs 라이더 더 많은 돈을 벌고 싶음 안전하고 저렴하며 시간 맞는 이동을 원함
블로그 플랫폼 작가 vs 독자 많은 독자와 도달 수단을 원함 좋은 글과 스팸 없는 인박스를 원함
페이스북 게임 개발사 vs 플레이어 게임 확산을 위해 친구 목록 등 사용자 데이터 접근을 원함 프라이버시를 지키고 싶어함
  • 상반되는 인센티브를 조율하는 것은 제품에서 가장 어려우면서도 가장 가치 있는 문제 중 하나임

사이드바 — 멀티어핑(Multiapping)

  • 미국에서 라이드헤일링 앱은 Lyft와 Uber가 가장 인기 있는 인센티브 불일치 사례를 보여줌

  • 저자가 Lyft를 호출한 뒤 지도의 점을 보고 있었는데, 놀랍게도 기사가 반대 방향으로 멀어지기 시작함 — 몇 분간 지속되다 결국 취소함. 앱이 취소 사유를 물었는데, “기사가 반대 방향으로 가고 있음”이라는 선택지가 이미 존재했음 — 이 정도로 흔한 기사 행동이라 전용 옵션까지 마련되어 있었다는 점이 흥미로웠음

  • 같은 날 다시 Lyft를 호출해 탑승함. 기사의 대시보드에 휴대폰 2대가 부착되어 있었고, 하차 몇 분 전 다른 앱을 켜는 모습을 목격함 — 하차 전에 Uber가 새로운 승객과 연결되는 것을 확인함

  • 정황이 이해됨: 기사들은 Uber와 Lyft를 동시에 켜두고(“멀티어핑”) 더 많이 벌려고 하지만, 승객은 신뢰할 수 있고 시간이 맞는 탑승을 원함

  • 인센티브 불일치의 전형적 사례. Lyft의 “왜 취소했나” 질문은 이런 행위 단속을 위한 시도의 일부로 추정됨

  • 조사에 따르면 Lyft와 Uber는 위반이 감지되면 경고, 정지, 계정 비활성화 등을 통해 인센티브를 다시 정렬시킴

  • 페이스북 게임 별점 도입 사례(저자가 자랑스러워하는 사례)

    • 게임 개발사가 시스템을 게임(gaming)하는 것을 원치 않았음 — 개발사는 더 좋은 게임을 만들어야 더 많은 사용자가 플레이하고 싶어지고, 별점은 그 목표에 대한 책임을 지움
    • 그러나 개발사도 돈을 벌어야 하므로 순위를 부풀리려는 부정한 시도를 할 수 있음
    • 해결책: 개발사가 언제, 누구에게 평점을 요청할지 통제하지 못하게 함(조작에 취약해지므로). 대신 뉴스피드와 facebook.com 사이드바에 무작위로 노출하는 방식으로 타이밍과 샘플링을 통제함
    • 편향 없는 무작위 샘플링 결과, 게임을 좋아하는 사용자와 아닌 사용자 모두에게서 별점이 모여 대체로 3~4.5점 사이의 넓은 분포가 나타났고, 이는 의미 있는 평점으로 여겨짐
  • 애플의 사례(비슷한 과제에서 다르게 접근해 실패한 예)

    • 개발사가 사용자에게 앱 평점을 요청할 수 있음 — 가장 만족스러운 행동 직후에 뜨는 “이 앱이 마음에 드시나요?” 팝업을 본 적이 있을 것임. 물론 “예”라고 답한 사용자에게만 평점을 요청함
    • 과거에는 앱스토어에도 낮은 평점의 유명 앱이 있었지만, 이제는 정치적 논란이나 큰 사고가 없는 한 거의 언제나 4.5점 이상임 — 평점이 거의 무의미해짐
  • 결론: 건강한 소프트웨어 생태계는 정렬된 인센티브뿐 아니라 건강한 페르소나 믹스도 필요함. 무엇이 건강한지 이해하려면 각 페르소나가 커뮤니티에 어떤 가치를 가져오는지 자세히 이해해야 함

고객 가치 이해하기

  • 모든 고객은 생태계에 가치를 가져옴 — 콘텐츠 작성, 버그 리포트, 재화/서비스 제공, 제품 개선에 쓸 수 있는 매출 등
  • 이 가치를 이해하고, 더 많은 기여가 필요한 사용자를 육성해야 함
  • 위키피디아 편집자는 전체 독자 수 대비 소수지만, 이들이 없으면 사이트가 무너질 정도로 막강한 임팩트를 가짐
  • 덜 명백한 사례: 페이스북 게임의 Penny는 매출을 전혀 만들지 않지만 생태계에 가치를 가져다줌 — 게임이 critical mass를 얻는 데 도움을 주고, 이는 Moby처럼 기꺼이 돈을 쓰는 다른 페르소나를 끌어들임. 프리투플레이 게임이 큰 매출을 내기 시작했을 때 세상은 놀랐지만, 아마 어떤 제품 중심 사고를 하는 사람이 이를 미리 파악해 비즈니스 모델로 제시했을 것임
  • 가치는 시장 요인에 따라 동적으로 변할 수 있음 — 라이드헤일링에서는 드라이버가 희소할 때 드라이버의 가치가 높아짐. 지급액을 늘리면 더 많은 드라이버가 좌석을 채우게 됨

경쟁 페르소나 간 우선순위 정하기

  • 제품이 다면 마켓플레이스(multisided marketplace)를 지원한다면, 각 사이드 간 기능 작업의 우선순위를 정해야 함 — 이는 앞서 다룬 타깃 오디언스 선정의 특수한 경우임
  • 콘텐츠 크리에이터와 독자를 함께 가진 플랫폼이라면, 양쪽 모두를 지속적으로 성장시켜야 하지만 때로는 한쪽에 더 즉각적인 관심이 필요한 시기가 있음
  • 이를 잘못 다루면 큰 곤경에 빠짐 — 현재의 예로 소프트웨어 질의응답 사이트 stackoverflow.com을 들 수 있음
    • 역사적으로 “답변자(answerer)” 페르소나에 집착해 피드백, 평판 시스템, 전용 게시판, 업적 등의 인센티브를 제공함. 추천(업보트)을 받으면 도움을 준 것 같은 좋은 기분을 느낄 수 있었음
    • AI 어시스턴트의 부상으로 독자(질문 답을 찾는 사람)들이 사이트에 덜 방문하게 되었고, 답변자들이 받는 보상(kudos)도 줄어듦
    • 이들의 기여는 여전히 똑같이, 혹은 더 중요함 — AI들이 답변을 읽고 활용하고 있기 때문. 하지만 답변자는 자신의 답변이 AI에 쓰였는지 어떻게 알 수 있는가? 기여가 줄어들면 답변되지 않은 질문이 쌓여 AI 답변의 품질도 나빠질 수 있음
    • 소프트웨어 엔지니어링 커뮤니티는 이 인센티브의 균형을 다시 맞춰야 함 — 질문에 답하는 사람들에게 애정을 보여주는 동시에, 그 답변이 실제로 필요한 곳(사용자가 질문하는 곳)까지 도달하게 만들어야 함

6.8 요약

  • 소프트웨어 팀은 타깃하는 페르소나 목록을 갖추어야 함 — 그 페르소나의 배경과 동기에 대한 세부사항, 그리고 커뮤니티에 가져오는 가치까지 포함해야 함
  • 팀이 페르소나 목록으로 정렬되면 전략적 의사결정이 훨씬 쉬워짐
    • 모두가 동일한 타깃 사용자 개념을 공유하므로 디자인 논의가 쉬워짐
    • 페르소나의 세부사항이 빈틈을 알아차리게 해줘 완전한 문제 해결로 이어짐
    • 후보 페르소나 중에서 타깃 오디언스를 선택할 수 있게 됨 — 즉 페르소나 간 우선순위를 정하면 기능 우선순위화도 훨씬 쉬워짐
  • 이 페르소나들을 파악하려면 자주 사용자와 대화해야 함. 고객 발견 인터뷰는 좋은 방법이지만, 많은 사용자가 자신이 실제로 무엇을 원하고 어떻게 행동할지 예측·전달하는 데 어려움을 겪는다는 점을 유념할 것 — 최상의 신호를 얻으려면 질문에서 편향을 제거하고 사용자 확신의 강도를 이끌어내야 함

6.9 예제

  • 배경: Google, Apple, Bing Maps와 유사한 지도 앱 “Applebingoo Maps”를 만드는 상황
  • 목표: 자전거+대중교통, 자동차+대중교통 같은 혼합 이동 수단(mixed-mode) 경로 안내 기능 개발
  • 2가지 제품 테제
    1. 사용 데이터상 멀티모달 이동이 어느 정도 흔함이 확인됨. 이미 혼합 이동을 하는 사람들이 총체적인 경로 안내라는 추가 편의성 때문에 Applebingoo Maps로 갈아탈 것이라는 가설
    2. 혼합 이동이 더 편리하고 발견하기 쉬워지면 사람들이 대중교통을 훨씬 더 많이 이용하게 될 것이라는 가설. 가장 좋은 방법은 기차/버스 정류장까지, 그리고 정류장에서 목적지까지 이어지는 경로를 제공하는 것이라 가정함
  • 고품질 대중교통이 있는 도시 지역 주민 수십 명을 인터뷰할 예정

질문

[문제 1] 인터뷰 대상자를 선정해야 함. 몇 개의 다른 그룹을 적절한 인원으로 인터뷰하기 위한 스크리닝 설문 질문을 설계할 것. 무엇을 묻고, 누구를 선정할 것인가? [문제 2] 인터뷰 첫 단계에서 인터뷰이의 동기와 지식을 이해하기 위한 질문 몇 가지를 구성할 것. [문제 3] 대중교통을 가끔 또는 거의 이용하지 않는 사람을 인터뷰하며, 두 번째 제품 테제(대중교통 이용을 늘릴 수 있다는 가설)가 맞는지 확인하고자 함. 무엇을 묻고, 어떤 유형의 응답이 아이디어가 좋다/나쁘다는 신호가 될 수 있는가? [문제 4] Applebingoo Maps 사용자로서 자기 자신을, 지역 이동수단에 대한 일반화된 태도와 함께 대표하는 페르소나 문단을 작성할 것. 인터뷰를 받았고 몇몇 다른 사람도 비슷한 응답을 했다고 상상하라. 페르소나에 재미있는 이름을 붙일 것. [문제 5] 마지막으로, 인터뷰의 마지막 단계에서 구체적인 제품 아이디어에 대한 피드백을 어떻게 얻을 것인가?

6.10 답안

[답안 1] 먼저 응답자의 이동에 대중교통이 선택지인지 확인함. 자동차·자전거에 대해서도 동일하게 질문함. 세 수단 각각에 대해 얼마나 자주 이용하는지 물음. 대중교통 접근성이 있고 자전거 및/또는 자동차를 보유한 사람으로 범위를 좁혀야 함. 첫 번째 테제 검증에는 대중교통을 자주 또는 가끔 이용하는 사람을 선정하고, 두 번째 테제 검증에는 대중교통을 거의/전혀 이용하지 않지만 이용할 수는 있는 사람을 선정함 [답안 2] 이동 패턴을 더 자세히 파악하는 것부터 시작함. 통근과 용무/개인 이동은 역학이 다를 수 있으므로 질문을 나눔. 통근 관련 질문 예시(개인 이동 질문도 유사한 방식)

  • 최근의 일반적인 통근 과정을 대략적으로 이야기해 주세요 — 중립적인 방식으로 물으며 왜 대중교통을 더 이용하지 않으세요? 같은 질문은 피함. 통근을 시뮬레이션해서 이야기하게 하면 예상치 못한 통찰이 나오거나 이후 답변을 설명해줄 수 있음
  • 통근에 얼마나 만족하시나요, 그 이유는 무엇인가요? — 이미 불만을 이야기했을 수도 있지만, 혹시 놓쳤다면 기회를 한 번 더 줌. 주차, 교통 체증, 기후 영향에 대한 우려, 이동 시간 등의 요인이 궁금하며, 혹은 예상치 못한 답(아파트의 차량 리프트가 자주 고장 남, 직장에서 충전 가능한 주차 공간을 찾기 어려움 등)이 나올 수도 있음 [답안 3] 프롬프트: 통근(또는 용무)에 대중교통을 이용하는 것에 대해 이야기해 주세요 — 아직 혼합 이동 경로 안내에 대해서는 “유도 질문”을 하지 않고, 대중교통을 이용하지 않는다고 나쁘게 느끼게 만들지도 않음. 너무 느리다, 배차 간격이 너무 길거나 불안정하다 같은 답을 예상함. 제품 테제 관점에서 더 흥미로운 것은 대중교통 옵션 자체를 잘 모른다는 응답(지도 앱이 이를 부각해줄 수 있음), 역까지 너무 멀다(자동차·자전거가 격차를 메울 수 있음) 같은 응답임. 또한 대중교통 요금 결제가 번거롭거나 낯설어서 아직 설정을 안 해봤다는 답이 많이 나오면, 팀이 초기 아이디어 대신 대중교통 결제 연동에 집중해야 할 수도 있음을 시사함 [답안 4] 교외 철도 덕후 스탠(Suburban Rail Stan) 페르소나 — 도시 근교에 살며 자동차를 소유한 통근자로, 인근에 철도가 있음. 대부분의 이동에는 기차가 맞지 않아 대중교통을 가끔만 이용함. 다만 도심에 갈 때는 파크앤라이드(park-and-ride)까지 차를 몰고 가 기차로 환승함. 도심 운전·주차 스트레스를 피하거나 탄소 발자국을 줄일 수 있어 이 방식을 선호함. 기차 안에서는 독서나 업무를 할 수 있어 이동 시간이 다소 길어져도 시간 낭비로 느끼지 않음. 지도 앱들이 자신의 이런 이용 패턴을 지원하지 않는다는 점에 답답함을 느낌
  • 참고: 페르소나 작성 시 지나치게 구체적인 개인 서사(예: 이 답답함을 소프트웨어 엔지니어링 책 예제로 승화시킨다는 식)를 넣는 것은 과도하게 특정적일 수 있으므로 주의가 필요함 [답안 5] 목표는 응답자의 실제 상황을 최대한 가깝게 시뮬레이션해 피드백의 신뢰도를 높이는 것. 프로토타입(AI로 생성한 것도 가능)이 있다면, 응답자가 평소 다니는 목적지로 실제 사용해보게 함. 궁극적으로는 제품 컨셉에 대한 피드백과, 이것이 실제로 통근 패턴을 바꿀지에 대한 감각 둘 다를 얻고자 함. 이를 위해 혼합 이동 경로 안내를 응답자가 전통적으로 써온 방식과 나란히 놓고 비교해보도록 요청함

CHAPTER 7 시뮬레이션을 통한 제품 탐색

저자가 인용한 스티브 잡스 발언의 취지: 고객 경험에서 출발해 기술로 거슬러 올라가야 하며, 기술에서 출발해 판매 방법을 고민해서는 안 된다.

  • 6장에서는 고객을 발견함 - 고객이 누구이고 그들의 문제가 무엇인지 학습, 이를 통해 **제품 테제(product thesis)**를 다듬음. 다만 솔루션에 대해서는 깊이 생각하지 않음
  • 7장에서는 본격적으로 **제품 발견(product discovery)**으로 방향을 전환
    • 사용자 시나리오는 캐릭터(character)와 시뮬레이션(simulation)으로 구성되는데, 6장까지는 캐릭터만 갖춘 상태였음
    • 이제 시뮬레이션, 즉 아직 존재하지 않는 제품·기능에 대해 사용자가 새로운 능력을 어떻게 다룰지 예측하는 “미니어처 SF 스토리”를 써야 함
  • 이 과정은 제품의 영역(product land)에서 시스템의 영역(system land)으로 향하는 큰 여정의 시작

7.1 비전에서 요구 사항으로의 전환

문제의식

  • 소프트웨어 개발의 골칫거리 중 하나: 사전에 눈치채지 못한 뒤늦은 제품 요구사항(late-breaking requirements)
    • 사용자 피드백에서 발견되기도 하고, 설계·구현 과정에서 더 많은 것을 알아차리기도 함
    • 로드맵을 지연시키고 스코프를 키움
    • 근본 원인: 불충분한 discovery 작업

중대형 프로젝트를 위한 여정(itinerary)

  • 특정 프로세스나 문서 템플릿을 강제하려는 것이 아니라, 집중해야 할 핵심 포인트를 제시
  1. **북극성 시나리오(north star scenarios)**로 제품 비전을 완성
  2. 북극성 시나리오를 잘게 쪼개 요구사항 형태(유스케이스 개요서, use case compendium)로 변환
  3. 가장 중요한 요구사항을 담을 첫 마일스톤 중심의 로드맵 작성
  4. 첫 마일스톤에서 만들 것을 제안 - 상세 스토리(사용자 흐름·user flow, UI가 있다면 스토리보드)로 구체화
  5. 선택한 유스케이스별 상세 시스템 요구사항 추가 - 필요하면 원래 요구사항에 피드백
  6. 사용자 흐름과 시스템 요구사항을 기반으로 JTBD(jobs to be done) 목록 작성
  • 이 지점부터 discovery에서 definition으로 전환 (definition은 이후 챕터에서 다룸)
  • 어떤 프로세스도 모든 것을 미리 내다보게 해주지는 못함 - 후반부 발견은 자연스러운 일이나, 그 규모가 작고 제품에 치명적이지 않아야 함

Figure 7-1. 제품 발견 프로세스 예시 (세 개의 문서로 구분)

flowchart TB
    subgraph PB["제품 브리프 Product brief"]
        A1[제품 테제] --> A3[북극성 시나리오]
        A2[타겟 오디언스] --> A3
        A4[목표 지표]
    end
    subgraph PRD["제품 요구사항 문서 PRD"]
        B1[유스케이스 개요서] --> B2[마일스톤 0]
        B1 --> B3[이후 과제]
    end
    subgraph PS["제품 명세 Product spec"]
        C1[사용자 흐름] --> C3["JTBD (할 일 목록)"]
        C2[시스템 요구사항] --> C3
    end
    PB --> PRD
    PRD -. 피드백 .-> PB
    PRD --> PS
    PS -. 피드백 .-> PRD
  • 제품 브리프(Product brief)
    • 모두를 목표에 정렬시키는 비전 문서. “problem brief” 또는 “one-pager”로도 불림
    • 최소 포함 요소: 제품 테제/안티테제, 타겟 오디언스(6장), 추적할 목표·제품 지표(5장), 북극성 시나리오
    • Double Diamond 모델 기준 목표: discovery의 언블록
  • 제품 요구사항 문서(PRD)
    • 제품이 가져야 할 속성을 구체화 - 장기 관점뿐 아니라 첫 딜리버러블·마일스톤도 제안
    • prescriptiveness(규정성)는 최소화
    • 목표: definition의 언블록
  • 제품 명세(Product specification)
    • 그 제품을 어떻게 설계해야 하는지 제시
    • 목표: development의 언블록
  • 저자는 특정 프로세스를 강매하려는 게 아니며, 대부분의 팀은 세 단계를 모두 공식화하지 않음. 다만 비전에서 실행 가능한 설계로 체계적으로 나아가는 방식을 보여주기 위해 이 구조로 챕터를 전개함

어떤 방식으로 일하든 지향해야 할 세 가지 핵심 속성

  1. Why · What · How 단계 구분 (모두 사용자 관점)
    • 1단계: 사용자가 왜 행동을 원하는가
    • 2단계: 사용자가 무엇을 요구하는가
    • 3단계: 사용자가 어떻게 목표를 달성하는가
    • 각 단계에서 아직 고려할 필요 없는 세부사항에 매몰(rathole)되지 않도록 주의
  2. 각 단계에서 시나리오 시뮬레이션 사용 - 팀이 정렬되고 완전한 문제를 풀고 있는지 확인하는 관행 = 시나리오 기반 발견(scenario-driven discovery, SDD)
  3. 이전 단계로의 피드백 허용
    • 제품 명세를 작업하는 엔지니어·디자이너는 제품 브리프를 작업한 엔지니어·PM과 긴밀히 협업해야 팀이 학습 가능
    • 피드백이 없으면 “워터폴” 방식이 됨 - 소프트웨어 개발의 복잡성 탓에 인기를 잃은 방식
    • 워터폴의 문제점: 초기 단계를 과도하게 생각하게 만듦(나중에 바꾸기 어렵기 때문) + 변화하는 정보에 적응 못함

이 챕터에서 함께 다룰 핵심 개념

  • 유스케이스를 정리하는 방법
  • 프로덕트 사고에서 시스템 사고로의 전환
  • 제품이 사용자에게 제공할 가치를 비판적으로 사고하는 법 (네 가지 “잔인한 진실” 포함)
  • 소프트웨어를 만들고 유지하는 데 드는 노력의 총체적 평가
  • 사용자 흐름을 통해 JTBD를 찾아내는 법
  • 챕터 도입부로 AI 에이전트 구축 사례 연구 제시

7.2 사례 연구 소개

  • 저자 집필 시점(2025년)은 “에이전트의 해”로 불림 - AI 어시스턴트·에이전트 사례 연구를 포함
  • 가상의 사례이나, 실제 여러 기업이 실험 중인 제품의 특징들을 조합함

배경 설정

  • 팀: 인터넷·케이블 제공업체 Kabletown의 지원 엔지니어링 팀, 팀 헌장은 “지원 업무를 원활하게 만들기”
  • 현황
    • 레거시 자동화 챗봇이 존재하나 LLM 이전 기술 기반이라 유연성이 떨어짐
    • 고객들은 Kabletown 지원을 싫어하고 갇힌 느낌을 받음
    • 인간 상담원은 과부하 - 이메일·채팅 응답까지 며칠 소요
  • 데이터
    • 상위 7개 계정 액션 유형이 지원 요청량의 65% 차지
    • 현재 솔루션은 그중 약 **15%**만 효과적으로 자동화

AI 관련 정의

  • Assistant(어시스턴트): 단일 상호작용 내에서 질문에 답하고 분석·작업 실행이 가능한 AI
  • Agent(에이전트): 목표를 향해 자율적으로 계획하고 행동할 수도 있는 AI
  • Tool(도구): 어시스턴트·에이전트에게 사용법을 알려주는 설명이 딸린 함수
  • Kabletown이 에이전트가 필요한지 어시스턴트가 필요한지 확실치 않으므로, 정밀함이 불필요할 때는 두 용어를 혼용

AI Assistant Product Brief - “Helpy McHelpface”

제품 테제(두 가지 핵심 주장)

  1. 사용자 불만 감소: 기존 어시스턴트는 대부분 요청을 제대로 처리하지 못하고, 인간에게 넘겨야 할 순간조차 80% 확률로 잘못 판단. Helpy는 사용자 의도를 더 잘 이해해 직접 소통을 개선, 불만을 낮춤
  2. 지원 업무 부담(toil) 완화: 기존 어시스턴트는 변경이 필요할 때마다 인간을 투입. Helpy에게 계정 변경·예약 생성·배송 주문 등을 처리할 도구를 주면 남은 미자동화 지원량의 절반 이상을 자동화 가능 → 대기 시간·사용자 만족도 개선, 상담원 적체 완화

안티테제 / 리스크

  • Helpy가 하지 말아야 할 행동을 해서 사용자가 불쾌하게 놀랄 위험
  • 어시스턴트만으로는 부족하고 자율 계획 에이전트가 필요해 구축·운영 비용이 증가할 가능성
  • 지식 베이스가 Helpy에게 필요한 맥락을 제공하기에 충분하지 않을 가능성

타겟 오디언스 (두 페르소나, 하나부터 시작할 수도 있음)

  • 계정 지원 사용자 - Tinker Tia: 오랜 인터넷·케이블 고객, 서비스 업/다운그레이드나 하드웨어 추가·업그레이드 등 계정 변경을 원함
  • 기술 지원 사용자 - Sad Lisa: 신규 인터넷 고객, 뭔가 작동하지 않아 수리를 원함

제품 목표

  • 주요 목표: 대규모 채용 없이 사용자 만족도 증가 + 지원 적체 개선을 동시에 달성. 단기적 수익성 개선은 목표가 아님
  • Adoption metric: 완전 자동화된 지원 상호작용 비율을 15% → 65%로 증가
  • Value metric: 지원 상호작용 후 고객만족도를 2.1 → 3.5(5점 만점)로 개선
  • KPI: 수익 중립(profit-neutral). 인간 지원 비용 절감은 기대하나, 다운그레이드를 쉽게 만들어 매출이 줄 수도 있음 → 다운그레이드를 추적해 매출 영향 파악

북극성 시나리오: 다음 절에서 다룸

7.3 북극성 시나리오로 프로덕트 비전 완성

시나리오 기반 발견 (Scenario-Driven Discovery, SDD)

  • 저자가 만든 용어(개념 자체는 기존에 존재) - discovery 전 과정에서 시나리오를 활용하는 방식
  • 업계에서 가장 유사한 개념은 “scenario-based design”이나 잘 알려져 있지 않음 - 요구사항·JTBD를 찾기 위한 시나리오 활용에 특화된 용어가 필요해 새로 명명
  • 시나리오에 의존해 완전하고 유용한 제품을 전달할 수 있는 이유
    • 캐릭터의 니즈를 강조해 Why를 명확히 하고, 요구사항과 원래 의도 간 정렬을 보장
    • 포괄적인 스토리를 조명해 중요한 기능·엣지 케이스를 발견할 가능성을 높임
    • 스토리는 추상적 요구사항 목록보다 오해하기 어려워, 팀 정렬이 쉬워짐
    • 요구사항 목록에서 버그를 찾는 것보다 시나리오의 플롯홀을 찾는 것이 더 쉬움
  • 제품 브리프에 담기는 시나리오 = 북극성 시나리오

북극성 시나리오란 제품 생애주기 내내 지니고 다닐 대표 스토리 모음. 무엇을 하고 있는지 설명할 때 쓰이고, 요구사항을 정당화·근거로 삼으며, 나중에 빠뜨린 것이 없는지 확인하는 체크리스트 역할을 함. 데모의 소재가 되며, 제품에 따라 마케팅 자료·시작 가이드·샘플로 이어질 수 있음. PRD에서는 세분화된 “요구사항”(애자일에서는 “스토리”)으로, 제품 명세에서는 “사용자 흐름” 또는 “스토리보드”로 불림.

시나리오를 팀으로 브레인스토밍하기

  • 모든 엔지니어가 완전한 유저 스토리 작성을 좋아하지는 않음 - 낯설어서 부담스러워하거나, 창작보다 비평 성향인 경우가 있음
  • 극복 방법: 짝을 짓거나 팀을 이뤄 협업 - 한 명이 핵심을 쓰고 다른 사람이 단계를 덧붙이는 식. 모두가 공동 소유감·장인정신을 느끼며 마무리
  • 브레인스토밍의 핵심: 모든 아이디어에 수용적 태도, 서로의 아이디어 위에 쌓아가기
  • 압박 속에서 침착함·포용성을 유지하는 법: 사람마다 discovery 시 무의식적으로 다른 시간축(timeline)을 염두에 둠 - 어떤 사람은 장기적 시야, 어떤 사람은 몇 주 앞만 봄. 더 미래지향적 역할과 더 전술적 역할이 섞여 있음
  • 브레인스토밍 중에는 사람들이 미래를 꿈꾸도록 허용
    • 값비싼 기능을 전제로 한 스토리를 쓴다고 해서 그것이 곧 만들어진다는 뜻은 아님을 팀에 확립 → 첫 릴리즈에 모든 기능을 욱여넣으려 한다고 오해하는 사람들과의 긴장을 해소
    • 첫 마일스톤을 만들 때까지 이 마인드셋 유지 - 목표는 최고의 아이디어를 놓치지 않을 만큼 많은 아이디어를 확보하고, 각 마일스톤이 장기 비전을 향하도록 하는 로드맵을 개발하는 것
  • 브레인스토밍은 재미있어야 함 - 애자일의 “스티키 노트” 연습(작은 스토리를 컬러 포스트잇에 적고 테마별로 분류 후 선정)이 유명한 예. 팀에 맞는 방식을 쓰되, 결과적으로 소수의 북극성 시나리오를 담은 공유 문서로 귀결되어야 함

북극성 시나리오 선정 및 다듬기

  • 로드맵에 올릴 만한 가장 중요하고 설명력 있는 시나리오를 선정 - 세부 우선순위화는 나중 단계, 첫 단계는 최고의 아이디어를 큐레이션하고 가장 잘 조명하는 스토리를 구체화하는 것
  • (1장에서 소개된 방식대로) 팀은 선정된 스토리의 플롯홀을 찾아 디버깅해야 함 - 사용자 여정에서 단계를 건너뛰는지, 사용자에 대해 비현실적(영웅적)인 가정을 하는지, 중요한 세부사항을 얼버무리는지 점검
  • 스토리 창작보다 이 디버깅을 더 잘하는 엔지니어도 있음 - 그래도 가치는 동일
  • 저자 경험: 구두보다 타이핑된 코멘트로 디버깅하는 것이 생산적이고 화기애애함 - 브레인스토밍을 건설적·긍정적으로 유지하는 데 도움
  • 이 단계의 스토리는 고수준 - 정확성(accuracy)은 추구하되 정밀함(precision)까지는 불필요. 사용자 흐름을 쓸 때 세부사항까지 깊이 들어감. 완전하고 동기부여되는 스토리로 무장하면 이후 모든 요구사항을 찾아낼 수 있음

Kabletown의 북극성 시나리오 다섯 가지 (요약)

① 케이블 박스 설치

  • Tinker Tia가 침실에 놓을 두 번째 TV용 새 케이블 박스가 필요해 kabletown.com에서 Helpy를 찾음
  • Helpy가 설치기사 지원 필요 여부를 확인 → 요금제 변경을 안내하고 동의를 받아 배송 주문 접수
  • 주문 발송 시 채팅으로 알림, 배송 도착 예정에 맞춰 설치 예약을 조율
  • 기술자의 설치 완료 보고 후 Helpy가 다시 연락해 만족도 설문 진행

시나리오 작성 방법 (저자의 방법론)

  1. 흔한 사용자 문제(“새 케이블 박스를 설치하고 싶다”)에서 출발
  2. “다음에 무슨 일이 필요할까?”, “Kabletown 프로세스는 어떻게 되어야 할까?“를 계속 자문하며 문제가 완전히 해결될 때까지 진행
  3. 가치 지표를 어떻게 추적할지 고려 - 마지막에 고객 설문을 추가
  4. 플롯홀 탐색 - 스토리가 모호하거나 특정 결과가 우려될 때 “요구사항을 이끌어낼 흥미로운 세부사항이 무엇인가?“를 자문
  5. 특정 솔루션에 지나치게 프리스크립티브(규정적)하지 않은지 재점검 - 초점은 “무엇을·왜”이지 “어떻게”가 아님

브레인스토밍 이후 팀 논의

  • 이 시나리오는 Helpy가 복잡한 워크플로우를 거쳐야 함을 보여줌 → Helpy가 높은 자율성을 가진 에이전트여야 하는지, 어시스턴트를 감싸는 하드코딩된 워크플로우로 충분한지 논쟁
  • 다양한 변형 시나리오를 스케치해 Helpy가 마주할 상황의 폭을 확인 → Helpy는 (단순 어시스턴트가 아닌) 에이전트여야 한다고 결론, 제품 테제/안티테제 업데이트
  • 북극성 시나리오는 널리 공유되므로 이런 변형들은 메인 목록에서 제외 - 조직 전체엔 불필요하지만 팀에는 관련 있는 시나리오를 담은 “부록”으로 별도 관리

추가 북극성들 - 다양한 요구사항을 조명하기 위해 작성 (모바일 vs PC, 영업 vs 기술지원 혹은 둘 다, 영업 내 업그레이드 vs 다운그레이드). 다운그레이드는 매출 관련 논쟁을 부를 수 있어 특히 부각 - 변경 비용이 커지기 전에 가장 큰 리스크·불일치에 조기 주의를 환기하고, 재무 담당 등 모든 이해관계자가 대표된다고 느끼게 하려는 목적

② 다운그레이드

  • Tia가 안 쓰는 침실 케이블 박스로 월 요금을 줄이고 싶어함
  • Helpy가 추가 박스 없는 비슷한 요금제를 찾아 절감액과 함께 제안 → 동의
  • 박스 반납 시 요금 인하 처리 안내 → 동의 → 반납용 포장재 발송
  • 발송 완료 후 박스 수령 시 설문 발송

③ 계정 변경에 화가 난 고객

  • Tia가 안 쓰는 케이블 박스로 요금을 줄이려 문의
  • Helpy는 그녀가 프로모션 요금제 중이라 절감 가능한 대안이 없다고 안내
  • Tia가 화가 나 상담원 연결 요구 → 인간 상담원이 채팅을 이어받음
  • 상담원이 박스 반납 조건으로 일회성 소액 할인을 제안 → 동의 → 설문 수신

④ 정전/장애

  • Sad Lisa의 케이블 인터넷이 끊겨 무선 요금제로 정상 작동하는 휴대폰의 Kabletown 앱에서 Helpy에게 문의
  • Helpy가 주소를 장애 지도와 대조해 장애를 확인 → 복구 예상 시간(ETA) 안내
  • 복구 완료 시 알림 및 만족도 설문 발송

⑤ 고객 하드웨어 문제

  • Sad Lisa의 인터넷이 느려 Kabletown 탓이라 생각 → kabletown.com의 “get support” 버튼 클릭
  • Helpy가 설정에 대해 질문해 그녀가 무선으로 연결된 PC 노트북 사용자임을 파악
  • 장애가 아님을 확인 → 과거 가장 효과적이었던 순서로 진단·제안 안내 (무선 공유기 재설정, 윈도우 네트워크 진단 등)
  • Kabletown 제공 무선 공유기 재설정으로 문제 해결
  • Helpy가 재발 시 다시 연락하라고 안내(공유기 결함 가능성 대비) → 설문 발송, Lisa가 높은 평점 부여

시나리오 작성 시 고려한 것

  • 봇을 싫어하는 사용자 등 마찰(friction)과 실패 상황을 의도적으로 추가 → Helpy의 회복탄력성 확보
  • 새로운 요구사항을 떠올릴 때마다 이를 포함하는 스토리를 만들려 함 - 예: Lisa의 인터넷이 끊겨 휴대전화로 전환해야 했다는 설정으로 모바일 지원의 필요성을 정당화. 스토리는 해당 기능의 “세일즈 피치” 역할을 하며, 이후 우선순위화 시 평가됨
  • 목표: 무엇을 하려는지 명확하고 직관적으로 전달 - 스토리는 어릴 때부터 배운 보편적 소통 매체이므로, 시스템을 다르게 개념화하더라도 스토리의 의미에는 대체로 공감 가능
  • 이 모든 것을 만드는 것이 쉽지 않을 수 있음 - 이는 좋은 신호. discovery 단계는 발산적(divergent)이고 탐험적이어야 함(서문의 Double Diamond 모델). 비전은 높은 기준을 제시해 어렵더라도 큰 임팩트를 낼 일을 하도록 밀어붙여야 함
    • 예: 배송 시 Tia에게 푸시 알림을 주는 게 어렵다면, 나중에 대안 스토리로 반복(iterate)하거나 점진적 릴리즈로 서서히 구현 - 단, 증분 릴리즈 자체가 실제 가치를 제공하는 한
  • 이 시나리오들은 나누고 만들기 쉬운 형태로 정리되어 있지 않음 - 다음 단계인 PRD 작성에서 다룸

7.4 북극성 시나리오를 요구 사항으로 변환

스토리 뷰 vs 시스템 뷰

  • 유저 스토리를 데이터베이스 테이블에 담는다면, 사용자 페르소나에 붙은 시간순 모먼트 목록이 됨 - 사용자의 제품 내 여정을 사고하기 좋은 표현
  • 시스템 설계에는 이 표현이 유용하지 않음 - 시스템은 그래프 데이터베이스에 가까움: 폼 안의 버튼, 페이지 안의 폼, 버튼 클릭이 스토리지와 통신하는 서비스 주변 API 게이트웨이로 연결됨
  • 우리 뇌가 이런 데이터베이스와 유사한 것을 담고 있다면, discovery 단계에서 design 단계로 이동하는 과정은 시간·스토리 기반 표현에서 그래프·시스템 기반 표현으로의 “대규모 재인덱싱(great reindexing)”

Figure 7-2. 스토리 뷰(story view) - 사용자가 시간 축을 따라 화면을 마주치는 표현

flowchart LR
    U((사용자)) --> T0["T0: 가입/로그인 선택 화면
지금 가입하기 / 계정이 있으신가요?"]
    T0 --> T1["T1: 로그인 화면
Email 입력 / Password 입력 / Log in 버튼"]
    T1 --> T2["T2: 목록 화면
Lorem / Ipsum / Dolor"]

Figure 7-3. 시스템 뷰(system view) - 동일 제품을 버튼·폼·API·저장소의 그래프로 표현

flowchart TB
    subgraph LoginPage["login_page 컴포넌트"]
        L1["Email:"]
        L2["Password:"]
        L3["Log in 버튼"]
    end
    subgraph LoremPage["lorem_page 컴포넌트"]
        R1[Lorem]
        R2[Ipsum]
        R3[Dolor]
        R4[Sit]
    end
    LoginPage -- "POST /login" --> DB1[(데이터베이스)]
    DB2[(데이터베이스)] -- "GET /lorem" --> LoremPage
  • 위 두 그림은 동일한 제품을 시간순(스토리) 인덱스와 그래프(시스템) 인덱스라는 두 가지 대안적 방식으로 표현한 것
  • 가장 다재다능한 엔지니어는 이 두 인덱스를 모두 뇌 안에 구축할 수 있는 사람
    • 첫 번째만 구축 가능 → 프로덕트 매니저에 가까움
    • 두 번째만 가능 → 한쪽에 전적으로 의존, 소통에 어려움
  • 두 표현 간 번역이 특히 중요 - PM과 엔지니어가 서로의 표현을 내재화하지 못할 때 오역이 빈번함
  • 대규모 재인덱싱은 흔히 고수준 요구사항과 설계 원칙에서 시작

고수준 요구사항 (High-Level Requirements)

  • 설계에서 “지향하는 바”를 확립하는 요약 - 어떤 페르소나를 우선하는지, 매출을 지향하는지, 사용성을 지향하는지 등
  • 모든 세부 유스케이스를 다 보지 못하는 사람들과의 소통·신뢰 확립에 도움

Kabletown의 고수준 요구사항 예시

  • 제품 브리프에 따라 사용자 만족도를 높이고 지원 부담을 낮추되 매출 중립을 유지

  • 첫 마일스톤은 기술 지원 상호작용에 집중

  • 가장 빈번한 작업(예: 느린 인터넷 진단)을 우선 타겟 - Helpy가 자신의 전문 범위 밖임을 감지하면 무리하지 않고 인간에게 위임

  • 안전을 초기부터 중시해, 때로는 최대 도움이 되지 못하더라도 Helpy를 명확한 가드레일 안에 유지

  • 서로 다른 두 원칙 사이에서 절충(tie-break)하는 지점을 짚어주는 것이 중요 - 흥미롭고 논쟁적이어서 좋은 토론을 촉발함

  • 이를 PRD 최상단에 두면 독자가 나머지 문서를 더 쉽게 이해 가능

PRD (제품 요구사항 문서)

  • 회사마다 PRD의 의미가 조금씩 다름 - 어떤 팀은 이후 다룰 제품 명세까지 PRD에 포함시킴. 얼마나 많은 설계를 담을지는 모호하며, 특히 대형 프로젝트에서는 팀과 명확히 조율할 가치가 있음
  • 저자 기준 PRD의 목표: 엔지니어·UI 디자이너가 상세 설계 작업을 시작할 수 있도록 언블록. (이후 다룰 제품 명세는 구현을 언블록.) 따라서 “무엇을(what)“에 무게, “어떻게(how)“는 가볍게
  • PRD 포함 요소
    • 고수준 제품 요구사항 및 설계 원칙
    • 세밀한 제품 요구사항 - 사용자가 원하는 구체적 상호작용을 기록한 유스케이스 개요서(또는 스토리 개요서)
    • 첫 릴리즈에 포함할 기능을 고르는 마일스톤 정의
    • 적절한 팀·이해관계자 확보 (이 책의 범위 밖)

유스케이스 개요서 (Use Case Compendium)

  • 각 요구사항이 사용자 관점에서 작성되기 때문에 “유스케이스 개요서”라 부름
  • 흔한 요구사항 템플릿
[사용자]는 [행동]을 할 수 있어서 [동기]를 달성한다.
[제품]은 [기능]을 제공하여 [사용자]가 [가치]를 얻을 수 있게 한다.
  • “고객 하드웨어 문제” 시나리오를 분해한 첫 요구사항: Sad Lisa는 기술 지원을 찾을 때 로그인 없이 홈페이지에서 Helpy를 쉽게 찾을 수 있어야 함

요구사항 작성 시 저자가 시도한 것

  • 명확하고 모호하지 않게 소통할 만큼 구체적으로 - “Helpy가 발견 가능하다” 수준이 아니라, 로그인 없이 홈페이지에 있다는 구체성
  • 의도를 표현 - 단지 홈페이지에 있다는 게 아니라 “발견 가능성(discoverability)“에 관한 것임을 명확히 함. 홈페이지 배치가 어렵다면 엔지니어가 다시 확인하고 대안을 찾을 수 있음
  • 엔지니어에게 세부 설계 재량을 여전히 부여 - 홈페이지의 특정 위치·메뉴를 지정하지 않고, 발견 가능하고 로그인 전이어야 한다고만 명시
  • 명확하되 지나치게 규정적이지 않기 어려울 때는 예시를 덧붙임 (예: “홈페이지(예: 우하단에 떠 있는 챗봇 버튼)”)

“고객 하드웨어 문제” 북극성의 나머지 요구사항

  • Helpy는 소프트웨어부터 하드웨어, 장애까지 다양한 원인의 느린 인터넷 진단을 통해 Lisa를 효율적으로 안내할 수 있어야 함
  • Helpy는 지원 데이터베이스에서 학습한 가장 성공적인 개입 방법을 Lisa에게 제안해야 함
  • Helpy는 무엇이 효과가 있었는지 Lisa에게 물어 기술 변화에 따라 계속 학습해야 함
  • 개선 조치가 결론에 이르지 못할 경우, Helpy는 채팅 종료 전 후속 조치를 제안해 Lisa가 포기하지 않도록 해야 함
  • Lisa는 만족도 설문을 받아 결과를 추적할 수 있어야 함
  • Helpy는 만족도 설문으로 학습해 시간이 지날수록 더 효과적으로 발전할 수 있어야 함
  • (나머지 북극성들도 유사하게 분해 - 생각하고 학습하며 요구사항을 추가하거나, 새 북극성을 더하거나, 우선순위 낮은 것을 제거 가능)

위임(delegation) 관련 추가 요구사항

  • Helpy는 요청이 지원용인지 영업용인지 95% 정확도로 파악할 수 있어야 함
  • Helpy는 사용자가 화났음을 인간 상담원 대비 최소 90% 수준으로 감지해 인간에게 위임할 수 있어야 함
  • 인간 상담원이 대화를 인계받을 때 Helpy와 상담할 수 있어, 인간 지원이 Helpy가 제공했을 수준 이상으로 유지될 수 있어야 함
  • 위 수치는 다소 임의적이며 정확히 달성 가능한지 아직 확실치 않지만 대략적인 숫자를 정한 것 - **evals(에이전트 품질 평가)**로 책임을 물을 것이며, 목표에 근접하지 못하면 접근법을 재고

마지막 요구사항 (이후 재차 언급)

  • Sad Lisa는 자택 인터넷을 사용하지 않는 모바일 기기로도 Helpy를 이용할 수 있어 정전 중에도 지원을 받을 수 있어야 함

유스케이스 개요서 정리하기

  • 개요서는 우선순위화·설계·실행 단계 내내 엔지니어를 안내함 - 모든 요구사항을 처음부터 다 파악하는 것이 이상적이나, 반복 과정에서 더 추가되는 경우가 많음
  • 이 때문에 개요서는 load-bearing(핵심 지지) 문서이며, 중대형 프로젝트에서는 잘 정리하고 태깅해두는 것이 중요
  • 요구사항을 계획 시스템의 티켓으로 전환 가능 - 예: Atlassian Jira의 Story 티켓 유형이 사용자 중심 요구사항 각각에 대응
  • 또는 스프레드시트, Notion 같은 임베디드 데이터베이스 테이블 앱 사용 가능 - 정렬·필터·관리 가능. 원래 제품 비전 요소로 태깅도 가능

추천 태그 / 섹션

  • 페르소나: Sad Lisa용 지원 요구사항, Tinker Tia용 영업 요구사항처럼 구분해 집중도를 높임. 페르소나 기준 우선순위 결정에도 활용
  • 북극성 시나리오: 특정 시나리오의 약속을 완성하는 데 필요한 모든 요구사항을 조회 가능
  • 엣지 케이스: 중요하지만 적은 인원만 읽고 이해하면 되므로 필터링 가능하게 함
  • 마일스톤: 우선순위화 및 로드맵 계획 시 유용
  • 상태: 프로젝트 트래킹 용도로도 활용 가능
  • 코멘터리: 필요에 따른 추가 정보

Table 7-1. Helpy를 위한 유스케이스 개요서 (우선순위화 이전 상태)

요구사항 마일스톤 페르소나 북극성
기술 지원을 찾을 때 Sad Lisa는 로그인 없이 홈페이지에서 Helpy를 쉽게 찾을 수 있다 - Anybody 고객 하드웨어 문제, 악성코드
Lisa는 자택 인터넷이 아닌 모바일 기기로 Helpy를 이용해 정전 중에도 지원받을 수 있다 - Anybody 전체
Helpy는 소프트웨어부터 하드웨어, 장애까지 다양한 원인의 느린 인터넷 진단을 효율적으로 안내할 수 있다 - Sad Lisa 정전, 고객 하드웨어 문제, 악성코드
Lisa는 Helpy가 지원 데이터베이스에서 학습한 가장 성공적인 개입 방법을 제안받는다 - Sad Lisa 정전, 고객 하드웨어 문제, 악성코드
Lisa는 만족도 설문을 받아 결과를 추적할 수 있다 - All All
개선 조치가 결론에 이르지 못하면 Helpy는 채팅 종료 전 후속 조치를 제안해 사용자가 필요시 재참여하게 한다 - Sad Lisa 고객 하드웨어 문제, 악성코드
Helpy는 만족도 설문으로 학습해 시간이 갈수록 더 효과적이 될 수 있다 - All All
Helpy는 요청이 지원용인지 영업용인지 95% 정확도로 판단할 수 있다 - All All
Helpy는 사용자가 화났음을 감지해 인간에게 위임할 수 있다 - All 계정 변경에 화난 고객, 성능 문제를 일으키는 악성코드
인간 상담원은 대화를 인계받을 때 Helpy와 상담할 수 있다 - All 계정 변경에 화난 고객

7.5 첫 마일스톤 요구 사항의 우선순위 결정

  • 장기 요구사항에 팀이 정렬한 후 다음 목표는 로드맵 작성 → 우선순위화가 필요
  • 우선순위화는 가장 어렵고 결과가 중대한 업무 중 하나로, 다소 지저분해지기 쉬움. 무엇이 중요한지에 대한 생각이 구성원마다 다르고 쉽게 산만해지기 쉬움
  • Table 7-1도 실제 전체 분량의 절반 이하 수준 - 전체 마일스톤 시퀀스 계획 + 조직·코드베이스 제약까지 겹치면 압도적
  • 게다가 사용자 앞에 무언가를 내놓기 전에 완료해야 할 것이 많음 - 이는 네 가지 잔인한 진실(brutal truths) 때문
  • 전략이 필요

접근 순서

  1. 경쟁하는 여러 관심사 중 무엇을 최적화할지 정하기
  2. SDD에서 자주 쓰이는 우선순위화 전략 - 첫 마일스톤에 하나의 북극성 시나리오를 집중
  3. 한 번에 한 마일스톤씩 계획하기
  4. Kabletown 사례 연구에 전략 적용

우선순위화에서 정말 중요한 것

  • 가장 근본적인 두 요소: **사용자 임팩트(user impact)**와 노력(effort)

TIP: bang-for-the-buck을 최적화할 것. 즉 사용자 임팩트(bang)와 전사적 노력(buck)의 조합을 기준으로 시나리오·기능의 우선순위를 매길 것.

  • 말은 쉬워도 실행은 어려움 - 실제로는 미로 탐색과 비슷. 대인관계 갈등, 조직 이슈, 자존심 보호, 특정 고객과의 약속 이행, 직원 간 인센티브 차이 등에서 비롯한 다른 우선순위가 끊임없이 끼어듦. 냉철함이란 노력 단위당 사용자가 얻는 가치에 계속 집중하는 것
  • Bang과 Buck을 측정하는 것 자체도 어려움
  • 대부분의 엔지니어는 Buck 또는 Bang 중 하나에 지나치게 치우쳐 있음(캐리커처) - 어떤 이는 코드를 덜 쓰고 코드베이스를 단순·유지보수 가능하게 유지하며 더 빨리 전달하는 것을 선호(사용자 경험·채택에 불리하더라도), 어떤 이는 완벽한 제품을 위해 내부적으로 필요한 모든 것을 함 - 지혜는 둘의 균형

Buck(비용) 최적화

  • 사용자 임팩트만이 중요하다고 순수주의적으로 말하고 싶지만 노력 수준도 중요 - 천 시간을 들여 오백 시간만 절약한다면 세상을 더 낫게 만든 게 아님
  • 엔지니어는 노력 수준에 이미 잘 맞춰져 있는 경향 - 그래도 두 가지 강조점
    1. 경쟁 우위를 만들거나 사용자를 기쁘게 할 것이라면 때로는 엄밀히 필요한 것보다 더 많은 노력을 투입 - 예: 사용자 기반이 어느 정도 크다면, 그들 모두의 시간을 집합적으로 많이 절약해줄 수 있는 약간의 추가 시간 투자는 대체로 좋은 트레이드오프
    2. 엔지니어링 비용뿐 아니라 전사적(all-up) 비용을 고려 - 양방향 작용
      • (주니어) 엔지니어는 구현 비용을 추정할 때 프로덕션 코드(해피 패스)의 규모만 생각하는 경향 - 엣지 케이스 방어 코딩, 시나리오 테스트, 문서화·도그푸딩·배포·A/B 테스트·마케팅 같은 비코딩 작업도 반영해야 함
      • 전사적 비용을 고려하면 스코프 크리프를 막을 수 있음 - “이건 추가하기 쉽다”는 생각이 들면 친한 이웃 PM에게 확인 - 그들은 제품을 출시해 사용자 앞에 내놓기까지 회사가 해야 할 모든 일을 반박으로 설명해줄 수 있음
      • 반대로, 만들지 않을 경우의 전사적 비용도 고려 - 예: Helpy가 하지 말아야 할 말을 하지 않도록 하는 가드레일을 추가하지 않기로 결정하면, 문제 발생 시 대비 전략이 필요해져 사용자 지원, 홍보팀, 법무팀에 부담을 줄 수 있음

Bang(임팩트) 최적화

  • 수학적으로, 사용자 임팩트는 규모·도달범위(scale/reach)와 사용자당 가치(value per user)를 원하는 시간축(time horizon)에 걸쳐 곱한 값

사용자 임팩트 = 사용자 가치 × 규모 × 시간

  • 사용자 임팩트는 **유틸리티 함수(utility function)**로 추정 가능 - 포함된 기능 대비 사용자가 얻는 가치를 그래프로 나타낸 것. 제품을 개선할수록 유틸리티 함수는 증가해야 함
  • 안타깝게도 전형적인 유틸리티 함수의 수학은 대단히 가혹함 - 큰 임팩트를 내기 전까지 해야 할 일이 매우 많음

네 가지 “잔인한 진실”(Brutal Truths)

사용자에게 가치 있는 것을 만들기가 얼마나 어려운지 보여주는 냉혹한 교훈

  1. 규모 확보는 어렵다 (Scale is hard)
  2. 사용자 가치는 후반부에 몰려 있다 (User value is back-loaded)
  3. 경쟁 차별화는 더욱 후반부에 몰려 있다 (Competitive differentiation is even more back-loaded)
  4. 시간이 지나도 가치를 유지하는 것은 어렵다 (Sustaining value over time is hard)

① 규모 확보는 어렵다

  • 임팩트와 스케일이 모두 높은 제품을 만드는 것은 도전적 - 소수에게 깊이 유용한 것을 만드는 건 그리 어렵지 않음
  • 반면 많은 사람에게 소구하려면 발견 가능성, 유용성, 사용자 친화성, 좋은 마케팅, 대규모에서의 견고함 등이 필요
  • 대조적으로, 홈페이지 폰트를 바꾸는 건 쉽고 도달범위는 크지만 그다지 유용하지는 않음

② 사용자 가치는 후반부에 몰려 있다

  • 유틸리티 함수는 기능이 늘어남에 따라 선형적이지 않음 - 임계 기능 총량을 갖추기 전까지는 쓸모가 없음
  • 예: 자전거는 바퀴 두 개, 브레이크, 방향을 트는 핸들바, 언덕을 위한 기어 변속이 갖춰지기 전까지는 유용하지 않음

Figure 7-4. 자전거의 유틸리티 함수 (기능 추가에 따른 가치)

누적 기능 상대적 가치 (0~100 정성적 추정) 해당 제품 유형
바퀴 1개 (Wheel 1) 약 2 (거의 0) 외발자전거 시작
+ 브레이크 약 3 외발자전거 완성
+ 핸들바 약 8 단일 기어 자전거 진행 중
+ 바퀴 2개 약 20 단일 기어 자전거 완성
+ 기어 약 75 (급격한 스파이크) 현대식 로드 바이크 완성
  • 그래프 형태는 “평평하다가 급격히 치솟는(flat-until-it-spikes)” 패턴이 특징
  • 초반 몇몇 기능은 훌륭할 필요 없이 그냥 “체크박스” 기능인 경우가 많음 - 그러나 있어야 제품이 대부분의 사용자에게 작동함
    • 죽고 싶지 않다면 브레이크가 필요
    • 외발자전거에서 균형 훈련을 전념할 게 아니라면 두 번째 바퀴가 필요
    • 트랙 레이싱을 하는 게 아니라면 기어 변속을 원하게 됨

③ 경쟁 차별화는 더욱 후반부에 몰려 있다

  • 경쟁이 추가 압박을 가함 - 완전히 새로운 것을 발명하는 게 아니라면, 시장 진입 시 누군가 진지하게 받아들이기 전까지 임계 수준의 기능 집합이 필요. 그 후에는 새 기능이나 비용 절감 등 또 다른 무언가를 추가해야 함. 때로는 과거 버전과 경쟁하며 제품을 다시 작성하는 경우도 있음
  • 저자는 이를 **Value over Replacement Product(VORP, 대체 제품 대비 가치)**로 명명
    • 세이버메트릭스(야구 통계 연구)에서 차용한 용어 - 원래 P는 “플레이어(Player)“를 뜻하며 대체 가능한 최상위 마이너리그 선수 대비 메이저리그 선수의 가치를 가리킴. 여기서는 P를 “프로덕트(Product)“로 응용
    • VORP 계산법: 유틸리티 함수에서 시장의 기존 제품 가치 혹은 제품의 이전 버전 가치를 빼는 것

Figure 7-5. 자전거의 VORP 함수 (기능 추가에 따른 대체 제품 대비 가치)

누적 기능 VORP (정성적 추정)
바퀴 1개 0
+ 브레이크 거의 0
+ 핸들바 거의 0
+ 바퀴 2개 낮음
+ 기어 다소 상승
+ ??? (미지의 차별화 요소) 뚜렷하게 상승
  • VORP는 단순 사용자 가치보다 더 늦게 양(positive)으로 전환됨 - 유용하기만 한 게 아니라 특별해야 함을 요구
  • Helpy의 경우, 기존 자동화 시스템뿐 아니라 실제 인간 상담원과도 경쟁해야 함 - 만만치 않은 과제. AI 기술이 성숙함에 따라 시장의 어시스턴트 성능 기대치도 계속 높아질 것

④ 시간이 지나도 가치를 유지하는 것은 어렵다

  • 시간 요소와 관련 - 일시적 승리보다 지속적인 사용자 가치를 제공하는 것이 더 어려움
  • 고장 나기 쉽거나 반복 사용을 유도할 만큼 편하지 않은 저렴한 자전거를 만드는 것이, 사용자가 수년간 애용할 자전거를 만드는 것보다 쉬움
  • 마찬가지로 Helpy도 계속 유용하려면 최신 지원 데이터로 지속적으로 학습되고 성능 저하를 모니터링해야 함

네 가지 잔인한 진실의 시사점

  • 대부분의 제품·기능이 큰 임팩트를 내지 못하는 이유: 덜 만들어져 있기(underbaked) 때문 - 핵심 기능이 빠졌거나, 장기간 확장·유지보수 가능하도록 엔지니어링되지 않음
  • 잔인한 진실은 언더엔지니어링에 대한 경고이나, 오버엔지니어링도 피해야 함 - 지나친 다듬기(over-polishing), 한꺼번에 너무 많은 목표를 시도하거나 덜 중요한 목표를 고르는 것, 시장 출시가 너무 느린 것, 자금 소진, 사용자 피드백 결핍 등
  • (6장에서 언급) 기존 솔루션에서 소외된 타겟 오디언스를 찾아 특별히 그들을 위한 것을 만들면 부족한 기능을 감수시킬 수 있음
  • 또 다른 유효한 해법: (5장에서 논의) 제품을 반복(iterate)하는 것 - 반복할 때는 하나 이상의 잔인한 진실을 일시적으로 무시하지만, 그 과정에서도 계속 배움

Bang과 Buck의 결합

  • 많은 팀이 **최소 기능 제품(MVP, Minimum Viable Product)**부터 시작
    • 저자의 정의: 유틸리티 함수에서 가치가 실질적으로 양(positive)이 되는 지점 - 작은 타겟 오디언스라도 상관없음(잔인한 진실 #1, 규모는 처음엔 무시)
  • **최소 소구 가능 제품(MLP, Minimum Lovable Product)**이라는 용어도 인기를 얻는 중 - MVP가 너무 거칠어지는 경향에 대한 반작용
    • 경쟁이 있는 상황에서는 MLP를 VORP가 실질적으로 양이 되는 지점으로 생각하는 것이 도움 - 사람들은 대체로 VORP가 높은 제품에만 애정을 느끼고 애착을 형성하기 때문. 체크박스 기능만 있는 자전거는 그것이 첫 자전거가 아니라면 사랑하지 않을 것
  • 잔인한 진실 용어로: **MVP는 진실 #2(사용자 가치)**에 초점, **MLP는 진실 #3(차별화된 가치)**에 부응

첫 마일스톤을 위한 타겟 북극성 시나리오 정하기

  • SDD 사용 시, 하나의 북극성 시나리오(혹은 최소한의 세트)에 대한 요구사항 우선순위화를 통해 MVP·MLP를 정의
  • 북극성에 집중하는 것의 장점
    • 완전한 스토리이므로 양의 유틸리티를 전달하게 됨
    • 최선의 제품 비전을 담고 있으므로 누군가에게는 양의 VORP를 지닌 제품을 출시하게 됨
    • 하나의 페르소나·스토리에만 집중하므로 불필요한 기능을 배제하고 일반화는 이후로 미룰 수 있음
  • 초기 타겟 오디언스를 좁힐 수 있음
    • 가장 흔한 예: 첫 릴리즈를 도그푸딩할 수 있는 직원(4장), 또는 초기 버전을 감수하는 대신 제품에 영향력을 행사하거나 화이트글러브 지원을 받는 디자인 파트너로 한정
    • 또 다른 옵션: 하나의 플랫폼(예: 안드로이드 vs iOS), 또는 별도 안내가 덜 필요한 파워 유저로 제한
  • 범위를 좁히면 북극성 시나리오의 일부 요소 없이도 넘어갈 수 있음 - 예: 직원을 타겟팅한다면, 스토리에 기능 발견 단계가 있어도 동료들에게 이메일로 알리고 확인해달라는 것으로 대체 가능
  • 이제 개별 요구사항 우선순위화를 준비할 차례

유스케이스 개요서 우선순위화

  • 우선순위화를 위한 큰 회의 진행 - 테크리드나 PM이 출발점을 미리 표시해두었을 수 있음
  • 표를 시나리오·페르소나로 태깅해두었으므로 무엇이 포함·제외되는지 파악하는 것이 비교적 수월함
  • 저자 경험: 첫 마일스톤에만 집중하는 것이 훨씬 효율적 - MLP에 속하지 않는 항목이 P2인지 P3인지 논쟁하며 시간을 낭비(ratholing)하기 쉬움

간단한 형식 (우선순위 라벨)

  • P0: MVP에 포함. 출시하거나 사용자 피드백을 받지 못하면 안 되는 것
  • P1: MLP에 포함. 매우 매력적인 스트레치 목표, 출시 시점에 가까워질 때 실행. 일정이 밀려 특정 날짜까지 끝내야 한다면 아쉽지만, 여전히 실행 가능한 무언가는 출시 가능
  • 공백: 그 외 나머지 - 다음 마일스톤 계획 시 우선순위 부여

팀이 마일스톤 단위로 일할 때 유용한 대안 스킴 (프로젝트 진행에 따라 재사용 가능)

  • 0: 마일스톤 0에 포함
  • 0.5: 마일스톤 0의 스트레치 목표
  • 그 외 나머지는 공백 - 다음 마일스톤 계획 시 1과 1.5를 각각 추가
  • 저자는 이 마일스톤 형식을 사용 - 단일하고 찾기 쉬운 문서에 제품 로드맵을 최신 상태로 유지하도록 독려하기 때문

Kabletown 사례 - 첫 마일스톤 결정 과정

  • 대부분의 직원이 자택에서 Kabletown 서비스를 사용 → 첫 마일스톤을 Kabletown 직원으로 한정 - 더 관대하고 사려 깊은 피드백 제공(4장). 제품이 완전히 다듬어지기 전에 Helpy를 출시하고 학습 가능. 직원 대상 릴리즈는 MLP가 아니라 MVP가 됨
  • 어떤 시나리오를 선택할지 고려사항
    • MVP는 지원(support) 시나리오 또는 영업(sales) 시나리오 중 하나로 한정 - AI에 학습 데이터를 공급하고 테스트하는 작업량을 크게 줄일 수 있음
    • 흔하거나 임팩트가 큰 시나리오를 고르면 유틸리티 함수가 가장 높게 치솟음 - 기존 상호작용의 대다수가 기술 지원(주로 인터넷이 안 되거나 느리다는 문의)이라고 가정
    • 데스크톱·노트북에서의 기술 지원 요청량이 모바일보다 많다고 가정(실제인지는 불명이나 그렇다고 가정하고 진행)
  • “고객 하드웨어 문제” 시나리오가 이 기준에 부합 - 상당히 묵직한 지원 문제라서 어시스턴트의 AI를 제대로 시험하고 방향이 맞는지 확인 가능
  • 좁힌 타겟 페르소나: “Sad Lisa, PC를 소유한 Kabletown 직원” - 해당 인원은 많지 않지만 Helpy를 좋아하고 좋은 피드백을 줄 것으로 기대
  • 걸림돌: Kabletown에서 웹사이트 프레임워크 리팩터링이 진행 중 → PC로 시작하면 추가로 약 1주일의 작업이 필요할 수 있음 → 모바일로 방향을 바꿔야 할까?
  • 전사적 비용을 고려하면, 관료제가 많은 Kabletown 같은 회사에서 엔지니어링 1주일은 큰 문제가 아님 - 여전히 전체 노력의 상대적으로 작은 부분
  • PC·모바일 사용량이 거의 비슷하다면 모바일을 우선하는 것이 마땅하나, 지원 상호작용의 80%가 PC에서 온다면 추가 1주일이 들더라도 PC를 다루는 것이 맞음
  • 매 단계에서 가치에 집중 - 로드맵 전체를 완주할 것이라 가정하지 않음. 경영진은 쉽게 주의가 분산되므로, 큰 성과를 내기 전에 다른 업무로 재배치되면 매우 곤란해짐

Table 7-2. 우선순위가 매겨진 Helpy 유스케이스 개요서

요구사항 마일스톤 페르소나 북극성
Helpy는 소프트웨어부터 하드웨어, 장애까지 다양한 원인의 느린 인터넷 진단을 통해 Lisa를 효율적으로 안내할 수 있다 0 Sad Lisa 정전, 고객 하드웨어 문제, 악성코드
Lisa는 Helpy가 지원 데이터베이스에서 학습한 가장 성공적인 개입 방법을 제안받는다 0 Sad Lisa 정전, 고객 하드웨어 문제, 악성코드
Helpy는 만족도 설문을 발송해 결과를 추적할 수 있다 (주1) 0 All All
기술 지원을 찾을 때 Sad Lisa는 로그인 없이 홈페이지에서 Helpy를 쉽게 찾을 수 있다 (주2) 0 Anybody 고객 하드웨어 문제, 악성코드
개선 조치가 결론에 이르지 못하면 Helpy는 채팅 종료 전 후속 조치를 제안해 사용자가 필요시 재참여하게 한다 0.5 Sad Lisa 고객 하드웨어 문제, 악성코드
Sad Lisa는 자택 인터넷이 아닌 모바일 기기로 Helpy를 이용해 정전 중에도 지원받을 수 있다 - Anybody All
Helpy는 만족도 설문으로 학습해 시간이 갈수록 더 효과적이 될 수 있다 - All All
Helpy는 요청이 지원용인지 영업용인지 95% 정확도로 판단할 수 있다 (주3) - All All
Helpy는 사용자가 화났음을 감지해 인간에게 위임할 수 있다 (주4) - All 계정 변경에 화난 고객, 성능 문제를 일으키는 악성코드
인간 상담원은 대화를 인계받을 때 Helpy와 상담할 수 있다 (주5) - All 계정 변경에 화난 고객
  • 주1: 단기적으로 사용자에게 필수는 아니지만, 로드맵 초기에는 사용자 피드백이 생명줄이기 때문
  • 주2: 현재 제품에서는 로그인 뒤에 묻혀 있는데, 조기 도그푸딩을 끌어내기 위해 부각시키고자 함 - 사람들이 매일 지원이 필요한 것은 아니기 때문. 또한 인터넷이 느릴 때 로그인하는 것 자체가 고통스러울 수 있음
  • 주3: 마일스톤 1에서는 Helpy가 사용자에게 어떤 종류의 요청인지 미리 물어보고, 영업 건이면 기존 봇+인간에게 넘기는 것으로 충분
  • 주4: 직원 도그푸더들은 더 관대할 것이기 때문
  • 주5: 대규모 상황에서 생산성·품질에 중요하겠지만, 제한된 테스트에서는 아님

우선순위화 이후 북극성 시나리오 재검토

  • “고객 하드웨어 문제” 시나리오는 우선순위화 이후에도 핵심 스토리(느린 인터넷 문의 → 진단 → 무선 공유기 재설정으로 해결 → 재발 시 안내 → 설문)가 그대로 유지됨
  • 다만 “결론에 이르지 못했을 때의 후속 조치 제안” 요구사항은 마일스톤 0.5(스트레치 목표)로 밀려남
  • 저자 평가: 변경 사항이 경미하며, 수정된 스토리도 여전히 설득력 있음
  • discovery를 마무리하고 design 단계로 들어가기 전 마지막 한 단계가 남음 - 상세 사용자 흐름 작성

7.6 첫 마일스톤을 위한 상세 사용자 흐름 설계

  • 지금까지의 스토리는 상당히 고수준이었고 많은 세부사항을 생략함 - 중요한 무언가를 놓쳤을 수도 있음
  • 또한 스토리는 “어떻게(how)“보다 “무엇을(what)“에 초점을 맞춰 왔음 - 사용자가 그 스토리를 달성하는 여러 방식을 여전히 오디션(검토)해야 함
    • 예: Helpy가 사용자를 위해 직접 윈도우 진단을 실행할 수도, 사용자에게 방법을 알려줄 수도 있음. Lisa에게 설문을 채팅 내에서 물어볼 수도, 별도 설문 폼으로 링크할 수도 있음
  • 이제 사용자 흐름을 구체화

사용자 흐름이란: 요구사항 하나를 달성하는 사용자의 상세한 단계 시퀀스와, 시뮬레이션을 작동시키는 데 필요한 전제조건을 보여주는 것

  • 이 절에서는 사용자 흐름을 만드는 방법과 사용자와 함께 검증하는 방법을 다룸
  • 이 작업을 하면서 discovery(연습을 하며 새 요구사항을 학습)에서 definition(제품 자체를 정의하기 시작)으로 전환
  • UI 디자인에서는 “스토리보드”라고도 불림 - 사용자 클릭을 시뮬레이션하는 UI 목업의 시퀀스일 수 있음
  • 인터페이스가 코드라면, 흐름은 코드 목록과 커맨드라인의 조합에 해설이 곁들여진 형태가 됨

흐름 작성을 위한 고수준 조언

  • 사용자 흐름에는 중요한 인터페이스 질문(8장)을 논쟁하고 명확한 시스템 명세(9장)를 추출할 수 있을 만큼의 세부사항이 담겨야 함
  • 북극성을 선정할 때처럼, 여러 옵션을 저울질 - 브레인스토밍하거나 대안적인 사용자 흐름을 제공하고 팀이 선택하도록 함. 다양한 의견을 반영하고 특정 접근법에 얽매이지 않았음을 알리는 좋은 방법
  • 첫 마일스톤에 노력을 집중하되, 전체적인 설계를 위해 조기에 이해하는 것이 중요하다고 판단되는 미우선순위 항목도 포함
  • 다른 시나리오와 마찬가지로 사용자 흐름도 완전한 스토리를 전달해야 함 - 무엇이 사용자를 동기부여했는지, 사용자가 어떻게 기능을 발견했는지부터 시작

Kabletown 요구사항 하나의 흐름화

대상 요구사항: “Helpy는 지원 데이터베이스에서 학습해 가장 흔한 개입 방법을 파악하고 Lisa에게 제안할 수 있다”

  • 초기 Helpy 학습 결과가 기대에 미치지 못함 - 레거시 시스템에서 지원 데이터베이스에 주석(annotation)을 단 적이 없어, 기술 지원 질문의 가장 흔한 해결책이 무엇인지 알 수 없음
  • 최선의 추정: 수천 건의 과거 지원 요청을 검토해 효과가 있었던 개입 방법 또는 “결론 없음”으로 분류해야 함. 또한 상담원의 판단 근거(reasoning)도 함께 주석 처리해 그들의 노하우를 Helpy에게 가르쳐야 함

사용자 흐름 (수동 버전) - 새 페르소나 “Support Rep Sam”

  1. Sam이 상사로부터 한 달 내 자신의 옛 고객 채팅 50건에 주석을 달아달라는 이메일을 받음
  2. 바쁘지만 상사의 요청이라 내부 도구를 열어 자신의 옛 지원 스레드 하나로 이동, 무엇이 문제를 해결했는지 회상하도록 요청받음
  3. “공유기 재설정”, “악성코드 스캔”, “기기 재부팅” 등 태그가 있는 드롭다운 메뉴 제시 - 맞는 태그가 없으면 새 태그를 추가 가능, 이후 다른 상담원들도 분류 시 이를 볼 수 있음
  4. Sam이 자신의 추천 순서를 선택한 이유에 대한 코멘터리 작성 요청받음 (예: “두 사용자의 기기가 오프라인 상태였기 때문에 특정 기기 문제가 아닌 것 같아 공유기 재시작부터 시작했다”)
  5. 제출 후 다음 채팅 스레드로 이동
  6. Sam이 퇴근 전까지 십여 건 정도 처리 - 며칠 뒤 더 많은 주석을 요청하는 알림 이메일을 받고, 링크를 클릭해 몇 건 더 완료

대안 사용자 흐름 (AI 보조 버전)

  1. AI가 천 건의 채팅을 스캔해 각각의 추정 개선 조치와 근거를 추출
  2. Sam이 상사로부터 자신의 옛 채팅 50건에 대해 AI의 해석을 검토해달라는 이메일을 받음
  3. Sam이 채팅을 열면 AI의 선택이 맞는지 바로 질문받음 - Yes를 클릭하거나 AI의 선택·근거를 인라인으로 직접 수정 가능
  4. 제출 시 다음 채팅 스레드로 이동
  5. 한편 새로운 선택들은 AI에 피드백으로 반영됨 - 다음날 밤 AI가 피드백을 바탕으로 재학습됨
  6. Sam은 AI가 4분의 3 정도는 맞다고 판단해 30분 만에 30건을 가볍게 처리
  • 팀과 논의해 접근법을 정하고, 확신이 서면 그에 맞는 스토리보드를 함께 제공. 몇몇 지원 인력을 대상으로 즉석 사용성 연구처럼 흐름을 시연해 의견(“이렇게 하시겠어요? 어떤 기능을 원하시나요?”)을 구할 수도 있음
  • 사용자 흐름을 스케치하고 검증하면 discovery 단계는 (놓친 것을 새로 배우기 전까지는) 완료됨

사용자 흐름 검증하기

  • 중요한 인터페이스는 실제 사용자와 함께 검증해 발견 가능성, 직관성, 가장 중요한 사용자 니즈 충족 여부를 확인해야 함
  • 두 가지 방법: **RFC(Request For Comments)**와 사용성 연구(usability study)

RFC (의견 요청서)

  • 목표가 주로 기능이 실제 세계에서 올바르게 작동하는지 확인하는 것이라면, 스토리보드를 잠재 사용자에게 보여주고 시나리오가 유용할지, 설계에 대한 피드백을 요청하는 RFC 문서를 작성 가능
  • Kabletown 사례: 상대적으로 소수의 직원을 위한 단명 내부 도구를 설계하는 상황이므로 아마 이 방식을 택할 것. 소수 상담원에게 RFC를 보내 기능 격차를 짚고 혼란스러운 부분을 강조하고, 놓쳤을 수 있는 평소 업무 방식에 대한 세부사항을 지적하게 함. 새로운 업무를 요청받는 것에 대한 상담원들의 반응이 걱정된다면 이들을 조기에 참여시키는 것도 도움
  • RFC의 한계: 답을 미리 제공하는 방식이므로, 답을 주지 않을 때보다 통찰을 덜 얻게 됨

사용성 연구

  • 작동하는 프로토타입을 기반으로 인터뷰 대상자에게 과제를 수행하도록 요청 - 발견 가능성, 사용성, 기능 격차 등 여러 문제를 드러냄
  • 보통 화상 채팅으로 인터뷰를 설정하고, 대상자가 수행할 일련의 과제를 준비
  • 작동하는 프로토타입은 과제의 목표를 달성하는 것처럼 보여야 하며 나머지 기능은 구현하지 않아도 됨 - 다양한 디자인 도구가 이런 프로토타입을 만들 수 있고, 기존 앱이나 스크린샷을 받아 새 기능의 프로토타입이 포함된 수정 버전을 만들어주는 도구도 있음

인터뷰 시작 절차

  1. 대상자가 편안함을 느끼도록 돕고 형식을 설명
  2. 기능적 프로토타입을 사용하며 생각을 소리 내어 말하도록 요청
  3. 질문을 해도 되지만 바로 답하지 않을 수도 있음을 안내
  4. 과제를 제시 - 핵심 사용자 흐름의 동기(motive) 부분에 해당 (예: “옛 지원 스레드에 문제 유형과 해결에 도움이 된 개선 조치로 주석을 달아달라는 요청을 받았다고 가정해보세요. 화면을 공유하고 이 클릭 가능한 프로토타입을 사용해 과제를 완수해보시기 바랍니다.”)
  5. 가능한 한 적은 안내만 제공(유스케이스 제시 외에는) - 질문에 답해야 한다면 나중에 디버그할 수 있도록 기록해둘 것
  • 저자는 다음 기회에 사용성 연구를 직접 진행해볼 것을 강력히 권장 - 저자 본인이 Microsoft에서 작성한 C# API에 대해 처음 진행했던 사용성 연구는 매우 겸허해지는 경험이었음. 자신이 쉬울 것이라 생각했던 것에서 프로그래머들이 헤매는 모습을 보며 API 설계라는 분야가 얼마나 도전적이고 심오한지 깨달음

7.7 사용자 흐름의 JTBD 전환

  • 대규모 재인덱싱의 마지막 단계: 만들어야 할 컴포넌트와 기능을 적어내는 것 - 이를 **JTBD(jobs to be done, 할 일)**라 부르기도 함
  • 사용자 흐름은, 북극성 시나리오가 요구사항으로 쪼개지는 것과 같은 방식으로 JTBD로 변환될 수 있음

예시: Sam이 자신의 옛 지원 채팅 전부에 수동으로 주석을 달게 하기로 결정했다면, 필요한 컴포넌트

  • 채팅에 주석을 다는 내부 도구

  • 편집 가능한 태그 데이터베이스 테이블

  • 태그와 자유 형식 텍스트를 포함한 주석 저장용 데이터베이스 테이블

  • 채팅 기록과 함께 주석을 Helpy에 공급하는 데이터 파이프라인

  • 직원별로 50건 주석 완료 목표 대비 진행 상황을 추적하는 기능

  • 대규모 재인덱싱이 완료됨 - 사용자·타임라인 중심 관점의 제품을 시스템·그래프 중심 관점으로 성공적으로 변환. (Part IV에서 이 컴포넌트와 시스템의 설계를 다룰 예정)

7.8 요구 사항으로의 피드백 반영

  • discovery에서 design으로 한 방향으로만 흐르는 아름다운 워터폴을 기대해서는 안 됨 - 실제로는 서로 다른 물이 섞이며 밀물과 썰물을 반복하는 **조수 웅덩이(tide pool)**에 가까움. 다른 것들을 설계하는 도중에도 가끔 시나리오를 추가해야 할 수 있음
  • 즉 북극성 시나리오 목록과 유스케이스 개요서는 계속 갱신해야 하는 **“살아있는 문서(living documents)”**여야 함

예시: Helpy에 영업 시나리오 추가하기

  • 기존 기술 지원 제품 위에 새로운 영업 시나리오를 추가하는 시점이 왔다고 가정 - Helpy에 더 많은 지식과 새로운 도구 접근 권한을 줘야 하므로 역량 확장이 필요
  • 관련 기존 요구사항: “Helpy는 요청이 지원용인지 영업용인지 95% 정확도로 판단할 수 있다”
  • 이 요구사항을 충분히 깊이 생각했는가?
  • 2025년 에이전트 설계의 현실 중 하나: 복잡한 에이전트는 하나의 거대하고 똑똑한 에이전트보다 **전문화된 에이전트들의 혼합(mixture)**을 사용할 때 실수가 줄어듦
  • 이 경우, 기술 지원 어시스턴트와 영업 어시스턴트를 각각 만들고, Helpy는 사용자와 소통하며 계획을 세우고 작업을 각 어시스턴트에게 위임하는 “컨시어지 에이전트” 역할을 하도록 설계 가능

Figure 7-6. 두 전문화된 어시스턴트와 하나의 에이전트로 구성된 혼합 구조

flowchart TB
    User((사용자)) <--> Helpy["Helpy
컨시어지 에이전트"]
    Helpy <--> Support[지원 어시스턴트]
    Helpy <--> Sales[영업 어시스턴트]
  • 이제 이 설계를 비판적으로 검토하고 이를 깨뜨릴 수 있는 스토리를 생각해봐야 함 - 예: 사용자가 기술 지원 질문을 했는데 해결책이 기기 업그레이드라면? Helpy가 한 채팅에서 하나의 에이전트에게만 위임하는 대신, 두 어시스턴트 모두와 상담해야 할 수도 있음
  • 이를 사전에 인지하지 못했다면, 지금이 새로운 사용자 스토리를 만들어 대규모 재인덱싱 과정에 다시 흘려보낼 시점

7.9 요약

  • 시나리오 형태의 제품 비전에서 전통적인 구현 요구사항에 가까운 것으로 나아감. SDD의 안내를 받아 성공을 위한 준비를 갖춤
    • 모두가 스토리를 읽을 수 있으므로 소통이 원활했음
    • 북극성 시나리오의 요소들에 집중했으므로 시간을 낭비하지 않음
    • 페르소나와 북극성 시나리오를 활용해 장기 비전을 세우되 하나의 증분 마일스톤만 실행 - 집중된 우선순위를 가지면서도 더 큰 전체 전략의 맥락 속에서 일관된 무언가를 향해 나아가고 있음을 알 수 있었음
    • 비용과 사용자 가치를 기준으로 최우선 과제를 선정하되, 네 가지 잔인한 진실(즉 최종 사용자에게 가치를 전달하려면 Helpy가 매우 많은 방면에서 매우 강력해야 한다는 사실)을 의식함
    • 출시 후 사용자 피드백이 대대적인 재설계를 요구하지 않을 것으로 기대 - 팀이 북극성 시나리오와 사용자 흐름에서 플롯홀을 찾았고, RFC와 사용성 연구로 사용자 흐름을 검증했기 때문
    • 설계·구현 단계에서 시나리오 테스트 작성 시 유스케이스 개요서와 사용자 흐름을 다시 참조할 것. 북극성 시나리오로 데모를 만들 것
  • 아직 SDD를 시도해보지 않았다면 한 번 시도해볼 것을 권함 - 요령을 익히면(특히 그룹으로) 스토리 쓰기가 재미있으며, 올바른 제품을 출시하기까지 걸리는 시도 횟수를 크게 줄여줄 수 있음

7.10 예제

[문제 1] 2010년대와 2020년대 초, Visual Studio Code는 린터, 문법 강조기, 테스트 프레임워크 등 확장 기능(extension)에 대한 훌륭한 발견(discovery) 메커니즘을 제공해 수백만 개발자의 마음을 사로잡음. “확장 마켓플레이스”를 통해 필요한 도구를 찾을 수 있는 IDE라고 상상하며, 확장 기능 발견을 제대로 어필하는 북극성 시나리오를 작성해볼 것 [문제 2] 자신의 시나리오(또는 저자의 답, 비교하고 싶다면)를 고수준 제품 요구사항으로 분해해볼 것 - 사용자 흐름 설계 시 유연성을 위해 특정 설계 요소에 지나치게 규정적이지 않도록 시도 [문제 3] 북극성 시나리오에서 직접 다뤄지지 않았을 수 있는 엣지 케이스 한두 개를 위 요구사항에 추가해볼 것 - 역시 고수준을 유지 [문제 4] 좋은 평가를 받는 확장 기능을 찾는 것에 대한 제품 요구사항을 따르는 사용자 흐름을 작성해볼 것

7.11 답안

[답안 1] 북극성 시나리오 (요약)

  • 개발자 Deanna가 Python 저장소에서 VS Code를 실행 - 해당 저장소에 익숙하지 않고 Python 경험도 적음
  • 저장소 관리자가 커밋해둔 코드 포매터 추천 덕분에 설치 팝업 제안을 받음
  • 소스 코드 대부분이 Python 파일이므로 Python 언어 서버·디버거 확장(다운로드 수·별점이 가장 높은 것들)을 추천받아 설치, 곧바로 순조롭게 시작함
  • 마지막으로 확장 마켓플레이스에서 둘러보다 평판 좋은 AI 코딩 어시스턴트도 설치

[답안 2] 고수준 요구사항

  • 사용자는 팀원이 저장소를 클론했을 때 빠르게 적응하도록 돕는 추천 언어 확장 목록을 작성하고 커밋할 수 있음
  • 특정 언어·저장소에 새로 온 사용자가 가장 효과적인 옵션을 찾을 수 있도록, 인기도·평점으로 순위가 매겨진 확장 라이브러리·스토어가 존재함
  • 사용자는 확장에 대해 사전에 알지 못했더라도 적응할 수 있도록 추천 확장을 능동적으로 안내받음
  • 사용자는 설명·키워드·이름으로 확장 마켓플레이스를 검색할 수 있어 즐겨 찾는 확장뿐 아니라 아직 높은 순위를 얻지 못한 코딩 어시스턴트 같은 신규 확장도 탐색할 수 있음

[답안 3] 엣지 케이스

  • 팀원이 추천 목록을 업데이트하면 사용자는 새로운 추천을 능동적으로 안내받아야 함
  • 사용자는 확장 설치를 거부할 수 있으며, VS Code는 이미 거부한 확장에 대해 다시 알리지 않도록 그 선호를 기억해야 함
  • 사용자는 저장소마다 다른 코드 포맷 규칙을 가질 수 있으므로, 저장소별 확장과 전역 확장을 구분해 가질 수 있음

[답안 4] 사용자 흐름

[답안 1] 개발자 Deanna가 새 저장소를 염 [답안 2] VS Code가 그녀에게 낯선 저장소임을 인지해 확장 기능을 살펴보라는 팝업을 보여줌 [답안 3] Deanna는 팝업을 닫거나 “확장 마켓플레이스로 이동” 버튼을 클릭 가능 [답안 4] 버튼 클릭 시 저장소의 언어에 맞게 필터링되고 인기도·평점 조합으로 정렬된 추천 확장 목록이 나타남 [답안 5] 각 항목에는 이름, 짧은 설명, 다운로드 수, 별점과 함께 설치·자세히 보기 두 버튼이 있음 [답안 6] 설치를 클릭하면 확장 제작자가 제공한 사용법 상세 페이지와 다운로드 진행 표시줄이 나타남

PART 4 정의

  • “정의(Define)” 단계는 제품 요구사항과 작업 범위를 파악한 뒤, 발견한 문제를 어떻게 해결할지 엄밀하게 규명하는 단계
  • 이 문제들을 적절한 수준의 일반성(generality)으로 해결하는 상세한 시스템 및 기능 설계를 개발하는 것이 목표

CHAPTER 8 인터랙션 설계

“모든 디테일을 완벽하게 만들되, 완벽하게 다듬을 디테일의 개수는 제한하라.” — 잭 도시(Jack Dorsey), Block 창업자

어포던스와 시그니파이어의 기본 개념

  • 어포던스(Affordance): 제품이 사용될 수 있는 방식 전부 (의도했든 안 했든)
    • 예: 소파는 “앉기”라는 어포던스를 가지지만, 고양이는 “긁기”라는 어포던스도 발견함 — 둘 다 어포던스이나 매뉴얼에는 하나만 적혀 있음
  • 어포던스는 위험할 수 있음
    • 예: 야외 콘서트장에서 의자를 서로 단단히 묶어두는 이유 — 의자가 탈출로를 막거나 던져지는 것을 방지하기 위함 (원치 않는 어포던스 차단)
  • 디지털 세계는 불안전한 코드/프로토콜로 인해 원치 않는 어포던스로 가득함
    • 스팸 문자/이메일, 개인정보 유출, 자금 세탁, 피싱 등
    • 일부는 불가피했지만, 일부는 더 선제적인 설계로 예방 가능했음
  • 돈 노먼(Don Norman)이 저서 《The Design of Everyday Things》에서 어포던스 개념을 대중화함
    • 시그니파이어(Signifier): 어포던스가 무엇을 할 수 있는지 알려주는 단서 (어포던스는 “할 수 있는 것”, 시그니파이어는 “무엇을 해야 하는지 알려주는 것”)
    • 예: 의자는 “던질 수 있는” 어포던스가 있지만 이를 알려주는 강한 시그니파이어는 없음. 던지도록 설계된 물건은 손잡이, 손에 맞는 공 모양, 공기역학적 디자인 등으로 신호를 줌

객체지향 프로그래밍에서의 시그니파이어 예시

  • Java/C#: public 키워드는 클래스 외부 호출자에게 “이건 어포던스다”라고 알리는 시그니파이어. private은 클래스 내부 호출자에게만 유효한 시그니파이어
  • Python: def가 메서드 시그니파이어 역할을 함. Python에는 private 메서드가 없으므로 관례상 이름 앞에 _를 붙여 “외부에서 호출하지 말 것”을 신호함 (강제력은 없고 “명예 시스템”에 의존) — 일종의 “안티 시그니파이어” (출입금지 표지판과 유사)

표 8-1. 메서드 어포던스와 외부 호출자에게 시그널되는지 여부

구분 어포던스? 시그널됨? 비고
public 메서드 (Java/C#) 사용자가 기대하는 대로 호출 가능
private 메서드 (Java/C#) 아니오 아니오 호출 불가, 사용자도 기대하지 않음
일반 메서드 (Python) -
언더스코어 메서드 (Python) 아니오 호출은 가능하지만 앞의 _가 호출하지 말라고 신호함
구현되지 않아 실패하는 공개 메서드 아니오 제공되는 것처럼 보이지만 실제로는 아님
  • 시그니파이어와 어포던스는 종종 함께 감. 예: 라디오 버튼(하나만 선택 가능 신호) 대 체크박스(여러 개 선택 가능 신호) — 서로 다른 시맨틱스가 표준 시각 패턴과 함께 옴
  • 인터페이스를 설계할 때 각 어포던스의 용도와 오용 가능성을 브레인스토밍하고 원치 않는 것은 제외해야 함 — 많은 설계 취향과 반복이 필요함 (예: 높은 처리량 유스케이스 허용이 어느 시점부터 서비스 거부 공격(DoS) 허용이 되는가?)
  • 노출하기로 한 어포던스에는 시그니파이어를 사용해 사용자를 가장 안전하고 유용한 것으로 유도해야 함

TIP: 소프트웨어 인터페이스 설계는 결국 “어떤 어포던스를 노출할 것인가”와 “그것을 사용자에게 어떻게 신호할 것인가”를 선택하는 문제로 귀결됨

이 챕터에서 다루는 내용 (어포던스, 시그니파이어, 시나리오, 페르소나로 무장하면 가능한 것)

  • 제품의 올바른 사용법에 주목을 이끌고 잘못된 사용법에서 멀어지게 하기
  • 출시하는 어포던스의 타이밍을 현명하게 조절하기
  • 언제 기능을 확장 가능하게 만들고, 언제 특정 문제만 해결하도록 좁힐지 학습하기

설계 조언에 앞서 다지는 세 가지 토대

  • 소프트웨어 설계에서 편향과 이념의 역할
  • 좋은 설계의 전제 조건
  • 조언의 틀을 잡아줄 사례 연구

8.1 소프트웨어 설계에서 편향과 이념의 역할

  • 소프트웨어 엔지니어는 자신의 업무에 이념적 신념과 편향을 들여옴
  • 예시
    • 유연하고 파워유저 친화적인 제품을 선호하는 사람 대 견해가 뚜렷하고(opinionated) 안전하며 사용하기 쉬운 제품을 선호하는 사람
    • 기능/제품의 단점과 부정적 영향을 보는 비관주의자 대 장점에 집중하는 낙관주의자
    • 무료/오픈소스 소프트웨어가 투명성, 윤리, 보안 측면에서 더 낫다고 믿는 사람 대 지적 재산권과 상업적 혁신 인센티브를 강조하는 독점 소프트웨어 옹호자
  • 이념은 힘과 목적의 원천이 될 수 있지만, 소프트웨어를 설계할 때는 단지 “출발점”에 불과함
  • 최적의 제품은 언제나 설계자의 신념보다 사용자와 그들의 니즈에 의해 정보를 얻어야 함

논쟁 1: 유연함 대 견해가 뚜렷함(Opinionated)

  • 애플 iOS와 구글 안드로이드는 매우 다른 이념에서 출발했지만 제품은 수렴함 (convergent evolution)
  • 애플은 더 유연한 개발자 플랫폼을 노출한 반면, 구글과 하드웨어 파트너들은 일부 자유를 제한하고 더 견해가 뚜렷한 경험을 노출함
  • 비즈니스/규제적 이유도 있지만 제품 설계상의 이유도 있음 — 유사한 사용자/파트너의 유사한 시나리오에 귀 기울인 결과로 볼 수 있음

논쟁 2: 낙관주의 대 비관주의

  • 새로운 기술의 물결마다 낙관주의와 사회적 영향에 대한 도덕적 공황이 함께 몰려옴 (지난 10년은 소셜미디어, 지금은 AI)
  • 더 나은 소프트웨어 설계가 흔히 해결책의 핵심 부분이 됨
  • 예: LLM 챗봇이 비판적 추론 능력에 미치는 영향, 범죄를 쉽게 만들 수 있다는 우려에 대응하여
    • 레드팀(Red teaming): 연구자들이 적대적 시나리오를 만들어 AI에 적용해, 생물무기나 신원도용 같은 위협에 대한 지식 유출에 강건하게 만드는 작업
    • 튜터링 모드: LLM 제공자들이 학생 시나리오를 돕는 학습 모드를 개발해, 답을 그냥 주기보다 비판적으로 사고하도록 돕는 기능
  • 저자는 시간이 지나며 다양한 시나리오와 페르소나를 위한 부모 통제 등 추가 보호 기능이 등장할 것으로 예상

논쟁 3: 오픈소스 대 독점

  • 2000년대에는 대부분 제품이 완전 독점 또는 완전 오픈소스였음
  • 현대 소프트웨어 기업 다수는 그 중간 모델을 취함
    • 오픈 코어(Open core) 모델: 대부분 기능은 오픈, 일부 기업용 시나리오는 독점 기능으로 제공
    • 듀얼 라이선싱 모델: 라이선스 조건이 사용자 페르소나에 따라 다름 (예: 상업적 사용 제한 추가)
  • 두 모델 모두 혁신 인센티브와 개방성 및 투명성의 균형을 맞추려는 시도
  • 결론: 논쟁이 오직 이념으로만 흐른다면, 고객과 그들의 유스케이스를 충분히 알고 있는지 의심해봐야 함

8.2 좋은 설계의 전제 조건

  • 이 챕터의 설계 조언은 시나리오, 페르소나, 그리고 경우에 따라 반복적 개발(iterative development)의 활용에 상당 부분 기반함
  • 이 전제 조건들이 좋은 설계를 보장하지는 않지만 분명히 도움이 되며, 책 앞부분에서 다진 토대를 활용함
    • 피드백 수집 및 빠른 반복 능력을 개발 관행에 내재화 (5장 참고) — 인터페이스가 너무 제한적이었다면 나중에 개선할 수 있다는 확신을 줌
    • 타겟 오디언스와 그들이 원하는 것에 대한 그림 발전시키기 (6장 참고) — 예: 모두가 같은 것을 원하면 그것을 유일한 옵션으로 만들고, 서로 다른 것을 원하면 유연성의 지점을 삽입
    • 시나리오 기반 발견(scenario-driven discovery) 활용 (7장 참고) — 고객이 제품을 어떻게 사용할지 세부사항까지 생각해보기. 시나리오가 안전상의 허점을 드러내면 일반성을 잃지 않고 그 허점을 메울 수 있는지 확인
  • 이 도구와 기법으로 무장하면 이념적 지름길에 덜 의존하게 되고, 최선의 정보와 가장 높은 실행 동기를 가졌을 때 설계 결정을 내릴 수 있음

8.3 사례 연구 소개

  • 2010년대 초, 저자는 페이스북의 소셜 그래프 데이터베이스 객체 모델링 스키마 설계를 이끔
    • 엔티티(Entity): User, Post, Group, Event 등의 객체
    • 엣지(Edge): 친구관계, 멤버십, 게시물 작성자 등의 연결 관계
    • 이 스키마를 EntSchema (“entity schema”의 줄임말)라고 부름
  • 북극성 시나리오(핵심 지향 시나리오): 스키마 작성(Schema authoring) — “모델 제작자가 하나의 표현(representation)만 작성하면 필요한 모든 것(데이터베이스 스키마, 읽기용 클래스, 쓰기용 클래스)이 자동으로 생성되길 원함”

최소 기능(테이블 스테이크) 버전 코드 예시

class PostSchema(EntSchema):
    def db_config() -> DatabaseConfig:
        return (DatabaseConfig()
                .name("posts"))

    def fields(self) -> dict:
        return {
            "id": int64_field().primary_key(),
            "text": varchar_field(4096),
            "created": int64_field(),
            # ...
        }

    def edges(self) -> dict:
        return {
            # 한 작성자(author)는 여러 개의 Post를 가질 수 있음
            "author": edge(UserSchema, cardinality=EdgeCardinality.ManyToOne),
            # ...
        }
  • 위 코드가 생성하는 것
    • id, text, created 컬럼을 가진 데이터베이스 테이블
    • author와 posts를 연결하는 엣지 데이터베이스 테이블
    • Python에서 읽기 접근용 클래스 Post
    • Post 생성/편집용 클래스 PostMutator

Post 읽기 클래스 예시

class Post:
    @property
    def text(self) -> str: ...
    @property
    def created(self) -> str: ...
    @property
    def id(self) -> int: ...
    async def fetch_author(self) -> User: ...
flowchart TD
    A["PostSchema 정의<br/>db_config / fields / edges"] --> B["데이터베이스 테이블<br/>id, text, created"]
    A --> C["엣지 데이터베이스 테이블<br/>author - posts 연결"]
    A --> D["읽기 클래스<br/>Post"]
    A --> E["쓰기 클래스<br/>PostMutator"]
  • 이 스키마에는 데이터베이스 계층에서 바로 유용한 정보 외에는 거의 명시되지 않음 — 시나리오 해결에 필요한 최소한만 담음
  • EntSchema 도입 당시 이미 수작업으로 작성된 많은 엔티티가 DB/코드베이스에 존재했음
    • 단순한 DB 수준 표현을 유지하는 큰 장점: DB에 저장된 표현으로부터 EntSchema를 자동 생성할 수 있음 (스키마에 DB에 없는 정보가 없으므로) — 대규모 코드베이스 마이그레이션 시 간단한 스크립트로 큰 이점을 얻을 수 있었음
  • 하지만 스키마 작성자만이 유일한 페르소나는 아니었음 — 스키마를 읽고 사용하는 사람들도 고려해야 함
    • 예: 엔지니어들은 프로덕션 이슈 디버깅을 위해 오브젝트 인스펙터(object inspector)를 자주 사용함
    • 만약 Post 작성자에게 created 필드가 정수가 아니라 타임스탬프(timestamp)임을 명시하도록 요구했다면, 디버깅 시 사람이 읽기 쉬운 시간 형태로 예쁘게 출력(pretty-print)될 수 있었음
  • 질문: 이것이 단지 스코프 크립(scope creep, 범위 확장)인가?
    • 점진적 전략은 위험했음: 쉬운 길을 택해 저수준 버전만 출시하면, 나중에 더 풍부한 타입 정보를 소급 적용하기 어려워짐
  • 이후 절들에서는 스키마 작성자가 추가로 제공해야 할 정보를 어떻게, 왜 선택했는지를 이 사례를 통해 다루며, PostSchema가 더 많은 시나리오에 적합한 형태로 변형되는 과정을 보여줌

NOTE: (ENTS IN GO) EntSchema를 기반으로 한 인기 오픈소스 Go 프레임워크 “ent”가 존재함. 전체적인 느낌을 파악하기에는 좋으나, 필드 타입 시스템은 메타(구 페이스북) 내부 버전만큼 풍부하지 않음

8.4 제품의 올바른 사용법 유도와 잘못된 사용 방지

  • 잘 설계된 제품은 생산적이고 안전한 어포던스를 신호하고 기본값으로 설정하며, 사람들이 실수로 더 위험한 어포던스를 사용하지 않도록 함
  • 어포던스는 신호등처럼 더 혹은 덜 “환영받는” 것으로 신호될 수 있음 (초록불이 안전을 알리듯)
    • 초록(Green): 안전하고 대체로 효과적이어야 함
    • 노랑(Yellow): 주의가 필요함
    • 빨강(Red): 금지됨

TIP: 어포던스를 얼마나 권장되는지에 따라 초록/노랑/빨강으로 나누고, 그에 맞게 신호를 줄 것

예시: 의심스러운 웹 URL 링크 클릭

색상 상황 설명
초록 동일 사이트 내 다른 페이지로 이동 안전
노랑 동일 사이트 밖의 페이지로 이동 “외부 사이트로 이동하시겠습니까?” 같은 경고를 표시하여 원본 사이트를 사칭하는 사이트로 가는 것을 방지
빨강 유효한 보안 인증서가 없는 의심스러운 사이트로 이동 브라우저가 이동 전 높은 장벽을 세움
  • 초록/노랑/빨강 어포던스 모두 이용 가능하게 둘 수도 있지만, 아예 어포던스를 완전히 제거할 수도 있음 (예: 악성으로 알려진 페이지는 브라우저가 아예 이동을 차단)
  • 초록 시나리오를 매우 발견하기 쉽게 만들고 노랑/빨강은 덜 그렇게 만드는 제품은 사용하기 즐거움 — 사용자는 자신감을 갖고 진행하며, 제품에 대한 신뢰가 쌓여 매번 재확인하지 않고도 작동함

사용자를 가장 유용한 어포던스로 유도하는 일반적 기법 4가지

  1. 안전하고 예측 가능한 기본값(default)을 선택하거나, 아예 기본값을 두지 않기
  2. 최소 저항 경로(path of least resistance)를 최적화하기
  3. 알맞은 시나리오에서 알맞은 페르소나에게 어포던스 제공하기
  4. 검증(validation) 수행하기

1. 안전하고 예측 가능한 기본값을 선택하거나, 아예 두지 않기

  • 유연성과 사용 편의성을 섞을 것: 대다수를 위한 적절한 기본값 제공 + 까다로운 사용자를 위한 커스터마이징 허용
  • 세부 설정을 들여다볼 시간이 없는 사용자는 합리적인 동작을 얻고, 커스터마이징에 시간을 투자할 의향이 있는 사용자는 더 강력한 기능을 찾을 수 있어야 함
  • 사용자는 제품 개발자가 좋은 기본 설정을 선택했다고 신뢰하고 그 대가를 지불함
  • 예: EntSchema는 기본적으로 필드명과 동일한 DB 컬럼명을 사용하지만 커스터마이즈 가능
"posted_at": integer_field().db_config(name="created"),
  • 모든 스키마 작성자가 중복으로 DB 이름을 명시하거나, 단지 코드상 이름을 바꾸기 위해 데이터베이스 마이그레이션을 할 필요 없이 유연함을 제공
  • 기본값 선택을 잘못하면 정치적으로 민감하거나 재앙적인 결과를 부를 수 있음
    • MS, 애플, 구글 등 OS 기업들이 특정 브라우저/검색엔진을 기본값으로 설정해 소송을 겪음
    • 초기 인터넷 익스플로러가 ActiveX 컨트롤을 기본 활성화하여 각종 바이러스/웜이 퍼질 수 있는 심각한 보안 취약점을 만듦
    • 2009년 페이스북이 새 게시물 기본값을 “전체 공개(Everyone)“로 설정하여 큰 반발과 신뢰 손실을 초래 (이후 더 명시적인 제어를 제공했음에도 여파가 지속됨)
  • 가장 간과되는 기본값 유형: 기본값을 아예 두지 않는 것
    • 개발자들은 콜사이트를 짧게 유지하고 제품 표면을 매끈하게 하려고 모든 것에 기본 인자를 제공하는 데 익숙해지지만, 이는 잘못된 트레이드오프일 수 있음
    • 어느 한쪽이 안전하지 않거나, 예상치 못하거나, 비효율적일 수 있는 경우 사용자가 의식적으로 결정을 내리게 해야 함
    • 예: 자율주행차는 좌/우회전을 기본값으로 두지 않고 내비게이터가 목적지를 입력하도록 요구함. 소셜미디어 기업도 신규 사용자에게 공개 범위에 대한 정보에 입각한 선택을 하도록 유도해야 함

TIP: 제품 인터페이스의 품질은 그것이 사용자에게 묻는 질문의 관련성(relevance)으로 측정될 수 있음

  • 실전 예시: PostSchema의 DB 샤딩(sharding) 문제
    • DB에는 여러 샤드가 있고, “이 작성자의 최근 게시물을 모두 가져오기”라는 흔한 쿼리가 있음
    • 게시물들이 모두 같은 샤드에 있지 않다면, 모든 샤드로 팬아웃(fan out)해야 하므로 매우 비용이 큼
    • 게시물이 모두 같은 샤드에 있으면 쿼리 성능이 좋음 — 따라서 PostSchema 작성자는 DB 설정에서 **콜로케이션(colocation)**을 명시해야 함
def db_config() -> DatabaseConfig:
    return (DatabaseConfig()
            .name("posts")
            .colocateWith("author"))
  • 만약 EntSchema가 콜로케이션 미지정 시 “콜로케이션 없음”으로 기본 처리했다면, 게시물이 무작위로 흩어졌을 것이고, 작성자는 프로덕션에서 스케일이 커지기 전까지 오류를 알아차리지 못했을 것
  • 따라서 기본값이 없어야 하며, 사용자가 반드시 선택하도록 강제하고 트레이드오프를 최대한 알려주어야 함

2. 최소 저항 경로 최적화하기

  • 사용자는 완벽하게 계획적으로 제품을 사용하지 않음. 종종 자연스럽게 최소 저항 경로를 택함
  • 오작동하는 신호등(malfunctioning traffic light): 두 기능의 상대적 발견 용이성/사용 편의성이 뒤바뀐 상태를 비유 (빨강이 초록/노랑보다 위에 있는 뒤집힌 신호등을 상상)
    • 예: 디자이너가 노랑 어포던스(페이스북 공개 게시)를 기본값으로 설정했어야 할 초록 어포던스(친구에게만 공유) 대신 사용한 경우 — 오작동하는 신호등
  • 이는 절대적인 사용 편의성/발견성보다, 서로 다른 어포던스 간의 “상대적” 편의성과 시그널 강도가 더 중요함을 보여줌 — 흔히 발생하는 문제
  • EntSchema 사례: 애플리케이션 수준 필드 타입 (이메일, 자연어 텍스트, 열거형(enum), 전화번호 등 문자열 서브타입) 설계
    • 모든 가능한 문자열 타입을 다 감당할 수 없다는 문제 — 시간이 지나며 사용자가 추가할 수 있게 했지만 번거로우므로 폴백(fallback)으로 string_field를 추가함
    • 문제: 가장 도움이 안 되는 옵션이 가장 발견하기 쉬웠음. 많은 스키마 작성자들이 string_field를 과도하게 사용하기 시작함
      • 다른 시스템에서 익숙한 형태의 매력적인 시그니파이어를 가지고 있어서 다른 옵션의 존재조차 모르는 엔지니어가 많았을 것으로 추정
      • 즉, 노랑 어포던스인 string_field가 초록 어포던스인 email_string_field보다 더 발견하기 쉬웠음 — 신호등의 초록이 아래, 노랑이 위에 있는 격
    • string_field는 작성하기도 더 쉬웠음 (더 짧고 별도 사고가 필요 없음) 반면 enum_string_field(MyEnum) 같은 초록 어포던스는 문자열 서브타입 메뉴를 살펴보고 신중히 선택해야 했음 — 다시 한 번 신호등 오작동
  • 해결책: string_field를 제거하고 더 노골적인 이름의 custom_string_field로 교체
    • 다른 문자열 서브타입들과 동등한 위치에 놓아 “주류 옵션이 아님”을 신호
    • 발견성 역전 문제는 해결되었지만, 바쁜 개발자들은 여전히 남용할 수 있었음 (다른 곳에서 복붙하거나 플레이스홀더로 넣고 나중에 돌아오는 것을 잊는 경우)
    • 추가 마찰 장치: custom_string_field 사용 시 검증기(validator) 를 강제로 추가하도록 함
'my_regex_field': custom_string_field().validator(lambda x: is_regex(x))
  • 빈 검증기를 넣는 것도 가능은 했지만, 대부분의 엔지니어가 한 번쯤 생각해보게 만드는 효과가 있었다고 판단함
  • 이것이 실패하더라도, 코드 리뷰어에게 “검증기가 없다”는 것을 알아채고 지적할 수 있는 시그널을 제공함
  • 결과: custom_string_fieldstring_field보다 훨씬 덜 유혹적이었고, 스키마 명세의 정확도를 크게 향상시킴

3. 알맞은 시나리오에서 알맞은 페르소나에게 어포던스 제공하기

  • 누구에게, 언제 선택권을 줄 것인지 신중히 생각할 것 — 먼저 “누구”부터
  • 예: id, posted_at, text 세 필드를 설정할 때, 게시물을 작성하는 개발자에게 어떤 문제가 생길 수 있는가?
post = await (PostMutator()
              .set_id(rand())
              .set_posted_at(now())
              .set_text(some_user_input)
              .create())
  • 발생 가능한 문제들
    • DB가 중복 방지와 공정한 샤딩을 위해 ID 공간에서 ID를 할당해야지, 사용자가 직접 할당하게 해서는 안 됨
    • id가 실수로 누락될 수 있음
    • posted_at이 누락되거나 0으로 설정될 수 있음 (타임스탬프로 1969년 12월 31일을 본 적이 있다면 이 버그를 목격한 것)
    • posted_at이 실수로 명백히 유효하지 않은 다른 정수로 설정될 수 있음
  • 스키마 작성자가 어떻게 “성공의 웅덩이(pit of success)” 를 만들어 객체를 생성/편집하는 사람들이 문제에 빠지지 않게 할 수 있을까?
    • 필드 명세에 조금만 더 의미를 담으면 이 모든 시나리오를 예방 가능
def fields(self) -> dict:
    return {
        "text": natural_language_string_field()
                .db_config(type='varchar(4096)'),
        "posted_at": timestamp_field().at_creation(),
        # ...
    }
  • 위 변경으로 엔지니어의 의사결정을 줄여 성공의 웅덩이를 넓힘
    • id는 완전히 제외 — 모든 스키마에 대해 생성 시점에 자동으로 만들어짐
    • posted_attimestamp 타입으로 명시되어 범위 검사(range check)가 가능해짐. 또한 특별한 at_creation 타임스탬프로 표시되어 PostMutator가 입력 없이 자동으로 설정함
  • 요컨대
    • id에 대한 결정을 “잘 모르는 페르소나(앱 개발자)“에서 “잘 아는 페르소나(데이터베이스 엔지니어)“로 이동시킴
    • posted_at에 대한 결정을 “Post를 생성하는 사람”에서 “PostSchema를 작성한 사람”으로 이동시킴

TIP: 어떤 페르소나가 선택을 내려야 하는지 신중히 생각할 것

NOTE: (MULTIPERSONA ONBOARDING FLOWS, 다중 페르소나 온보딩 플로우) SaaS 구매 플로우는 종종 다중 페르소나로 이루어짐. 개인 기여자(IC)를 대상으로 제품을 만들지만 회사에 판매하는 경우, 제품 도입에 신난 IC가 결제 화면을 마주하는 상황을 상상해보면, IC는 회사의 지갑에 접근권이 없고 지갑을 쥔 사람은 IC에게 회사 카드 정보를 문자로 보내고 싶어하지 않을 것임. IC에게는 “구매 담당자의 이메일”을 물어보는 것이 더 쉬운 방법일 수 있음. IC가 담당자에게 이메일을 확인해달라고 알리면, 재무 담당자가 클릭하여 양식을 작성하면 됨

  • 알맞은 페르소나를 타겟팅하는 것을 넘어, 알맞은 “시점(timing)“에 어포던스를 제공해야 함 — 즉 사용자가 결정에 필요한 정보를 가지고 있고, 결정할 동기가 있을 때
  • EntSchema의 콜로케이션 결정은 언제 내려야 하는가? 처음부터 고민해야 하는가?
    • 핵심 시나리오 중 하나였던 “해커톤 시나리오”: 빠른 프로토타이핑을 하는 사용자가 제품 모델을 빠르게 스케치하고 UI에 연결해 아이디어를 시연하고자 하는 경우
    • 이를 위해 프로토타이핑 사용자를 위한 드래프트 모드(draft mode) 를 구축함 — 사용자는 프로덕션화할 준비가 되면 이 모드를 해제할 수 있음
    • 드래프트 모드에서는 콜로케이션 선택 여부 확인을 포함한 많은 프로덕션 준비성 체크가 꺼짐 — 결정을 사용자가 무엇을 원하는지 더 잘 알게 될 때까지 미루고, 프로덕션화를 확실히 원할 때까지 추가 작업도 미룸

4. 검증(Validation) 수행하기

  • 사용자의 결정을 검증할 것
"text": natural_language_string_field()
        .max_length(4096)
        .user_input(),
  • 위 예시에서의 변경
    • varchar(4096) 선언을 스키마 상의 max_length로 격상 — API 계층에서 더 일찍 입력 길이를 검증 가능 (3장에서 논의한 “왼쪽으로 이동(shift left)” 원칙 적용: 더 일찍 검증하여 데이터베이스 부하 감소, 인터페이스 계층에서 검증하여 더 명확한 애플리케이션 수준 오류 제공)
    • user_input으로 표시 — 내부 엔지니어가 아닌 사용자 입력임을 나타내어, 악성 URL 등 보안 취약점을 스크리닝할 수 있게 함 (게시물을 만드는 모든 콜사이트가 개별적으로 이를 처리하는 대신 한 곳에서 처리)

8.5 어포던스 출시 타이밍의 최적화

  • “기술은 좋게도 나쁘게도 쓰일 수 있다”고 하며, 가장 안전한 어포던스는 존재하지 않는 어포던스임
  • 사소해 보이는 것(예: 소셜 네트워크에서 이름을 지정하게 허용하는 것)조차 욕설/남용 같은 벡터를 열어줄 수 있음 — 노출하는 대부분의 것을 강화(harden)해야 함
  • 따라서 충분히 고민하지 않은 기능을 노출하는 것은 일종의 도박임
  • 이상적으로 반복(iterate)하고 사용자 말에 귀 기울이는 환경이라면, “추가해야 하는가?“가 아니라 “언제 추가해야 하는가?“가 질문이 되어야 함 — 중요한 기능을 빼놓았다면 나중에 그냥 추가하면 됨
  • 이런 환경에서는 무엇을 노출할지에 대해 다소 보수적인 게 유리함 — 사용자로부터 더 많은 세부 정보를 얻은 후 나중에 추가 가능

TIP: 의심스러우면 뺄 것 (When in doubt, leave it out)

  • 너무 공격적으로, 너무 일찍 기능을 출시하면 생길 수 있는 나쁜 일들
    • 사용자 데이터가 충분하지 않아 이념적 기본값에 의존한 잘못된 설계 결정을 내리게 됨
    • 부실한 테스트로 조악한 결과물이 나옴
    • 설계에 충분한 시간을 쏟지 않아 사람들이 사용에 어려움을 겪음
    • 더 가치 있는 것을 만들 수도 있었음
    • 준비되지 않은 유지보수/장기적 소유 부담이 필요할 수 있음
    • API처럼 안정성이 필요한 소프트웨어라면, 나중에 개정하고 호환성을 깨뜨릴 때 사용자들이 싫어할 것
  • 위 문제들은 그 기능이 팀의 최우선순위가 아닐 때 특히 발생하기 쉬움

TIP: 충분히 집중할 수 있을 때 기능을 만들 것

  • “의심스러우면 뺄 것”의 또 다른 따름정리는 “의심 상태로 머무르지 말라”
    • 기능이 필요한지에 대한 확신을 세우는 데는 6장에서 소개한 방식의 사용자와의 몇 번의 대화, 혹은 지표를 살펴보는 정도로 충분할 수 있음
    • 결국 기능을 만들거나, 생각만큼 중요하지 않았음을 확인하거나 — 둘 다 좋은 결과임 (엔지니어는 “아이디어를 낸 사람”이 되고 싶어하지만, 관리자는 가정을 검증하고 데이터로부터 배우는 것을 더 좋아하며, 사용자는 그 결과를 좋아함)
  • 실무에서는 코드 리뷰, 배포, 지식 전달, 관리, 문서화 등 기능 출시에 드는 오버헤드가 있음 — 우선순위가 낮은 nice-to-have를 핵심 기능에 편의상 묶는 경우도 있으나, 메타 목표는 이런 오버헤드를 줄여 사용자의 목표와 더 잘 맞추는 것이어야 함

기능 출시를 위한 유용한 격언 5가지 (이론과 실전 모두에서 효과적)

  1. 만들 가치가 있다면 검증할 가치도 있음
  2. 낙관주의자도 비관주의자도 되지 말라
  3. 3의 법칙(Rule of Three) 적용하기
  4. 단계적으로 구축하기
  5. 필요하다면 실험적 버전으로 시작하기

1. 만들 가치가 있다면 검증할 가치도 있다

  • 검증되지 않은 기능은 출시하지 말 것
  • 많은 엔지니어가 “혹시 몰라서” 추가 어포던스를 슬쩍 끼워넣는 것을 좋아함 — 좋지만, 테스트 하나 작성할 시간조차 없다면 나쁜 냄새(bad smell)일 수 있음
  • 4장에서 기능이 작동하는지 배우는 다양한 방법을 다룸 — 테스트 작성은 최소한의 기준이어야 함
  • 해피 패스(happy path)만 테스트하지 말 것. 노출하고 있는 부정적 어포던스도 찾아 테스트할 것 (사용자가 잘못된 데이터를 넣으면? 경쟁 조건(race condition)이 있다면?)

2. 낙관주의자도 비관주의자도 되지 말라

  • 또 다른 이념적 기본값: 낙관주의 대 비관주의
    • 일부 엔지니어는 타고난 낙관적 빌더로 해피 패스만 생각함
    • 다른 엔지니어는 본능적으로 아이디어를 시험하고 단점을 찾는 비관주의자 (복잡성에 회의적)
  • 두 성향 모두 강점이 될 수 있음 (비관주의자는 뛰어난 보안 엔지니어가 될 수 있고, 낙관주의자는 어려움을 깨닫지 못한 채 회사를 창업할 수 있음)
  • 하지만 소프트웨어 설계 시에는 약점이 될 수 있음 — 시나리오가 타고난 성격을 넘어서 균형 잡힌 설계자가 되도록 도와줌
  • 모든 기능에는 단점이 있음 (인터페이스를 어수선하게 하는 것부터 사용자에게 직접적 피해를 주는 것까지) — 설계자는 자신의 아이디어를 깨뜨리기 위한 적대적 시나리오를 만들어야 함
    • 보안 분야 용어: 설계 단계에서는 위협 모델링(threat modeling), 코드 작성 후에는 레드팀(red teaming)
  • 반대로, 사용자를 위해 어려운 것을 만드는 것은 제품이 세상에 가치를 더하는 방법의 일부임 (쉬우면 다른 사람들이 쉽게 복제 가능) — 기능이 가져올 좋은 시나리오도 브레인스토밍할 것
  • 낙관/비관 양쪽 관점을 모두 고려하면 초록 어포던스를 극대화하고 빨강 어포던스를 제한하는 해법을 찾을 수 있음
  • 개인적 편향과 무관하게 초록/빨강 시나리오를 항상 시뮬레이션할 것. 자신과 반대 성향의, 자주 의견이 갈리는 엔지니어와 짝을 이루는 것도 방법

3. 3의 법칙(Rule of Three) 적용하기

  • 소비자 소프트웨어를 만들 때 파워유저나 지인들로부터 특이한 기능 요청을 자주 받게 됨. 비즈니스 소프트웨어도 유사함 (큰돈을 지불하는 기업 고객의 고유 니즈에 대한 압박)
  • 모든 요청에 예라고 답하는 것은 좋지 않음 — 모든 기능을 고품질로 유지보수할 수 없고, 고객도 방대하고 미로 같은 인터페이스를 원치 않음. 요청한 고객조차도 자기만 쓰는 기능이 되어 테스트 매트릭스에서 뒷전이 되길 원치 않음
  • 하지만 서로 다른 세 고객으로부터 같은 기능으로 해결 가능한 피드백을 받았다면, 시간이 지나며 더 많은 사람이 사용할 가능성이 높음 — 만들 가치가 있을 확률이 높음
  • 사용자의 근본적 시나리오를 충분히 이해해야 이런 판단을 내릴 수 있음
  • 기능을 정당화하기 위해 세 가지 사용자 시뮬레이션을 떠올려보는 것도 방법
  • 예: EntSchema에서 필드 설명(description)을 스키마의 정식 일부로 포함할지 결정
def fields(self) -> dict:
    return {
        "text": natural_language_string_field()
                .description("What the user wrote, in plain text"),
        "posted_at": timestamp_field().at_object_creation()
                .description(
                    "When the user posted this. If they put it on a schedule, "+
                    "this is when the post was scheduled to go live."),
        # ...
    }
  • 스키마 작성자에게는 추가 작업이지만, 다음과 같은 여러 시나리오가 있었음
    • 코드 생성된 읽기 인터페이스에 주석이 삽입되어, IDE에서 정의로 이동한 사용자가 주석을 볼 수 있음
    • 쓰기 인터페이스도 마찬가지
    • 언젠가 웹 문서(web docs)를 자동 생성할 계획 (실제로 나중에 구현함)
    • 프로덕션 오브젝트 인스펙터가 설명을 인라인 또는 툴팁으로 보여줘 사용자가 객체를 이해/디버깅하는 데 도움
    • 사람들이 설명을 추가하고 있는지 검증 가능해져, 단순 주석으로는 불가능한 커버리지 확대 가능
  • 3의 법칙을 훌륭히 통과하여 채택됨

TIP: 설득력 있는 시나리오 세 가지를 떠올릴 수 있거나, 세 명의 사용자가 요청했다면 그 기능을 진지하게 고려할 것

4. 단계적으로 구축하기

  • 때로는 타겟팅하려는 사용자 시나리오가 있지만 최우선순위는 아님. 하지만 나중에 그 시나리오를 지원하려면 지금 무언가를 만들어두지 않으면 훨씬 어려워짐
    • 트랩도어 결정(trapdoor decision): 나중에 되돌리거나 복구하기 어려운 결정
  • 다른 경우, 기능이 한 번에 출시하기엔 너무 복잡함 — 초기 버전을 출시하고 피드백을 받은 후 완성해야 함
  • 이런 경우 구축하면서 경계(perimeter) 를 설정할 것
    • 비유: 공사장을 둘러싼 체인링크 펜스 — 건설 노동자들이 안전하게 작업할 수 있도록 사람들을 막아둠. 결국 사람들을 초대하겠지만 벽/설비/안전기능을 모두 갖춘 후에. 일반인이 경계 밖에 있으므로 무엇을, 어떻게 마무리할지에 대한 선택지가 여전히 많이 남아있음
    • 이런 경계는 기능을 나중에 추가할 시간을 벌어줌
flowchart LR
    S1["1단계<br/>경계(perimeter) 설정<br/>예: 페이월, 강제 선택, 데이터 선요청"] --> S2["사용자 피드백 및<br/>데이터 축적"] --> S3["기반이 갖춰지면<br/>경계 완화 또는 제거<br/>기능 정식 오픈"]
  • 단계적 구축의 예시 (첫 단계에서 경계 설정)
    • 서비스에 무료 티어를 제공하고 싶지만, 이를 위해서는 스팸 방지와 효율성 최적화가 필요함 — 경계는 이런 기능이 갖춰질 때까지의 페이월(paywall)
    • 좋은 기본값을 제공하고 싶지만 그것이 무엇이어야 할지 확신이 없거나, 모두에게 적합하게 만들려면 더 작업이 필요함 — 사용자가 정보에 입각한 선택을 하도록 강제하고, 사용자 선호를 알게 되거나 다듬어질 준비가 되면 그 강제를 해제
    • 향후 기능에 데이터 수집이 필요함. 처음부터 데이터를 얻지 않으면 기존 사용자에 대해 나중에 다시 얻기 어려움 — 초기 사용자 일부를 이탈시킬 위험을 감수하고서라도 미리 데이터를 요청. 성장에 방해되거나 사용자 불만이 크면 나중에 요청을 중단 가능
  • EntSchema의 설명(description) 기능이 마지막 카테고리에 해당
    • 최우선순위는 신규 모델 작성 속도 향상이었지만, 모델 사용자를 위한 훌륭한 설명도 원함
    • 처음부터 요구하지 않으면 대부분의 작성자가 작성하지 않을 것이고, 나중에 추가하려면 마이그레이션 프로세스가 필요할 것이라 판단 — 빠르게 추가할 수 있는 기능이었으므로 바로 구현
    • 다만 스키마 작성자에게 너무 많은 마찰을 줘서 이탈시키는 것도 우려함 — “최선의 균형점”이 있었겠지만 다른 우선순위 문제들이 있었음
    • 초기 경계: 설명 없는 필드를 금지
    • 피드백에 대응하여, 설명을 건너뛰고 싶은 사람들을 위한 .self_explanatory() 태그를 빠르게 추가
      • 다만 이는 노랑 어포던스였음 (중요한 문서화를 쉽게 건너뛰는 데 악용될 수 있으므로) — 스키마의 최소 일부 필드/엣지는 반드시 설명하도록 요구하여 최소한의 주석 작성을 강제함 (드래프트 모드에서는 프로토타이퍼를 방해하지 않기 위해 이 체크를 수행하지 않음)
    • 이후 “게시물의 텍스트” 같은 무의미한 설명이 많음을 발견 — 해커톤에서 무의미한 주석을 감지하는 휴리스틱 검증을 추가함
      • 텍스트 전체가 클래스명+필드명과 “of”, “the” 같은 필러 단어로만 이루어져 있으면 무의미한 코멘트로 플래그하고 상세 설명이나 self_explanatory를 요구
      • 저자는 이것이 성가시게 여겨질까 걱정했고 실제로 그랬을 수도 있지만, 몇몇 엔지니어로부터 “들켰네요!“라는 반가운 메모를 받기도 함
    • 처음엔 단순하고 단호하게 시작했다가, 점진적으로 기능을 다듬고 경계를 낮춰 나감 — 결국 사용 편의성과 노랑 어포던스 제한 사이의 균형을 맞춤. 사용자 피드백이 전혀 없던 초기에 이렇게까지 신중했다면 시기상조였을 것

5. 필요하다면 실험적 버전으로 시작하기

  • 페이스북의 문구: “코드가 논쟁에서 이긴다(code wins arguments)” — 작동하는, 시연 가능한 코드가 추상적 논쟁/시나리오/요구사항 문서보다 더 설득력 있음
  • 기능 출시 시 항상 완벽하게 확신하고 조사된 상태일 수는 없음. 때로는 무언가를 만들어 가정을 시험하는 것이 최선의 조사 방법
  • 이런 경우 4장의 도그푸딩(dogfooding)이나 5장의 실험으로 출시하여 더 많은 정보를 얻을 수 있음
    • 일부 사용자에게 롤아웃되는 기능 플래그(feature flag) 사용, A/B 테스트 실행
    • 코멘트 요청(RFC) 발송
    • 베타 사용자에게 테스트 요청
  • 나중에 언십(unship, 기능 회수)할 수도 있지만 그래도 괜찮음
  • EntSchema의 경우 실험을 실행할 능력은 없었지만, 도그푸딩으로 검증하며 몰랐던 것들을 학습함
    • 초기 몇 개의 스키마를 직접 만들어 사용자보다 먼저 그 고통과 즐거움을 경험함
    • 실전에서 검증된 오래된 모델 일부를 EntSchema로 마이그레이션하여, 장난감이 아닌 실제 프로덕션 유스케이스를 다루고 있는지 확인 — 실제 사용 사례에 대한 설계 검증이 초기에 중요한 기능의 우선순위를 정하는 데 도움이 됨

8.6 제한적 기능 대 확장 가능한 기능

  • 더 많은 유스케이스를 여는 확장 가능한 버전을 만들 것인가, 아니면 특정 시나리오에 맞춘 좁은 버전을 만들 것인가? 어떻게 결정할 것인가?
  • 유연한 인터페이스 또는 견해가 뚜렷한 인터페이스에 대한 개인적 편향이 있을 수 있음 — 시나리오 기반 설계로 자신의 기본 성향을 넘어설 수 있음
  • EntSchema에서 varchar_field, int64_field 같은 DB 수준 타입을 쓸지, timestamp_field, string_enum_field, email_string_field 같은 애플리케이션 수준 타입을 쓸지 결정할 때, 세 가지 설득력 있는 시나리오 동기를 도출할 수 있었음
    • 입력 검증(Input validation): 손상된 DB 객체가 생성되기 전에 입력된 열거형이 유효한지, 지정되었는지 확인하려는 요구. 이메일도 유효한 이메일 주소 형식이어야 함
    • 가독성(Readability): 사용자가 타입만 보고도 필드/엣지의 용도를 알고 싶어함
    • 프로덕션 오브젝트 인스펙터: 엔지니어가 timestamp_field로부터 예쁘게 출력된 데이터를 보고, url_string_field의 웹 주소를 클릭 가능하게 만들고, ID 필드로부터 User 객체로의 링크를 렌더링하는 등
  • 이 세 유스케이스가 저자를 설득함 — “3의 법칙” 휴리스틱의 또 다른 버전을 만족시킴

NOTE: 기능의 확장판이 세 가지 뚜렷하고 강력하며 근시일 내의 시나리오를 열어준다면 만들 것. 그렇지 않다면 사용자가 신경 쓰는 한두 시나리오만을 위한 특정 대상의 것으로 시작할 것

flowchart TD
    Q["확장 가능한 기능을 만들 것인가?"] --> C1{"설득력 있는 시나리오가<br/>3개 이상인가?"}
    C1 -->|예| E1["확장 가능한 버전 구축<br/>각 시나리오별 테스트 작성"]
    C1 -->|아니오| E2["1~2개 시나리오에 맞춘<br/>좁고 구체적인 기능으로 시작"]
  • 확장 가능한 버전을 만드는 비용을 계산할 때 유념할 점
    • 만들 가치가 있다면 테스트할 가치도 있음 — 세 가지 시나리오를 위해 만든다면 각각에 대한 시나리오 테스트(4장) 작성 — 기능이 생각한 만큼 정말 확장 가능한지 확인
    • 확장성은 복잡성을 더하고, 종종 더 많은 빨강/노랑 어포던스를 동반함 — 이런 것들도 함께 발견하고 완화할 것
    • 유스케이스 간 우선순위가 크게 다를 때 주의할 것. 앞의 목록에서 시나리오는 우선순위가 감소하는 순서였으며 런타임 검증이 가장 시급했음 — 검증기(validator)만 특정하게 만들 수도 있었을 것. 하지만 마지막 시나리오조차 꽤 설득력 있어서, 시간이 될 때까지 오브젝트 프리티 프린터(pretty printer) 구현을 미룸. 즉, 처음부터 만들기 위한 토대는 마련해두되 실제 구현은 나중으로 미룸
  • 시기상조의 확장성(premature extensibility) 예시
    • 첫 비디오 게임을 만드는 스튜디오를 가정. 스튜디오의 꿈은 여러 게임을 만드는 것이므로 게임 제작 플랫폼을 함께 구축하고 싶은 유혹이 있음
    • 하지만 첫 게임을 완성할 자금조차 확신할 수 없다면, 일반성을 최소화하는 것이 최선일 수 있음
    • 두 번째, 세 번째 게임을 만들 자금이 생길 때까지 기다릴 것 — 그때쯤이면 게임들 간의 공통점과 차이점이 보이기 시작하여 올바른 추상화를 선택하는 데 도움이 됨

8.7 요약

  • EntSchema는 시나리오 활용의 수혜를 입어 안전하고 생산적인 개발자 경험을 제공함. “3의 법칙”은 틀리지 않았음: 스키마 작성자로부터 얻은 지식을 바탕으로 일반적인 개발 플랫폼으로 진화함
  • 수년에 걸쳐 웹 문서, 오브젝트 인스펙터, 데이터 마이그레이션 도구, 데이터 무결성 체크, GraphQL 스키마 생성 등 많은 기능을 지원하게 됨
  • 개발자 생산성 향상 도구로 시작했지만, 일부 기능이 미션 크리티컬해지면서 결국 모든 엔티티에 필수가 됨

핵심 조언 정리

  • 독단적이지 말 것. 무엇을 만들지 결정하기 위해 사용자 페르소나와 시나리오의 세부사항을 파고들 것
  • 시그니파이어에 주의를 기울이고, 그것이 최선의 어포던스를 어떻게 부각(highlight)하고 나쁜 것을 어떻게 덜 부각(lowlight)하는지 살펴 “성공의 웅덩이”를 만들 것
  • 사용자에게 주는 결정이 중요하고, 검증되었으며, 알맞은 페르소나에 의해 처리되도록 할 것
  • 낙관주의자도 비관주의자도 되지 말 것. 시나리오를 만들어 기능의 이점과 반갑지 않은 단점 모두를 부각할 것

시간과 반복 요소를 더한 추가 조언

  • 최상의 작업은 집중했을 때 나옴 — 기능이 우선순위가 높고 집중할 가치가 있을 때 만들 것
  • 트랩도어 결정을 피해야 할 때는 경계(perimeter)를 구축하여, 데이터를 수집하면서도 향후 개발을 위한 선택지를 보존하는 방식으로 접근을 제한할 것
  • 반대로, 3의 법칙을 기억할 것: 여러 설득력 있는 시나리오로 뒷받침되지 않는 기능과 확장 포인트에는 시간을 쓰지 말 것
  • 마지막으로, 여전히 결정할 수 없다면: 의심스러우면 뺄 것. 나중에 사례가 더 명확해지면 추가할 수 있음

NOTE: (각주) 2003년 리코 마리아니(Rico Mariani)가 “성공의 웅덩이(pit of success)” — 사용자가 자연스럽게 “이기는 습관에 빠지도록” 돕는 관행 — 라는 용어를 만들었음. 저자는 이 개념을 매우 좋아한다고 언급함

8.8 예제

  • 함께 비밀번호 관리자(패스워드 매니저)를 만들어보기 (LastPass, 1Password, Bitwarden과 유사)
  • 타겟 페르소나: 모든 앱/웹사이트의 고품질 비밀번호를 안전하게 저장하고 싶어하며, 이를 모두 기억할 필요가 없기를 원하는 디지털 네이티브
    • 비밀번호 생성/수정/저장이 쉽고, 오용하기는 어렵기를 원함
    • 다시는 비밀번호를 신경 쓰지 않기 위해 소액의 월 구독료를 지불할 의향이 있음
  • 논의 범위: 패스키(passkey)는 제외하고 전통적인 비밀번호에 집중 (패스키가 더 현대적/안전하지만 아직 덜 보편적으로 쓰이므로)

[문제 1]

  • 비밀번호 생성 시 좋은 습관을 갖도록, 초록/노랑/빨강 어포던스를 나열할 것. 두 가지 큰 시나리오를 고려: (1) 신규 계정 자격증명 생성, (2) 과거에 생성한 계정의 로그인 정보 입력

[문제 2]

  • 문제 1의 답을 바탕으로, 초록 어포던스를 노랑/빨강 대비 부각시키도록 비밀번호 관리자를 설계하는 아이디어를 최소 두 가지 생각해볼 것. 표준 가입 웹페이지에서 계정을 생성하는 사용자 시나리오를 고려

[문제 3]

  • 사용자를 보호하기 위해 웹사이트 도메인과 최신 보안 정보를 담은 자체 데이터베이스 구축을 고려 중. 이는 보안 커뮤니티와 사용자로부터 데이터를 수집하는 방법을 만들어야 하므로 큰 투자가 필요함. 유일한 대안은 피싱 의심 도메인 목록을 제공하는 서드파티 데이터베이스에 의존하는 것이라 가정. “3의 법칙”을 고려하여, 자체적이고 더 일반적인 데이터베이스를 구축할 만큼 충분히 중요한 시나리오를 떠올릴 수 있는지 검토할 것

8.9 답안

[답안 1]

색상 시나리오 설명
초록 신규 계정 생성 특정 앱마다 고유한, 자동 생성된 강력한 무작위 비밀번호를 선택
노랑 신규 계정 생성 흔한 비밀번호로 학습된 알고리즘으로 추측 가능한, 사용자 본인이 직접 입력한 기억하기 쉬운 비밀번호를 사용. 또는 이미 다른 사이트/앱에서 사용 중인 것으로 감지된 비밀번호를 입력하여 “크리덴셜 스터핑(credential stuffing, 유출된 이메일/비밀번호 조합을 다른 사이트에 대입하는 공격)“에 노출됨
빨강 기존 로그인 입력 데이터 유출을 통해 이미 손상된 것으로 알려진 이메일/비밀번호 조합을 입력함

[답안 2]

  • 자신의 비밀번호를 직접 입력하는 것(특히 짧고 익숙한 것)은 매우 쉬움 — 그러므로 자동 생성 비밀번호 기능이 훨씬 더 쉽고 발견하기 쉬워야 함
  • 저자가 사용하는 1Password 사례: 브라우저 플러그인이 다음 세 가지를 수행
    • 비밀번호 입력 양식을 자동으로 감지해 비밀번호 관리자 사용 대화상자를 팝업시킴 (발견성 역전 방지)
    • 원클릭으로 강력한 비밀번호를 생성하는 옵션을 제시하여, 직접 입력하는 것보다 더 쉽게 만듦 (사용성 역전 방지)
    • 일부 웹사이트의 기호/길이 등 비밀번호 제약조건에 맞춰 정책을 설정할 수 있게 하여, 사용자가 자신의 열등한 비밀번호를 직접 만들 가능성을 낮춤

[답안 3]

  • 자체 데이터베이스는 검증된 안전한 도메인, 데이터 유출이 있었던 도메인, 서드파티가 제공하는 피싱 도메인 정보를 모두 포함할 수 있음. 도메인을 유출된 비밀번호 목록에 매핑할 수도 있고, 앞서 언급한 비밀번호 요구사항처럼 어떤 도메인이 어떤 제약을 갖는지도 추적 가능
  • 가능한 유스케이스들
    • 사용자가 피싱 의심 도메인에서 아이디/비밀번호를 요청받으면 빨강 경고 대화상자를 표시
    • 자격증명이 원래 저장된 도메인이 아닌, 검증되지 않은 도메인으로 전달되려 할 때 추가 경고 표시
    • 사용자의 아이디/비밀번호가 데이터 유출로 노출되었을 가능성이 있을 때 경고 (다른 웹사이트/앱과 동일 비밀번호를 공유한 경우 포함)
    • 충분히 많은 사용자가 특정 도메인에서 특정 제약이 있는 비밀번호를 생성하면 이를 기록하여 해당 사이트의 제약을 추론 — 향후 사용자들에게 해당 제약에 맞춰 비밀번호를 자동 생성
  • 이 분석만으로 결정을 내리기엔 충분하지 않지만, 이런 여러 시나리오를 고려하면 자체적이고 확장 가능한 데이터베이스를 구축해 회사의 핵심 역량으로 삼을 가치가 있는지 최소한 진지하게 검토해볼 만함

CHAPTER 9 프로덕트 아키텍처

소프트웨어에는 의미 있는 요구사항이 갖추어지는 경우가 드물다. 설사 갖추어지더라도, 성공을 가늠하는 유일한 척도는 결국 우리의 솔루션이 고객의 끊임없이 바뀌는 문제 인식을 해결해주는가이다. — 제프 앳우드(Jeff Atwood)

  • 기능 요구사항(Functional Requirements): 사용자가 원하는 동작을 수행하는 제품 어포던스에 대한 목표 집합 (8장 주제)
  • 기능 요구사항만 신경 쓰면 겉보기엔 그럴듯하지만 실제로는 작동하지 않는 “포템킨 마을” 같은 결과물이 나옴
  • 비기능 요구사항(Non-Functional Requirements, NFR): 엔지니어가 설계 시 고려하는 핵심 속성
    • 비용, 확장성, 지연시간(latency), 처리량(throughput), 데이터 일관성, 탄력성(elasticity), 가용성(availability)
    • 프라이버시와 보안도 포함되지만 이 장에서는 다루지 않음
    • 사용자 목표를 직접 달성시키기보다는 “목적을 위한 수단”에 가까움 - 작동하지 않을 때만 사용자가 인지하는 경우가 많음
  • 프로덕트 아키텍처(Product Architecture): 사용자와 비즈니스 제약을 고려한 시스템 설계 - 사용자 중심 사고와 시스템 중심 사고의 순수한 종합
    • PM 등 다른 역할이 설계를 도와주기 가장 어려운 영역이기도 함

이 장의 구성

  1. 프로덕트 아키텍처에 편집자적 사고방식을 적용하는 토대
  2. 사례 연구(스트라이프)로 이후 논의의 틀 잡기
  3. 두 카테고리의 NFR에 프로덕트 사고 적용
    • 빠르고 신뢰할 수 있는 사용자 경험을 만드는 요인: 지연시간, 가용성, 데이터 일관성
    • 다수 사용자가 동시에 사용할 때만 발동하는 확장성 요인: 처리량, 격리 등
    • (데이터 거버넌스 요구사항인 컴플라이언스·보안·프라이버시는 이 책에서 생략)
  4. 고객에게 시스템 특성을 전달하는 방법

9.1 프로덕트 아키텍처의 토대

  • 프로덕트 아키텍처는 인터랙션 디자인과 관련이 있음 - 내부 추상화(abstraction)도 일종의 축소판 제품이므로, 8장의 교훈(유스케이스, 3의 법칙, 반복적 개발 등)이 대부분 적용됨
  • 그러나 NFR은 다루기 까다로운 특성이 있음
    • 종류가 매우 많고 충족시키기 어려움 → 시간·비용 부족으로 전부 대응 불가
    • 서로 트레이드오프 관계 (예: 견고한 데이터 복제 → 지연시간 증가, 보안 강화 → 가용성 저하)
  • 따라서 **편집자적 마인드셋(editorial mindset)**이 필요함: 사용자가 진짜 중요하게 여기는 것은 무엇인가?

저자가 사용하는 4가지 우선순위 판단 기법 (조기 최적화 방지 목적)

  1. 큰 그림을 보기 위해 줌아웃하기
  2. 가로등 효과(streetlight effect) 피하기
  3. 사용자 영향(user impact) 전달하기
  4. 시스템-프로덕트 갭을 잇는 기술을 활용해 영향 조사하기

NOTE: 조기 최적화(Premature Optimization): 지출할 근거가 충분히 확보되기 전에 NFR을 개선하는 코드 변경. 시간 낭비이며 다른 요구사항에 해를 끼칠 수도 있음

큰 그림을 보기 위해 줌아웃하기

  • 예: 캐싱으로 알고리즘을 O(n)에서 O(log n) 또는 O(1)로 줄이는 것은 그럴듯해 보이지만, 작업량이 크고 복잡도를 높임
  • 맥락(context)을 고려해 줌아웃해야 함 - 이 알고리즘의 실행시간이 다른 문제에 비하면 미미할 수 있음
  • “한 문제가 다른 문제를 압도한다(dwarf)”: 같은 유형의 문제인데 한쪽이 자릿수(order of magnitude) 단위로 더 중요한 경우
    • 예: 함수 실행시간을 마이크로초 단위로 줄여도, 그 함수가 항상 네트워크 왕복 뒤에 호출된다면 의미가 없음
    • 네트워크 왕복을 최적화해도, 그것이 일주일 걸리는 택배 배송을 트리거하는 것이라면 역시 의미가 없음
  • 이런 경우는 조기 최적화임을 쉽게 증명할 수 있는 사례이며, 이를 일반화할 필요가 있음

가로등 효과 피하기

  • 많은 엔지니어는 시스템 문제를 발견하면 곧바로 해결하려는 방향으로 훈련되어 있음 → 불필요한 하드닝과 조기 최적화에 빠지기 쉬움
  • 가로등 효과: 술 취한 남자가 실제로는 다른 곳에서 잃어버린 열쇠를, 가로등 밑에서 찾는 우화 [그림 9-1]. “여기가 빛이 있는 곳이니까”라는 이유로
    • 여기서 “빛이 비치는 영역” = 엔지니어로서 훈련받은 분야, 가장 익숙한 코드베이스 영역
  • 대응법: 술주정뱅이가 되지 말 것. 기존 코드에서 경쟁 상태(race condition)에 취약한 DB 접근을 발견했다면, 수정 우선순위를 정하기 전에 실제로 발생할 가능성을 머릿속으로 시뮬레이션해볼 것
  • 스스로를 정직하게 유지하는 방법: 해결하려는 문제와 그 “왜(why)” — 그 문제를 고려하게 만든 사용자 시나리오 — 사이에 선을 그어보는 것. 사용자 영향 관점에서 근거를 적어두면 도움이 됨

제안의 사용자 영향 전달하기

  • 핵심 질문: “이 제안을 사용자에게 어떻게 설득할 것인가?”
    • “O(log n) 알고리즘” → 사용자는 이해하지 못함
    • “페이지 로딩이 25% 빨라짐” → 설득력 있음
  • 처리량(throughput) 예시
    • “초당 백만 건 요청을 처리하도록 확장해야 한다” → 추상적이라 와닿지 않음
    • “활성 고객이 5초마다 한 번씩 요청을 보낸다”고 계산하면 → “우리 시스템은 500만 동시 고객을 지원한다”로 표현 가능 → 사용자 언어로 표현하면 좋은 의사결정의 토대가 됨 (성장률 기반으로 언제 확장 역량에 투자할지 예측 가능)
  • 이런 분석이 항상 저렴한 것은 아님 - 프로덕트 맥락 확보 비용이 비쌀 때는 과도하게 고민할 필요 없음. 모범 사례(best practice)로 NFR을 개선하는 것도 충분히 정당함
  • 데이터 일관성 예시: 소규모 애플리케이션에서는 “혹시 몰라서” 강한 일관성을 보장하는 DB 트랜잭션을 쓰는 것이 합리적임 - 시스템을 추론하기 쉬워져 팀 생산성도 높아짐. 나중에 스케일이 커져 트랜잭션이 병목이 되면 그때 일관성 보장 수준을 완화하면 됨
  • 핵심 역량: 언제 프로덕트 맥락을 수집해야 하고, 언제 표준 관행에 의존해도 되는지 판단하는 것이 프로덕트 아키텍트의 핵심 스킬

시스템-프로덕트 갭을 잇는 기술 활용하기

  • 시스템-프로덕트 갭(system-product gap): 컴퓨팅 기본 요소를 중심으로 만들어진 저수준 추상화와, 사용자 행동을 지원하기 위해 만들어진 고수준 컴포넌트 사이의 간극

갭이 드러나는 두 가지 방식

측면 내용 예시
관측 가능성(observability) 갭이 넓으면 저수준 결정이 사용자에게 미치는 영향을 파악하기 어려움 → 가로등 효과 심화, 큰 그림 사고 저해 내 마이크로서비스의 지연시간이 실제 흐름에 영향을 주는가(관측성 도구), 웹페이지 컴포넌트의 메모리 사용이 앱 피크 메모리에 얼마나 기여하는가(메모리 프로파일러)
신뢰성(reliability) 컴포넌트의 근본적 한계를 제품에 반영할 때 고려 필요 (예: 네트워크 장애는 항상 발생함). 저수준 컴포넌트를 직접 고칠지, 한계를 보완하는 맥락에 배치할지 결정해야 함 불안정(flaky)한 LLM → 재시도(retry)와 가드레일을 갖춘 프레임워크 사용, 정적 콘텐츠 과부하 → CDN으로 콘텐츠를 캐시하고 사용자 가까이 배치
  • 여러 계층에서 수정이 가능하다는 것을 인식하는 것이 중요함 - 적절한 기술을 쓰면 저수준 한계를 무관하게 만들거나, 관련이 있을 때는 그 영향을 이해하고 어디에 집중할지 알 수 있음

9.2 사례 연구 소개

  • 스트라이프(Stripe): 극한의 규모로 온라인 결제를 처리 - 규모와 신뢰성 요구사항이 프로덕트 아키텍처 사고에 좋은 사례
  • 저자가 스트라이프 재직 시절 사용했던 객체 모델을 수정·단순화하여 소개

핵심 개념

  • 고객(Customer): 온라인에서 물건을 구매
  • 판매자(Merchant): 고객으로부터 결제를 받음
  • 잔액(Balance): 판매자가 보유한 금액을 추적. 주기적으로 판매자에게 지급(payout)되며, 환불(refund) 처리 시에도 음수가 되면 안 됨
  • 결제(Payment): 고객이 판매자에게 하는 지불

[그림 9-2] 단순화된 데이터 모델 다이어그램 (선 위의 십자 표시는 일대다 관계의 “다(many)” 쪽을 의미)

erDiagram
    MERCHANT ||--o{ PAYMENT : "받는다 (many)"
    MERCHANT ||--|| BALANCE : "보유한다"
    CUSTOMER ||--o{ PAYMENT : "지불한다"
  • 표준적인 이커머스 데이터 스키마: 고객이 판매자에게 결제하고, 판매자는 받은 돈을 모두 저장하고 필요시 환불하기 위한 잔액을 보유
  • 이 셋업은 흥미로운 프로덕트 아키텍처 과제를 제공함 - 이후 섹션에서 계속 이 사례로 트레이드오프를 다룸

9.3 안정적인 사용자 경험

  • 사용자 공감(user-empathetic) 관점에서 흔한 신뢰성·속도 이슈를 탐구 → 프로덕트 아키텍처를 더 전략적으로 만드는 데 도움
  • 핵심 원칙: 지연시간, 가용성, 데이터 일관성은 진짜 중요한 것 — 사용자가 제품에 참여(engage)하고 작업을 성공적으로 완료하는가 — 의 대리 지표(proxy)일 뿐임
  • 이 섹션 전체에서 사고를 “사용자에게 더 가깝게” 가져가고, 지표도 그렇게 추적할 것을 지속적으로 주장함

지연시간(Latency)

시스템 중심 측정

  • 가장 간단한 방법: 엔드포인트, 함수, RPC 등 작업(operation)의 시간을 측정하고 관측성 시스템에 지표로 저장
  • 장점
    • 모든 작업을 측정하면 포괄적인 지표 세트 확보
    • 느린 사용자 인터랙션을 발견했을 때, 각 작업의 상세 데이터로 범인을 찾는 데 도움
    • 지연시간 최적화 시 지표로 개선 추적 가능
    • 큰 회귀(regression) 발생 시 팀에 알림 가능
  • 지연시간이 우려사항이라면 이런 방식으로 제품을 계측(instrument)해야 함 - 스트라이프는 API 호출 각각, DB·개별 서비스 지연시간도 모두 계측
  • 주의: 이런 지표들도 각각 하나의 가로등과 같음 → 거기에 너무 많은 시간을 쏟기 쉬움. 궁극적으로 중요한 것은 사용자 경험이며, 단일 작업의 지연시간 증가가 사용자에게 보이지 않을 수도 있음

어디서 지연시간이 문제가 되는지 파악하는 리트머스 테스트

  • 사용자·업스트림 시스템이 능동적으로 기다리는(awaiting) 작업은 무엇이고, 백그라운드에서 일어나는 작업은 무엇인가?
    • 스트라이프 결제 수락 사례: 동기적(synchronous) 부분(자금 존재 여부 검증, 결제 가능 여부 확인) vs 비동기적(asynchronous) 부분(감사 로그 기록, 실제 자금 이동 등)으로 분리
  • 어떤 사용자 흐름이 가장 흔한가? 그것을 최적화하면 가장 많은 사용자 시간을 절약
    • 스트라이프에서는 판매자 가입(Merchant signup)보다 결제 흐름의 지연시간 최적화가 자릿수 단위로 더 자주 발생하므로 더 중요
  • 사용자가 지연시간에 가장 민감한 상황은 언제인가?
    • 저의도 시간(low intent times): 사용자가 계속 읽을지·구매할지·탐색할지 확신이 없는 시점 - 지연 중 딴 데로 정신이 팔릴 수 있음 (예: 장바구니 담는 중)
    • 반대로 구매를 완료하는 시점에는 더 집중하는 경향
    • 예외: 신용카드가 거절(decline)되었을 때 - 알려주는 데 시간이 걸리면 사용자가 탭을 이동해버려 결제 실패를 인지하지 못할 수 있음 → 검증(validation)은 최대한 빨리 완료해야 함

더 엄격한 접근: 전환율 기반 분석

  • 이커머스 결제 흐름의 3단계
    1. 쇼핑 후 장바구니에 아이템 추가
    2. 체크아웃 페이지 로드
    3. 구매 완료 (이후 결제 처리)
  • 핵심적으로 추구하는 사용자 지표: 전환율(conversion rate) - 장바구니 담기부터 결제까지 끝까지 완주하는 사용자 비율
  • 시나리오 지표(scenario metric): 사용자가 시나리오를 진행하는 결과를 추적하는 지표 (전환율이 대표적 예)
  • 프로덕트 명제(product thesis): 지연시간을 줄이면 전환율이 개선될 것이라는 가설 (6장에서 정의)
  • 기법: A/B 테스트로 인위적 지연 주입
    • 세 단계 각각에서 소수 사용자에게 인위적 지연 도입 (예: 1초 지연 추가)
    • 지연 없는 대조군과 전환율 비교
    • 전환율이 가장 크게 악영향을 받는 단계를 최적화 대상으로 선정
  • 또 다른 시나리오 지표 예: 장바구니 추가~체크아웃까지 걸린 시간(elapsed time)
    • 예: 체크아웃 페이지에서 신용카드 번호 검증용 자바스크립트(체크섬 등)를 개선하면, 사용자가 오타를 냈을 때 흐름 속도 개선 가능 - 개별 지표로는 추적 안 해도 종단간(end-to-end) 흐름 지표가 이를 포착함

분산 추적(distributed tracing)으로 시스템-프로덕트 갭 잇기

  • 문제: 이 지표가 코드 배포 후 급증하면 원인을 어떻게 찾을까? → 프로덕트 지표와 상세 시스템 지표를 잇는 기술 필요
  • 분산 추적(distributed tracing): 각 사용자 흐름에 “트레이스 ID”를 부여하고 이를 모든 작업에 전달(thread through). 각 작업은 해당 ID로 이벤트를 로깅하며, 특정 세션의 모든 이벤트를 쿼리해 큰 지연이 어디서 발생했는지 파악 가능. 이후 플레임 그래프(flame graph)나 워터폴 차트(waterfall chart)로 시각화

[그림 9-3] 체크아웃 흐름 첫 두 단계의 계층별 지연시간 워터폴 차트 (원본은 “add to cart” 클릭(0초)과 “checkout” 클릭(1초) 각각에서 Client rendering → Network → Gateway → API(POST:/cart, GET:/checkout) → DB/Cache 순으로 요청이 계층을 통과하며 소요되는 시간을 계단식으로 보여주는 차트)

graph LR
    subgraph S1["1단계: add to cart 클릭 (0초 시점)"]
    A1[클라이언트 렌더링] --> A2[네트워크]
    A2 --> A3[게이트웨이]
    A3 --> A4["API: POST /cart"]
    A4 --> A5[DB]
    end
    subgraph S2["2단계: checkout 클릭 (1초 시점)"]
    B1[클라이언트 렌더링] --> B2[네트워크]
    B2 --> B3[게이트웨이]
    B3 --> B4["API: GET /checkout"]
    B4 --> B5[캐시]
    end
    A5 -. 약 1초 후 .-> B1
  • 여러 추상화 계층을 하나의 이미지로 보는 것이 가장 강력한 시점 - 전체 트레이스에서 문제 지점으로 “줌인”하거나, 특정 실패에서 그것이 속한 트레이스로 “줌아웃” 가능
  • 분산 추적 같은 기술 투자는 매일 쓰는 게 아니라서 미루기 쉽지만, 필요한 순간에는 100배의 생산성을 가져다줌

가용성(Availability)

  • 지연시간과 마찬가지로, 사용자 관점에서의 작업 가용성 최적화와 기저 시스템 동작 이해가 모두 필요함
  • 일반적 셋업: 프론트엔드 팀과 백엔드 팀이 있는 웹사이트 - 백엔드 팀은 자사 서비스 엔드포인트의 요청 성공률을 알 수 있고, 트래픽 급감 알림도 가능
  • 하지만 이 지표들이 포착하지 못하는 사용자 경험 요소
    • 네트워크 요청이 서비스에 도달하고 있는가?
    • 클라이언트 측 재시도(retry)가 서비스 결함을 보완해 가용성 문제를 지연시간 증가로 바꾸고 있는가?
    • 서비스를 호출하는 버튼이 있는 웹페이지가 애초에 렌더링되고 있는가?
    • 사용자가 클라이언트 에러를 만나 서버 호출 자체를 못하고 있는가?
    • 최근 UI 변경으로 사용자가 혼란스러워하며 버튼을 못 찾고 있는가?
  • 두 팀이 협업하면 프로덕트 경험에 대한 총체적(holistic) 그림을 그릴 수 있음 - 클라이언트 계측은 인사이트를 사용자에게 더 가깝게 만들고, 백엔드 지표와 연결하면 가용성 문제를 정확히 짚어낼 수 있음
  • 실시간 사용자 모니터링(Real User Monitoring, RUM): 시스템-프로덕트 갭을 잇는 클라이언트 모니터링 기술 - 페이지 로드 시간, 클라이언트 에러, 네트워크 타임아웃 등을 포착하고 브라우저·앱 버전, 지역 등 사용자 요인과 상관관계 분석 가능

시스템 가용성 문제를 보완하는 기술

기술 설명
워크플로 엔진(Workflow engines) 여러 단계에 걸친 사용자 흐름을 오케스트레이션하며 전체적인(top-level) 시야도 제공. 개별 단계를 재시도하고 병렬성을 관리함으로써 흐름 완료에 책임을 짐 → 가용성 문제에 강함. 관측성에도 도움 - 대개 프로덕트 기능·시나리오를 나타내며 각 단계 추적 가능 → 한 단계가 실패·지연되면 프로덕트 영향과 정확한 실패 지점을 모두 짚어낼 수 있음
콘텐츠 전송 네트워크(CDN) 데이터는 클라우드에 중앙집중식으로 저장·관리되길 원하지만 사용자는 네트워크 문제·지연을 줄이기 위해 데이터가 가까이 있길 원함 - CDN은 정적 데이터를 전 세계에 복제·캐싱하여 DB와 사용자 사이의 간극을 문자 그대로 이어줌

데이터 일관성(Data Consistency)

  • 사용자 관점에서 풀기 가장 어려운 과제 중 하나 - 다양한 수준의 일관성을 제공할 수 있고 극명한 트레이드오프가 존재
  • 마틴 클레프만(Martin Kleppmann)의 「데이터 중심 애플리케이션 설계」에서 언급하듯, 일관성 보장은 공짜가 아니며 강한 보장을 제공하는 시스템은 약한 보장의 시스템보다 성능이 나쁘거나 장애 내성이 떨어질 수 있음
  • 일관성 보장의 종류: 강한(strong), 인과적(causal), 최종적(eventual) 일관성 등 - 유용하지만 추상적인 용어
  • 저자는 즉각적 일관성(immediate consistency) ~ 최종적 일관성(eventual consistency) 스펙트럼에 집중 (일관성 보장이 전혀 없는 시스템은 보통 사용자 대면용이 아님)
  • 읽기-쓰기 일관성(read-your-write consistency) 계열 용어를 선호 - “당신(your)“이라는 사용자 관점이 용어에 내재되어 있기 때문
  • 액터를 3그룹으로 분류: 나(사용자 본인), 다른 사용자들, 시스템

[표 9-1] 사용자 중심 일관성 보장 카탈로그 (가장 흔한 것부터 강한 순서로)

보장 의미
WyW 내 쓰기 이후 내 쓰기(Write your Writes) 이후의 모든 쓰기는 이전 쓰기를 반영한다
RyW 내 쓰기 이후 내 읽기(Read your Writes) 사용자가 데이터를 읽을 때, 자신이 이전에 한 모든 쓰기가 반영되어 있다
WsW 시스템 쓰기 이후 쓰기(Write after system Writes) 자동화·에이전트에 의한 업데이트가 즉시 반영된다
WoW 다른 사용자 쓰기 이후 쓰기(Write after others’ Writes) 다른 사용자의 쓰기를 모른 채 쓰기를 할 수 없다 (양방향)
RoW 다른 사용자 쓰기 이후 읽기(Read others’ Writes) 다른 사용자의 쓰기가 성공 확인된 즉시 그 결과를 볼 수 있다
RsW 시스템 쓰기 이후 읽기(Read after system Writes) 자동화·에이전트의 수정 사항이 시스템에 확인된 즉시 전파된다
  • 이 보장들은 데이터베이스가 아니라 프로덕트 기능의 속성임에 유의 - DB는 보통 “누가 읽는지” 개념이 없으므로, 어떤 시맨틱을 선택할지는 애플리케이션 개발자의 몫
  • 약한 보장을 제공하는 DB 위에서도 더 강한 보장을 짜맞출 수 있고, 반대로 DB에 강하게 일관된 쿼리를 해도 자동으로 강한 일관성이 보장되는 것은 아님 → 여러 계층에서 결정을 내려야 함

예시: 단체 음식 주문(group food order) 엔드포인트 설계 - 계층별 일관성 설계

  • 시나리오: 여러 사용자가 동시에 장바구니를 편집하다가 주문 생성자가 최종 제출
계층 설계 내용
데이터베이스 트랜잭셔널 DB로 모든 쓰기를 일관되게 처리하거나, 상위 계층에서 해결
시스템 설계 겹치는 쓰기 대신 리스트에 append하는 방식으로 RoW/WoW 관련 문제를 원천 차단
엔드포인트 입력 멱등성 키(idempotency key)로 반복 요청에 의한 중복 주문 방지 - 이미 커밋된 키인지 확인 후 통과
엔드포인트 출력 갱신된 데이터를 클라이언트에 반환 - 재조회 없이 최신 상태 확인, 오래된(stale) 복제본 조회 방지
클라이언트 쿠키·로컬 스토리지에 데이터를 보관해 앱을 반응성 있게 유지, 주기적으로 새로고침
프로덕트 결정 계정 소유자가 체크아웃 흐름에 들어가면 장바구니를 잠금(lock) → 경쟁 상태 방지. 불만 발생 시 소유자가 잠금 해제

원문에는 코드가 없으나, 위 “엔드포인트 입력” 계층의 멱등성 키 개념을 이해하기 쉽도록 예시 코드를 덧붙임

// 멱등성 키(idempotency key)를 활용한 중복 주문 방지 예시
async function addItemToCart(
  cartId: string,
  item: OrderItem,
  idempotencyKey: string
) {
  const alreadyProcessed = await idempotencyStore.has(idempotencyKey);
  if (alreadyProcessed) {
    return idempotencyStore.getResult(idempotencyKey); // 동일 응답 재반환
  }

  const result = await cartService.append(cartId, item);
  await idempotencyStore.save(idempotencyKey, result);
  return result;
}

DB가 지원하지 않는 트랜잭션적 시맨틱을 보완하는 브리징 프레임워크

기법 설명
이벤트 소싱(Event sourcing) 발생한 모든 일을 “이벤트”로 추적, 장애 시 재생(replay) 및 이어서 진행 가능 (예: 체크포인트부터 새 DB 채우기). 전체 연산 체인이 하나의 시퀀스로 묶여 사용자는 전체 시퀀스에 대해 최종적 일관성 동작을 인지하게 됨
사가 패턴(Saga pattern) 이벤트 소싱 위에 계층화 가능. 트랜잭셔널 DB를 쓸 수 없는 연산들 위에서 전부-아니면-전무(all-or-nothing) 보장 제공 - 각 단계에 “실행 취소(undo)“를 부여해 실패 시 발동, 모두 일어나거나 아무것도 일어나지 않도록 보장
데이터 무결성 검사기(Data integrity checkers) 사용자 결과(user outcomes) 관점에서 신경 쓰는 것을 기준으로 DB 간 일관성 점검 (예: 금융 거래 후 지불자·수취자 기록 일치 여부 확인)

지연시간, 가용성, 데이터 일관성의 트레이드오프

  • 마틴 파울러(Martin Fowler)가 언급했듯, 아키텍처의 본질은 완벽한 시스템을 만드는 것이 아니라 트레이드오프를 다루는 데 있음

스트라이프의 샤딩된 DB 구조

  • 각 샤드는 데이터 유실 방지를 위해 여러 방식으로 복제됨
  • 그중 하나가 쓰기를 받는 리더(leader) - 리더로부터의 읽기는 즉시 일관됨(1차 읽기, primary read)
  • 다른 복제본에서의 읽기는 데이터가 전파되기 전이라 오래될 수 있음(2차 읽기, secondary read)
  • 정리: 1차 읽기 = 가용성 낮음 + 강한 일관성 / 2차 읽기 = 가용성 높음 + 약한 일관성 보장
  • 스트라이프 초기에는 주로 리더로부터 읽어 시스템 추론과 정확성 확보가 쉬웠으나, 회사가 성장하며 프로덕트 시나리오별로 엔지니어가 1차·2차 읽기를 선택할 수 있도록 공식화할 필요가 생김

사례별 시맨틱 선택 연습

판매자 온보딩(Onboarding Merchants)

  • 지원자가 폼에 세부정보를 입력해 검증받고 판매자가 되는 흐름 - 사업자 유형에 따라 분기, 최종 제출 내용 검토 가능해야 함
  • 필요 시맨틱: RyW, WyW
  • 1차 읽기가 가장 확실한 방법 - 이 유스케이스는 결제 대비 스케일이 훨씬 작으므로(평균적으로 온보딩된 판매자당 결제 건수가 자릿수로 더 많음) 더 비싼 읽기·쓰기를 감당 가능

결제 잔액(Payment balances)

  • 고객 잔액: 음수가 되면 안 됨 → 연속으로 두 건을 결제할 경우를 대비해 WyW 시맨틱 필요 → 1차 읽기로 처리
  • 판매자 잔액: 즉시 자금을 받을 필요는 없음(고객이 언제 받는지 신경 쓰지 않는 경우가 많고, 최대 잔액 강제도 불필요) → RoW 시맨틱 불필요 → 2차 읽기로 처리(다소 오래된 상태를 볼 수 있음). 판매자가 고객과 직접 대화 중일 때만 어색할 수 있으나 매우 드문 경우

판매자 블랙홀 처리(Blackholing Merchants)

  • 판매자가 사기꾼·테러리스트로 판명되면 최대한 빨리 결제 지급 능력을 차단해야 함
  • 단순한 접근: 판매자 테이블에 “블랙홀 비트”를 기록하고, 고객이 결제 시도할 때마다 체크
  • 매번 판매자 테이블을 1차 읽기하여 RsW 시맨틱을 줄 것인가? → 가용성·성능 문제 유발 가능. 2차 읽기로 하면 트래픽이 많을 때 보안 업데이트 전파가 지연될 수 있음(대개 몇 초 이내)
  • 상황에 따라 다름
    • 수동 검토 프로세스(분석가가 결정 후 버튼 클릭)라면 몇 초 추가 전파 지연은 전체 흐름에 비해 사소함
    • 자동화된 프로세스로 빠르게 움직이는 조직적 위협에 대응하는 것이라면 그 전파 지연이 큰 문제가 될 수 있음 → 위협 대응에 특화된 솔루션(다른 데이터 아키텍처, 자동 트리거 보안 조치에 우선순위 부여 등) 필요할 수 있음

실제 장애 사례: 일관성이 미친 영향

  • 일관성이 부족하면 사용자 경험에 극적 영향을 줄 수 있지만, 반대로 일관성을 과도하게 추구하면 가용성에 치명적 영향을 줄 수 있음
  • 스트라이프는 “모든 것이 일관됨” 모드에서 혼합(mixed) 모드로 진화
  • 전환의 계기가 된 장애
    • 스트라이프는 Shopify, DoorDash 같은 플랫폼에게도 결제 서비스를 제공 (이들이 자체 고객에게 스트라이프 결제를 포함한 서비스를 번들링, 수천~수백만 판매자를 온보딩)
    • Shopify·DoorDash의 판매자가 고객으로부터 결제를 받을 때마다 플랫폼에 소액 수수료를 지불해야 함
    • 이 수수료를 매 결제 요청마다 플랫폼의 리더 DB에 동기적(synchronously)으로 기록하고 있었음 → 호출자에게 즉시 수수료 상세정보를 반환하는 RyW 일관성 제공
    • 문제: 대형 플랫폼 하나를 담당하던 DB 샤드가 약 한 시간 가까이 다운되면서, 그 샤드에 연결된 모든 판매자의 결제 요청이 실패
    • 후속 조치: 플랫폼 수수료 기록을 **비동기(asynchronous)**로 전환 → 병목 제거, 광범위한 장애 재발 방지
    • 팀은 이후 전사적 일관성 태세(consistency posture)를 재평가, 여러 프로덕트 시나리오를 광범위하게 분석
  • 데이터 일관성의 신중한 재고(reconsideration)는 스트라이프가 최근 99.999% 신뢰성을 일관되게 달성하는 데 중요한 부분이었음

9.4 확장성

  • 인터넷 기반 제품에서 규모(scale)가 커지면 문제가 따름
  • 시스템의 처리량(throughput)이 제한적일 수 있고, 사용자가 처리량 한계를 초과했을 때의 결과는 (성공적인 서비스 거부 공격에서 보듯) 치명적일 수 있음
  • 이는 격리(isolation) 부족 때문 - 일부 사용자가 다른 사용자의 경험에 악영향을 줄 수 있음을 의미

처리량을 제한하는 두 카테고리

  1. 병목(Bottlenecks): 시스템이 더 많은 동시 사용자를 지원하는 것을 막는 걸림돌
  2. 탄력성(elasticity) 문제: 더 많은 처리량을 지원할 역량은 있지만 사용자 수요 변화에 충분히 반응하지 못하는 것 (예: 머신 수 확장이 너무 느림)
  • 두 카테고리의 문제 모두 테스트로 사전에 짚어낼 수 있음 - 경쟁 자원(contended resources)을 두고 다투는 사용자, 갑작스러운 수요 변화, 의도적으로 처리량 한계 초과 등의 시나리오를 시뮬레이션해서 얼마나 나쁘게 저하(degrade)되는지 평가

스케일 시뮬레이션(Scale Simulations)

  • 확장성 역학(scalability dynamics)은 너무 커서 머릿속으로 다룰 수 없음 → 부하, 트래픽 급증, 용량을 테스트하는 정교한 스크립트가 필요
  • 용어 선택: “테스트(test)“와 “injection” 대신 **“시뮬레이션(simulation)”**을 선호 - 주 목표 중 하나가 실제 세계와 최대한 가깝게 맞는 테스트 환경과 입력을 만드는 것이기 때문
    • “부하 테스트(load test)“라고만 하면, 같은 서비스를 수백만 번 호출하는 단순 스크립트로 오해하기 쉬움

스케일 시뮬레이션의 3가지 용어

용어 정의
부하 시뮬레이션(Load simulation) 특정 지점까지 확장 가능한지 판단하기 위해 현실적인 프로덕션 트래픽 양을 시뮬레이션
용량 시뮬레이션(Capacity simulation) 트래픽을 점점 늘려가며 시스템의 한계를 찾고, 한계 도달 시 얼마나 우아하게(gracefully) 저하되는지 확인
스파이크 시뮬레이션(Spike simulation) 트래픽을 빠르게 증가시켜 현실적인 급증을 시뮬레이션, 시스템 탄력성 확인

NOTE: 스케일 시뮬레이션은 프로덕션과 최대한 유사한 충실도(fidelity)를 목표로 해야 함

고충실도(high-fidelity) 스케일 시뮬레이션 예시 (인터넷 인프라 기업 기준, 현재 부하의 2배 확장 가능 여부 검증 목표)

  1. 지표·로깅·샘플링·분산 추적으로 코드를 계측해서 발견한 문제를 디버깅할 수 있도록 함
  2. 모든 요청을 타이밍과 함께 기록 → 시뮬레이션을 재생(replay)하고 이슈를 재현 가능하게 함
  3. 프로덕션 클러스터 전체의 완전한 복제본, 섀도 클러스터(shadow cluster) 구성 - 프로덕션과 최대한 유사하되 외부 세계와 상호작용하지 않는 것이 유일한 차이
  4. 프로덕션 클러스터에 요청이 들어올 때마다 이를 (ID만 다른) 두 개의 유사한 합성 요청으로 복제해 섀도 클러스터로 전송
  5. 용량 시뮬레이션을 위해 복제 비율(replication factor)을 늘릴 수 있어야 함
  6. 장시간 실행하여 메모리 누수, 증가하는 DB 인덱스 등 **“상태 축적(state buildup)”**으로 인한 서서히 발생하는 문제 포착 (극적인 성능 저하 유발 가능)
  7. 문제가 언제 발생하는지 짚어내기 위해 트래픽 양을 더 늘릴 수 있어야 함
  8. 시스템이 부하 상태일 때 일반 사용자에게 미치는 영향을 이해하기 위해 시나리오 지표 측정
  • 대형 신규 고객 추가를 시뮬레이션하려는 경우: 동일한 셋업을 만들되, 트래픽을 2배로 늘리는 대신 그 고객이 하려는 행동을 기반으로 합성 트래픽을 신중하게 추가 → 기존의 광범위한 사용량 맥락 속에서 신규 고객의 트래픽 테스트 가능

이런 테스트 아키텍처의 이점

  • 실제로 발생할 이슈를 발견 → 가로등 효과 회피
  • 엔지니어가 조사·수정 우선순위를 확신 있게 정할 수 있음
  • 문제가 발생한 규모와 향후 언제 그 규모에 도달할지 예측을 기반으로 수정의 긴급성을 이해 가능
  • 동일한 셋업으로 성능 개선 사항을 테스트 가능
  • 이 비전은 상당히 확장적이며 구축·운영 비용이 큼 → 많은 팀은 이보다 축소된 형태로 타협해야 함

스케일 시뮬레이션 팁

  • 완전한 프로덕션 충실도는 높은 목표 - 현실에서는 시간·예산 내에서 최대한 근접하게 만들려고 노력
  • 단순 부하 테스트(알려진 병목 엔드포인트 하나를 두드리는 방식)는 단순함 덕분에 좋은 ROI를 제공, 특히 아직 시뮬레이션할 사용자 트래픽이 없을 때 유용. 하지만 포괄적이라고 착각하면 안 되고, 트래픽이 비현실적이면 위양성·위음성을 발견하기 쉬움
  • 고충실도 시뮬레이션 + 단순하고 타겟팅된 부하 테스트 스크립트의 조합도 효과적 - 전자는 병목을 찾고, 후자는 재현 가능하고 테스트 가능하게 만듦
  • 테스트 하니스(harness) 구성 시 주의점
    • 하니스가 프로덕션을 시뮬레이션하는 데 제약이 없도록 할 것 - 예: 단순 루프로 요청을 발행하면 각 요청이 이전 요청의 타이밍에 따라 밀리는 문제 발생 가능 (실제 사용자는 줄서서 기다리지 않음)
    • 시뮬레이션 환경을 프로덕션과 최대한 유사하게 만들 것 - 일반적인 백그라운드 작업(job)들도 함께 돌아가게 할 수도 있음
  • 동료들이 발견된 이슈를 실제로 수정하도록 동기부여하는 방법
    • 재현성(reproducibility): 하니스를 결정론적(deterministic)으로 만들고 랜덤 시드(random seed)를 사용해 변동성을 줄일 것
    • 시뮬레이션을 신뢰할 수 있게 만드는 데 추가 시간을 쓸 것 - 위양성률 25%(현실적 트래픽)인 방법과 위양성률 90%(비현실적 트래픽)인 방법이 있다면, 사용하기 어렵더라도 사람들은 전자가 발견한 이슈를 훨씬 더 적극적으로 조사함
    • 저하(degradation)의 실제 영향을 시나리오 지표로 보여줄 것
  • 현실 세계 사용 패턴을 시뮬레이션하는 방법
    • 프로덕션 트래픽을 섀도잉(shadowing)하거나, 기록 후 재생하는 것이 좋은 관행. 하지만 우려되는 트래픽이 오늘 일어나는 것과 다르다면 고객이 실제로 할 행동의 세부사항을 예측해야 함
    • 개별 대형 고객을 다루는 경우: 그들과 긴밀히 협력해서 부하 테스트 진행
    • 개별 호출이 아닌 전체 시나리오를 생성할 것 - 예: 신용카드 폼을 로드한 뒤 곧이어 결제를 제출하는 흐름을 순서대로 수행하고 그 사이 시간을 무작위화하는 시나리오 작성
  • 시간(time)에 대한 고려
    • 현실 세계는 멈추지 않으므로 장시간 테스트를 실행할 것 → 메모리 누수 등으로 인한 가비지 컬렉션 이벤트 등 상태 축적 이슈 다수 발견 가능
    • 캘린더 효과 예측: 스파이키(spiky)한 트래픽인지 매끄러운(smooth) 트래픽인지, 하루 중·연중 특정 시점의 피크 예상 (예: 매년 블랙프라이데이·사이버먼데이를 위한 대비)
    • 성능 프로파일을 DB에 기록해서 제품이 진화함에 따라 시간에 따른 회귀(regression)를 감지할 수 있게 할 것
  • 스케일 시뮬레이션은 풀스택 사고(full-stack thinking)의 진가가 발휘되는 영역

브리징 기술(Bridging technologies)

  • 부하 시뮬레이션, 트래픽 캡처, 장애 주입(fault injection) 도구는 매우 다양하며 어떤 것을 선택할지는 여러 요인에 달림
  • 도구·접근법 선택 시 프로덕션을 모델링할 수 있는 능력을 목표로 삼는 동시에, 발견한 병목을 디버깅할 수 있는 충분한 계측(instrumentation)을 갖출 것

9.5 사용자에게 NFR 전달

  • 온라인 플랫폼 고객은 신뢰할 수 있고 확장 가능한 시스템뿐 아니라 **보증(guarantees)**도 원함
  • 조직들은 의존하는 회사들과 협상해서 특정 품질 기준을 계약에 명시하는 경우가 많음
  • 엔지니어의 역할: 제공 가능한 보증이 무엇인지 파악, 고객에게 정확한 정보 전달, 신뢰성 목표를 달성할 수 있는 엔지니어링 변경 수행
  • 고객 커뮤니케이션의 두 핵심 목표: 인센티브 정렬(align incentives), 고객과의 신뢰 구축(build trust)

서비스 수준 계약(Service Level Agreements, SLA) - 대표적으로 다음을 규율함

  • 가용성(Availability): 서비스 가동시간(uptime), 백분율로 측정

  • 온콜 응답 시간(On-call response times): 인시던트에 엔지니어가 얼마나 빨리 대응해야 하는가

  • 지연시간(Latency): 핵심 엔드포인트의 응답성

  • 예: 일정 기간 동안 API 호출의 최소 99.9%가 성공해야 한다는 조항 - 미달 시 고객에게 크레딧 형태의 환불 등 보상 필요 → 회사가 신뢰성에 집착하도록 인센티브 부여

SLA와 SLO의 차이

  • SLA는 목표가 아니라 하한선(lower bound) - 고객은 실무적으로 더 높은 가용성을 원함
  • 플랫폼 기업은 더 높은 **서비스 수준 목표(Service Level Objective, SLO)**를 내부적으로 추구함 - 고객에게 혼란·오해를 줄 수 있어 보통 비공개로 유지

신뢰 구축 방법 (정직함과 투명성)

  • 예: https://status.stripe.com 같은 사이트에서 장기간의 가용성을 정직하게 보고
  • **근본 원인 분석(Root Cause Analyses, RCA)**을 고객에게, 더 나아가 인터넷에 공개
  • 인시던트 대응 중 어떤 조치를 취했고 얼마나 빨리 대응했는지 실시간으로 업데이트
  • 재발 방지를 위해 수행했거나 진행 중인 개선(remediation) 사항 공유
  • 정보를 숨기거나 변명하는 것(예: 자사 인프라 공급자 탓으로 돌리는 것)의 유혹에 굴복하지 말 것 → 신뢰 구축에 도움이 안 됨
    • 예: AWS가 클라우드 컴퓨트 제공자인데 리전 장애를 AWS 탓으로 돌리면, 고객은 회사가 향후 아무 조치도 안 할 것이라 받아들임. 대신 재해 대비를 위해 리전·클라우드 제공자를 추가하겠다고 이야기하면, 고객은 회사가 배우고 개선하고 있다고 이해하며 장기 고객이 될 가능성이 높아짐

내부 투명성의 선행 조건

  • 고객에게 투명하기 전에 내부적으로 먼저 투명해야 함 - 개별 직원이나 매니저가 해고·승진 실패를 두려워해 무슨 일이 있었는지 말하기를 꺼리면, 사실이 절대 밖으로 드러나지 않음
  • 스트라이프가 “급진적 투명성(radical transparency)” 문화를 장려하는 이유 중 하나

블레임리스 포스트모템(Blameless postmortem)

  • 페이스북과 스트라이프 둘 다에서 인프라 개선을 대폭 가속화한 문화적 관행
  • 팀이 모여 프로덕션 인시던트의 원인을 파악할 때, 개인의 실패를 비난하기보다 **인시던트를 가능하게 한 시스템적 실패(systemic failures)**에 우선 집중
  • 개인이 실수를 했더라도 처벌이 아닌 학습이 기대됨 - 저자는 이런 포스트모템에 참석하는 것을 좋아함 (건설적, 창의적, 화기애애함)
  • 예시: 엔지니어가 DB 롤아웃 중 설정값을 잘못 입력해 장애 발생
    • 개인 차원 질문: 향후 롤아웃을 어떻게 더 잘 검증할 수 있을까? → 매니저·테크리드와 사적으로 논의할 주제
    • 시스템 차원 질문: 왜 이를 막을 안전장치(sanity check)가 없었는가? → 그룹 포스트모템에서 다룰 좋은 주제
  • 모든 인프라도 하나의 제품이므로, 블레임리스 포스트모템은 사용자가 아닌 제품을 탓하는 연습이라 할 수 있음

9.6 요약

  • 이 챕터는 시스템 설계와 사용자 중심 사고를 프로덕트 아키텍처 영역으로 종합함
  • 프로덕트 아키텍처 역량을 갖추면 더 전략적이고 확신 있는 선택을 할 수 있고, 사용자도 더 행복해짐

핵심 요약

  • 가로등 밑만 들여다보지 말 것 - 사용자와 그들의 행동에 대한 지식에 의존할 것
  • 시스템-프로덕트 갭을 잇는 도구를 쓰면 삶이 훨씬 쉬워짐 - 워크플로 엔진, 분산 추적 프레임워크, 부하 시뮬레이터 등 프로덕트 행동을 총체적으로 모델링하면서 동시에 시스템 세부사항도 추적할 수 있는 도구를 선택할 것
  • 저수준 지표를 넘어서 볼 것 - 동시 사용자 수, 시나리오 완료율, 클라이언트 측 가용성 등 사용자에게 직접적으로 중요한 행동을 추적하고 목표로 삼을 것
  • 데이터 일관성을 DB 관점뿐 아니라 “내 쓰기 이후 읽기”, “다른 사용자 쓰기 이후 쓰기” 같은 개념을 통해 사용자 시각에서 바라볼 것 - 누구의 읽기·쓰기가 누구와 동기화되어야 하는지 고려하고, 제품 기술 스택의 여러 계층에서 창의적으로 문제를 해결할 것
  • 신뢰성과 확장성에 대한 사고는 트레이드오프가 지배함 - 사용자가 무엇을 원하는지 파악해서 올바른 균형을 선택할 것
  • 스케일 시뮬레이션을 할 때는 실제 사용자가 제품을 실사용하는 방식을 재현할 것
  • 플랫폼 사업을 한다면, 고객과의 인센티브를 정렬하는 메커니즘을 갖추는 동시에 정직하고 투명하게 신뢰를 구축할 것

9.7 예제

  • 시나리오: 구글 시트와 유사한 온라인 스프레드시트에 협업 편집(collaborative editing) 기능을 추가
  • 팀 브레인스토밍 세션, 해커톤(사람들이 셀에 자기 이름을 넣어 작업을 등록하는 식) 같은 유스케이스를 지원하기 위해 최대 50명 동시 편집 지원 목표
  • 이를 위해 확장성, 데이터 일관성, 경쟁 상태(race condition), 가용성을 모두 고려해야 함
  • 저자는 각 질문의 답을 읽은 후 다음 질문으로 넘어갈 것을 권장

질문 목록

[문제 1] 데이터를 잃지 않기 위해 안전하게 가려는 사용자라면, 각 셀에 대해 어떤 카테고리의 일관성 보장을 원할까? [문제 2] 앞 답변에서 언급한 시나리오에서 이 수준의 일관성을 DB 레벨에서 제공하려면 어떻게 해야 할까? 그리고 이를 제품에서는 어떻게 다뤄야 할까? [문제 3] 구글 시트는 실제로는 강한 일관성 보장을 강제하지 않고(수요 부족 또는 트레이드오프 없이 설계할 방법을 못 찾았을 가능성), 대신 단순한 “마지막에 쓴 사람이 이긴다(last writer wins)” 정책을 씀. WoW 시맨틱을 실제로 제공하지 않으면서도 최대한 일관성 있어 보이는 경험을 제공할 수 있는 우회책은 무엇일까? 최소 2가지를 생각해볼 것 (하나는 구글이 이미 하고 있는 방식, 하나는 아닐 것) [문제 4] 편집 관련 지표 1개, 편집과 무관한 지표 1개를 포함해서, 제품 성능을 파악하면서 동시에 지연시간 회귀 시 문제를 좁혀나갈 수 있는 지연시간 지표·트레이스를 몇 가지 이름 붙여볼 것 [문제 5] 실제 세계에 가까운 충실도(fidelity)로 문서를 동시 편집하는 사용자들의 부하 테스트를 어떻게 접근할 것인가?


9.8 답안

[답안 1] 필요한 일관성 카테고리

  • 신중한 사용자는 다른 사용자 쓰기 이후 쓰기(WoW) 일관성을 원할 것 - 자신이 인지하지 못한 채 다른 사람의 항목을 실수로 덮어쓰고 싶지 않기 때문

[답안 2] DB 레벨 구현 방법: 조건부 쓰기(conditional writes)

  • 각 클라이언트가 각 셀에 대해 자신의 데이터 사본이 얼마나 최신인지 보여주는 시퀀스 번호(sequence number)를 유지
  • 사용자가 쓰기를 커밋할 때마다 영향받는 셀의 전역 시퀀스 번호가 증가
  • 모든 쓰기는 시퀀스 번호가 일치하는지에 대해 조건부(conditional)로 처리 → 쓰기 도중 전역 번호가 내 클라이언트 번호보다 앞서 있으면 쓰기가 거부됨
  • UX 처리: 이런 상황이 발생하면 팝업으로 최신 값과 사용자 본인의 편집 내용을 나란히 보여주고 충돌 해결(conflict resolution) 옵션 제공
  • 다중 셀 편집(복사/붙여넣기 등): 사용자는 아마 원자성(atomicity)을 원할 것 - 전부 적용하거나 전부 거부한 뒤 충돌 해결 화면 표시. 그렇지 않으면 셀 간 데이터 불일치가 빠르게 혼란스러워지고 관리하기 어려워짐

원문에는 코드가 없으나, 위 조건부 쓰기 개념을 이해하기 쉽도록 예시 코드를 덧붙임

// 낙관적 동시성 제어(optimistic concurrency control) 예시 - 셀 단위 조건부 쓰기
interface CellWrite {
  cellId: string;
  newValue: string;
  clientSeq: number; // 클라이언트가 알고 있는 마지막 시퀀스 번호
}

async function writeCell(write: CellWrite): Promise<"applied" | "conflict"> {
  const currentSeq = await db.getSequence(write.cellId);

  if (write.clientSeq !== currentSeq) {
    // 다른 사용자의 쓰기가 이미 반영됨 -> WoW 위반 가능성
    return "conflict";
  }

  await db.updateCell(write.cellId, write.newValue, currentSeq + 1);
  return "applied";
}

[답안 3] WoW 시맨틱 없이 일관성 있어 보이는 경험을 만드는 우회책

  • (구글 방식으로 추정) 각 사용자에게 색상을 배정, 다른 협업자가 셀을 클릭하면 그 색상 박스를 표시 - 충돌 가능성이 있다는 강력한 시각적 신호를 줘서 사용자가 스스로 조율(police)하게 함. 구현도 상대적으로 간단했을 것. 데이터 일관성 문제가 항상 DB만의 문제는 아니며, 솔루션이 사용자에게 가까울수록 더 비용 효율적이고 타겟팅된 해법이 될 수 있음을 보여줌
  • (구글이 하지 않을 것으로 추정되는 방식) 앞서 언급한 시퀀스·히스토리 추적 방식을 쓰되, 시퀀스 번호 기반으로 셀 충돌이 감지되었을 때 하드 에러를 던지는 대신 해당 셀 옆에 경고 아이콘을 표시. 사용자가 클릭하면 충돌 해결. 이를 위해서는 사용자가 효과적으로 충돌을 해결할 수 있도록 맥락을 제공하는 편집 히스토리를 유지해야 함

[답안 4] 지연시간 지표·트레이스 제안

  • 각 서버 호출의 타이밍을 측정 → 클라이언트·서버 문제 구분에 도움, 서버 내 핵심 DB 호출도 측정
  • 스프레드시트 로딩 및 렌더링을 개별적으로, 그리고 함께 추적 → 매우 흔한 시나리오이자 사용자의 첫인상이 되는 지점이기 때문
  • 편집 관련 지표: 사용자가 셀에 데이터를 입력한 시점부터 클라우드에 저장되고 다른 사용자에게 전파되는 시점까지 측정
  • 색상 박스 기반 일관성 접근은 박스가 빠르게 나타나지 않으면 무너짐 → 편집과 무관한 지표로는 스프레드시트 로딩·렌더링 지표, 편집 관련 지표로는 사용자가 셀을 클릭한 후 다른 사용자에게 색상 박스가 보이기까지 걸리는 시간을 측정

[답안 5] 부하 테스트 접근법

  • 첫 단계: 합리적이라고 여겨지는 범위의 상단에 가까운 고스케일의 현실적 시나리오를 만들고, 실제 사용자들을 모아 두드려보게 함. 또는 원하는 속성을 가진 실제 사용량 기반의 고스케일 세션을 식별
  • 어느 쪽이든 모든 행동을 (물론 익명화하여) 기록해서 “헤드리스(headless)” 브라우저 안에서 사용자 개입 없이 재생하는 부하 시뮬레이션으로 재생 가능하게 함
  • 서버만 테스트하고 싶다면 브라우저를 빼고 서버 요청만 기록해도 됨
  • 우려사항: 실제 데이터를 단순 재생(replay)하는 것만으로는 부족할 수 있음 - 예를 들어 클라이언트 측 렌더링 지연을 개선하면 사용자 자신이 더 빨라져서 DB에 더 많은 부하를 줄 수 있음 → 주요 변경 후에는 재시뮬레이션이 필요할 수 있음
  • 실제 데이터 기반으로 문제 지점을 찾은 뒤에는, 시스템의 특정 부분에 집중해서 더 타겟팅된 부하 테스트를 구축 → 최적화 시 더 촘촘한 피드백 루프 확보, 이후 원래의 시뮬레이션된 트래픽으로 최적화 결과를 테스트

9.9 정리

  • 바다수달(sea otter)처럼, 프로덕트 중심 엔지니어는 희귀한 조합의 역량을 지님
  • 시나리오, 페르소나, 시그니파이어(signifier), 어포던스(affordance) 관점의 사고를 기술적 역량과 결합하면 일종의 **“구조화된 공감(structured empathy)”**을 갖추게 됨 - 트레이드오프를 탐색해야 하는 엔지니어링 분야에 특히 잘 맞는 강점

이런 역량을 기르기 위해 가장 중요한 것은 사용자에게 집중하는 연습

  • 끊임없이 “왜?“를 물을 것 - 사람들은 왜 이렇게 행동하는가? 내가 만드는 것에서 그들은 어떤 이득을 얻을 것인가?

  • 소프트웨어를 검증(validate)할 것 - 직접 사용해보고(dogfood), 테스트하고, 시뮬레이션하고, 자신과 팀을 고객과 그들의 피드백에 노출시킬 것

  • 반복적으로(iteratively) 구축할 것 - 부분적으로는 사용자에게 더 자주 노출되기 위함

  • 사용자 여정을 시뮬레이션하는 연습을 할 것 - 체스 선수가 점점 더 많은 수를 내다보는 법을 배우듯, 사용자에 대해 더 길고 나은 이야기를 하는 능력을 향상시킬 수 있음

  • 개인과 팀의 목표를 사용자의 목표와 정렬시킬 것 - 주의를 기울이는 데 도움이 됨

  • 저자는 LinkedIn과 Substack(drewhoskins.substack.com)을 통해 소프트웨어 엔지니어링과 프로덕트 매니지먼트에 대한 글을 씀 - 오류 발견, 놓친 트레이드오프, 혼란스러운 부분 등에 대한 피드백을 환영함