Skip to content

[SW-92] fix : 지하철명 통일 및 병렬처리 개선#87

Merged
casylm merged 1 commit intodevelopfrom
fix/SW-92-async-midpoint-calculation
Feb 8, 2026
Merged

[SW-92] fix : 지하철명 통일 및 병렬처리 개선#87
casylm merged 1 commit intodevelopfrom
fix/SW-92-async-midpoint-calculation

Conversation

@casylm
Copy link
Copy Markdown
Collaborator

@casylm casylm commented Feb 8, 2026

PR 제목

작업 유형

  • 새로운 기능 추가
  • 기존 기능 수정
  • 버그 수정
  • 리팩토링
  • 문서 업데이트
  • 테스트 추가/수정

작업 내용

  • 작업 1 :지하철명 통일 및 병렬처리 개선
  • 작업 2
  • 작업 3

체크리스트

  • 코드 컴파일 및 테스트 통과
  • 커밋 메시지 컨벤션 준수
  • 불필요한 디버깅 코드 제거

참고 사항 (선택)

  • 추가로 팀원이 참고할 내용

Summary by CodeRabbit

Release Notes

  • Performance

    • Optimized midpoint candidate extraction through concurrent route API processing for faster results.
  • Bug Fixes

    • Improved station name matching consistency by standardizing station name formats across route parsing and candidate evaluation.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 8, 2026

📝 Walkthrough

Walkthrough

This PR introduces parallelization to candidate extraction in MidPointAsyncUseCase using CompletableFuture's allOf pattern, replacing synchronous per-candidate processing. Additionally, station name normalization (removing trailing "역") is added to both MidPointAsyncUseCase and SeoulMetroRouteParser for consistent station matching.

Changes

Cohort / File(s) Summary
Async parallelization
src/main/java/swyp/mingling/domain/meeting/service/MidPointAsyncUseCase.java
Replaces per-candidate synchronous inner processing with CompletableFuture allOf-based parallelization. Route API calls now start concurrently, followed by non-blocking thenApply processing to compute travel time statistics and build MidPointCandidate objects. Overall synchronization ensures all candidate futures complete before final result aggregation and sorting.
Station name normalization
src/main/java/swyp/mingling/domain/meeting/service/MidPointAsyncUseCase.java, src/main/java/swyp/mingling/domain/subway/parser/SeoulMetroRouteParser.java
Introduces private normalizeStationName() helper method in both classes to remove trailing "역" from station names. Applied consistently across start/end station extraction, station list construction, arrival station handling, and transfer path generation to standardize equality checks.

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly Related PRs

Suggested Reviewers

  • sik2Boii
  • rkdrudgns0412
  • Yongseok-2

Poem

🐰 The async hare hops fast and free,
Futures dancing in harmony,
Station names now clean and bright,
Parallel routes—a speedy flight!
Midpoint logic shines tonight ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Description check ❓ Inconclusive The description follows the template structure but lacks critical details: the checklist items are unchecked (compile, convention, debug code), and there are no implementation details, changes explanation, or references to explain the actual modifications. Fill in the checklist confirming code compilation and testing, provide detailed explanation of station name normalization and parallelization improvements, and explain the asynchronous changes made.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes: subway station name unification and parallelization improvements, which directly correspond to the primary modifications in both affected files.
Docstring Coverage ✅ Passed Docstring coverage is 87.50% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/SW-92-async-midpoint-calculation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Collaborator

@Yongseok-2 Yongseok-2 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

확인했습니다

@casylm casylm merged commit 5dfbc8e into develop Feb 8, 2026
1 check was pending
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/main/java/swyp/mingling/domain/meeting/service/MidPointAsyncUseCase.java (2)

93-119: ⚠️ Potential issue | 🟠 Major

Blocking I/O on ForkJoinPool.commonPool() can starve the JVM-wide shared pool.

CompletableFuture.supplyAsync(...) without an explicit Executor runs on ForkJoinPool.commonPool(), which has limited parallelism (typically Runtime.availableProcessors() - 1). The lambdas here perform blocking HTTP calls (subwayRouteService.getRoute). With 5 candidates × N participants, you can easily saturate the common pool and block unrelated async work across the application.

Inject a dedicated Executor (e.g., a ThreadPoolTaskExecutor bean) and pass it as the second argument:

CompletableFuture.supplyAsync(() -> { ... }, asyncExecutor)

