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

37장. 관례가 없는 코드베이스에서 — 자체 프레임워크와 계층 미분리

지금까지의 예시에는 조용한 전제가 하나 있었다.

Spring이라는 것.

@RestController 를 보면 Agent는 무엇인지 알고,
JPA 엔티티를 읽으면 스키마를 안다.
이 사전 지식 위에 이 책의 상당 부분이 서 있다.

그런데 이런 코드베이스가 있다.

자체 프레임워크 (10년 전 사내 개발)
Controller 에 비즈니스 로직이 그대로
데이터 접근은 자체 쿼리 빌더
계층이라 부를 만한 것이 없음

드문 상황이 아니다. 그리고 앞의 전제가 깨진다.


무엇이 달라지는가

34~36장은 그대로 통한다.
진입점 조사, 지도, 특성화 테스트는 프레임워크와 무관하다.

문제는 이 셋이다.

책의 전제이 상황
26장최근에 잘 만든 API를 지목한다지목할 좋은 예시가 없다
27장JPA 엔티티가 스키마 문서다스키마가 코드에 안 드러난다
40장계층을 정리한다계층이 없다. 신설이다

그리고 책이 아예 다루지 않은 문제가 하나 더 있다.


가장 큰 문제 — Agent가 아는 척한다

⚠️ 이것이 이 장의 핵심이다.

Agent는 모르면 멈추지 않는다.
비슷하게 생긴 것의 관례를 가져다 쓴다.

// 우리 쿼리 빌더
Query.from("orders").where("status", "=", "PAID").list()

// Agent가 QueryDSL 관례로 쓴 것
Query.from("orders").where(order.status.eq("PAID")).fetch()

이건 컴파일 에러라 차라리 낫다.

🔥 무서운 것은 이름은 같은데 동작이 다른 경우다.
.limit(10) 이 우리는 페이지당 개수인데
다른 라이브러리 관례로는 전체 상한이라면, 아무도 모른다.

DI도 같다. @Autowired 를 붙였는데
우리 컨테이너의 주입 시점이 다르면 운영에서 NPE가 난다.


방어 1 — “이건 무엇이 아니다” 를 먼저 쓴다

14장에서 CLAUDE.md 에 무엇을 적을지 다뤘다.
이 상황에서는 아닌 것을 먼저 적는다.

# order-service

⚠️ 이 프로젝트는 Spring 을 쓰지 않는다.
자체 프레임워크(`core/`)와 자체 쿼리 빌더(`core/query/`)를 쓴다.

## 절대 가정하지 말 것

- Spring 애노테이션은 존재하지 않는다. 쓰면 컴파일되지 않거나 무시된다
- JPA·Hibernate 는 없다
- 쿼리 빌더는 QueryDSL·jOOQ 와 이름만 비슷하다.
  용법은 `docs/query-builder.md` 를 반드시 확인한다
- 모르는 API 는 추측하지 말고 `core/` 에서 실제 정의를 찾아 확인한다

마지막 줄이 핵심이다.

15장에서 “덕목은 규칙이 아니다” 라고 했는데
이건 덕목이 아니라 판정 가능한 행동 지시다.

core/ 를 열어봤는지는 도구 호출 기록에 남는다.


방어 2 — 프레임워크 레퍼런스를 만든다

문제의 뿌리는 참고할 문서가 없다는 것이다.
Spring은 학습 데이터에 있지만 우리 프레임워크는 어디에도 없다.

만들어야 하고, 이것도 Agent가 돕는다.

core/query/ 의 쿼리 빌더 API 문서를 만들어줘.

- public 메서드 전부와 시그니처
- 각 메서드가 실제로 생성하는 SQL (구현을 읽고 확인해줘)
- 이름이 비슷한 공개 라이브러리와 동작이 다른 부분

추측하지 말고 구현 코드에서 확인한 것만 적어줘.
확인 안 되는 건 "확인 필요" 로 남겨줘.

⚠️ 마지막 두 줄이 없으면 QueryDSL 문서를 재현해온다.

🔥 이 문서 하나가 투자 대비 효과가 가장 크다.
없으면 매 세션 같은 실수가 반복된다.


방어 3 — 자주 틀리는 대비표

15장의 “자주 틀리는 것만 남긴다” 를
이 상황에 맞게 쓰면 대비표가 된다.

Agent가 쓰려는 것우리 프로젝트에서는
@TransactionalTx.run { } 블록
@Autowired생성자 파라미터
repository.findById(id)Query.from("t")...first()
.fetch().list()

사고가 날 때마다 한 줄씩 늘어난다.
16장의 개선 루프가 여기서는 훨씬 빨리 돈다.


참고할 좋은 예시가 없을 때

26장의 핵심 전략이 무력화된다.

가장 최근에 잘 만든 것을 지목하라

그런 파일이 없으면 하나를 만든다.

새 요구사항 중 가장 작은 것을 골라
사람이 목표 구조대로 직접 구현하고, 기준으로 지정한다.

## 기준 구현

신규 코드는 아래를 본보기로 삼는다.

- `order/CreateOrderHandler.kt` — 핸들러 구조
- `order/OrderService.kt` — 로직 분리
- `order/OrderQuery.kt` — 데이터 접근

⚠️ 위 세 파일 외의 기존 코드는 본보기가 아니다.

⚠️ 이때 Agent를 쓰지 않는 것이 중요하다.

기준이 될 파일은 관례가 없는 상태에서 만들어진다.
Agent가 만들면 다른 프레임워크의 관례가 섞이고,
그것이 이후 모든 코드에 복제된다.


계층이 아예 없을 때

