[Chore/#9] Spotless 도입 및 전체 코드 포맷 적용 - #10
Open
sangrae2325 wants to merge 3 commits into
Open
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
#️⃣연관된 이슈
🎯 해결하려는 문제가 무엇인가요?
저장소에 코드 포맷을 강제하는 장치가 전혀 없습니다.
.editorconfig가 없습니다coding-style.md에 들여쓰기·import 순서·줄 길이에 대한 규정이 한 줄도 없습니다이 상태로 인원이 늘면 "로직은 그대로인데 공백·import 순서만 바뀐 diff"가 PR에 섞여 들어옵니다. 리뷰어가 진짜 변경을 찾느라 시간을 쓰게 되고, 서로 다른 IDE 설정끼리 같은 줄을 번갈아 고치는 충돌도 생깁니다.
❓ 왜 해결해야 하나요?
지금이 가장 싼 시점이기 때문입니다.
⭐ 어떻게 해결했나요?
설정 (
87bfc20)gradle/libs.versions.toml에 플러그인을 등록하고, 루트build.gradle.kts의subprojects {}에 적용했습니다.기존
alias(libs.plugins.springBoot) apply false+subprojects {}공통 설정(Java 21 toolchain, lombok, JUnit) 패턴을 그대로 따랐습니다. 새 패턴을 만들지 않았습니다.spotlessCheck는 플러그인이 자동으로check에 연결합니다. 배선 코드를 따로 쓰지 않았습니다실행으로 확인한 것
spotlessCheck→check자동 연결 (16개 모듈)check --dry-run으로 확인--add-exports옵션 불필요check실패BUILD FAILED확인.git-blame-ignore-revs동작게이트 동작 검증 —
BusinessException.java의 들여쓰기를 한 줄 망가뜨리고./gradlew :core:common:check를 돌렸습니다.blame 보존 (
f06fb1e)전체 포맷의 실질적 부작용은
git blame이 망가지는 것입니다. 포맷된 모든 줄의 마지막 수정자가 포맷 커밋으로 찍혀서 "이 코드 왜 이렇게 짰지"를 추적할 수 없게 됩니다..git-blame-ignore-revs에2befe6b를 등록했습니다. GitHub 웹 blame은 이 파일을 자동으로 인식합니다. 로컬 CLI에서도 쓰시려면 각자 한 번만 설정하시면 됩니다.실제 효과 (
SecurityConfig.java29~34줄):🧩 이 PR의 한계 & 트레이드오프
1. CI 워크플로가 없어서 실제로는 강제되지 않습니다⚠️
이게 가장 큰 한계입니다.
git-convention.md3-4절은 "CI 필수 체크:gradle check가 통과해야 Merge할 수 있다"고, 4절은deploy-dev.yml/deploy-prod.yml을 설명합니다. 그런데.github/workflows디렉터리 자체가 없습니다. 문서에 적힌 CI 게이트가 실제로는 존재하지 않습니다.따라서 이 PR로
spotlessCheck를check에 물려도, 실제 강제력은 각자 로컬에서./gradlew check를 돌릴 때만 생깁니다. PR 단계에서 자동으로 막히지 않습니다.CI 워크플로 작성을 별도 이슈로 올려야 이 작업이 실효를 갖습니다.
2. Spring Security 체인 가독성이 나빠졌습니다
google-java-format은 fluent 빌더 체인을 잘 다루지 못합니다.
SecurityConfig가 직격탄입니다.이건 개선이 아니라 후퇴입니다. 감수하고 가는 트레이드오프입니다 — 아래 "검토한 대안"에 palantir와 실측 비교를 적었습니다.
3. IDE 설정 안내가 필요합니다
google-java-format은 import를 알파벳순 단일 블록으로 정렬하고 4칸/100자를 강제합니다. IntelliJ 기본 설정과 다르면 저장할 때마다 IDE와 spotless가 서로 다른 결과를 만들어 매번
spotlessApply로 되돌리게 됩니다.머지 전에 각자 IntelliJ에 google-java-format 플러그인을 설치하고 AOSP 스타일로 설정해주셔야 합니다.
4. 이번 범위에서 제외한 것
.gradle.kts/md) 포맷 규칙.editorconfig— IDE 플러그인으로 충분한지 먼저 보고 판단하는 게 낫다고 봤습니다5.
87bfc20시점에는check가 실패합니다포맷터는 깔렸는데 코드는 아직 포맷 전이라 중간 상태가 red입니다.
2befe6b에서 green으로 돌아옵니다. 이 PR이 merge commit으로 머지되면 그 중간 커밋이main히스토리에 남아,git bisect가 그 한 커밋에 걸릴 경우 포맷 때문에 빌드가 깨진 것처럼 보일 수 있습니다.설정과 포맷을 한 커밋으로 합치면 이 문제는 사라지지만, 리뷰어가 "설정 13줄"과 "22파일 리포맷"을 한 diff에서 봐야 합니다. 리뷰 편의를 택했습니다.
⛓️ 기존 기능에 미치는 영향
런타임 동작 변경 없습니다. 포맷 커밋은 공백·줄바꿈 재배치뿐입니다.
git diff -U0 | grep '^[+-]import'결과가 비어 있습니다. 미사용 import 제거가 일어나지 않아 의존성이 사라질 위험이 없습니다./gradlew clean build통과 (93개 태스크)ModularityTests2건 +StreamServerApplicationTests1건 통과 — 모듈 경계verify()정상architecture.md의 의존 방향이나 Modulith 경계에 영향 없습니다다른 브랜치와의 충돌 — 현재 열린 PR이 0개라 지금은 문제가 없습니다. 다만 이 PR이 머지된 뒤 파생된 브랜치가 있다면
main을 머지할 때 광범위한 충돌을 겪습니다. 해결법은 아래에 적었습니다.🔀 Edge Case & 실패 시나리오
./gradlew check실패 (검증 완료)./gradlew spotlessApplymain머지 시 충돌./gradlew spotlessApply→ 커밋git blame이 포맷 커밋으로 덮임git config blame.ignoreRevsFile .git-blame-ignore-revs📋 검토한 대안과 선택 이유
palantir-java-format — 실제로 돌려서 비교했습니다
체인 가독성 문제 때문에 palantir로 전체 포맷을 한 번 돌려 같은 파일을 비교했습니다.
sessionManagementexceptionHandlingALL_PATH_PATTERNS체인.requestMatchers()/.permitAll()쌍palantir가 객관적으로 덜 망가뜨립니다. 그럼에도 google-java-format을 선택했습니다:
.requestMatchers(...).permitAll()쌍 분리는 palantir도 똑같습니다. 체인이 한 줄에 안 들어가면 메서드당 한 줄로 끊는 건 두 포맷터 공통 동작이라, palantir로 바꿔도SecurityConfig의 핵심 가독성 문제는 해결되지 않습니다바꾸실 거면 지금이 제일 쌉니다. 팀이 쓰기 시작한 뒤에는 전체 리포맷 커밋을 한 번 더 해야 합니다. 이견 있으시면 이 PR에서 말씀해주세요.
ratchet (
ratchetFrom("origin/main")) — 채택하지 않음매 빌드마다
origin/main대비 변경된 파일만 검사하는 옵션입니다. 대규모 리포맷 커밋 없이 손대는 파일부터 점진적으로 포맷할 수 있습니다.actions/checkout기본fetch-depth: 1)과 충돌해 빌드가 깨집니다. 나중에 CI 만들 때fetch-depth: 0을 기억해야 하는 함정이 생깁니다pre-commit 훅 — 이번엔 제외
spotlessApply를 자동 실행할 수 있지만 팀원 각자 설치해야 하고 커밋이 느려집니다. CI가 먼저 있어야 의미가 있다고 봤습니다.💬 리뷰 포인트
[r]포맷터 선택 — google-java-format AOSP vs palantir. 위 실측 비교를 보고 판단해주세요. 바꾸려면 지금이 가장 쌉니다[r]SecurityConfig가독성 후퇴를 감수할 만한가 —gateway/auth/.../SecurityConfig.java의authorizeHttpRequests블록입니다. 이게 받아들이기 어려우면 포맷터 선택을 다시 논의해야 합니다[c]설정 커밋(87bfc20)의 13줄 — 포맷 커밋은spotlessApply출력이라 넘기셔도 됩니다.subprojects {}배치가 기존 패턴과 맞는지만 봐주세요[c]CI 이슈를 누가 언제 만들지 — 이게 없으면 이 PR은 로컬 규율에 그칩니다[a].git-blame-ignore-revs주석 문구