112-116: ⚠️ Potential issue | 🔴 Critical

Original concern is addressed, but inconsistent normalization implementations create a data mismatch issue.

Lines 114–115 do pass raw station names to subwayRouteService.getRoute(), but the API handles normalization internally at lines 31–32 of SubwayRouteService, so the concern about mismatched names in the API call is not an issue.

However, verification reveals a critical problem: normalization logic is inconsistent across the codebase. SubwayRouteService.normalizeStationName() has a special case that preserves "서울역" unchanged (line 121), while MidPointAsyncUseCase.normalizeStationName() always removes the "역" suffix. This means when MidPointAsyncUseCase normalizes "서울역" to "서울" for comparison (line 99), but then passes raw "서울역" to the API (lines 114–115), the API normalizes it to "서울역", creating a data inconsistency. The same names are used inconsistently throughout the response objects.

Align the normalization implementations so all code applies the same rules.

🤖 Fix all issues with AI agents
In
`@src/main/java/swyp/mingling/domain/meeting/service/MidPointAsyncUseCase.java`:
- Around line 294-305: The normalizeStationName(String stationName) method in
MidPointAsyncUseCase incorrectly strips the trailing "역" from "서울역" (causing
mismatches); update normalizeStationName to preserve "서울역" by adding a guard
like "서울역".equals(stationName) before trimming, or better, extract and reuse a
shared utility (e.g., a new StationNameUtils.normalize or the existing
SubwayRouteService helper) and call that from
MidPointAsyncUseCase.normalizeStationName to ensure consistent behavior with
SeoulMetroRouteParser/SubwayRouteService.
- Around line 162-167: The code currently calls allCandidates.join() in
MidPointAsyncUseCase which can block indefinitely if any
subwayRouteService.getRoute() future hangs; modify the completion wait to apply
a timeout (e.g., use CompletableFuture.orTimeout on allCandidates or call get
with a TimeUnit timeout) and handle the TimeoutException/CompletionException
path to fail the request gracefully or cancel lingering candidateFutures—update
references to allCandidates, candidateFutures and the
subwayRouteService.getRoute() callers so timed-out futures are logged/cancelled
and a proper error response is returned instead of blocking forever.
- Around line 136-154: The code in MidPointAsyncUseCase that computes
min/max/avg over "routes" must guard against an empty list: before computing
min/max/avg and creating a MidPointCandidate, add an early check like "if
(routes == null || routes.isEmpty())" and handle it (either return a safe value
— e.g., null or Optional.empty() — or throw a clear exception such as
IllegalArgumentException with a descriptive message); update the method's return
type/signature if you choose Optional to propagate the empty-case safely instead
of causing divide-by-zero and wrong min/max values when creating the
MidPointCandidate.

Comment on lines +136 to +154
int min = Integer.MAX_VALUE;
int max = Integer.MIN_VALUE;

int deviation = max - min;
for (SubwayRouteInfo route : routes) {
int time = route.getTotalTravelTime();
min = Math.min(min, time);
max = Math.max(max, time);
}

int sum = 0;
for (SubwayRouteInfo route : routes) {
sum += route.getTotalTravelTime();
}
int deviation = max - min;

int sum = 0;
for (SubwayRouteInfo route : routes) {
sum += route.getTotalTravelTime();
}

int avgTime = sum / routes.size();

return new MidPointCandidate(routes, deviation, avgTime);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Edge case: routes being empty would cause ArithmeticException on division and incorrect min/max.

If departurelists is empty, routes will be an empty list. min stays Integer.MAX_VALUE, max stays Integer.MIN_VALUE, deviation overflows, and line 152 divides by zero. Consider adding a guard.

💡 Suggested guard
+                        if (routes.isEmpty()) {
+                            return new MidPointCandidate(routes, Integer.MAX_VALUE, Integer.MAX_VALUE);
+                        }
+
                         int min = Integer.MAX_VALUE;
                         int max = Integer.MIN_VALUE;
🤖 Prompt for AI Agents
In `@src/main/java/swyp/mingling/domain/meeting/service/MidPointAsyncUseCase.java`
around lines 136 - 154, The code in MidPointAsyncUseCase that computes
min/max/avg over "routes" must guard against an empty list: before computing
min/max/avg and creating a MidPointCandidate, add an early check like "if
(routes == null || routes.isEmpty())" and handle it (either return a safe value
— e.g., null or Optional.empty() — or throw a clear exception such as
IllegalArgumentException with a descriptive message); update the method's return
type/signature if you choose Optional to propagate the empty-case safely instead
of causing divide-by-zero and wrong min/max values when creating the
MidPointCandidate.

