$ cd /blog
오류 해결

세이브를 통째로 날리는 디버그 버튼이 스토어 스크린샷에 찍혀 있었습니다

출시 직전 스크린샷을 보다가 「전체 초기화 (디버그)」 버튼을 발견했습니다. 가드가 하나도 없어서 릴리스 빌드에도 그대로 나갈 상태였죠. 컴파일 심볼로 막는 법과, 그것만으로는 스크린샷을 막지 못하는 이유를 정리했습니다.


안녕하세요. Jay입니다!

출시 준비 중인 게임의 스토어 스크린샷을 찍고 있었습니다. 21장을 쭉 뽑아놓고 넘겨보는데 메인 화면 하단에 못 보던 게 있었습니다.

「전체 초기화 (디버그)」

개발 중에 세이브를 밀려고 만들어둔 버튼이었습니다. 누르면 저장 데이터가 통째로 날아갑니다. 그게 스토어에 올릴 스크린샷에 찍혀 있었습니다.

😰 더 큰 문제는 스크린샷이 아니었습니다

스크린샷은 다시 찍으면 그만입니다. 진짜 문제는 코드를 열어보고 알았습니다.

이 버튼에 가드가 하나도 없었습니다.

개발 빌드에서만 보이도록 막아둔 게 아니라, 그냥 항상 그려지고 있었던 겁니다. 즉 그대로 심사에 올라갔으면 사용자 화면에도 떴을 것이고, 누군가 눌렀다면 그 사람의 진행 데이터가 사라졌을 겁니다.

심사에서 걸렸을 수도 있지만, 안 걸렸을 가능성이 더 큽니다. 심사자가 보기에 그냥 버튼 하나거든요. 실제로 "앱이 정상 동작하는가"만 보면 통과할 만한 물건입니다.

이런 버튼이 살아남는 이유는 단순합니다. 개발 내내 편했기 때문입니다. 매번 필요하니까 잘 보이는 곳에 뒀고, 잘 보이는 곳에 있으니 눈에 익어서 안 보이게 됩니다.

🛡 1단계 — 컴파일 시점에 잘라낸다

정석은 빌드에서 아예 빼버리는 것입니다. 숨기는 게 아니라 코드 자체가 들어가지 않게요.

유니티라면 이렇게 합니다.

#if DEVELOPMENT_BUILD || UNITY_EDITOR
    // 전체 초기화 버튼을 여기서만 만든다
    CreateHardResetButton();
#endif

DEVELOPMENT_BUILD는 개발 빌드에서만 켜지는 심볼이라, 릴리스 빌드에서는 이 블록이 컴파일 단계에서 통째로 사라집니다. 런타임 조건문(if (isDebug))과 다른 점이 이겁니다 — 조건문은 코드가 빌드에 남아 있어서, 실수로 플래그가 켜지거나 누가 뜯어보면 다시 살아납니다.

다른 환경에도 같은 게 있습니다.

환경 개발 빌드 판별
Unity #if DEVELOPMENT_BUILD || UNITY_EDITOR
Flutter kDebugMode / assert() 블록
Android BuildConfig.DEBUG
iOS (Swift) #if DEBUG
process.env.NODE_ENV !== 'production'

디버그 기능을 만들 때 이 가드를 같이 쓰는 게 나중에 찾아 없애는 것보다 훨씬 쌉니다. 저는 반대로 했다가 출시 직전에 발견한 셈이고요.

📸 2단계 — 스크린샷은 그걸로 안 막힌다

여기서 한 번 더 걸렸습니다. 가드를 넣고 스크린샷을 다시 찍었는데 버튼이 또 찍혔습니다.

이유는 간단합니다. 스토어 스크린샷은 보통 에디터에서 찍습니다. 그런데 제가 쓴 조건에는 UNITY_EDITOR가 들어 있죠. 에디터에서는 켜지는 게 맞습니다 — 개발 중엔 그래야 하니까요.

그래서 스크린샷 촬영용 플래그를 따로 뒀습니다.

bool showDebug = IsDevBuild && !StoreShotMode;

촬영 스크립트가 StoreShotMode를 켜면 디버그 UI가 숨습니다. 컴파일 심볼과 판단 시점이 다른 문제라 따로 둘 수밖에 없었습니다.

⏱ 3단계 — 판단하는 "시점"이 틀렸다

고쳤는데도 또 찍혔습니다. 이번엔 타이밍 문제였습니다.

처음엔 UI를 만들 때 한 번 판정하도록 짰습니다. 유니티로 치면 Awake에서요. 그런데 촬영 스크립트는 플레이에 들어간 뒤에 플래그를 켭니다.

Awake()            ← 여기서 판정: StoreShotMode = false → 버튼 생성
플레이 시작
StoreShots 실행    ← 여기서 플래그 ON (이미 늦음)
촬영               ← 버튼이 그대로 찍힘

UI를 만드는 시점에는 아직 아무도 플래그를 켜지 않았습니다. 그래서 화면을 갱신할 때마다 다시 판정하도록 바꿨습니다. 한 번 정하고 끝내는 게 아니라, 그릴 때마다 현재 상태를 보게요.

이런 종류의 버그는 코드만 읽으면 잘 안 보입니다. 조건은 맞는데 그 조건을 언제 확인하느냐가 틀린 것이라, 결과물을 직접 봐야 드러납니다.

💡 Jay의 정리 — 결국 눈으로 봐야 잡힙니다

이번 건은 세 번 고쳤고, 세 번 다 스크린샷을 다시 찍어서 확인했습니다. 코드를 아무리 들여다봐도 "이제 안 나오겠지"밖에 못 합니다.

그래서 마지막에 21장을 전부 열어 훑었습니다. 개발용 UI가 없는지 눈으로요. 오래 걸리지 않았고, 그 전까지 두 번이나 "고쳤다"고 착각했던 걸 생각하면 반드시 필요한 단계였습니다.

몇 가지 습관으로 정리하면 이렇습니다.

디버그 기능은 만들 때 가드를 같이 넣습니다. 나중에 찾아서 지우는 건 거의 안 됩니다. 눈에 익어서 안 보이거든요.

"숨긴다"와 "빼낸다"를 구분합니다. 릴리스에서는 코드 자체가 없어야 합니다. 런타임 조건문은 숨기는 것일 뿐입니다.

출시 전에 산출물을 눈으로 한 번 훑습니다. 스크린샷, 스토어 문구, 앱 아이콘 같은 것들요. 빌드가 통과했다는 건 "동작한다"는 뜻이지 "내보낼 준비가 됐다"는 뜻이 아닙니다.

그리고 이번에 제가 배운 건 발견 경로였습니다. 이 버튼은 코드 리뷰로 찾은 게 아니라 스크린샷을 넘겨보다가 찾았습니다. 내보낼 물건을 한 번 쭉 보는 것만으로 잡히는 결함이 생각보다 많습니다.

결론

디버그 기능은 개발 중에 가장 편한 자리에 놓이고, 그래서 가장 잘 잊힙니다. 출시 전에 「~하지 않아도 되는데 화면에 있는 것」이 없는지 한 번만 확인해 보세요.

특히 누르면 데이터가 사라지는 버튼이라면, 그게 사용자 손에 닿기 전에 발견하는 게 훨씬 낫습니다.

다음에도 유익한 포스팅으로 찾아오겠습니다. 감사합니다!

#Unity#릴리스 빌드#앱스토어#스크린샷#1인개발

$ ls related/

더 많은 IT·AI 활용법이 궁금하다면?

매주 새로운 실전 가이드가 업데이트됩니다.

전체 글 보기