입사한지 8개월이 지났습니다.

 

현재 속한 팀에 배정받은건 11월이니 3개월정도 지났네요. 아직 우리팀의 업무 흐름을 완벽하게 파악 못한 상태입니다..

 

이유를 들어보면

 

1. 팀에서 관리하는 컴포넌트가 많습니다.

각각이 별개의 컴포넌트가 아니고, 전체가 하나의 큰 시스템을 이루고 있습니다..(그렇다고 MSA는 또 아님...근데 몇 십개..)

팀 내에 꽤 오래 다니신 분도 아직도 파악 못한 컴포넌트가 있을 정도라고 하십니다.

 

2. 자체 인프라, 자체 플랫폼

네이버와 동일한 인프라시스템, 플랫폼을 공유합니다. 오픈소스도 직접적인 경로로 하는게 아닌 좀 말아서 제공하기때문에, 좀 더 찾아봐야 합니다.. 배포도 AWS를 사용하는게 아니라 IDC에서 배포하기 때문에 초기 러닝커브가 좀 있습니다..

 

물론, 위의 것들은 누군가가 일일히 가르쳐줘야 한다고 절대! 생각하지 않습니다. 제가 당연히 알아가야 하는것이라고 생각하고, 계속 노력중입니다.


팀에서 그래도 생태계를 파악할 수 있도록 다양한 이슈를 할당해 주시기 때문에 현재까지 처리해온 이슈들과 관련된 내용은 다 정리가 되어있습니다.

 

그치만 언제까지 알아가기만 할 수는 없겠죠...아무리 신입버프가 있다고 해도 팀에서는 쓸모있고 잘하는 사람이 필요할 것입니다.

이번주에 설계 문서와 각종 다이어그램에 대해 생각해보며 문서화의 중요성을 한 번 더 깨우쳤습니다.

 

현재도 이슈를 할당받고 이슈분석을 하는 과정에서, 이걸 수정하려면 어디를 봐야 하는지, 어떤 컴포넌트와 연관되어있고 어떤 영향이 가는지, 배포시 어떤 컴포넌트랑 어떻게 배포해야하는지,...등등을 파악하는게 가장 큰 병목입니다...이런 고민들에 비해 구현은 진짜 별거없는 경우도 많구요...(2줄 수정하면 되는거 파악하려고 하루 쓴 사람...)

 

그래서 제가 앞으로 하려는 것은.. 시퀀스 다이어그램, 컴포넌트 다이어그램, 스테이트 머신 다이어그램, 디플로이먼트 다이어그램, C4다이어그램을 그려보려고 합니다. 문서화를 잘 해놓고, 모두 살아있는 문서로 관리하여 다음에 들어오실 개발자분의 온보딩 과정에 도움이 되었으면 합니다.

 

살아있는 문서로 관리한다는 것.

"살아있는 문서"라고 했는데, 완벽한 문서를 만드는 게 아니라, 업데이트 가능한 문서를 만드는 것입니다. 이슈를 처리하면서 기존 다이어그램과 달라진 부분이 있으면 함께 수정하는 것. 그래서 별도의 문서 도구보다는 코드와 함께 버전 관리가 가능한 형태(Mermaid 등...)를 우선 고려하고 있습니다. 그림 파일로 관리하면 수정이 귀찮아서 결국 안 하게 되니까요.

 

80%짜리 문서가 0%짜리 문서보다 낫습니다. 그리고 80%짜리 문서를 꾸준히 업데이트하면 언젠가 95%가 됩니다. 100%는 애초에 불가능하다고 생각합니다.

 


 

그래서 뭘, 어떻게 할건데?

 

"다이어그램 그리는건,, 기본은 글쓰기가 중요하고, 그 글을 이해하기 좋은 도표를 고르는 형태가 되어야함." 

 

위 말을 듣고  다이어그램을 그리기 전에, 먼저 글로 정리해보는 과정을 거치려 합니다. "A 컴포넌트가 B에게 요청을 보내고, B는 C를 조회한 뒤 결과를 돌려준다" 이 한 문장을 쓸 수 있어야 시퀀스 다이어그램도 그릴 수 있다고 생각합니다. 글로 설명이 안 되는 건 다이어그램으로 그려도 안 됩니다. 그리고 글로 먼저 정리하면 "이걸 어떤 다이어그램으로 표현하는 게 적합한가?"도 자연스럽게 보일것이라고 생각합니다.

 

시퀀스 다이어그램

"이 요청이 어디를 거쳐서 어디까지 가는거지??" 를 잘 표현하면 좋을것 같습니다. 구체적인 표현은 최대한 줄이고, 흐름 파악만 되면 시퀀스 다이어그램의 역할은 끝이라고 생각합니다.

주의할 점으로, 모든 분기와 예외를 다 그리지 않으려 합니다. 메인 플로우 하나, 대표적인 예외 플로우 하나. 이 정도면 충분하다고 생각합니다. 전부 다 담으려고 하면 다이어그램이 코드만큼 복잡해지고, 그러면 그냥 코드를 보는 게 낫습니다.

그래서 시퀀스 다이어그램만 보고도 이슈 분석시 어떤부분을 들여다 보면 되겠구나를 알아채기 쉽게 하는 것을 목표로 할 것입니다.

 

컴포넌트 다이어그램

"이걸 고치면 어디에 영향이 가지???" 가 잘 나타나면 좋을것 같습니다.

여기서 중요한 건 "방향"이라고 생각합니다. A → B 의존이 있다는 것만으로는 부족하고, "A가 B의 무엇을 왜 의존하는가"가 한 줄이라도 적혀있으면 나중에 영향도 분석할 때 훨씬 빨라지는 것을 목표로 합니다.

 

스테이트 머신 다이어그램

"이 상태에서 저 상태로 언제 넘어가는거지??"

복잡한 비즈니스더라도 상태 전이는 "정상 흐름"만 그리면 쉬운데, 진짜 삽질하게 되는 건 "이 상태에서 이 상태로는 갈 수 없다"는 제약조건입니다. 불가능한 전이도 문서에 명시하려 합니다.

 

디플로이먼트 다이어그램

"배포할때 무엇을 신경써야하지??"

CI/CD를 구축하지않고, CI까지만 구축한 후에 배포는 수동으로 하고있습니다. 그리고 컴포넌트별 배포 순서도 중요한 환경입니다.

수동 배포 환경이다보니, 배포 순서를 틀리면 장애로 이어질 수 있습니다. 단순히 구조만 그리는 것이 아니라 "A를 먼저 배포하고, B를 배포해야 한다. 순서가 바뀌면 X 문제가 발생한다" 수준의 주의사항도 함께 적으려 합니다.

 

C4 다이어그램

"전체 시스템이 어떻게 생겼지??"

C4는 레벨 구분이 핵심이라고 생각합니다. Level 1(System Context)에 클래스 이름이 들어가거나, Level 3(Component)에 인프라 정보가 섞이면 오히려 혼란스러워집니다. 각 레벨에서 "이 레벨의 독자는 누구인가?"를 먼저 정하고 그리려 합니다.

 


마무리

 

이 작업의 가장 큰 수혜자는 아마 미래의 저일 것입니다. 6개월 뒤의 제가 "이게 뭐였지?" 할 때, 지금의 제가 남겨둔 문서가 답을 줄 수 있으면 좋겠습니다. 그리고 그 다음 수혜자는, 다음에 이 팀에 합류하실 분이겠죠. 3개월 전의 제가 이런 게 있었으면 좋았을 것들을 만드는 겁니다.

시간이 얼마나 걸릴지는 솔직히 모르겠습니다. 하지만 이슈 하나 처리할 때마다 관련된 부분을 조금씩 문서화하면, 이슈를 처리하는 것 자체가 문서화 작업이 됩니다. 별도의 시간을 크게 내지 않아도 쌓이는 구조를 만들어보려 합니다. AI도 있기도 하고요...

 

 

 

참고할 링크

더보기