Comment on lines +162 to +167
CompletableFuture<Void> allCandidates = CompletableFuture.allOf(
candidateFutures.toArray(new CompletableFuture[0])
);

return new MidPointCandidate(routes, deviation, avgTime);
}))
.toList(); // 객체를 하나의 리스트로 생성
// 모든 작업 완료 대기 후 결과 수집
allCandidates.join();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

No timeout — a hung API call will block the request indefinitely.

allCandidates.join() at line 167 has no timeout. If any subwayRouteService.getRoute() call hangs or is extremely slow, this will block the calling thread (and the HTTP request) forever.

Consider using orTimeout or get with a timeout:

💡 Proposed fix
-        allCandidates.join();
+        try {
+            allCandidates.get(30, java.util.concurrent.TimeUnit.SECONDS);
+        } catch (Exception e) {
+            log.error("중간지점 계산 타임아웃 - meetingId: {}", meetingId, e);
+            throw new RuntimeException("중간지점 계산 시간 초과", e);
+        }

Or if on Java 9+:

allCandidates.orTimeout(30, TimeUnit.SECONDS).join();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
CompletableFuture<Void> allCandidates = CompletableFuture.allOf(
candidateFutures.toArray(new CompletableFuture[0])
);
return new MidPointCandidate(routes, deviation, avgTime);
}))
.toList(); // 객체를 하나의 리스트로 생성
// 모든 작업 완료 대기 후 결과 수집
allCandidates.join();
CompletableFuture<Void> allCandidates = CompletableFuture.allOf(
candidateFutures.toArray(new CompletableFuture[0])
);
// 모든 작업 완료 대기 후 결과 수집
try {
allCandidates.get(30, java.util.concurrent.TimeUnit.SECONDS);
} catch (Exception e) {
log.error("중간지점 계산 타임아웃 - meetingId: {}", meetingId, e);
throw new RuntimeException("중간지점 계산 시간 초과", e);
}
🤖 Prompt for AI Agents
In `@src/main/java/swyp/mingling/domain/meeting/service/MidPointAsyncUseCase.java`
around lines 162 - 167, The code currently calls allCandidates.join() in
MidPointAsyncUseCase which can block indefinitely if any
subwayRouteService.getRoute() future hangs; modify the completion wait to apply
a timeout (e.g., use CompletableFuture.orTimeout on allCandidates or call get
with a TimeUnit timeout) and handle the TimeoutException/CompletionException
path to fail the request gracefully or cancel lingering candidateFutures—update
references to allCandidates, candidateFutures and the
subwayRouteService.getRoute() callers so timed-out futures are logged/cancelled
and a proper error response is returned instead of blocking forever.

Comment on lines +294 to +305
private String normalizeStationName(String stationName) {
if (stationName == null || stationName.isEmpty()) {
return stationName;
}

// "역"이 끝에 붙어있으면 제거
if (stationName.endsWith("역")) {
return stationName.substring(0, stationName.length() - 1);
}

return stationName;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Bug: Same missing "서울역" special case as in SeoulMetroRouteParser.

This copy also lacks the "서울역" guard present in SubwayRouteService. "서울역" will be incorrectly shortened to "서울", breaking the equality check at line 99 and any downstream lookups.

Apply the same fix (add the "서울역".equals(stationName) guard), or better yet, share a single utility method.

🤖 Prompt for AI Agents
In `@src/main/java/swyp/mingling/domain/meeting/service/MidPointAsyncUseCase.java`
around lines 294 - 305, The normalizeStationName(String stationName) method in
MidPointAsyncUseCase incorrectly strips the trailing "역" from "서울역" (causing
mismatches); update normalizeStationName to preserve "서울역" by adding a guard
like "서울역".equals(stationName) before trimming, or better, extract and reuse a
shared utility (e.g., a new StationNameUtils.normalize or the existing
SubwayRouteService helper) and call that from
MidPointAsyncUseCase.normalizeStationName to ensure consistent behavior with
SeoulMetroRouteParser/SubwayRouteService.

@coderabbitai coderabbitai Bot mentioned this pull request Feb 11, 2026
12 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants