Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

34장. 문서 없는 레거시를 Agent와 함께 읽기 — Entry Point에서 Side Effect까지

8장에서 첫날을 보냈다.

CLAUDE.md 초안과 금지 목록을 만들었고,
개략적인 구조를 파악했다.

이제 본격적인 조사다.

목적이 달라졌기 때문이다.

8장의 목적: Agent가 일할 수 있게 만들기
34장의 목적: 경계를 그을 수 있을 만큼 이해하기


레거시를 읽는 일이 어려운 이유

신규 코드레거시
의도가 코드에 있다의도가 사라졌다
쓰는 코드만 있다죽은 코드가 섞여 있다
한 가지 방식세 세대의 방식이 공존

⚠️ 뒤의 두 줄이 Agent에게 특히 위험하다.

Agent는 읽은 코드를 모두 유효한 것으로 취급하는데,
레거시에서는 그 절반이 유효하지 않다.

12장의 Context Pollution이 여기서 실제 피해가 된다.
그래서 조사 단계에서 무엇이 살아 있는지를 함께 확인해야 한다.


진입점을 전수 조사한다

모든 흐름은 진입점에서 시작한다.

그런데 진입점은 Controller만이 아니다.

이 프로젝트의 진입점을 전부 찾아줘. 종류별로 나눠서.

- HTTP: @RestController, @Controller
- 메시지: @KafkaListener, @RabbitListener, @SqsListener
- 스케줄러: @Scheduled, Quartz Job
- 배치: Spring Batch Job, main 함수를 가진 클래스
- 이벤트: @EventListener, @TransactionalEventListener
- 관리자: 별도 포트나 경로로 열린 것
- 웹훅: 외부에서 호출하는 콜백 엔드포인트

각각 개수와 파일 위치를 표로 만들어줘.

🔥 이 목록에서 대개 놀란다.

Controller 40개는 알고 있었는데
스케줄러 17개와 이벤트 리스너 23개는 몰랐던 경우가 흔하다.

그리고 8장에서 만든 CLAUDE.md 의 구조 설명이
얼마나 부실했는지도 여기서 드러난다.


흐름 하나를 끝까지 따라간다

목록이 나오면 그중 하나를 정해 파고든다.

POST /orders/{id}/cancel 요청이 들어왔을 때
실제로 실행되는 코드를 순서대로 따라가줘.

- 각 단계의 파일과 메서드를 적어줘
- 조건에 따라 갈라지는 지점을 표시해줘
- 트랜잭션이 시작되고 끝나는 지점을 표시해줘
- 비동기로 넘어가는 지점을 표시해줘

마지막 두 요구가 백엔드 조사의 핵심이다.

OrderCancelController.cancel()
  └─ [TX 시작] OrderCancelFacade.cancel()
       ├─ OrderService.validateCancellable()
       │    └─ 분기: 배송 상태에 따라 예외
       ├─ PaymentCancelHandler.cancel()
       │    └─ [외부] PgClient.cancel()      ← TX 안에서 외부 호출
       ├─ PointRefundService.refund()
       └─ [이벤트 발행] OrderCancelledEvent
  [TX 종료]
       └─ [비동기] NotificationListener.handle()

이 한 장이 나오면
28장에서 다룬 문제(트랜잭션 안 외부 호출)가 눈에 보인다.

조사가 곧 문제 발견이다.


Side Effect를 추적한다

이 절이 이 장에서 가장 중요하다.

리팩터링할 때 실제로 위험한 것은
계산 로직이 아니라 부작용이다.

주문 취소 흐름에서 발생하는 모든 Side Effect를 찾아줘.

- DB 쓰기 (어느 테이블에 무엇을)
- 외부 API 호출
- 메시지·이벤트 발행
- 캐시 조작
- 파일 쓰기
- 알림 발송

각각이 트랜잭션 안인지 밖인지도 표시해줘.

결과는 이런 표가 된다.

Side Effect대상트랜잭션
DB 쓰기orders, payments, point_histories
외부 호출PG 취소 API안 ⚠️
이벤트 발행OrderCancelledEvent
알림카카오 알림톡밖 (리스너)
캐시주문 요약 캐시 삭제