1. 시퀀스 다이어그램

2. 컴포넌트 다이어그램

3. 스테이트 머신 다이어그램

4. 디플로이먼트 다이어그램

5. C4 다이어그램

  • 공식 사이트 (Simon Brown): https://c4model.com/ — C4의 원저자가 직접 관리하는 공식 페이지. Level 1~4 정의, 예제, FAQ 전부 여기에 있음
  • C4 FAQ: https://c4model.com/faq — "C4는 프로세스가 아니라 표현 방법이다", arc42와의 호환 등 자주 하는 오해 정리

 

'TW' 카테고리의 다른 글

변경하기 좋고 확장성 좋은 코드는 누구를 위한것인가?  (3) 2026.02.06

들어가기 전에..

최근에 Claude Max플랜을 구독을 시작했습니다.

기존에 Pro플랜을 구독할때는 필요할때만 사용하고 어차피 대화 2~3번 하면 세션만료되는 claude code도 사용을 안하다시피 했습니다.

opus 4.5는 구경도 못했죠.

근데, 100달러짜리 Max를 구독하고 나니까 돈이 아까워서 억지로라도 AI 잘 쓰는 방법, 여러 도구들을 찾아보고 쓰게되고 개발을 더 하게되는듯 합니다.

아무튼? Max 구독과 동시에 토큰 사용량 제한이 늘어났더라도 제한된 사용량에서 가성비 있게 쓰는 방법에 대해 고민하던 중 한 가지 실험이 하고싶어졌습니다.

" 사람이 변경하기 좋은 형태, 사람이 확장하기 좋은 형태의 코드도 AI한테도 동일할까??"

라는 의문에서부터 시작하게 되었습니다.


먼저, 변경하기 좋은 형태, 확장성이 좋은 형태의 코드에 대해 말을 해봐야 할 것 같습니다.

저는 Java와 Spring으로 개발을 하고있고, 공부를 오래 해왔습니다.

 

Java와 Spring의 핵심은 무엇일까요?

Java는 객체지향 프로그래밍 언어입니다.

Spring은 제가 생각하기에 OOP를 더 잘 하게해주는 프레임워크라고 생각합니다.

개인적으로 Spring 프레임워크의 가장 강력한 기능은 "테스트"라고 생각합니다. DI컨테이너를 통한 런타임 의존성 주입으로 테스트를 짜기 굉장히 좋은 환경이라고 생각합니다.

 

취업준비를 하던 시절에 저는 "기술" 혹은 "패러다임" 그 자체에 몰입해 있었습니다. MQ(카프카, RabbitMQ) , 레디스, MSA, DDD, 헥사고날... 등등

해당 시기에 가장 관심이 있었던 것은 "헥사고날 아키텍처" 였습니다. 그냥 이름도 멋있었고, 그림도 멋있었거든요.

 

헥사고날 아키텍처에 대해서 대충 보고, 무작정 헥사고날 아키텍처를 적용하려고 하니 잘 안되었습니다.

그 안의 본질을 이해하지 못한 채로 활용을 하려다 보니 잘 쓸리가 없었죠.

 

또, 제가 한창 공부를 하던 시기에 여러 패러다임들이 화두가 되었었습니다.

도메인 모델 패턴, DDD, 헥사고날 아키텍처, 클린 아키텍처, 양파 아키텍처, 모듈러 모놀리스, MSA, TDD...

본질을 이해하지 못한상태에서 위 키워드만 봐도 머리가 아파왔습니다. "나는 개발 못하겠다" 같은 생각도 했습니다.

 

하지만 이제는 깨달았습니다.

위 키워드 전부 한 가지 이야기를 하고있는 것이고, 이름만 바꾼채로 새로운"척"하는 패러다임이라는 것을요.

그리고, 깨달음을 통해 앞으로 기술들을 어떻게 바라봐야 하는지도요.

도메인 모델 패턴, DDD, 헥사고날 아키텍처, 클린 아키텍처, 양파 아키텍처, 모듈러 모놀리스, MSA, TDD 는 모두 OOP에 대해서 이야기 하고 있습니다.

그럼 OOP에 대해 이야기 해볼까요?

OOP는 왜 쓸까요? OOP의 원칙이라고 하는 SOLID는 왜 지켜야 하는것일까요?

혹시 결합도는 낮추고 응집도는 높이면서, 이해하기 좋은 코드를 짤 수 있기 때문이라고 기계적으로 답변이 나오시나요?

저도 그렇고 Java로 취업준비하는 아무나 붙잡고 SOLID에 대해 각각이 무엇인지 물어보면 대부분은 대답을 잘 합니다.

SRP는 단일책임원칙으로써, 클래스가 한 가지 책임만 가져야 하는것입니다.

OCP는 개방폐쇄 원칙으로써, 수정에는 닫혀있고 확장에는 열려있어야 하는것입니다.

LSP는 리스코프치환 원칙으로써, S 타입의 객체 o1과 T 타입 객체 o2가 있고, T 타입을 이용해서 정의한 프로그램 P 에서 o2를 o1으로 치환해도 P의 행위가 변하지 않는다면, S는 T의 하위타입임을 말하는 것입니다.

ISP는 인터페이스분리 원칙으로써, 인터페이스를 분리해야 한다는 원칙입니다.

DIP는 의존성 역전 원칙으로써, 의존성을 역전시킬 수 있다는 것입니다.

위처럼, 저도 기계적으로 답변은 잘 했습니다.

그런데 "그래서 그걸 왜 해야하는데? 그걸 적용하면 어떤점 때문에 좋은데?" 에 대한 대답은 잘 못했습니다.

 

OOP, SOLID 의 핵심은 결국 "변경 범위를 줄이자" 입니다.

위에서 말한 답변들을 보면 결국 다 변경 범위를 최소화하기 위함임을 알 수 있어요.

DIP만 정의에서 왜 변경이 줄어들지? 에 대한 의문이 있을 수 있는데,

DIP만 잠깐 짚고 넘어가 볼게요.

혹시 위와 같은 형태로, 인터페이스 없이 각 구체클래스가 다른 구체클래스를 직접 참조하도록 개발해보신 경험이 있으신가요?

화살표는 의존성의 방향을 나타냅니다. 그리고, 의존성의 방향의 정반대가 변경 가능성의 방향입니다.

이 말은 무엇이냐, repository계층이 변경되면, service 계층에 변경 가능성이 생깁니다. 그리고 service계층이 변경되면 controller계층에도 변경 가능성이 생깁니다. 그러면, repository 하나 변경됐는데 최악의 경우에는 service, controller까지 변경해야 할수도 있다는 말이 됩니다.

지금은 클래스 3개라서 별거 아닌것 처럼 보여도,

A -> B -> C -> D -> E -> ....

와 같은 의존성을 갖게 되면 하나 바꾸려면 전체를 바꿔야 하는 경우가 생길것이고, 사람이 하는 일이다 보니 누락되거나 실수하는 경우가 생길 수 있을것입니다.

그럼 이제 위 구조에서 interface를 두어볼까요?

Controller는 추상화된 Service에 의존하고, 그걸 구현하는 impl이 있고, ServiceImpl은 추상화된 Repository를 의존하고, 그걸 구현한 Repository Impl이 있게 됩니다.

그리고 앞서, 의존성의 방향의 정반대가 변경 가능성의 방향이다. 라고 했죠?

위 구조에서 화살표 방향을 보면, Controller의 변경의 이유는 Service 인터페이스에 의존하고있고, 인터페이스의 구현체가 어떻게 바뀌어도 Controller에는 영향이 없습니다.

ServiceImpl도 마찬가지고요.

 

첫 번째 구조와 다르게, ServiceImpl계층이 위쪽 추상화계층을 의존하고있습니다. 의존성이 역전되었습니다.

의존성의 방향이 바뀌었기 때문에 추상적인 것들이 바뀌지 않는 한, 구체적인 것들에 변경전파는 되지 않습니다.