40장은 계층을 재배치하는 이야기였다.
여기서는 신설이라 규모가 다르다.

⚠️ 전면 재배치는 실패한다.
파일 400개를 옮기는 동안 기능 개발이 멈춘다.

handler/ service/ query/   ← 새 구조 (신규 코드만)
legacy/                    ← 기존 전부 (건드리지 않음)

- 기존 파일을 수정할 일이 생기면
  그 파일의 비즈니스 로직만 service/ 로 분리한다
- 분리 전에 그 경로의 특성화 테스트를 먼저 만든다 (36장)
- 한 번에 한 파일만. 옮기는 김에 주변을 정리하지 않는다

그리고 신구조 비율을 지표로 본다. 4% → 17% → 31%.

🔥 이 숫자가 있으면 “언제 끝나냐” 에
“이 속도면 2년” 이라고 답할 수 있다.


스키마를 어떻게 파악시키는가

27장은 JPA 엔티티를 스키마 문서로 썼다.
여기서는 테이블·컬럼이 문자열로 흩어져 있다.

Query.from("orders").select("id", "user_id", "total_amount")

DDL 덤프와 코드 문자열을 대조하는 편이 낫다.

코드에서 참조하는 테이블·컬럼 문자열을 전부 추출해줘.
그다음 실제 DDL과 대조해서 세 가지로 나눠줘.

- 코드에도 있고 DB에도 있다
- 코드에는 있는데 DB에 없다 (⚠️ 오타 또는 죽은 코드)
- DB에는 있는데 코드에서 안 쓴다 (죽은 컬럼 후보)

⚠️ 두 번째 목록이 나오면 대개 놀란다.

문자열이라 컴파일러가 안 잡아준다.
조건 분기 안에 있으면 몇 년간 아무도 모른다.

35장의 데이터 소유권 표는 이 결과 위에서 만든다.


아키텍처 테스트를 어떻게 거는가

42장의 의존성 테스트는 패키지 참조를 검사하므로
프레임워크와 무관하게 쓸 수 있다.

⚠️ 그런데 이 상황의 핵심 문제는 안 잡힌다.

“Controller에 비즈니스 로직이 있다”
의존성 규칙으로 표현되지 않는다.

증상으로 대신 잡는다.

@Test fun `handler 메서드는 40줄을 넘지 않는다`()
@Test fun `handler 에서 Query 를 직접 만들지 않는다`()   // 문자열 검사
@Test fun `handler 의 조건 분기는 5개를 넘지 않는다`()

셋 다 거칠다.
로직이 있는지가 아니라 로직이 있을 때 나타나는 증상을 본다.

🔥 그래도 효과가 있다.
핸들러에 로직을 쓰기 시작하면 길이나 분기 수에서 걸린다.

42장의 baseline 방식으로
기존 위반을 예외 목록에 넣고 시작한다.


우선순위가 바뀐다

39장의 축을 그대로 쓰되 가중치가 달라진다.

작업일반적인 경우이 상황
특성화 테스트중요🔥 최우선
프레임워크 레퍼런스불필요🔥 최우선
기준 구현 하나불필요높음
경계 찾기최우선나중에

⚠️ 경계 작업을 미루는 것이 맞다.

38장에서 “경계는 코드가 알려준다” 고 했는데,
그 코드가 아무 말도 하지 않는 상태다.

안전망(특성화 테스트) → 언어(레퍼런스) → 본보기(기준 구현)
→ 방벽(길이·분기 규칙) → 축적(새 코드부터) → 그다음에 경계

그래도 나아지는 것

얻는 것이유
문서가 처음 생긴다레퍼런스는 사람에게도 없던 것
죽은 코드·컬럼이 드러난다문자열 대조의 부수 효과
기준이 생긴다10년 만에 “이렇게 쓰자” 가 정해진다
신규 입사자 학습이 빨라진다그 문서가 온보딩 자료

🔥 마지막 줄이 크다.

자체 프레임워크 프로젝트의 가장 큰 비용은
사람이 적응하는 시간이고, 그 레퍼런스가 그것을 직접 줄인다.

4장에서 말한 그대로다.

사람에게 관행으로 남겨둔 암묵지를
Agent에게는 파일로 꺼내야 한다.

관행조차 없던 프로젝트에서는
그 작업이 곧 관행을 만드는 일이 된다.


이 장의 핵심

  • 이 책의 예시는 Spring이라는 전제 위에 있고, 자체 프레임워크에서는 깨진다
  • 34~36장의 조사·지도·특성화 테스트는 프레임워크와 무관하게 통한다
  • Agent는 모르면 멈추지 않고 비슷하게 생긴 것의 관례를 가져다 쓴다
  • 이름은 같은데 동작이 다른 API가 가장 위험하다
  • CLAUDE.md 첫 줄에 “이것은 무엇이 아니다” 를 적는다
  • 프레임워크 레퍼런스 문서가 투자 대비 효과가 가장 크다
  • 참고할 좋은 예시가 없으면 사람이 하나를 직접 만들어 기준으로 삼는다
  • 기준 구현을 Agent에게 맡기면 다른 관례가 섞여 이후 전부에 복제된다
  • 계층이 없으면 재배치가 아니라 신설이다 — 새 코드부터, 비율을 지표로
  • 테이블·컬럼 문자열과 DDL을 대조하면 죽은 코드와 오타가 드러난다
  • 의존성 규칙으로는 “핸들러에 로직 있음” 을 못 잡는다 — 길이·분기 수로 대신한다
  • 경계 작업은 뒤로 미룬다. 코드가 아무 말도 하지 않는 상태이기 때문이다