시리즈: AI와 함께 코딩 배우기

데모가 조용히 망가졌을 때

엔진이 조용히 실패하기를 거부하도록 만들기.

데모가 조용히 망가졌을 때 완성된 것처럼 보인다. 아무것도 하지 않는다. 아무 말도 하지 않는다. INFO scene loaded INFO prefab fallback... INFO ...298 more lines INFO 레벨에 파묻혀 아무도 보지 못한다 승격 ERROR PLACEHOLDER MODE: gameplay prefabs missing, demo will not function 어떤 로그 필터도 뚫고 살아남는다 grep으로 즉시 찾아낸다
조용한 저하는 결함이다. 폴백은 비명을 질러야 한다.

크래시는 무언가 잘못됐다고 말해준다. 조용한 실패는 사용자에게, 그리고 파트너에게 잘 다듬어진 빈 껍데기를 출시해 버린다. 신뢰를 얻는 엔진은 속삭이기를 거부하는 엔진이다.

크래시보다 나쁜 부류의 버그가 있다. 크래시는 적어도 무언가 잘못됐다고 말해준다. 이번 토요일 아침 내가 마주 앉은 버그는 다른 종류였다. 데모는 부팅됐다. 씬이 로드됐다. UI가 렌더링됐다. 일시정지 메뉴는 반응하지 않았다. 게임플레이는 플레이스홀더였다. 에러 없음. 예외 없음. INFO보다 높은 로그 줄 없음. 엔진은 조용히 실패한 채, 작동하는 게임인 척 예의 바르게 시늉을 하고 있었다.

이 글은 여러분의 엔진이 그렇게 하기를 거부하게 만드는 방법에 관한 것이다.

사용자에게 데모가 어떻게 보였나

깨끗하게 부팅됐다. 스플래시 화면. 메뉴 화면. “Play” 클릭. 씬 전환. 깔끔해 보이는 씬. 가운데 함선 하나. 우측 상단의 점수 표시.

함선은 움직이지 않았다. 입력은 죽어 있었다. 일시정지 메뉴는, 호출되면 시각적으로는 존재했지만 클릭에 반응하지 않았다. 점수는 0을 표시하고 0으로 남았다. 쏠 적이 없었다. 세계는 예의 바르고, 텅 비어 있고, 작동하는 것처럼 보이는 껍데기였다.

이것을 읽는 개발자에게, 결론은 30초 안에 명백해진다. 이것은 플레이스홀더다. 진짜 게임플레이 프리팹이 없다. 엔진은 저하된 모드로 폴백했고 누구에게도 알리는 것을 잊었다.

사용자에게, 이것은 최악의 종류의 실패다. 애플리케이션은 망가진 것처럼 보이지 않는다. 완성된 것처럼, 그리고 나쁘게 보인다.

실제로 무엇이 잘못됐나

두 개의 별개 문제, 각각 미묘하다.

씬 로더가 조용히 저하되고 있었다. SceneContentLoader는 게임플레이 프리팹을 찾아 활성 씬에 인스턴스화하는 역할을 담당한다. 프리팹이 없을 때(빌드 문제, 누락된 애셋 팩, 혹은 설정 불일치 때문에), 그것은 플레이스홀더 설정으로 폴백한다. 폴백은 명확한 에러를 로깅하기로 되어 있었다. 그것은 INFO 레벨로 로깅되고 있었다. 에러 메시지는 필터링되지 않은 로그 스트림 안에 300줄의 다른 INFO 줄들과 함께 파묻혀 있었다. 데모를 실행하고 로그를 훑어보는 사람은 누구도 경보를 볼 만한 것을 보지 못했다.

일시정지 메뉴 캔버스에 GraphicRaycaster가 빠져 있었다. 이것은 Unity 쪽 이야기다. GraphicRaycaster 컴포넌트가 없는 캔버스는 포인터 이벤트를 받을 수 없다. 일시정지 메뉴는 올바르게 인스턴스화되고, 올바르게 렌더링되고 있었지만, 사용자 입력에 완전히 귀머거리였다. 그 컴포넌트의 부재는 여러 캔버스 설정을 하나로 통합한 리팩터를 통해 슬며시 스며들었다. 그 통합이 캔버스 중 하나에서 레이캐스터를 떨어뜨렸다.

두 버그는 같은 성격을 가졌다. 무언가가 조용히 잘못됐다. 코드 경로는 계속됐다. 사용자는 작동하는 것처럼 보이지만 그렇지 않은 무언가를 봤다.

어떻게 찾았나

감사는 수정보다 오래 걸렸다. 수정은 토요일 오후를 잡아먹었다. 감사는 오전을 잡아먹었다. 반복될 것이기에 적어 두고 싶은 패턴이다.

첫 단서는 점수가 0에 고정되어 있다는 것이었다. 지난 주말의 버그(점수 이중 카운팅)가 과도하게 수정된 것이라 짐작했다. 틀렸다. 점수가 0이었던 것은 적이 없었기 때문이었다. 적이 없었던 것은 게임플레이 프리팹이 로드되지 않았기 때문이었다. 프리팹이 로드되지 않았던 것은 씬 콘텐츠 로더가 플레이스홀더로 폴백하면서 그것을 ERROR 레벨이 아니라 INFO 레벨로 말하고 있었기 때문이었다.

그것을 알고 나자, 두 번째 버그는 명백해졌다. 일시정지 메뉴가 반응하지 않는 것은 같은 데모 안의 별개 문제였고, 같은 토요일 세션에서 드러났다. 감사 패턴은 “조용한 실패 하나를 찾았다면, 인접한 것들도 찾아보라”다.

무엇을 고쳤나

작지만 정밀한 일련의 변경이다.

