조용한 폴백은 조용한 거짓말이 된다
없는 값 하나를 try/catch가 삼켰고, 그 자리에 떨어진 대체 그림이 다섯 달 뒤 제품 소개 자료에 실렸습니다. 폴백은 «아직»과 «틀렸다»를 갈라야 합니다.
HappyBirthTown은 생일을 맞은 사람을 위해 다 같이 모이는 픽셀 타운입니다. 이 서비스에는 초대장을 한 장씩 굽는 생성기가 있고, 그 안에 케이크 맛을 고르는 표가 있습니다. 표의 한 줄에 mint라고 적혀 있었습니다.
그런 맛은 제품에 없습니다. cream, choco, strawberry, cheese, matcha 다섯 가지뿐입니다. 그림을 굽는 함수가 그 값을 받고 예외를 던졌고, 그 함수를 감싸고 있던 try/catch가 예외를 삼켰습니다. 케이크 자리에는 대신 이모지가 떨어졌습니다. 폴백은 설계된 동작이었습니다 — "아트를 못 구우면 이모지로". 아무도 이상하다고 생각하지 않았고, 그 상태로 다섯 달 가까이 지났습니다.
삼킨 예외는 코드 안에 머물지 않았다
이 문제는 버그 리포트로 발견되지 않았습니다. 마케팅 자료를 검토하다가 나왔습니다.
초대장 생성기가 뱉은 그림이 소셜 채널의 한 회차에 실렸고, 하필 그 회차가 팔려던 것이 "고른 맛이 카드에 선다"였습니다. 그 문장을 뒷받침하라고 붙인 증거 사진에서, 케이크만 우리 것이 아닌 남의 이모지였습니다. 제품이 자기가 못 하는 일을 자랑하는 사진을 스스로 만들어 낸 셈입니다.
여기서 멈춰 생각할 부분은 예외가 삼켜졌다는 사실 자체가 아닙니다. 삼켜진 예외가 결과물을 만들어 냈고, 그 결과물이 레포 밖으로 나갔다는 것입니다. 아무도 거짓말을 하지 않았습니다. 시스템이 했습니다.
«아직 안 왔다»와 «그런 값은 없다»는 다른 사고다
폴백 자체는 옳은 설계였습니다. 아트가 늦게 도착하는 경우는 실제로 있고, 그때 화면이 빈 채로 남는 것보다 이모지라도 서는 편이 낫습니다.
틀린 것은 폴백이 성격이 전혀 다른 두 가지 사고를 같은 얼굴로 처리한 것이었습니다.
- 아직 안 왔다 — 기다리면 해결됩니다. 폴백이 정확히 이걸 위해 있습니다.
- 그런 값은 없다 — 기다려도 영원히 안 옵니다. 사람이 고쳐야 합니다.
두 경우가 같은 catch 블록에 들어가 같은 이모지 케이크로 나왔습니다. 그래서 두 번째 종류의 사고가 첫 번째인 척하며 다섯 달을 버틸 수 있었습니다. 폴백이 있다는 것은 알고 있었으니, 이모지 케이크를 봐도 "아트가 아직인가 보다"로 읽혔습니다.
폴백 앞에 정직한 문을 하나씩 세웠다
폴백을 걷어내지는 않았습니다. 대신 그 앞에 문을 두 개 세웠습니다.
값을 만드는 자리인 맛 표에는 빠진 항목이 있으면 던지는 단언을 넣었습니다. 이 단언 하나 때문에 일곱 개 타운의 맛을 전부 명시적으로 적어야 했고, 적는 과정에서 mint 같은 항목은 애초에 살아남을 수 없게 되었습니다.
값을 받는 자리인 장면 생성 함수에는 씻는 함수를 하나 넣었습니다. 들어온 값이 아는 맛인지 확인하고, 아니면 아틀라스가 통째로 안 구워집니다. 화면이 빕니다. 시끄럽게 실패하는 쪽을 일부러 골랐습니다 — 빈 화면은 반드시 누군가 보고하지만, 이모지 케이크는 아무도 보고하지 않기 때문입니다.
결과물이 남의 재료가 되는 자리를 특히 조심한다
이 판단은 이 제품 밖에서도 참이라고 생각합니다. 삼킨 예외는 시간이 지나면 사실이 됩니다. 로그에 안 남고, 알림도 안 울리고, 화면은 그럴듯하게 채워져 있으니 아무도 그것을 사고로 부르지 않습니다. 그러다 어느 날 그 자리에 있던 대체품이 정상 동작으로 굳습니다.
특히 위험한 자리는 그 결과물이 다른 사람의 재료가 되는 곳입니다. 에셋 생성기, 리포트, 내보내기 기능이 그렇습니다. 여기서 나온 폴백 결과물은 코드 리뷰를 받지 않고, 테스트를 통과할 필요도 없으며, 그냥 파일로 나가서 "우리 제품은 이렇게 생겼습니다"라는 증거가 됩니다.
폴백을 쓸 자리라면 못 만든 이유를 갈라 두는 편이 낫습니다. 아직인지, 틀렸는지. 틀린 쪽은 시끄럽게 실패시키는 값이 다섯 달 뒤에 잘못된 증거 사진을 회수하는 값보다 훨씬 쌉니다.