
Junie CLI Plan mode를 사용하여 구현 전 변경 계획서를 작성해 보았습니다
요약
AI 코딩 에이전트 Junie의 정식 버전(GA) 출시와 함께 새롭게 도입된 Advanced Plan mode 사용법을 소개합니다. 구현 전 단계에서 AI가 작성한 계획서를 먼저 검토함으로써 코드 변경의 정확도를 높이는 과정을 다룹니다.
핵심 포인트
- Junie의 Advanced Plan mode를 통해 구현 전 변경 계획서 작성 가능
- Plan mode는 .junie/plans 하위에 문서 형태로 저장되어 관리 용이
- AI가 제안한 계획을 먼저 확인하고 승인(Confirm)하는 워크플로우 제공
- 실제 Spring 프로젝트의 ID 충돌 버그 수정 사례를 통한 활용법 제시
Junie가 베타 버전을 종료하고 정식 버전(GA)이 되었습니다.
이번에는 지난번에 이어 공식 발표 내용 중 하나인 Advanced Plan mode에 대해 다루겠습니다.
AI 코딩 에이전트(AI Coding Agent)를 사용하면서 갑자기 구현에 들어가 곤란했던 적은 없으신가요?
아직 확인하지 않았는데 에이전트가 자신만만하게 코드를 작성하기 시작하면, 나중에 차이점(diff)을 보고 "거기가 아니야"라고 깨닫는 경우가 있습니다.
공식 기사에 따르면 Advanced Plan mode를 통해 Junie는 계획서(plan)를 작성하고, 이를 확인한 뒤에 구현을 할 수 있다고 합니다.
Plan mode에 진입하려면 Shift+Tab을, 생성된 plan을 열려면 Ctrl+P를, 구현으로 진행하려면 Confirm을 누르는 흐름이 소개되어 있습니다.
또한, plan은 단순한 채팅상의 답변이 아니라 .junie/plans 하위에 남는 문서(document)로 취급됩니다.
이번 기사에서는 이 Advanced Plan mode를 spring-demo-project의 버그 수정에 사용해 보았습니다.
지난번에는 동일한 spring-demo-project를 대상으로 Junie CLI의 Debug mode를 테스트했습니다.
지난번에는 원인을 조사해 달라고 요청했지만, 이번에는 "원인을 파악한 후, 구현 전에 수정 방침"을 Plan mode로 시도합니다.
그럼 함께 살펴보겠습니다!
대상은 주문을 생성하는 OrderService#createOrder입니다.
기존 구현에서는 주문 ID에 System.currentTimeMillis()를 사용하고 있었습니다.
public Order createOrder(Long userId, BigDecimal amount) {
long id = System.currentTimeMillis();
Order order = new Order(id, userId, amount);
...
저장소는 Long 타입의 ID를 키(key)로 하는 Map입니다.
@Repository
public class OrderRepository {
private final Map<Long, Order> store = new HashMap<>();
...
이 조합은 짧은 시간에 여러 주문을 생성했을 때, 동일한 밀리초(millisecond)가 되어 같은 ID가 생성될 가능성이 있습니다.
Map은 동일한 키가 들어오면 나중에 들어온 값으로 덮어쓰기 때문에, 주문이 사라진 것처럼 보이는 원인이 됩니다.
이번에는 Plan mode에 진입한 상태에서 다음과 같은 프롬프트(prompt)를 전달했습니다.
spring-demo-project의 OrderService#createOrder에서 사용 중인 System.currentTimeMillis() 기반의 주문 ID 채번 방식을 재검토하고 싶습니다.
짧은 시간에 여러 주문을 생성하면 ID가 충돌하여, OrderRepository의 Map에서 기존 주문이 덮어씌워질 가능성이 있습니다.
이 문제를 최소한의 변경으로 안정적인 ID 채번 방식으로 수정하는 계획을 세워주세요.
변경 대상 파일이나 테스트 방침은 별도로 지정하지 않았습니다.
최소한의 변경으로 안정적인 ID 채번을 하고 싶다는 목표(goal)만 전달했습니다.

Junie는 .junie/plans/fix-order-id-generation.md를 생성했습니다.
생성된 plan에는 다음과 같은 구성이 있었습니다.
내용으로는 현재의 문제를 다음과 같이 정리하고 있었습니다.
OrderService#createOrder가 System.currentTimeMillis()로 ID를 생성하고 있음 - 동일한 밀리초에 여러 주문을 생성하면 동일한 ID가 될 가능성이 있음
OrderRepository는 HashMap<Long, Order>로 저장하고 있기 때문에 동일한 ID의 주문이 덮어씌워짐 - Long 타입의 ID 형식은 유지함 - UUID, DB, 외부 ID 채번 서비스는 도입하지 않음
수정 방침으로는 AtomicLong을 사용하는 안이 선택되었습니다.
ID 생성 방식 (ID Generation Approach): System.currentTimeMillis()로 초기화된 AtomicLong을 사용합니다. incrementAndGet()을 호출하면 동시 요청(concurrent requests) 중에도 고유한 ID를 보장하며, Order 모델이나 Long ID 타입을 변경할 필요가 없습니다.
또한, OrderRepository에 대해서도 HashMap에서 ConcurrentHashMap으로 변경하는 방침이 포함되어 있었습니다.
이는 이 프롬프트에서 명시적으로 지시하지 않은 내용입니다.
ID 충돌뿐만 아니라, 짧은 시간 내의 병렬 액세스(parallel access)라는 맥락에서 저장소의 스레드 안전성(thread-safety)까지 포함하여 계획을 세운 것으로 보입니다.
그대로 'Confirm and Implement'를 실행했습니다.
구현 후, Junie는 다음과 같이 보고했습니다.
Summary
• Implemented thread-safe and collision-free order ID generation as requested in the plan.
• Upgraded the in-memory order store to handle concurrent accesses securely.
...
변경된 파일은 다음 3개입니다.
src/main/java/com/example/order/OrderService.java
src/main/java/com/example/order/OrderRepository.java
.junie/plans/fix-order-id-generation.md
마지막 plan 파일은 구현 후에 Delivery Step이 완료 상태로 업데이트되어 있었습니다.
### ✓ Step 1: Implement thread-safe ID generation in OrderService
...
### ✓ Step 2: Ensure thread safety in OrderRepository
...
plan이 단순한 채팅 답변이 아니라, 구현 후에도 상태가 업데이트되는 문서로 취급되고 있음을 알 수 있습니다.
OrderService는 AtomicLong을 가지는 형태로 바뀌었습니다.
private final AtomicLong idGenerator = new AtomicLong(System.currentTimeMillis());
public Order createOrder(Long userId, BigDecimal amount) {
long id = idGenerator.incrementAndGet();
...
이를 통해 동일한 밀리초(millisecond) 내에 createOrder가 여러 번 호출되더라도, incrementAndGet()에 의해 ID는 단조 증가합니다.
OrderRepository는 ConcurrentHashMap을 사용하도록 변경되었습니다.
@Repository
public class OrderRepository {
private final Map<Long, Order> store = new ConcurrentHashMap<>();
...
이번 데모 프로젝트는 DB나 외부 ID 채번 서비스를 도입할 정도의 규모는 아닙니다.
따라서 Long ID를 유지하면서 최소한의 변경으로 충돌을 피하는 방침은 자연스럽습니다.
Junie는 구현 후 JetBrains builder로 컴파일 확인을 수행했습니다.
반면, JUnit 자동 테스트는 실행되지 않았습니다.
이유는 Junie의 실행 환경에서 mvn과 mvnw를 찾을 수 없었기 때문입니다.
이 프로젝트에는 pom.xml은 있지만, Maven Wrapper인 mvnw / mvnw.cmd는 없었습니다.
즉, Junie의 보고는 Maven용 테스트 명령어를 실행할 수단이 CLI 환경에 없었기 때문에 mvn test를 실행할 수 없었다는 의미입니다.
또한, 이번 plan에는 Testing Strategy의 독립된 섹션은 없었습니다.
이 프롬프트에서도 테스트 추가나 테스트 실행은 명시하지 않았습니다.
그렇기 때문에, Junie는 plan에 적혀 있던 구현 단계(implementation steps)를 완료하고, 컴파일 확인까지 수행한 뒤 완료했습니다.
테스트 추가나 검증 방침까지 확실히 포함하고 싶다면, 첫 요청 시점이나 plan을 리뷰하는 시점에 명시하는 것이 좋아 보입니다.
이번에 Plan mode가 좋았던 점은, 구현 전에 수정 방침을 확인할 수 있었다는 것입니다.
일반적인 채팅에서는 에이전트가 그대로 구현에 들어갈 수도 있습니다.
Plan mode에서는 먼저 .junie/plans/fix-order-id-generation.md가 생성되었습니다.
그 안에서 다음의 판단을 구현 전에 확인할 수 있었습니다. 이 시점에서 위화감이 있다면, 구현 전에 plan을 편집할 수 있습니다.
Junie CLI Plan mode를 사용하여 개선되었다고 느낀 점은 다음과 같습니다.
구현 전에 방침을 리뷰할 수 있음
코드가 변경되기 전에 설계 판단이나 변경 범위를 확인할 수 있습니다.
계획이 파일로 남음
.junie/plans 하위에 Markdown 형식으로 남기 때문에, 나중에 "무엇을 합의하고 구현했는지"를 추적할 수 있습니다.
Confirm 이후의 구현이 plan을 따라 진행됨
이번에는 plan에 적힌 AtomicLong과 ConcurrentHashMap의 변경 사항이 그대로 구현되었습니다.
spring-demo-project의 OrderService#createOrder를 주제로 Junie CLI의 Plan mode를 시도해 보았습니다.
이번 요청은 상당히 짧은 것이었음에도 불구하고, Junie는 .junie/plans에 계획을 작성하고, Implement 이후에 plan대로 구현을 진행했으며, plan의 단계(steps)도 완료 상태로 업데이트했습니다.
Plan mode는 구현 전의 합의점을 파일로 시각화할 수 있습니다.
적은 프롬프트로도 수정 방침의 초안은 충분히 나옵니다.
거기에 테스트 방침이나 제약 사항을 추가한 뒤 Confirm 할 수 있는 것이, Plan mode의 실용적인 활용법이라고 느꼈습니다.
저희 회사는 JetBrains 제품에 관한 질문이나 상담 등을 받고 있습니다. 저희의 X(구 Twitter) 또는 이메일로 연락해 주시기 바랍니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기