이것이 "구체화에 의존하지 말고 추상화에 의존하라", "상위계층, 하위계층 모두 추상화에 의존해야한다" 를 말하는 DIP입니다.

 

결국? 변경범위를 줄이자!

 

도메인 모델 패턴, DDD, 헥사고날 아키텍처, 클린 아키텍처, 양파 아키텍처, 모듈러 모놀리스, MSA, TDD 는요?

그냥 OOP를 잘 하다보면 위 패턴이나 패러다임을 따라갈 수 밖에 없습니다.

 

위 그림인 DIP를 적용한 상태에서 domain이라는 도메인로직이 들어간 클래스를 추가해봅시다

이렇게 되는데, 형태를 조금만 바꿔볼게요.

그냥 배치만 다르게 한겁니다. 의존방향은 여전히 똑같습니다.

 

이제, 상하 반전을 시켜볼게요.

반전된거 말고 여전히 DIP를 잘 지키는 구조입니다.

 

근데, 여기에서 Controller, Service, Repository의 "명칭"만 바꿔보겠습니다.

응~ 형이야~ port & adapter 패턴~

여기에 육각형을 그리면?

"나야, 헥사고날"

결국 클린 아키텍처나 헥사고날 아키텍처도 의존 방향과 경계 관리 이야기입니다. OOP의 본질이죠.

그럼 DDD? 도메인모델패턴은요? Bounded Context를 강조하지 않던가요?

Bounded Context도 결국엔 경계 나누기입니다. "클래스 수준의 캡슐화를 컨텍스트 수준으로" 확장한것입니다. 또 OOP네?

 

"클래스 수준의 캡슐화를 컨텍스트 수준으로" 에서 "클래스 수준의 캡슐화를 모듈 수준으로" 확장하면

모듈러 모놀리스가 됩니다. 얘도 OOP네?

 

모듈러 모놀리스에서 각 모듈을 떼어내고 별도 서버로 배포하면? MSA입니다.

???

결국, 본질은 같다.

서론에서 말했던 " 도메인 모델 패턴, DDD, 헥사고날 아키텍처, 클린 아키텍처, 양파 아키텍처, 모듈러 모놀리스, MSA, TDD 는 모두 OOP에 대해서 이야기 하고 있습니다." 에서 TDD를 제외한 나머지는 OOP에 대해서 얘기하고있다는걸 깨달았습니다.

그럼 TDD는요?!

설계는 무시하고 TDD만 하면 된다? red - green -refactor만 잘 지키면 된다? 아닙니다.

경계가 무너지면 테스트도 무너집니다. TDD의 본질은 경계를 잘 잡아놨을때 그 경계를 지켜주고, 리팩토링 속도를 올려주는 역할이라고 생각합니다. 설계 먼저, 테스트는 그 다음.

 

TDD를 하면서 테스트가 어려워지고 복잡해진다고 느껴지면, 설계를 리팩토링하라는 신호를 TDD가 보내는 것입니다.

 

자, 이제. 화려한 설계원칙, 패러다임들의 근본은 전부 하나를 가르킨다는 것을 알았습니다. 그리고 그것은 사람을 위한, 변경 범위를 최소화 하기 위한 것이라는 것도요. 소프트웨어의 요구사항은 늘 바뀌고 그걸 고치는 것은 사람이 직접했습니다. 그래서 다양한 방법론, 패러다임이 나온것이고요.

근데 요즘은 AI시대입니다.

AI가 코드베이스를 파악하고 AI가 직접 고치는데, 읽기 좋은 코드가 의미가 있을까? 에 대한 실험을 해보려고 하는겁니다.


실험 조건은 다음과 같습니다. 

 

모델 : Opus 4.5

플러그인 : lsp

 

1. 동일한 요구사항을 구현한다.

2. 한 쪽은 OOP와 헥사고날 아키텍처를 따라서 구현한다.

3. 다른 한 쪽은 트랜잭션 스크립트로 구현하고 최대한 "더럽게" 구현한다.

4. 요구사항 하나를 완료할 때마다 exit 후 사용 토큰을 측정하고 기록한다.

5. 다시 클로드 세션을 열어서 다음 task를 진행한다.

그래서 CleanCode라는 프로젝트와, messyCode라는 프로젝트를 만들고, 각각 claude code에게 구현을 시켜볼겁니다.

 

먼저, 두 프로젝트가 구현할 api-spec은 아래와 같습니다.

api-spec.md

더보기
# Anonymous Board API Specification

## 공통 사항

- Base URL: `/api`
- Content-Type: `application/json`
- 비밀번호 검증 실패 시: `403 Forbidden`
- 리소스 없음: `404 Not Found`
- 유효성 검증 실패: `400 Bad Request`
- 좋아요 사용자 식별: `X-Guest-Id` 헤더 (UUID)

---

## 1. 게시글 CRUD

### 1.1 게시글 작성

`POST /api/posts`

**Request Body:**
```json
{
  "title": "string (필수, 1~200자)",
  "content": "string (필수)",
  "author": "string (필수, 1~50자)",
  "password": "string (필수, 4자 이상)",
  "hashtags": ["string"] // 선택, 최대 5개, 각 태그 1~30자
}
```

**Response:** `201 Created`
```json
{
  "id": 1,
  "title": "제목",
  "content": "내용",
  "author": "작성자",
  "hashtags": ["태그1", "태그2"],
  "viewCount": 0,
  "likeCount": 0,
  "commentCount": 0,
  "createdAt": "2024-01-01T00:00:00",
  "updatedAt": "2024-01-01T00:00:00"
}
```

### 1.2 게시글 상세 조회

`GET /api/posts/{id}`

- 호출 시 조회수(viewCount) 1 증가

**Response:** `200 OK`
```json
{
  "id": 1,
  "title": "제목",
  "content": "내용",
  "author": "작성자",
  "hashtags": ["태그1", "태그2"],
  "viewCount": 43,
  "likeCount": 0,
  "commentCount": 0,
  "createdAt": "2024-01-01T00:00:00",
  "updatedAt": "2024-01-01T00:00:00"
}
```

- 이 단계에서는 댓글, 좋아요 관련 필드는 기본값(0, 빈 목록)으로 반환

### 1.3 게시글 수정

`PUT /api/posts/{id}`

**Request Body:**
```json
{
  "title": "수정된 제목",
  "content": "수정된 내용",
  "password": "비밀번호",
  "hashtags": ["태그1", "태그3"]
}
```

- 비밀번호 일치 시에만 수정 가능

**Response:** `200 OK` (게시글 상세와 동일한 형태, 단 viewCount 증가 없음)

### 1.4 게시글 삭제

`DELETE /api/posts/{id}`

**Request Body:**
```json
{
  "password": "비밀번호"
}
```

- 비밀번호 일치 시에만 삭제 (Hard Delete)

**Response:** `204 No Content`

---

## 2. 댓글 (Comments)

### 2.1 댓글 작성

`POST /api/posts/{postId}/comments`

**Request Body:**
```json
{
  "author": "string (필수, 1~50자)",
  "password": "string (필수, 4자 이상)",
  "content": "string (필수)"
}
```

**Response:** `201 Created`
```json
{
  "id": 1,
  "author": "댓글작성자",
  "content": "댓글 내용",
  "createdAt": "2024-01-02T00:00:00",
  "deleted": false
}
```

### 2.2 댓글 목록 조회 (더보기)

`GET /api/posts/{postId}/comments`

**Query Parameters:**
| Parameter | Type | Default | Description           |
|-----------|------|---------|-----------------------|
| page      | int  | 0       | 페이지 번호 (0-based) |
| size      | int  | 5       | 페이지 크기 (고정 5)  |

**Response:** `200 OK`
```json
{
  "content": [
    {
      "id": 1,
      "author": "댓글작성자",
      "content": "댓글 내용",
      "createdAt": "2024-01-02T00:00:00",
      "deleted": false
    }
  ],
  "page": 0,
  "size": 5,
  "totalElements": 12,
  "hasMore": true
}
```

