앱이 백업하다 툭 죽는다면 — null과 예외를 막는 ‘방어 코딩’ 이야기

습관 기록 앱(습관의 힘)을 만들다가, 사용자 데이터를 구글 드라이브에 백업하고 복원하는 기능을 붙였습니다. 평소엔 잘 됩니다. 그런데 이런 기능은 평소가 아니라 ‘어쩌다 한 번’에 죽어요. 백업 파일을 고르다 취소하거나, 시스템이 예상과 다른 값을 돌려주는 그 순간에요.

앱이 죽으면 사용자는 이유를 모릅니다. 그냥 “이 앱 자꾸 꺼지네” 하고 지워버리죠. 그래서 이번엔 잘 되게 만드는 것보다 안 죽게 만드는 데 집중했습니다. 같은 고민 하는 분들을 위해 정리합니다.

앱은 대개 두 곳에서 죽는다: null과 예외

백업/복원처럼 다른 앱이나 시스템과 주고받는 기능은 죽는 지점이 거의 정해져 있습니다. 이번에 막은 것도 딱 두 가지였어요.

1) 돌려받은 값이 null일 때

안드로이드에서 “파일 선택창을 열어줘” 하고 시스템을 부르면, 사용자가 파일을 고른 결과를 intent라는 상자에 담아 돌려줍니다. 문제는 사용자가 취소를 누르거나 뭔가 어긋나면 그 상자가 비어서(null) 돌아온다는 거예요.

null(널): “값이 아예 없음”을 뜻하는 특수한 상태. 빈 상자가 아니라, 상자 자체가 없는 겁니다. 없는 상자를 열려고 하면 앱이 그 자리에서 죽습니다(NullPointerException).

intent(인텐트): 안드로이드에서 화면·앱·시스템끼리 “이거 해줘”, “이거 결과야” 하고 주고받는 메시지 상자.

코드가 그 null 상자를 당연히 값이 있겠거니 하고 열면 앱이 즉시 멈춥니다. 수학 문제로 치면, 정의역 밖의 값을 함수에 넣은 거예요. 1/xx=0을 넣으면 답이 없듯, 없는 값을 처리하려 하면 코드도 갈 곳을 잃습니다.

2) 파일 접근 중 예외가 튈 때

파일을 실제로 읽고 쓸 때 안드로이드는 SAF라는 통로를 씁니다. 그런데 이 통로는 사용자가 권한을 안 줬거나, 파일이 사라졌거나, 저장 공간이 막히면 예외를 던집니다.

SAF(Storage Access Framework): 안드로이드에서 앱이 파일을 안전하게 고르고 접근하게 해주는 시스템 통로. 보안을 위해 절차가 까다로워, 중간에 막히는 경우가 많습니다.

예외(exception): 프로그램이 “이건 내가 처리 못 하는 상황이야” 하고 던지는 신호. 아무도 안 받으면 앱이 그대로 죽습니다.

해결: 죽는 대신 ‘실패했다’고 안전하게 처리한다

핵심은 예외를 없애는 게 아니라, 예외가 나도 앱이 안 죽게 하는 것이었습니다. 예외가 날 수 있는 자리를 미리 감싸두고, 문제가 생기면 앱을 멈추는 대신 “이번 백업은 실패했어요” 하고 조용히 물러나게 만드는 거죠. 이걸 흔히 방어 코딩이라고 부릅니다.

  • null이 올 수 있는 상자는 열기 전에 비었는지 먼저 확인한다.
  • 예외가 튈 수 있는 작업은 미리 감싸서(try-catch), 터지면 앱을 죽이지 말고 붙잡는다.

말은 간단한데, 실제로는 “어디가 죽을 수 있는가”를 하나하나 짚어야 합니다. 그래서 저는 단위 테스트로 그 예외 상황들을 일부러 만들어보며 확인했어요.

단위 테스트: 코드의 작은 조각 하나가 “이런 입력엔 이렇게 동작해야 한다”를 자동으로 검사하는 것. null을 일부러 넣어보고 앱이 안 죽는지 미리 확인할 수 있습니다.

리뷰봇의 한 마디: “조용히 실패하지 마라”

작업을 마치고 코드를 올리자, 리뷰봇이 코멘트를 하나 달아줬습니다. 요지는 이랬어요. *”intentnull이면 그냥 false를 돌려주는 데서 끝내지 말고, 에러 로그도 함께 남겨라.“*

리뷰봇: 코드를 올리면 자동으로 읽고 문제나 개선점을 짚어주는 도구. 혼자 개발하면 봐줄 사람이 없는데, 이럴 때 특히 고맙습니다.

이 지적이 좋았습니다. 저는 “실패하면 false 반환”까지만 생각했는데, 그러면 왜 실패했는지 흔적이 안 남습니다. 나중에 “백업이 왜 안 됐지?” 할 때 단서가 없는 거예요. 그래서 실패할 때 에러 로그를 남기도록 고쳤습니다. 조용히 실패하는 것과, 실패를 기록으로 남기는 것은 디버깅할 때 하늘과 땅 차이입니다. 틀린 문제를 그냥 넘기는 것과, 왜 틀렸는지 오답노트에 적어두는 것의 차이와 같죠.

그래서 뭘 하면 되나

백업·복원처럼 시스템과 주고받는 기능을 만든다면, 이 지점들을 미리 방어하세요.

죽는 지점방어
돌려받은 intentnull🔴 열기 전에 null 확인 → 아니면 안전하게 실패 처리
파일 접근(SAF)에서 예외🔴 작업을 try-catch로 감싸 앱이 안 죽게
실패했을 때🟡 false만 반환 말고 에러 로그 남기기(원인 추적용)
이 모든 걸 확인🟢 단위 테스트로 예외 상황을 일부러 만들어 검증

한 문장 요약: 잘 되게 만드는 것만큼 ‘안 죽게’ 만드는 게 중요하고, 실패는 없애는 게 아니라 안전하게 처리하고 기록으로 남기는 것이다.

앱이 예상 밖 상황에서 죽는 건 대부분 null과 예외, 이 둘입니다. 이 둘만 미리 짚어 감싸도 “자꾸 꺼지는 앱”이 “가끔 실패해도 멀쩡한 앱”이 됩니다. 그리고 리뷰봇 같은 도구가 곁에 있으면, 나 혼자선 “여기까지면 됐지” 하고 넘어갈 지점을 한 번 더 짚어줘요. 완벽한 코드를 처음부터 쓰는 게 아니라, 죽을 자리를 하나씩 막아가는 것 — 그게 안정적인 앱을 만드는 실제 과정이더라고요.


댓글 남기기