pile·
백엔드·여기어때 (GC컴퍼니)여기어때 (GC컴퍼니)·

NPE 하나를 팀의 방어 체계로 바꾸기까지

여기어때 유저혜택개발팀에서 운영 환경 NPE를 원인 추적하다 코드 구조적 결함을 발견하고, 개발자 실수가 아닌 타입과 도메인 설계 수준에서 원천 차단하는 방법으로 전환한 과정을 다룬다. 나아가 Claude Code 기반 AI 에이전트 파이프라인에 장애 교훈을 자동화 체크리스트로 녹이는 방법까지 이어진다.

핵심 포인트
  • 리팩토링 중 예산 제한 쿠폰 검증 if문을 호출부로 이동하면서 null 게이트가 사라져 일반 쿠폰(issueEndDate=null)에서 운영 NPE 발생
  • 컴파일러·테스트·코드 리뷰 모두 이 버그를 잡지 못한 이유는 Java 타입 시스템이 null 가능성을 컴파일 시점에 강제하지 않기 때문
  • 해법은 null 체크 추가가 아니라 "깜빡하는 것 자체가 불가능한 구조" — Optional, VO 응집, 경계 null 정리, @Nullable 어노테이션
  • Claude Code SSOT 브랜치 + AI 에이전트 파이프라인을 통해 장애 패턴을 implementer.md 체크리스트로 자동화해 재발 차단
상세 정리
  • 사건: 쿠폰 복원(주문 취소 시 Redis 예산 집계 되돌리기) 로직 리팩토링 중 일반 쿠폰에서 NPE 발생, 쿠폰 복원 자체가 멈춤
  • NPE 경로: CommonUseService에서 BudgetVerifyVO.of(policy, issue) 직접 호출 → getTtlExpireDate → DateUtil.getLaterDate(a=null, b) → a.isBefore() NPE
  • 원래 구조: UseRedisService 내부에 if (policy.isBudgetLimitPolicy()) 게이트가 null 유입을 차단하고 있었음
  • 리팩토링 실수: of() 메서드를 서비스 밖 호출부로 이동하면서 게이트(if문)를 함께 옮기지 않음 — 조건문이 내부 코드의 null 전제조건을 지키던 울타리였다
  • 왜 못 잡았나: Java 타입으로 null 강제 불가, 비예산×복원 엣지 케이스 테스트 미비, 다중 레이어로 코드 리뷰 시 직관적 발견 어려움
  • 설계 1 — Optional/VO 승격: Policy.budget()을 Optional<Budget> 반환, .map().ifPresent() 체이닝으로 게이트 누락 자체가 구조적으로 불가능하게
  • 설계 2 — null 판단 소유자 이관: 범용 DateUtil이 아닌 IssuePeriod 값 객체 내부에서 endDate null 여부를 laterOf() 메서드로 처리 — 도메인 지식의 도메인 객체 응집
  • 설계 3 — 경계에서 null 즉시 정리: Mapper/Adapter/팩토리에서 requireNonNull 또는 기본값으로 차단, 도메인 내부는 null 안전 구역으로 유지
  • 설계 4 — @Nullable 명시: JPA 엔티티 등 Optional 불가 지점에 어노테이션으로 정적 분석 도구(IntelliJ·SonarQube) 경고 유발
  • 피드백 루프: 장애 분석 후 /improve-issue 명령으로 implementer.md에 "신규 nullable 필드 매핑 시 Objects.isNull 가드 필수" 체크리스트 자동 커밋
  • 사이클: /issue 지라 티켓 → planner→test-writer→implementer→code-reviewer→ac-checker 5단계 에이전트 파이프라인
  • 핵심 교훈: 장애 교훈이 회고 문서에만 머물면 잊힌다 — 에이전트 체크리스트로, 매 작업마다 로드되는 룰로, 사이클 안의 리뷰 기준으로 남겨야 한다
왜 읽나운영 NPE를 구조 설계로 예방하고 장애 교훈을 AI 에이전트 체크리스트로 자동화하려는 Java 백엔드 엔지니어에게.
여기어때 (GC컴퍼니)
여기어때 (GC컴퍼니) 블로그
원문은 여기서 이어서 읽을 수 있어요
원문 읽기
읽음 (0)

이 글과 비슷한

  1. 백엔드·twilio-engTwilio Engineering·

    Programmable Messaging에서 Verify API로 마이그레이션하기

    Twilio의 Programmable Messaging API로 자체 OTP 솔루션을 운영하던 서비스가 Verify API로 전환하는 방법을 코드 예시와 함께 설명한다. Verify는 OTP 전송·검증을 위한 전용 API로, 전화번호 구매, 토큰 생성, DB 저장·만료 관리를 내부에서 처리해 개발자가 직접 구현할 코드를 크게 줄인다.

    요약 이어보기
    #authentication#twilio#sms+2
  2. 백엔드·포스타입포스타입·

    포스타입이 개인화 추천을 하는 방법 2부

    포스타입 백엔드 엔지니어가 벡터 기반 개인화 추천 시스템을 실제 운영하며 맞닥뜨린 성능 장애와 용량 문제를 해결한 과정을 담은 2부다. 수백만 개의 벡터 KNN 검색이 피크 시간대에 전체 Elasticsearch 검색 성능을 흔드는 문제부터 클러스터 OOM 사태까지, 쿼리 최적화와 인프라 분리 두 가지 경로로 근본 해결에 이른다.

    요약 이어보기
    #elasticsearch#vector-search#recommendation-system+2
  3. 백엔드·포스타입포스타입·

    포스타입이 개인화 추천을 하는 방법 1부

    포스타입이 태그 기반 추천의 한계를 극복하고 벡터 임베딩 기반 개인화 추천 시스템을 구축한 과정을 담은 1부다. 유사한 콘텐츠가 다른 용어를 쓰거나 동일한 태그가 전혀 다른 톤의 콘텐츠를 가리키는 문제를 임베딩 벡터로 해결하고, OpenSearch의 HNSW ANN 검색으로 수백만 벡터를 실시간 검색하는 시스템을 구축해 구매율 15% 향상을 달성했다.

    요약 이어보기
    #opensearch#vector-search#recommendation-system+2