- 정렬: 최신순 (createdAt DESC)
- `deleted`가 `true`인 댓글은 `content`를 `"삭제된 댓글입니다."` 로 반환

### 2.3 댓글 삭제

`DELETE /api/posts/{postId}/comments/{commentId}`

**Request Body:**
```json
{
  "password": "비밀번호"
}
```

- 비밀번호 일치 시 Soft Delete (deleted 플래그 처리)

**Response:** `204 No Content`

### 2.4 게시글 상세 조회에 댓글 포함

`GET /api/posts/{id}` 응답이 아래와 같이 확장된다:

```json
{
  "id": 1,
  "title": "제목",
  "content": "내용",
  "author": "작성자",
  "hashtags": ["태그1", "태그2"],
  "viewCount": 43,
  "likeCount": 0,
  "commentCount": 5,
  "createdAt": "2024-01-01T00:00:00",
  "updatedAt": "2024-01-01T00:00:00",
  "comments": {
    "content": [
      {
        "id": 10,
        "author": "댓글작성자",
        "content": "댓글 내용",
        "createdAt": "2024-01-02T00:00:00",
        "deleted": false
      }
    ],
    "page": 0,
    "size": 5,
    "totalElements": 12,
    "hasMore": true
  }
}
```

- `comments`: 최신순 5건
- `deleted`가 `true`인 댓글은 `content`를 `"삭제된 댓글입니다."` 로 반환
- `commentCount`: 삭제되지 않은 댓글의 총 수
- 게시글 삭제 시 연관 댓글도 함께 삭제

---

## 3. 게시글 목록 + 검색

### 3.1 게시글 목록 조회

`GET /api/posts`

**Query Parameters:**
| Parameter  | Type   | Default | Description                                |
|------------|--------|---------|--------------------------------------------|
| page       | int    | 0       | 페이지 번호 (0-based)                      |
| size       | int    | 10      | 페이지 크기 (10 또는 20만 허용)            |
| searchType | string | -       | 검색 유형: title, author, hashtag, content  |
| keyword    | string | -       | 검색 키워드                                |

**Response:** `200 OK`
```json
{
  "totalPostCount": 100,
  "totalCommentCount": 523,
  "posts": [
    {
      "id": 1,
      "title": "제목",
      "author": "작성자",
      "createdAt": "2024-01-01T00:00:00",
      "commentCount": 5,
      "viewCount": 42,
      "likeCount": 3,
      "isNew": true
    }
  ],
  "page": 0,
  "size": 10,
  "totalPages": 10,
  "totalElements": 100
}
```

- `totalPostCount`: 전체 게시글 수 (검색 필터 무관)
- `totalCommentCount`: 전체 댓글 수 (검색 필터 무관, 삭제된 댓글 제외)
- `isNew`: 작성일이 현재 기준 3일 이내이면 `true`
- 정렬: 최신순 (createdAt DESC)

### 3.2 검색

- `searchType`과 `keyword`가 모두 있을 때만 검색 적용
- title, author, content: 부분 일치 검색 (LIKE '%keyword%')
- hashtag: 해시태그 이름과 정확히 일치

---

## 4. 좋아요 (Likes)

### 4.1 좋아요

`POST /api/posts/{postId}/likes`

**Required Header:** `X-Guest-Id: {uuid}`

- `X-Guest-Id` 헤더가 없으면: `400 Bad Request`
- 이미 좋아요한 상태에서 재요청 시: `409 Conflict`

**Response:** `200 OK`
```json
{
  "likeCount": 4,
  "liked": true
}
```

### 4.2 좋아요 취소

`DELETE /api/posts/{postId}/likes`

**Required Header:** `X-Guest-Id: {uuid}`

- `X-Guest-Id` 헤더가 없으면: `400 Bad Request`
- 좋아요하지 않은 상태에서 요청 시: `409 Conflict`

**Response:** `200 OK`
```json
{
  "likeCount": 3,
  "liked": false
}
```

### 4.3 게시글 상세 조회에 좋아요 포함

`GET /api/posts/{id}` 응답에 아래 필드가 추가된다:

- `liked`: `X-Guest-Id` 헤더 기준으로 좋아요 여부 (`true`/`false`)
- `X-Guest-Id` 헤더가 없으면 `liked`는 `false`
- `likeCount`: 실제 좋아요 수
- 게시글 삭제 시 연관 좋아요 데이터도 함께 삭제



 

그리고,

CleanCode 프로젝트에 들어갈 CLAUDE.md는 아래와 같습니다.

더보기
# Project: Anonymous Board (Clean Architecture)

## Tech Stack
- Java 17+
- Spring Boot 3.x
- Spring Data JPA
- H2 Database (in-memory)
- BCrypt for password hashing

## Architecture: Hexagonal (Ports & Adapters)

### Package Structure
```
com.board.clean
├── domain/                  # 핵심 도메인 (외부 의존성 없음)
│   ├── model/               # 도메인 모델 (Entity가 아닌 순수 도메인 객체)
│   ├── port/
│   │   ├── in/              # Inbound Port (UseCase 인터페이스)
│   │   └── out/             # Outbound Port (Repository 인터페이스)
│   └── exception/           # 도메인 예외
├── application/             # 유스케이스 구현
│   └── service/             # Port(in) 구현체
├── adapter/
│   ├── in/
│   │   └── web/             # Controller + Request/Response DTO
│   └── out/
│       └── persistence/     # JPA Entity + Repository 구현체 (Port(out) 구현)
└── config/                  # Spring 설정
```

### Architecture Rules (반드시 준수)

1. **Domain Layer는 외부 프레임워크에 의존하지 않는다**
   - `domain/model`에는 JPA 어노테이션, Spring 어노테이션 사용 금지
   - 순수 Java 객체로만 구성
   - 도메인 모델 안에 비즈니스 규칙을 캡슐화한다

2. **UseCase별 Port 정의**
   - 각 유스케이스는 별도의 Inbound Port 인터페이스를 갖는다
   - 예: `CreatePostUseCase`, `GetPostListUseCase`, `LikePostUseCase` 등

3. **Adapter는 Port를 통해서만 통신한다**
   - Controller → Inbound Port → Service → Outbound Port → Repository
   - Controller에서 Repository 직접 접근 금지

4. **DTO 변환은 Adapter 레이어에서 처리한다**
   - Web Adapter: Request DTO → Domain Model, Domain Model → Response DTO
   - Persistence Adapter: Domain Model ↔ JPA Entity

5. **입력 유효성 검증**
   - 형식 검증(길이, 필수값 등): Web Adapter (Controller or Request DTO)
   - 비즈니스 규칙 검증: Domain Model 또는 Application Service

6. **에러 처리**
   - 도메인 예외를 정의하고, Web Adapter에서 HTTP 상태코드로 변환
   - `@RestControllerAdvice`로 글로벌 예외 처리

## Testing

### 테스트 전략
- **통합 테스트 (필수)**: `@SpringBootTest` + `MockMvc`로 API 엔드포인트 검증
- **도메인 단위 테스트 (권장)**: 도메인 모델의 비즈니스 규칙을 순수 단위 테스트로 검증

### 통합 테스트 규칙
- 각 API 엔드포인트별로 정상 케이스와 주요 에러 케이스를 테스트한다
- 테스트 데이터는 각 테스트 메서드에서 직접 설정한다
- H2 in-memory DB를 사용하므로 별도 테스트 DB 설정 불필요
- `@Transactional`로 테스트 간 데이터 격리

### 검증 항목 (모든 API 공통)
- HTTP 상태 코드
- 응답 본문의 필수 필드 존재 여부
- 비즈니스 규칙 (비밀번호 불일치 시 403, 리소스 없음 시 404 등)

## API Specification
- 상위 디렉토리의 `api-spec.md` 참조

## Build & Run
```bash
./gradlew bootRun
./gradlew test
```



 

그다음, messyCode에 들어갈 CLAUDE.md는 아래와 같습니다.