⚠️ 이 표가 없으면 리팩터링에서 사고가 난다.

메서드를 옮기다가 알림 발송이 두 번 일어나거나,
캐시 무효화가 빠지는 식이다.

31장에서 본 그 문제다.


죽은 코드를 판별한다

레거시 조사에서 가장 조심할 부분이다.

이 클래스를 호출하는 곳이 없어 보입니다. 삭제 가능합니다.

🔥 Agent의 이 판정을 그대로 믿으면 안 된다.

정적 분석으로 잡히지 않는 호출 경로가 있다.

놓치는 경로
리플렉션Bean 이름 문자열로 조회
스케줄러cron 설정이 DB나 외부에 있음
프로파일별 활성화@Profile("batch")
설정 기반 분기피처 플래그
외부 시스템 호출다른 서비스가 직접 호출
문자열 기반 라우팅이벤트 타입 문자열 매핑

그래서 판정을 이렇게 나눈다.

사용되지 않아 보이는 클래스를 찾아줘.

단, 다음을 함께 확인하고 판정 근거를 적어줘:
- 리플렉션이나 문자열로 참조되는지
- @Profile, @ConditionalOn* 이 붙어 있는지
- 설정 파일에서 클래스명이 언급되는지

"확실히 미사용" 과 "확인 필요" 로 나눠서 보여줘.

그리고 실제 삭제 판단은
운영 로그나 APM 지표로 확인한 뒤에 한다.

정적으로는 “호출하는 코드가 없다” 까지만 알 수 있고,
“실행되지 않는다” 는 운영 데이터가 말해준다.


git log 를 읽힌다

코드에 없는 정보를 얻는 방법이다.
9장에서 판단을 뒤집었던 그 방법의 확장이다.

최근 1년 커밋 통계를 뽑아줘.

- 변경 빈도가 높은 파일 상위 20개
- 1년 이상 변경되지 않은 패키지
- 한 커밋에서 자주 함께 바뀌는 파일 쌍
발견의미
변경이 잦다활발한 영역. 리팩터링 효과가 크다
1년째 그대로안정 또는 죽은 코드
항상 함께 바뀐다🔥 경계가 잘못 그어져 있다

세 번째가 8부의 직접적인 입력이 된다.

orderpoint 의 파일이 늘 함께 바뀐다면
두 도메인 사이의 경계가 실제로는 작동하지 않는 것이다.


조사 결과는 문서로

19장의 원칙이다.

지금까지 조사한 것을 docs/order-cancel-flow.md 에 정리해줘.

- 진입점과 호출 흐름
- Side Effect 표
- 확인한 사실과 확인하지 못한 것을 구분해서
- 조사 과정에서 발견한 이상한 점도 별도 절로

마지막 항목이 유용하다.

조사 중에 눈에 걸린 것들은
그 자리에서 고치지 않고 모아둔다.

## 발견한 이상한 점
- PG 취소가 트랜잭션 안에서 호출됨 (28장 규칙 위반)
- AdminOrderService 는 Facade를 거치지 않고 Service 직접 호출
- 부분 취소 횟수 제한이 세 곳에서 다르게 구현됨

이 목록이 나중에 티켓이 된다.


이 장의 핵심

  • 8장의 목적은 Agent를 일하게 하는 것, 이 장의 목적은 경계를 그을 만큼 이해하는 것이다
  • 레거시에는 죽은 코드와 여러 세대의 방식이 섞여 있다
  • 진입점은 Controller만이 아니다 — 스케줄러·리스너·배치·웹훅까지 전수 조사한다
  • 흐름을 따라갈 때 트랜잭션 경계와 비동기 전환 지점을 함께 표시하게 한다
  • 리팩터링에서 위험한 것은 계산 로직이 아니라 Side Effect다
  • Agent의 “미사용” 판정을 그대로 믿지 않는다 — 리플렉션·프로파일·설정 기반 호출이 있다
  • 정적으로는 “호출하는 코드가 없다” 까지만 알 수 있다
  • 함께 자주 바뀌는 파일 쌍은 경계가 잘못 그어졌다는 신호다
  • 조사 중 발견한 이상한 점은 고치지 말고 모아서 티켓으로 만든다