플레이스홀더 경고를 ERROR로 승격. 씬 콘텐츠 로더가 실제 게임플레이 프리팹을 찾지 못해 플레이스홀더 모드로 폴백할 때, 이제 “PLACEHOLDER MODE: gameplay prefabs missing, demo will not function correctly”라는 메시지와 함께 ERROR로 로깅한다. ERROR 심각도는 어떤 합리적인 로그 필터링도 뚫고 살아남게 만든다. 로그를 grep할 때 정확한 문구 “PLACEHOLDER MODE”는 놓칠 수 없다.

EnsureCrossPlatformInputManager 단계 추가. 플레이스홀더 모드에서도, 입력 시스템은 작동해야 한다. 개발자가 (전체 애셋 팩 없이 UI 작업을 하느라) 플레이스홀더 모드에서 데모를 테스트하고 있다면, 메뉴와 상호작용할 수 있어야 한다. 부트스트랩은 이제 플레이스홀더 씬을 포함한 어떤 씬에서도 CrossPlatformInputManager 싱글턴이 존재하도록 보장한다.

필요한 캔버스에 GraphicRaycaster를 자동으로 추가. 방어적 수정은 캔버스 스폰 헬퍼가 GraphicRaycaster 컴포넌트를 확인하고 없으면 추가하도록 만드는 것이다. 이것이 같은 형태의 미래 버그를 덮어 가리지는 않지만, 이 특정 실패 모드는 가능성에서 제거한다.

AutoBootstrap의 상세 부팅 로깅. 데모가 부팅될 때, 이제 로그는 어떤 씬이 로드됐는지, 어떤 모드(실제 대 플레이스홀더)로 로드됐는지, 어떤 서브시스템이 존재하는지 짧은 요약을 출력한다. 로그를 읽는 개발자는 “데모가 내가 기대한 모드로 부팅됐는가”라는 질문에 10초 안에 답할 수 있다. 오늘 아침 이전에는, 그 답을 얻으려면 300줄의 로그를 읽고 추론해야 했다.

캔버스 헬퍼에 대한 유닛 테스트. 버그가 컴포넌트 누락이었기 때문에, 올바른 종류의 테스트는 헬퍼가 실행된 후 그 컴포넌트가 존재하는지 단언하는 것이다. 그 테스트는 이제 스위트 안에 있다. 누군가 캔버스 헬퍼를 리팩터하면서 다시 레이캐스터를 떨어뜨린다면, 테스트가 시끄럽게 실패할 것이다.

이것이 무엇으로 일반화되는가

오늘 아침에서 얻은 세 가지 패턴이다.

조용한 실패는 최악의 실패 모드다. 여러분의 코드가 저하된 모드로 폴백하는 어느 곳이든, 폴백은 비명을 질러야 한다. INFO가 아니다. ERROR다. grep이 찾아낼 문자열과 함께. 사용자가 폴백이 발동했는지 말할 수 없다면, 3일 후 로그를 읽는 개발자도 마찬가지로 말할 수 없다.

설정 기반 시스템에서 누락된 컴포넌트는 테스트가 필요하다. Unity, Unreal, 씬이 컴포넌트로부터 설정되는 어떤 엔진이든: 설정은 조용히 표류할 수 있다. 방어책은 “타입 X의 씬은 컴포넌트 A, B, C를 가진다”를 단언하는 작은 테스트 집합이다. 지루한 테스트다. 중대한 테스트다. 작성할 가치가 있다.

인접성으로 감사하라. 조용한 실패를 찾았을 때, 인접한 모든 시스템을 살펴보라. 버그는 무리를 짓는다. 레이캐스터를 떨어뜨린 같은 리팩터가 다른 캔버스의 다른 컴포넌트도 떨어뜨렸을 수 있다. 플레이스홀더 경고를 파묻은 같은 로깅 규율 이완이 다른 경고들도 파묻었을 수 있다. 원래 발견한 곳뿐 아니라 그 주변을 조사하라.

파트너와 빌더가 여기서 얻어야 할 것

엔진이 여러 저하 모드(애셋 누락, 네트워크 단절, 벤더 SDK 사용 불가)를 가지는 무언가를 만들고 있다면, 엔진은 매번 그것이 어느 모드에 있는지 시끄럽게 말해줘야 한다. 조용한 저하는 결함이다.

파트너십을 위해 엔진을 평가하고 있다면, 팀에게 조용한 실패를 어떻게 처리하는지 물어보라. 올바른 답은 “grep 가능한 문자열과 함께 ERROR로 그것들을 드러내고, 그것들을 찾기 위한 감사가 있다”다. 잘못된 답은 “아직 그 문제를 본 적이 없다”다.

에이전트 기반 워크플로를 운영하고 있고 여러분의 에이전트가 리팩터를 하고 있다면, 그 리팩터는 가끔 컴포넌트를 실수로 떨어뜨리거나 로그 심각도를 실수로 낮출 것이다. 수정은 중대한 컴포넌트가 존재하고 중대한 로그 메시지가 올바른 심각도로 살아남는지 검증하는 작은 CI 게이트다. 화려하지 않다. 효과적이다.

토요일 오후. 무언가 잘못되면 데모가 말을 한다. 일시정지 메뉴는 다시 클릭에 반응한다. 예의 바른 빈 데모의 파티는 끝났다.

다시 만드는 일로 돌아간다.

어느 모드에 있는지 말해주는 엔진

RakuAI는 모든 저하 모드를 ERROR로, grep 가능한 문자열과 함께, 조용한 실패를 먼저 찾아내는 감사와 함께 시끄럽게 드러낸다. 그것이 공간 런타임이 그 위에 만드는 팀들에게 빚지고 있는 신뢰성 규율이다.

← 전체 글