더보기
# Project: Anonymous Board (Messy)

## Tech Stack
- Java 17+
- Spring Boot 3.x
- Spring Data JPA
- H2 Database (in-memory)
- BCrypt for password hashing

## Architecture Rules (반드시 준수)

이 프로젝트는 의도적으로 아래의 스타일로 작성한다. 리팩토링하지 말 것.

### Package Structure
```
com.board.messy
├── controller/    # Controller (비즈니스 로직 포함 가능)
├── service/       # Service (God Class 스타일)
├── repository/    # JPA Repository
├── entity/        # JPA Entity (DTO 겸용)
└── config/        # Spring 설정
```

### 코딩 스타일 (반드시 따를 것)

1. **Service는 기능 도메인별로 하나씩만 만든다**
   - `PostService` 하나에 게시글 관련 모든 로직 포함
   - `CommentService` 하나에 댓글 관련 모든 로직 포함
   - 메서드가 길어져도 분리하지 않는다

2. **JPA Entity를 API 응답에 그대로 사용한다**
   - 별도의 Response DTO 클래스를 만들지 않는다
   - `@JsonIgnore`로 숨길 필드를 처리한다
   - 필요하면 Entity에 API 응답용 필드/메서드를 추가한다
   - 목록 조회 등 별도 형태가 필요한 경우에만 `Map<String, Object>` 또는 inline DTO를 사용한다

3. **비즈니스 로직을 Controller에 일부 포함시킨다**
   - 예: 비밀번호 검증, 조건 분기 등을 Controller에서 직접 처리해도 된다
   - Service와 Controller 사이에 로직이 분산되어도 괜찮다

4. **중복 코드를 허용한다**
   - 비슷한 로직이 여러 곳에 있어도 공통 메서드로 추출하지 않는다
   - 각 메서드가 독립적으로 전체 로직을 포함한다

5. **에러 처리는 각 메서드에서 직접 한다**
   - 글로벌 예외 핸들러를 만들지 않는다
   - 각 Controller 메서드에서 try-catch 또는 if-else로 직접 처리한다
   - `ResponseEntity`로 상태코드를 직접 반환한다

6. **매직 넘버와 하드코딩을 사용한다**
   - 상수를 별도로 정의하지 않는다
   - 예: 페이지 크기를 `10`, 해시태그 최대 개수를 `5`로 코드에 직접 작성

7. **도메인 검증 로직을 Entity나 별도 클래스로 분리하지 않는다**
   - 모든 검증은 Service 또는 Controller 메서드 내에서 if문으로 처리

## Testing

### 테스트 규칙
- `@SpringBootTest` + `MockMvc`로 API 엔드포인트를 통합 테스트한다
- 단위 테스트는 작성하지 않는다. 통합 테스트만 작성한다
- 하나의 테스트 클래스에 관련 API 테스트를 모두 포함한다 (예: `PostApiTest`에 게시글 CRUD 전부)
- 테스트 데이터는 각 테스트 메서드에서 직접 설정한다
- H2 in-memory DB를 사용하므로 별도 테스트 DB 설정 불필요
- `@Transactional`로 테스트 간 데이터 격리

### 검증 항목 (모든 API 공통)
- HTTP 상태 코드
- 응답 본문의 필수 필드 존재 여부
- 비즈니스 규칙 (비밀번호 불일치 시 403, 리소스 없음 시 404 등)

## API Specification
- 상위 디렉토리의 `api-spec.md` 참조

## Build & Run
```bash
./gradlew bootRun
./gradlew test
```



이제 7가지의 Task를 던질겁니다. 1~4번은 단순히 새로운 기능 추가이고, 5~7번은 기존 요구사항 변경입니다.

 

 

1. 게시글 CRUD

더보기
Spring Boot 프로젝트를 초기 세팅하고, 게시글 CRUD API를 구현해줘.
## 구현할 API

상위 디렉토리의 `api-spec.md`를 참조하여 아래 API를 구현해줘:

1. **게시글 작성** (POST /api/posts)
   - 제목, 내용, 작성자, 비밀번호, 해시태그(최대 5개)를 입력받는다
   - 비밀번호는 BCrypt로 암호화하여 저장한다
   - 유효성 검증: 제목 1~200자, 작성자 1~50자, 비밀번호 4자 이상, 해시태그 최대 5개/각 1~30자

2. **게시글 상세 조회** (GET /api/posts/{id})
   - 조회 시 viewCount를 1 증가시킨다
   - 댓글과 좋아요 관련 필드는 이 단계에서는 기본값(0, 빈 목록)으로 반환한다

3. **게시글 수정** (PUT /api/posts/{id})
   - 비밀번호가 일치해야 수정 가능
   - 제목, 내용, 해시태그를 수정할 수 있다

4. **게시글 삭제** (DELETE /api/posts/{id})
   - 비밀번호가 일치해야 삭제 가능
   - Hard Delete

## 테스트

구현한 API에 대한 테스트 코드를 작성해줘:
- 각 엔드포인트의 정상 케이스
- 주요 에러 케이스 (유효성 검증 실패, 비밀번호 불일치, 존재하지 않는 리소스 등)
- CLAUDE.md의 테스트 규칙을 따를 것

구현 완료 후 `./gradlew test`로 테스트가 통과하는지 확인해줘.

CLAUDE.md에 정의된 아키텍처 규칙을 반드시 따라서 구현해줘.



 

2. 댓글 CRUD

더보기
댓글 기능을 구현해줘.

## 구현할 API

상위 디렉토리의 `api-spec.md`를 참조하여 아래 API를 구현해줘:

1. **댓글 작성** (POST /api/posts/{postId}/comments)
   - 작성자, 비밀번호, 내용을 입력받는다
   - 비밀번호는 BCrypt로 암호화하여 저장한다
   - 존재하지 않는 게시글에 댓글 작성 시 404

2. **댓글 목록 조회** (GET /api/posts/{postId}/comments)
   - 최신순 정렬, 5건씩 페이징
   - `deleted`가 true인 댓글은 content를 `"삭제된 댓글입니다."`로 반환
   - `hasMore`로 다음 페이지 존재 여부 반환

3. **댓글 삭제** (DELETE /api/posts/{postId}/comments/{commentId})
   - 비밀번호 일치 시 Soft Delete (deleted 플래그 true 처리)
   - 실제 데이터는 삭제하지 않음

4. **게시글 상세 조회 수정** (GET /api/posts/{id})
   - 기존 게시글 상세 응답에 댓글 정보를 포함시킨다
   - 최신 5건의 댓글을 함께 반환
   - `deleted`가 true인 댓글은 content를 `"삭제된 댓글입니다."`로 반환
   - commentCount 필드를 실제 댓글 수로 반환

## 테스트

구현한 API에 대한 테스트 코드를 작성해줘:
- 각 엔드포인트의 정상 케이스
- 주요 에러 케이스 (존재하지 않는 게시글, 비밀번호 불일치, 삭제된 댓글 표시 등)
- CLAUDE.md의 테스트 규칙을 따를 것

구현 완료 후 `./gradlew test`로 테스트가 통과하는지 확인해줘.

CLAUDE.md에 정의된 아키텍처 규칙을 반드시 따라서 구현해줘.



 

3. 게시글 조회/검색

더보기
게시글 목록 조회와 검색 기능을 구현해줘.

## 구현할 API

상위 디렉토리의 `api-spec.md`를 참조하여 아래 기능을 구현해줘:

1. **게시글 목록 조회** (GET /api/posts)
   - 최신순(createdAt DESC) 정렬
   - 페이징: page(0-based), size(10 또는 20만 허용, 기본값 10)
   - 응답에 포함할 정보:
     - `totalPostCount`: 전체 게시글 수 (검색 필터 무관, 전체)
     - `totalCommentCount`: 전체 댓글 수 (검색 필터 무관, 전체, 삭제된 댓글 제외)
     - 각 게시글: id, title, author, createdAt, commentCount, viewCount, likeCount
     - `isNew`: 작성일이 현재 기준 3일 이내이면 true
   - 페이징 메타: page, size, totalPages, totalElements

