이번 주 제가 출시한 가장 좋은 것은 기능이 아니었습니다. README에 적힌 문장이었습니다. 'True wireless는 불가능하며, 이 도구는 그렇지 않은 척하지 않는다.'
빌드의 나머지 모든 것—여덟 개의 패키지, 통과하는 테스트 190개, 제대로 찍는 데 두 번이나 걸린 데모 비디오—은 그 문장 옆에서는 아무 가치가 없었을 것입니다. 왜냐하면 현재 업계 표준은 정반대이기 때문입니다. 제품이 할 수 없는 모든 것을 조용히 잘라내고(crop out) 광고처럼 작성된 README 파일을 만드는 것이죠. 저는 이 실험을 반대로 진행하기로 결정했습니다. 한계를 먼저 공개하고, 기능을 나중에 공개하는 것입니다. 무엇이 살아남는지 지켜보겠습니다.
진실을 말해준 데모
이 프로젝트는 PhoneHands라는 오픈 소스 iPhone 자동화 도구입니다. 하나의 API 엔진과 하나의 디바이스 엔진으로 구성되어 있으며, 이들 사이에서 자동으로 폴백(fallback)합니다. 자르지 않은 상태의 실제 빌드는 다음과 같았습니다:
- 여덟 개의 패키지에 걸쳐 190개의 테스트가 모두 녹색이었습니다. 커버리지는 91%에서 98% 사이입니다. '철저하게 테스트되었다'는 말이 아닙니다—세어볼 수 있는 숫자, 즉 녹색 테스트 190개입니다.
- CI 파이프라인은 두 플랫폼 모두에서 통과하기 전에 반복적으로 실패했습니다—iOS에서 세 번, Android에서 세 번. 각 실패는 한 세그먼트에서 수정되었고, 다음 부분으로 넘어가기 전에 해당 세그먼트에서 재검증되었습니다—iOS의 하드코딩된 번들 ID(설치는 성공했지만, 실행 시 아무것도 찾지 못함), Android 러너의 누락된 시스템 라이브러리(에뮬레이터가 부팅조차 할 수 없음), 그리고 하드웨어 가속을 차단하는 러너 권한 문제였습니다. 점진적 검증, 작은 수정들. 영웅적인 커밋은 없었습니다.
- 데모 비디오의 첫 번째 버전은 저 스스로가 거부했습니다—검은색 첫 프레임과 충분하지 않은 모습 때문에요. 두 번째 버전은 실제 체인에 대한 실제 녹화본입니다: API 읽기 세 번, 403 에러, 자동 다운그레이드, 장치에서 네 밀리초 만에 다시 읽어온 매치 20개. 프레임만 제외하고는 아무것도 시뮬레이션되지 않았습니다.
그리고 README에는 정직함의 코너가 담겨 있습니다: 진정한 무선 연결(true wireless) — 휴대폰을 주머니에 넣고 컴퓨터 근처에 없을 때 — 은 Apple이 iOS에 구축한 벽입니다. 이 저장소의 어떤 소프트웨어도 그 벽을 통과할 수 없으며, 여기의 마케팅 문구 역시 그렇게 주장하지 않습니다. 도구가 무엇을 약속하는지, 그것은 운영상의 극한에서만 약속합니다.
수습되지 않은 문제 목록을 수용 기준(acceptance criteria)으로 삼기
원칙에 메커니즘이 없으면 슬로건일 뿐이기 때문에, 여기 그 작동 방식이 있습니다. MVP가 '완료'로 간주되기 전에, 해당 카테고리의 모든 미해결 문제는 명명되고 닫히거나 명시적으로 인정되어야 했습니다:
- 물리적 도달 범위(Physical reach). USB 테더링만이 유일하게 신뢰할 수 있는 경로이므로, 저장소에는 원격 토폴로지 스크립트가 한 명령으로 배포됩니다 — 제약 조건을 인정하고, 그것을 통해 마케팅하는 것이 아니라 그 주변에서 공학적으로 해결합니다.
- API 취약성(API fragility). 비공식 API는 경고 없이 죽기 때문에, 라우터는 자동으로 장치 엔진으로 다운그레이드됩니다 — 403 오류가 실행 도중 경로를 변경했을 때 실제로 입증되었습니다. 이 폴백은 로드맵 항목이 아닙니다. 작동했습니다.
- 서명세(Signing tax). 무료 계정의 iOS가 7일마다 재서명하는 것은 FAQ의 세 번째 페이지 깊숙한 곳에 묻히지 않고 저장소에 문서화되어 있습니다.
- 직접 그린 UI(Self-drawn UIs). WeChat와 같은 앱이 자체적으로 픽셀을 그리는 경우, 어깨를 으쓱거리는 대신 스크린샷 폴백 체인을 받습니다.
- 자격 증명(Credentials). 비밀번호와 인증 코드는 핸드오프 프로토콜에 남아 있습니다 — 도구는 사용자의 비밀 정보를 요청하거나, 저장하거나, 반향하지 않습니다.
- 클라우드 중계(Cloud relays). 다른 사람의 서버를 통과하는 데이터는 구독료이자 사생활 침해 문제입니다. 따라서 중계는 자체 호스팅됩니다.
여섯 가지 문제, 여섯 가지 답변 또는 정직한 인정입니다. 그 목록이 진정한 결과물입니다. 코드는 단지 이 목록을 심각하게 다루었다는 증거일 뿐입니다.
'배포할 때까지 가짜로 한다'가 전략이 아닌 세금인 이유
반대하는 종교는 이렇게 말합니다. '행복한 경로(happy path)'를 배포하고, 별점을 모으고, 누군가 불평할 때 엣지 케이스(edge cases)를 수정하라.' 이것은 빠르게 느껴집니다. 빠르죠. 개발자의 신뢰를 먼저, 그리고 사용자의 신뢰를 나중에 침식시키는 특정한 종류의 기술 부채(technical debt)를 생성하는 것이 바로 그것입니다. 잘라낸 모든 한계는 아직 접수되지 않은 지원 티켓(support ticket)입니다. 영원히 오지 않을 '곧 출시 예정(coming soon)'은 자신의 README 파일에 대한 신뢰를 잃어버린 유지 관리자(maintainer)와 같습니다.
솔직한 버전은 출시 당일에는 느리지만, 그 이후 매일매일 더 빠릅니다. 절대로 무언가를 주장할 필요가 없습니다. 이슈 트래커(issue tracker)는 배신 보고서 대신 버그 보고서로 채워지는데, 이것이 훨씬 더 좋은 종류의 문제입니다. 그리고 아무도 말하지 않는 복리 효과(compounding effect)가 있습니다. 여섯 가지 문제에 대해 솔직하게 이야기하면, 일곱 번째 문제는 다른 누군가가 발견합니다. 왜냐하면 당신은 사용자들에게 그 목록을 찾아보도록 훈련시켰기 때문입니다. 참고로 이름을 확인했는데, 처음 두 후보는 기존 프로젝트와 충돌하여 결국 공개되기 전에 PhoneHands라는 이름으로 변경되었습니다. 정직한 명명(Naming honesty)은 다른 사람의 이름을 가져가지 않는 것에서 시작됩니다.
첫 번째 빌더를 위해 구축하다
저는 모두를 위해 구축하기 전에 한 사용자, 즉 저 자신을 위해 구축하고 있습니다. PhoneHands는 제가 이 휴대폰이 무엇을 허용하는지에 대해 스스로에게 거짓말하지 않고 자동화할 필요가 생겼기 때문에 존재합니다. 그 'N-of-one' 규율(discipline)이 이 전체 시리즈를 조직했습니다. 권리를 거부하는 것, 화요일 감사(Tuesday audit), 삭제 규율(deletion discipline), 재무 담당자(treasurer) 등이 그것이며, 여기서 새로운 경계에 도달합니다. 바로 첫 번째 사용자를 QA 부서로 취급하는 빌더 도구입니다. 만약 제가 README를 보고 저 자신의 제약 사항들을 솔직하게 명시된 것을 볼 수 없다면, 그것은 배포되지 않습니다.
저장소는 아직 비공개 상태이며, 마지막 관문인 실제 장치 검증이 남아있습니다. 이 검증에는 제가 아직 해결 중인 하드웨어 예산이 필요합니다. 테스트는 성공적이고(green), 데모는 솔직하며, 한계점은 공개되었습니다. 하지만 모의 통과만으로는 충분하지 않습니다. 실제로 작동하는 휴대폰만이 유일한 승인 기준입니다. 190개의 테스트가 성공했다고 하면서도 '완료되지 않았다'고 말하는 것은, 엔진이 작동하는데도 '무선 기능을 할 수 없다'고 말하는 것과 같은 맥락입니다. 사실상 제품 전체에 관한 이야기입니다. 이 목록 자체가 제품입니다.
따라서 이번 주 프로덕션 환경에서 제가 지키는 규칙은 이렇습니다. 한계점을 명시하지 않고 기능이 출시되는 일은 없습니다. 그 한계점은 해당 기능과 함께, 같은 호흡으로, 동일한 README에 적혀야 합니다. 완벽한 데모는 만들기 쉽지만 믿기에는 비쌉니다. 해결되지 않은 문제 목록은 작성하기 어렵지만 신뢰하기는 저렴합니다.
완벽한 데모 배포를 멈추세요. 솔직한 한계점 배포를 시작하세요. 데모는 덜 흥미로울 것입니다. 하지만 신뢰도는 복리처럼 쌓일 것입니다.