10 KiB
Phase 9 최근 소식 nested payload 리뷰
1. 리뷰 정보
| 항목 | 내용 |
|---|---|
| 리뷰 대상 | Phase 9 / Task 9.1~9.7 |
| 기준 commit 또는 working tree | f600d10e8b63b6fdc812b18a874b077dfe072ca8 + 2026-07-31 working tree |
| 리뷰 일자 | 2026-07-31 |
| 리뷰어 | Codex |
| 기준 문서 | 대상 prd.md, plan-task.md |
| 리뷰 상태 | 수정 검증 완료 |
2. 리뷰 목적과 범위
목적
- type별 nested payload DTO, mapper 필터, nullable image, adapter binding과 target ID 보존을 확인한다.
- Task 9.7의 미사용
FeedAdapter·FeedImageViews제거가 실제 호출자 0건과 일치하는지 확인한다.
포함 범위
HomeFollowingModels.kt,HomeFollowingUiModels.kt,HomeFollowingMappers.kt,HomeFollowingNewsAdapter.kt- 삭제된
v2/widget/feed/FeedAdapter.kt와 현재 재사용 중인FeedItem,FeedRankView,FeedContentView,FeedCommunityView - mapper/source tests와 content ranking string resource
제외 범위
- 실제 서버 응답 샘플과 Phase 11/13 터치 목적지.
3. 판정 기준
- PRD의 nested payload 필드, matching payload null 제외,
contentImageUrl: String?,visibleFromAtUtc정책을 기준으로 판정했다.
4. 검토한 근거
HomeFollowingModels.kt:47-111HomeFollowingMappers.kt:64-149HomeFollowingUiModels.kt:55-103HomeFollowingNewsAdapter.kt:68-167HomeFollowingMapperTest.kt:88-182
실행한 검증
| 명령 | 결과 | 핵심 증거 |
|---|---|---|
| mapper + Fragment source focused test | 성공 | mapper 10, source 16 테스트 실패 0 |
| 팔로잉 전체 회귀 | 성공 | 39개 테스트 실패 0 |
| resource/compile/ktlint/diff | 성공 | 모두 PASS |
nullable image binding·Coil 2.7.0 source·저장소 dispose() 사용례 대조 |
문제 확인 | blank 분기가 drawable만 비우고 View 요청을 해제하지 않음 |
| DTO→mapper→Feed item의 ID 의미 대조 | 문제 확인 | content/post ID가 creator ID 필드에 대입됨 |
| `rg -n "FeedAdapter | FeedImageViews" app/src/main/java app/src/test/java docs/20260625_메인_홈_팔로잉_탭 docs/agent-guides` | 성공 |
5. 발견 사항 요약
| ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal |
|---|---|---|---|---|---|
REV-P9-001 |
Medium | 수정 완료 | nullable 최근 소식 이미지 바인딩이 이전 Coil 요청을 해제하지 않음 | Task 9.5 | P9-R1 완료 |
REV-P9-002 |
Low | 수정 완료 | API에 없는 creator ID를 content/post ID로 합성해 Feed model에 전달함 | Task 9.6 | P9-R2 완료 |
6. 발견 사항 상세
matching payload가 null이면 mapNotNull로 제외되고, content image null은 UI model까지 보존된다. visibleFromAtUtc 기준 상대 시간과 payload별 target ID도 현재 계약과 일치한다. Task 9.7에서 삭제한 FeedAdapter와 FeedImageViews는 production/test 호출자가 없고, 최근 소식은 현재 FeedItem과 타입별 Feed view를 직접 재사용하므로 삭제에 따른 확정 발견 사항은 없다.
REV-P9-001 — nullable 최근 소식 이미지 바인딩이 이전 Coil 요청을 해제하지 않음
- 심각도: Medium
- 상태: 수정 완료
- 관련 요구사항: PRD
contentImageUrl·imageUrlnullable 계약, Task 9.3 Feed image fallback - 소유 Task: Task 9.5
관찰 내용
HomeFollowingNewsAdapter.bindImage()는 blank URL에서 setImageDrawable(null)만 호출한다. 같은 ViewHolder가 이미지가 있는 item에서 nullable image item으로 재사용될 때 이전 Coil 요청이 진행 중이면, 그 요청이 완료되면서 현재 item에 이전 이미지가 표시될 수 있다.
근거
- 코드:
HomeFollowingNewsAdapter.kt:187-203의 blank 분기에dispose()가 없음 - 계약:
FollowingContentNewsResponse.contentImageUrl,FollowingContentRankingNewsResponse.contentImageUrl,FollowingCommunityPostNewsResponse.imageUrl은 nullable - 테스트:
HomeFollowingMapperTest는 null 보존만 확인하고 adapter 재사용 시 이미지 요청 상태는 검증하지 않음 - 저장소 정상 사용례:
HomePopularCommunityAdapter.kt:67-70은 blank image에서imageView.dispose()와 drawable 초기화를 함께 수행함 - Coil 2.7.0 로컬 source:
ImageView.dispose()는 View의 현재 요청을 취소하고 분리함
재현 또는 검증 절차
- 최근 소식 ViewHolder의 image view에 item A의 완료 전 Coil 요청을 연결한다.
- 같은 holder에
contentImageUrl = null또는imageUrl = null인 item B를 바인딩한다. - 현재 blank 분기는 drawable만 비우므로 요청 A가 남는다.
- 요청 A가 완료되면 B에 A의 이미지가 표시될 수 있다. 요구 결과는 blank 바인딩 시 요청을 해제하고 빈 이미지 상태를 유지하는 것이다.
영향
빠른 스크롤이나 느린 이미지 응답 조건에서 ranking/content/community 최근 소식 카드에 다른 item의 이미지가 노출될 수 있다.
권장 조치
공유되는 bindImage() blank 분기에서 imageView.dispose() 후 drawable을 비운다. 한 대표 nullable content item의 ViewHolder 재사용을 Robolectric test로 고정하고 mapper/Feed 회귀를 실행한다.
판정 기록
- 2026-07-30 — nullable DTO·adapter binding·Coil request lifecycle·저장소 내 정상 해제 패턴을 대조해 확정.
- 2026-07-30 — 구현 전 Phase 9 Task 9.5 /
P9-R1로 전환. - 2026-07-30 — Task 9.5에서
HomeFollowingNewsAdapter.bindImage()blank 분기에dispose()를 추가하고, Robolectric adapter binding test와 팔로잉 회귀 검증을 완료.
REV-P9-002 — API에 없는 creator ID를 다른 target ID로 합성함
- 심각도: Low
- 상태: 수정 완료
- 관련 요구사항: Task 6.4의 API 미제공 값 비합성 정책, Phase 9 nested payload 계약
- 소유 Task: 신규 Task 9.6
관찰 내용
FollowingContentNewsResponse와 FollowingCommunityPostNewsResponse에는 creatorId가 없지만 adapter가 FeedItem.Content.creatorId에는 content.targetId, FeedItem.Community.creatorId에는 community.targetId를 문자열로 넣는다. 두 target은 각각 contentId, postId이므로 creator ID가 아니다.
근거
- DTO:
HomeFollowingModels.kt의 content/community nested payload에는creatorId없음 - mapper: content
targetId = contentId, communitytargetId = postId - adapter:
HomeFollowingNewsAdapter.kt에서 두FeedItem.creatorId에 각각 위 target ID를 대입 - 현재 클릭 경로: adapter callback은 원본
HomeFollowingNewsUiItem을 캡처하므로 현행 상세 이동에는 잘못된creatorId가 사용되지 않음
재현 또는 검증 절차
- content/community nested payload와 UI model의 target ID 의미를 대조한다.
- 두 Feed item binding의
creatorId대입값을 확인한다. - 실제 결과는 content/post ID가 creator ID 필드에 들어가며, 요구 결과는 서버가 제공하지 않은 ID를 합성하지 않는 것이다.
영향
현재 화면 클릭에는 직접 영향이 없지만 Feed model의 의미가 잘못되고, 공통 Feed 위젯이 추후 creator ID를 사용하면 잘못된 크리에이터 이동이나 분석 값으로 이어질 수 있다.
권장 조치
서버 계약을 추정해 확장하지 말고 두 Feed item의 creatorId를 빈 값으로 전달한다. 실제 contentId, postId와 기존 원본 UI model click callback은 유지한다.
판정 기록
- 2026-07-31 — DTO→mapper→adapter→click callback을 추적해 확정하고 Phase 9 Task 9.6 /
P9-R2로 전환. - 2026-07-31 — Task 9.6에서
FeedItem.Content와FeedItem.Community의creatorId를 빈 값으로 전달하도록 수정하고 focused/팔로잉 회귀 검증을 완료.
7. 확정 항목의 plan·goal 전환
- Phase 9에 Task 9.5 /
P9-R1을 추가했으며 구현·검증을 완료했다. - 최종 재점검에서 Task 9.6 /
P9-R2를 추가했으며 creator ID 합성 제거를 완료했다. - Task 9.7 /
P9-R3의 미사용FeedAdapter제거와 문서 정리를 현재 호출자 검색·컴파일·회귀 결과로 확인했다.
8. 리뷰 종료 판정
| 판정 항목 | 결과 | 근거 |
|---|---|---|
| 리뷰 범위 전체 확인 | 충족 | DTO→mapper→adapter 추적과 Task 9.7 미사용 호출자 검색 |
| 후보 항목 판정 완료 | 충족 | REV-P9-001~002 판정 완료 |
| 확정 항목 plan 반영 | 충족 | Task 9.5, Task 9.6 추가 |
| 검증 명령과 결과 기록 | 충족 | focused/통합 PASS |
최종 결론: 확정 발견 사항 모두 수정 완료
남은 항목: 없음
9. 수정 후 검증 기록
- 2026-07-30:
REV-P9-001보완으로HomeFollowingNewsAdapter.bindImage()의 blank image 분기에서 기존 Coil 요청을dispose()로 해제한 뒤 drawable을 비우도록 변경했다. 구현 전HomeFollowingNewsAdapterTestRED, 구현 후 focused GREEN, 팔로잉 전체 회귀와 신규 프로필 테스트,:app:mergeDebugResources,:app:compileDebugKotlin,:app:ktlintCheck,git diff --checkPASS. - 2026-07-31 재점검: nested payload mapper·nullable image 회귀를 포함한 Phase 1~13 focused 113개 test, resource merge, Kotlin compile, ktlint PASS. 추가 확정 발견 사항 없음.
- 2026-07-31 최종 재점검: API에 없는 creator ID를 content/post ID로 대신 채우는 binding을
REV-P9-002로 확정하고, production/test 코드를 변경하기 전에 Task 9.6 /P9-R2로 전환했다. - 2026-07-31 최종 검증: nested payload·adapter 회귀를 포함한 통합 20개 suite·306개 test와 resource merge, Kotlin compile, ktlint가 PASS했다. 자동 검증 성공과 별개로 ID 의미 계약 불일치
REV-P9-002는 Task 9.6의 수정 항목으로 유지했다. - 2026-07-31:
REV-P9-002보완으로HomeFollowingNewsAdapter의 content/community Feed model에서 API가 제공하지 않는creatorId를 content/post ID로 합성하지 않고 빈 값으로 전달하도록 변경했다. RED focused test 실패를 확인한 뒤 GREEN으로 전환했고, focused test, 팔로잉 전체 회귀,:app:compileDebugKotlin,:app:ktlintCheckPASS. - 2026-07-31 이번 리뷰 재검증: nested payload·nullable image·ID 의미와 Task 9.7의 미사용
FeedAdapter삭제 상태를 다시 대조했다. production/testFeedAdapter|FeedImageViews호출자는 0건이며, 통합 16개 suite·120개 test, resource merge, Kotlin compile, ktlint가 PASS해 추가 확정 발견 사항이 없다.