2. **검색 기능**
   - `searchType` 파라미터: title, author, hashtag, content 중 하나
   - `keyword` 파라미터: 검색어
   - searchType과 keyword가 모두 있을 때만 검색 적용
   - 부분 일치 검색 (LIKE '%keyword%')
   - hashtag 검색은 해시태그 이름과 정확히 일치

## 테스트

구현한 API에 대한 테스트 코드를 작성해줘:
- 페이징 정상 동작 (size 10, 20)
- 허용되지 않는 size 값 처리
- 각 searchType별 검색 동작
- totalPostCount, totalCommentCount 정확성
- isNew 플래그 동작
- CLAUDE.md의 테스트 규칙을 따를 것

구현 완료 후 `./gradlew test`로 테스트가 통과하는지 확인해줘.

CLAUDE.md에 정의된 아키텍처 규칙을 반드시 따라서 구현해줘.



 

4. 좋아요 기능

더보기
좋아요(Like) 기능을 구현해줘.

## 구현할 API

상위 디렉토리의 `api-spec.md`를 참조하여 아래 API를 구현해줘:

1. **좋아요** (POST /api/posts/{postId}/likes)
   - `X-Guest-Id` 헤더(UUID)로 사용자를 식별한다
   - `X-Guest-Id` 헤더가 없으면 400 Bad Request
   - 이미 좋아요한 상태에서 재요청 시 409 Conflict
   - 좋아요 후 해당 게시글의 likeCount와 liked 상태를 반환

2. **좋아요 취소** (DELETE /api/posts/{postId}/likes)
   - `X-Guest-Id` 헤더(UUID)로 사용자를 식별한다
   - `X-Guest-Id` 헤더가 없으면 400 Bad Request
   - 좋아요하지 않은 상태에서 요청 시 409 Conflict
   - 취소 후 해당 게시글의 likeCount와 liked 상태를 반환

3. **게시글 상세 조회 수정** (GET /api/posts/{id})
   - 기존 응답에 `liked` 필드를 추가한다
   - `X-Guest-Id` 헤더가 있으면 해당 guest의 좋아요 여부를 반환
   - `X-Guest-Id` 헤더가 없으면 `liked`는 false
   - `likeCount`를 실제 좋아요 수로 반환

4. **게시글 삭제 시**
   - 게시글 삭제 시 연관된 좋아요 데이터도 함께 삭제

## 테스트

구현한 API에 대한 테스트 코드를 작성해줘:
- 좋아요 / 좋아요 취소 정상 동작
- 중복 좋아요 시 409
- 좋아요하지 않은 상태에서 취소 시 409
- X-Guest-Id 헤더 없을 때 400
- 게시글 상세에서 liked 필드 정확성
- CLAUDE.md의 테스트 규칙을 따를 것

구현 완료 후 `./gradlew test`로 테스트가 통과하는지 확인해줘.

CLAUDE.md에 정의된 아키텍처 규칙을 반드시 따라서 구현해줘.



 

5. 비밀번호 변경 기능 추가

더보기
게시글 비밀번호 변경 기능을 추가해줘.

## 변경 요구사항

기존에 게시글 수정/삭제 시에만 사용하던 비밀번호를 **별도로 변경**할 수 있는 API를 추가한다.

### 새 API: 게시글 비밀번호 변경

`PATCH /api/posts/{id}/password`

**Request Body:**
```json
{
  "currentPassword": "현재 비밀번호",
  "newPassword": "새 비밀번호 (4자 이상)"
}
```

**Response:**
- 성공 시: `200 OK` (본문 없음 또는 `{"message": "비밀번호가 변경되었습니다."}`)
- 현재 비밀번호 불일치: `403 Forbidden`
- 새 비밀번호 유효성 실패 (4자 미만): `400 Bad Request`
- 게시글 없음: `404 Not Found`

### 구현 시 고려사항
- 새 비밀번호도 BCrypt로 암호화하여 저장
- 기존 비밀번호 검증 로직을 재사용하거나 참고할 것

## 테스트

- 비밀번호 변경 정상 동작
- 변경 후 새 비밀번호로 게시글 수정/삭제 가능 확인
- 현재 비밀번호 불일치 시 403
- 새 비밀번호 4자 미만 시 400
- 존재하지 않는 게시글 404
- CLAUDE.md의 테스트 규칙을 따를 것

구현 완료 후 `./gradlew test`로 테스트가 통과하는지 확인해줘.

CLAUDE.md에 정의된 아키텍처 규칙을 반드시 따라서 구현해줘.



 

6. 대댓글 기능 추가 (1-depth)

더보기
댓글에 대댓글(답글) 기능을 추가해줘.

## 변경 요구사항

기존 댓글 시스템을 확장하여 **댓글에 대한 답글**을 달 수 있게 한다. 답글은 1단계만 허용한다 (답글에 답글 불가).

### API 변경사항

#### 1. 답글 작성

`POST /api/posts/{postId}/comments/{commentId}/replies`

**Request Body:**
```json
{
  "author": "string (필수, 1~50자)",
  "password": "string (필수, 4자 이상)",
  "content": "string (필수)"
}
```

**Response:** `201 Created`
```json
{
  "id": 2,
  "parentId": 1,
  "author": "답글작성자",
  "content": "답글 내용",
  "createdAt": "2024-01-02T00:00:00",
  "deleted": false
}
```

- 부모 댓글이 이미 답글인 경우 (parentId가 있는 경우): `400 Bad Request`
- 부모 댓글이 삭제된 경우에도 답글 작성 가능

#### 2. 댓글 목록 조회 변경

`GET /api/posts/{postId}/comments` 응답에서:

```json
{
  "content": [
    {
      "id": 1,
      "parentId": null,
      "author": "댓글작성자",
      "content": "댓글 내용",
      "createdAt": "2024-01-02T00:00:00",
      "deleted": false,
      "replyCount": 3
    }
  ],
  "page": 0,
  "size": 5,
  "totalElements": 12,
  "hasMore": true
}
```

- `parentId`: 답글인 경우 부모 댓글 ID, 일반 댓글은 `null`
- `replyCount`: 해당 댓글의 답글 수 (삭제된 답글 제외)
- 목록에는 **일반 댓글만** 표시 (답글은 별도 조회)

#### 3. 답글 목록 조회 (새 API)

`GET /api/posts/{postId}/comments/{commentId}/replies`

**Query Parameters:**
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| page      | int  | 0       | 페이지 번호 |
| size      | int  | 5       | 페이지 크기 |

**Response:** `200 OK`
```json
{
  "content": [
    {
      "id": 2,
      "parentId": 1,
      "author": "답글작성자",
      "content": "답글 내용",
      "createdAt": "2024-01-02T00:00:00",
      "deleted": false
    }
  ],
  "page": 0,
  "size": 5,
  "totalElements": 3,
  "hasMore": false
}
```

- 정렬: 오래된순 (createdAt ASC) — 대화 흐름 유지
- 삭제된 답글은 `"삭제된 댓글입니다."` 처리 동일

#### 4. 댓글 삭제 시
- 답글이 있는 댓글을 삭제해도 답글은 유지됨
- 답글 삭제는 기존 댓글 삭제 API와 동일하게 동작

### 기존 API 영향
- `GET /api/posts/{id}` (게시글 상세): comments 필드에 `replyCount` 추가
- `commentCount`는 일반 댓글 + 답글 전체 수

## 테스트

- 답글 작성 정상 동작
- 답글에 답글 시도 시 400
- 답글 목록 조회 (오래된순 정렬)
- 삭제된 부모 댓글에 답글 작성 가능
- replyCount 정확성
- 부모 댓글 삭제 후 답글 유지 확인
- CLAUDE.md의 테스트 규칙을 따를 것

구현 완료 후 `./gradlew test`로 테스트가 통과하는지 확인해줘.

CLAUDE.md에 정의된 아키텍처 규칙을 반드시 따라서 구현해줘.



7. 게시글 정렬 옵션 추가

더보기
게시글 목록에 정렬 옵션을 추가해줘.

## 변경 요구사항

기존에 최신순(createdAt DESC)으로만 정렬되던 게시글 목록에 **정렬 옵션**을 추가한다.

### API 변경사항

`GET /api/posts`

**추가 Query Parameter:**

| Parameter | Type   | Default  | Description |
|-----------|--------|----------|-------------|
| sort      | string | latest   | 정렬 기준: latest, views, likes |

**정렬 기준:**
- `latest`: 최신순 (createdAt DESC) — 기존과 동일
- `views`: 조회수 높은순 (viewCount DESC), 동일 시 최신순
- `likes`: 좋아요 많은순 (likeCount DESC), 동일 시 최신순

**에러 처리:**
- 허용되지 않은 sort 값: `400 Bad Request`

### 구현 시 고려사항
- 기존 검색 기능과 함께 동작해야 함 (searchType + keyword + sort 조합)
- 페이징과 함께 동작해야 함

## 테스트

- sort=latest 정상 동작 (기존과 동일)
- sort=views 정렬 확인
- sort=likes 정렬 확인
- 동일 값일 때 2차 정렬(최신순) 확인
- 검색 + 정렬 조합 동작
- 허용되지 않은 sort 값 시 400
- sort 파라미터 없을 때 기본값(latest) 동작
- CLAUDE.md의 테스트 규칙을 따를 것

구현 완료 후 `./gradlew test`로 테스트가 통과하는지 확인해줘.

CLAUDE.md에 정의된 아키텍처 규칙을 반드시 따라서 구현해줘.



 

구현된 코드와 사용한 md, 토큰추출 스크립트 등은 아래 레포지토리에서 확인 가능합니다.

 

https://github.com/yoon-yoo-tak/Clean_Architecture_Experiment

 

GitHub - yoon-yoo-tak/Clean_Architecture_Experiment

Contribute to yoon-yoo-tak/Clean_Architecture_Experiment development by creating an account on GitHub.

github.com

 

 


이제, 각 기능을 수행할 때마다 기록을 할것입니다.
클로드코드는 세션마다 세션 로그 JSONL파일에 모든 API호출의 토큰 사용량을 기록합니다. 그래서 각 세션에서 작업 수행하고 세션을 나갔다 들어오는 행위를 반복한 것입니다.

기록할 템플릿은 아래와 같습니다.

# Experiment Results: [messy/clean]

## Phase 1: 프로젝트 초기 세팅 + 게시글 CRUD

| Metric              | Value |
|---------------------|---|
| Input Tokens        |  |
| Output Tokens       |  |
| Total Tokens        |  |
| Cache Creation      |  |
| Cache Read          |  |
| Tool Calls          |  |
| Conversation Turns  |  |
| Build Success       |  |
| API Test Pass       |  |
| Notes               |  |

## Phase 2: 댓글 기능

| Metric              | Value |
|---------------------|---|
| Input Tokens        |  |
| Output Tokens       |  |
| Total Tokens        |  |
| Cache Creation      |  |
| Cache Read          |  |
| Tool Calls          |  |
| Conversation Turns  |  |
| Build Success       |  |
| API Test Pass       |  |
| Notes               |  |

## Phase 3: 게시글 목록 + 검색

| Metric              | Value |
|---------------------|---|
| Input Tokens        |  |
| Output Tokens       |  |
| Total Tokens        |  |
| Cache Creation      |  |
| Cache Read          |  |
| Tool Calls          |  |
| Conversation Turns  |  |
| Build Success       |  |
| API Test Pass       |  |
| Notes               |  |

## Phase 4: 좋아요 기능

| Metric              | Value |
|---------------------|---|
| Input Tokens        |  |
| Output Tokens       |  |
| Total Tokens        |  |
| Cache Creation      |  |
| Cache Read          |  |
| Tool Calls          |  |
| Conversation Turns  |  |
| Build Success       |  |
| API Test Pass       |  |
| Notes               |  |

## Phase 5: 비밀번호 변경 기능 (기능 변경)

| Metric              | Value |
|---------------------|---|
| Input Tokens        |  |
| Output Tokens       |  |
| Total Tokens        |  |
| Cache Creation      |  |
| Cache Read          |  |
| Tool Calls          |  |
| Conversation Turns  |  |
| Build Success       |  |
| API Test Pass       |  |
| Notes               |  |

## Phase 6: 대댓글 기능 (기능 변경)

| Metric              | Value |
|---------------------|---|
| Input Tokens        |  |
| Output Tokens       |  |
| Total Tokens        |  |
| Cache Creation      |  |
| Cache Read          |  |
| Tool Calls          |  |
| Conversation Turns  |  |
| Build Success       |  |
| API Test Pass       |  |
| Notes               |  |

## Phase 7: 정렬 옵션 (기능 변경)

| Metric              | Value |
|---------------------|---|
| Input Tokens        |  |
| Output Tokens       |  |
| Total Tokens        |  |
| Cache Creation      |  |
| Cache Read          |  |
| Tool Calls          |  |
| Conversation Turns  |  |
| Build Success       |  |
| API Test Pass       |  |
| Notes               |  |

## Total Summary

| Metric              | Value |
|---------------------|-------|
| Total Input Tokens  |  |
| Total Output Tokens |  |
| Total Tokens        |  |
| Total Tool Calls    |  |
| Total Turns         |  |

 

 

 

결과는??

(모든 사진에서 왼쪽이 clean, 오른쪽이 messy입니다.)

 

첫 번째 기능 구현 결과입니다.

 

두 번째 기능 구현 결과입니다.

 

 

세 번째 기능 구현 결과입니다.

 

네 번째 기능 구현 결과입니다.

 

다섯 번째 기능 구현 결과입니다.

 

여섯 번째 기능 구현 결과입니다.

 

마지막 기능 구현 결과입니다.

 

 

Total Summary입니다.

 

또한, 평균적으로 수행시간도 Clean쪽이 더 길었습니다.

 

Total Summary 비교입니다.

  Metric: 총 사용 토큰                                      
  CleanCode: 12,890,844
  MessyCode: 6,908,059
  차이: Clean이 1.87배 더 사용                                    
  ────────────────────────────────────────
  Metric: 출력 토큰
  CleanCode: 87,805                                             
  MessyCode: 58,300
  차이: Clean이 1.51배 더 사용
  ────────────────────────────────────────
  Metric: Tool 호출
  CleanCode: 237
  MessyCode: 143
  차이: Clean이 1.66배 더 사용
  ────────────────────────────────────────
  Metric: 대화 턴
  CleanCode: 381
  MessyCode: 214
  차이: Clean이 1.78배 더 사용

 

사실, 실험을 하기 전에 당연히 이해하기 쉬운 코드가 초기에는 토큰이 더 들더라도 가면 갈수록 격차가 줄어들 줄 알았습니다.

그런데, 거의 모든 구간에서 조금 난잡한? 코드가 비교적 토큰 사용을 효율적으로 하는 것을 볼 수 있습니다.

 

왜 그럴까???

토큰 사용의 기조를 보면,

 

1. 파일 수가 토큰 사용량을 지배한다.

CleanCode의 구조는 하나의 기능을 구현하거나 수정할 때, 경계를 지키고 분리를 한 탓에 6~8개의 파일을 건드려야 합니다.

AI는 각 파일을 읽고, 관계를 파악하고, 일관성 있게 수정해야 합니다.

 

MessyCode의 구조로는 2~3개의 파일만 건드리면 됩니다. Controller에 로직이 있고, Service에 나머지 로직이 있습니다. Entity가 곧 DTO입니다.

 

2. 기능 추가보다 기능 변경에서 차이가 커진다.

 

기능 변경은 기존 코드를 "이해"해야 합니다. 

clean architecture를 따르는 코드에서는

"비밀번호 검증로직이 어디있지?" Domain? Service? Adapter?

"이 Port를 구현한건 뭐지?" -> Adapter를 찾야아함

"이 변경이 다른 UseCase에 영향을 주나?" -> 모든 관련 Port/Service확인

 

Messy에서는

"비밀번호 검증 로직이 어디 있지?" → PostService.java 하나 열면 다 있음

끝.

 

3. 대댓글이 아키텍처 차이를 극명하게 보여준다.

대댓글 기능은 기존 도메인 모델을 변경해야 합니다.
  - Comment에 parentId 추가
  - 자기참조 관계 설정
  - 조회 로직 변경 (일반 댓글 vs 답글 분리)
  - 새 API 엔드포인트 추가

  Clean (3,696,764 토큰, 62 tool calls, 94 turns)
  - Domain Model(Comment) 수정
  - Outbound Port 수정
  - Persistence Adapter(JPA Entity + Repository) 수정
  - Inbound Port(UseCase 인터페이스) 추가
  - Application Service 수정
  - Web Adapter(Controller + DTO) 수정
  - 각 레이어 간 매핑 로직 수정

  Edit:23번이 이걸 말해줍니다. 23개 수정 지점이 있었어요.

  Messy (790,704 토큰, 17 tool calls, 23 turns)
  - Entity 수정 (자기참조 추가)
  - Repository에 메서드 추가
  - Service에 로직 추가
  - Controller에 엔드포인트 추가

  Edit:9번. 모든 게 한 곳에 모여있으니 수정도 간단합니다.

 

4.  Output Tokens도 Clean이 더 많다.

 

Phase1에서 Messy가 더 많은건 아마도 보일러 플레이트가 적어서 한 번에 많이 썼기 때문인것 같습니다.

그러나 Phase 2, 6처럼 기존 코드 수정이 필요한 경우, Clean은 여러 파일을 수정하면서 각각 생성해야 하고, Messy는 적은 파일을  수정하므로 생성량도 적다는 것을 유추할 수 있습니다.

 

 

그래서 추측컨데,,,,(진짜 개인적인 추측임)

1. AI는 "찾기"에 비용이 많이 든다

Clean 아키텍처의 장점은 인간이 책임을 분리해서 이해하기 쉽다는 것입니다.

 

하지만 AI는?
  - 전체 구조를 한 번에 파악하기 어렵다 (파일을 하나씩 읽어야 함)
  - "이 기능의 진입점이 어디지?"를 찾는 데 탐색이 필요하다
  - 레이어 간 매핑을 이해하는 데 추가 읽기가 필요하다

  Messy는 모든 게 한 파일에 있습니다. AI가 PostService.java 하나 읽으면 게시글 관련 모든 로직이 눈에 들어옵니다.

 

2. AI는 "일관성 유지"에 비용이 많이 든다

  Clean에서 Comment 도메인을 수정하면
  - Domain Model 수정
  - JPA Entity 수정 (매핑 일치해야 함)
  - Port 인터페이스 수정
  - Service에서 변환 로직 수정
  - DTO 수정

  5곳을 일관성 있게 수정해야 합니다. AI는 각 수정 후 다른 곳과 맞는지 확인합니다.

  Messy에서는 Entity 수정하면 끝입니다. Entity가 곧 DTO니까. ㄹㅇ ㅋㅋ

 

3. 인간의 "읽기 좋음"과 AI의 "처리 효율"은 다르다

인간은:
  - 한 번에 화면에 보이는 양이 제한적이다
  - 파일을 분리하면 "이 파일은 이 책임"이라고 머릿속에서 캐싱한다
  - 분리된 구조가 인지 부하를 줄여준다

  AI는:
  - 컨텍스트 윈도우 안에서는 "한 번에" 다 본다
  - 파일이 분리되면 오히려 매번 새로 읽어야 한다
  - 분리된 구조가 탐색 비용을 증가시킨다

 

라고 정리할 수 있을것 같습니다.

 

 

물론, 이 실험이 말해주지 않는 것도 있습니다.


1. 정확성: 둘 다 Build Success, API Test Pass가 Y입니다. 토큰을 더 썼다고 더 정확한 건 아닐수도 있습니다.
2. 유지보수성: 6개월 후 다른 개발자가 코드를 볼 때의 이해도는 측정하지 않았습니다.
3. 확장성: 프로젝트가 10배 커졌을 때 어떻게 될지는 모릅니다. Messy는 God Class가 되면 AI도 읽기 어려워질 수 있습니다.
4. 재현성: 1회 실험입니다. AI의 비결정성 때문에 다시 하면 다른 결과가 나올 수 있습니다.

 


결론

"인간이 읽기 좋은 코드가 AI한테도 좋을까?"

이 실험이서의 답: 아니요, 적어도 토큰 효율성 측면에서는 그렇지 않습니다.

- Clean 아키텍처는 파일 분리로 인한 탐색 비용이 큽니다.
- 특히 기능 변경 시 여러 레이어를 수정해야 해서 비용이 급증합니다.
- Messy 코드는 "모든 게 한 곳에"라서 AI가 빠르게 찾고, 빠르게 수정합니다.

하지만 이게 "Messy가 좋다"는 의미는 절대! 아닙니다. 오히려 AI 도구 사용 시 아키텍처 선택의 트레이드오프를 보여줍니다.
- Clean: 토큰 비용 높음, 인간 유지보수 용이
- Messy: 토큰 비용 낮음, 인간 유지보수 어려움 (코드가 커지면)

 


 

소감

 

솔직히, 이 실험을 해보는거 어떻겠냐고 주변에 말했을때 거의 모두가 "무조건 클린 아키텍처가 토큰을 덜 먹을거다" 라는 말을 했습니다.

저 또한 그렇게 생각했고, AI한테 물어봤을때도 똑같이 말했거든요.

 

아래의 글들은 AI시대에 아키텍처의 중요성에 대해 말하는 글들입니다.

https://medium.com/@gryquandtestomb/spaghetti-code-is-dead-why-ai-demands-clean-architecture-and-how-to-achieve-it-96a2e0835c43

https://medium.com/@bardia.khosravi/backend-coding-rules-for-ai-coding-agents-ddd-and-hexagonal-architecture-ecafe91c753f 

 

https://shapedthoughts.io/ai-coding-structured-requirements-enterprise-software/ 

이런 글이 있다는것도 조사하면서 알았습니다.

그래서 그냥 일단 해보자 마인드로 실험을 해봤는데, 의외의 결과가 나와서 놀랐고 재미있었습니다.

 

물론, 단순한 기능만을 구현해서 이런결과가 나왔을 수도 있습니다.

매 Task를 수행하고 나서 /exit으로 나가버려서 그랬을 수도 있습니다.

plan모드를 사용하지 않아서 그랬을 수도 있습니다.

영향을 주는 요소는 엄청 많을 것입니다.

확실한건, 단순한 기능을 구현하는데에는 아키텍처가 가성비가 안좋을수도 있다. 입니다.

 

그렇다고 제가 앞으로 AI를 이용해서 개발할때 유지보수성 박살난 코드를 만들라고 하지 않을겁니다.

저도 유지보수성 좋은 아키텍처, 사람이 이해하기 쉬운 아키텍처를 선호합니다. 어쨋든 아직까지는 인간이 체킹을 하고, 개입을 해야하긴 하니까요. 먼 미래 AI를 블랙박스처럼 사용하게 되는 날이 온다면 어떻게 될지도 궁금하긴 합니다.

 

그 전까지 AI를 잘 쓰는 방법, 잘 부려먹는 방법에 대해 끊임없이 연구하고 실험하겠습니다. 긴 글 읽어주셔서 감사합니다.

 

끝.

 

 

 

참고 문서

더보기

 

'TW' 카테고리의 다른 글

다음에 들어올 개발자분에게 보여주고 싶은 것  (4) 2026.02.12

+ Recent posts