지금까지 정한 표준은 전부 근거를 적을 수 있는 것이었다. 애그리거트 경계는 트랜잭션으로 갈랐고, 페이징은 임의 페이지 점프로 갈랐다. 버린 안마다 “언제 무엇이 깨지는가”를 적었다.
포맷은 다르다. 들여쓰기가 2칸이든 4칸이든 아무것도 깨지지 않는다.
// 안 A
public class OrderSearchController {
private final OrderSearchUseCase useCase;
}
// 안 B
public class OrderSearchController {
private final OrderSearchUseCase useCase;
}
어느 쪽이 옳은지 물으면 답이 없다. 그런데 답을 안 하면 코드가 두 모양으로 갈린다. 근거가 없는데 정해야만 하는 결정이고, 이 저장소가 처음 만나는 종류다.
이 글은 그 결정을 어떻게 내렸는지를 적는다.
먼저: 여기서는 코드를 주로 쓰는 게 사람이 아니다
일반적인 포맷터 논의의 근거는 “팀원끼리 diff로 싸우지 말자”다. 이 프로젝트에는 팀원이 없다. 대신 Claude Code와 Codex가 파일을 직접 쓴다.
이 차이가 판단을 두 군데서 뒤집는다.
하나, AI는 규칙을 확률적으로 지킨다. 규칙 100개 중 97개를 지키면 로직에서는 훌륭한 결과지만, 포맷에서는 3%가 그대로 코드에 남는다.
둘, 남은 3%가 규칙 노릇을 시작한다. AI에게 주변 코드는 지침보다 강한 신호다. 어긋난 포맷이 한 번 커밋되면 다음에 AI가 그 파일을 읽고 그 모양을 따라 쓴다. 혼자 쓰는 저장소라 그걸 잡아줄 리뷰가 없다.
문서에 적어둔 포맷 규칙은 어겨지는 게 아니라 조용히 대체된다.
그리고 하나 더. AI는 IDE를 안 거친다. 저장 이벤트가 없으므로 “저장할 때 자동 포맷”은 여기서 AI가 만든 코드를 하나도 잡지 못한다.
원칙 1. 포맷은 도구가 강제한다
정해야 했던 건 이거였다 — 포맷을 무엇이 지키게 할 것인가.
안 A — 도구가 강제한다. 포맷터를 빌드에 걸고 도구가 코드를 고친다.
안 B — 규칙으로 지킨다. .editorconfig와 표준 문서에 규칙을 적고 사람과 AI가 지킨다.
얻는 것은 도구 도입 비용이 0이고, 마음에 안 드는 모양이 나와도 손댈 수 있다는 것이다.
버린 이유는 위에서 본 자기복제다. 그리고 비용이 하나 더 있다. 포맷은 파일을 만들 때마다 걸리는 규칙이라 3단 구조에서 1단(CLAUDE.md) 후보가 되는데, 1단은 매 요청에 토큰을 낸다. 안 B는 포맷을 가장 비싼 칸에 넣는 선택이다.
안 C — 아무것도 안 한다. IDE 기본값에 맡긴다. 버린 이유는 도구마다 기본값이 달라서 안 B의 결과에 무작위가 더해질 뿐이라는 것이다.
골랐다 — 안 A. 안 B와 안 C가 못 지켜주는 건 결정론이다. 이 저장소는 같은 판단을 이미 한 번 내렸다. .claude/skills와 .agents/skills를 함께 고치는 건 문서에 적힌 규칙이었는데 실제로 4개가 어긋난 채 배포됐고, 그래서 배포 스크립트가 해시를 대조하게 만들었다. 사람이 기억해야만 지켜지는 규칙은 기계로 옮긴다가 그때 세운 것이다.
대신 포맷 규칙이 도구 설정으로 이사하면서 표준 문서에서 사라지는 것을 감수한다. 규칙이 어디 있는지 알려면 build.gradle을 봐야 한다.
원칙 2. 포맷 값은 취향으로 정하고, 항목마다 근거를 적지 않는다
도구를 쓰기로 했으면 그 도구에 무슨 규칙을 줄지가 남는다.
안 가 — 기성품 규칙을 통째로 받는다. google-java-format은 설정 항목이 아예 없고 변형 둘(GOOGLE/AOSP)만 있다. 얻는 것은 고를 게 없어서 논쟁이 닫힌다는 것이다.
안 나 — palantir-java-format을 받는다. 같은 계열인데 줄바꿈이 사람 친화적이다. 버린 이유는 없는 문제를 위해 덜 검증된 도구를 고르는 것이라서다. 긴 체이닝이 실제로 문제가 될지 아직 모른다.
안 다 — Eclipse 포맷터 설정 XML을 직접 만든다. 얻는 것은 원하는 모양을 정확히 만들 수 있다는 것이다.
안 라 — 전체 재포맷 없이 개별 스텝만 건다. removeUnusedImports, trimTrailingWhitespace 정도만 쓴다. 얻는 것은 기존 코드가 안 뒤집히는 것인데, 소스가 아직 없어서 지킬 기존 코드가 없다. 이 안의 유일한 장점이 지금은 값이 0이고, 들여쓰기를 안 잡으면 원칙 1의 자기복제 경로가 그대로 남는다.
골랐다 — 안 다. 그런데 고른 이유가 앞의 표준들과 다르다. “안 가가 못 지켜주는 무엇”이 있어서가 아니라, 이 영역은 내 취향으로 정하는 게 맞다고 판단해서다.
여기서 반드시 구분해야 할 게 있다. 항목마다 근거를 못 적는 것과, 이 결정에 근거가 없는 것은 다르다.
포맷의 목적은 “옳은 모양”이 아니라 “한 가지 모양”이다. 어떤 값이든 일관되기만 하면 목적을 달성한다. 그래서 항목마다 근거를 요구하면 결정이 멈추기만 하고 얻는 게 없다. 이 영역은 근거 없이 정하기로 정했다.
근거가 항목이 아니라 영역 단위로 붙는다. 이건 6개월 뒤에 복원된다 — “왜 중괄호가 여기 있지”의 답이 “그건 근거 없이 정한 영역이다”가 되고, 그 순간 다시 논쟁하지 않는다. 반대로 항목마다 그럴싸한 근거를 지어 붙였으면 6개월 뒤의 내가 그 지어낸 근거를 검토하느라 시간을 쓴다.
근거 없음을 명시하는 것이 근거를 지어내는 것보다 낫다.
원칙 3. 값은 아홉 개를 정하고 그 모양을 코드로 남긴다
정한 값이다. 근거는 없다 — 원칙 2가 그렇게 정했다.
| 항목 | 값 |
|---|---|
| 들여쓰기 | 4칸 |
| 이어지는 줄 | 8칸 |
| 줄 길이 | 180 |
| 중괄호 | 줄 끝에 붙인다 |
| import 순서 | java → org → com → 나머지 |
| import 그룹 사이 | 빈 줄 없음 |
static import |
맨 아래, 앞에 빈 줄 하나 |
| 빈 줄 | 클래스 첫 줄·메서드 사이·멤버 종류가 바뀌는 자리 |
| 파라미터 | 한 줄에 안 들어가면 전부 내려 하나씩 나열 |
값 목록만으로는 모양이 복원되지 않는다. 그래서 실제로 돌려서 나온 코드를 같이 남긴다.
package com.example.order.api;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestParam;
import com.example.order.application.OrderSearchUseCase;
import jakarta.validation.Valid;
import static java.util.Objects.requireNonNull;
public class OrderSearchController {
private final OrderSearchUseCase orderSearchUseCase;
public OrderSearchController(OrderSearchUseCase orderSearchUseCase) {
this.orderSearchUseCase = requireNonNull(orderSearchUseCase);
}
public ResponseEntity<HttpApiResponse<CursorResult<OrderSummaryResponse>>> searchOrdersByConditionWithCursor(
@Valid @RequestParam List<String> sort,
@RequestParam(required = false) String cursor,
@RequestParam(defaultValue = "20") int size) {
CursorResult<OrderSummary> result = orderSearchUseCase.search(sort, cursor, size);
return ResponseEntity.ok(HttpApiResponse.success(result.map(OrderSummaryResponse::from)));
}
public ResponseEntity<HttpApiResponse<OrderSummaryResponse>> findOne(@RequestParam Long orderId, @RequestParam Instant at) {
return ResponseEntity.ok(HttpApiResponse.success(orderSearchUseCase.findOne(orderId, at)));
}
}
여기서 줄 길이 180이 무슨 뜻인지가 보인다. findOne은 127자라 안 접혔고 긴 쪽만 접혔다. 즉 “파라미터를 전부 내려 나열한다”는 규칙은 예외적으로만 발동한다. 값 두 개가 이렇게 맞물린다는 건 표로는 안 보이고 코드로만 보인다.
대가도 여기 있다. 180자면 이 블로그의 코드 블록에서 가로 스크롤이 생긴다. 글에 싣는 예시는 손으로 짧게 쓰면 되지만 프로젝트 코드를 그대로 붙이면 걸린다.
원칙 4. 명시하지 않은 항목은 발견할 때마다 채운다
이게 안 다의 실제 비용이고, 재보기 전에는 몰랐던 것이다.
설정 XML에 안 적은 항목은 Eclipse 기본값으로 채워진다. 문제는 그 기본값이 우리가 본 적 없는 모양을 만든다는 것이다.
1차로 돌렸더니 메서드 사이에 빈 줄이 하나도 없었다. blank_lines_before_method 기본값이 0이라서다. XML에 한 줄 넣어 고쳤더니 2차에서 또 나왔다 — 필드와 생성자 사이가 붙어 있었다. 이건 blank_lines_before_new_chunk가 따로 관장한다.
// 두 번 돌려서 두 번 나왔다
private final OrderSearchUseCase orderSearchUseCase;
public OrderSearchController(OrderSearchUseCase orderSearchUseCase) {
그래서 설정 파일은 한 번에 완성되지 않는다. 코드를 쓰다가 이상한 모양이 나오면 그건 취향이 어긋난 게 아니라 그 항목이 XML에 없다는 신호이고, 한 줄을 추가한다.
이제 안 가와 안 다의 진짜 차이가 보인다. 항목을 고르는 자유가 아니라 기본값을 발견해가는 노동이다. 안 가를 골랐으면 이 과정이 통째로 없었다.
이 대가를 감수한다. 다만 감수한다는 걸 여기 적어둔다 — 나중에 XML이 계속 길어질 때 “이게 정상인가”를 다시 묻지 않기 위해서다. 정상이다.
원칙 5. import 순서는 포맷터가 아니라 별도 스텝이 강제한다
이것도 돌려보고 알았다. Eclipse 포맷터는 import를 아예 건드리지 않는다. 정렬은 포맷이 아니라 “Organize Imports”라는 별개 동작이라서다.
그래서 Spotless의 importOrder 스텝이 따로 필요하다. 그런데 여기서 한 번 더 걸렸다.
// 이렇게 쓰면 그룹 사이에 빈 줄이 들어간다
importOrder('java', 'org', 'com', '', '\\#')
// 이렇게 써야 한 덩어리가 된다
importOrder('java|org|com|', '\\#')
|가 그룹을 빈 줄 없이 잇는 문법이다. 마지막 | 뒤의 빈칸이 catch-all이라 jakarta처럼 앞의 어디에도 안 걸리는 것들이 같은 덩어리에 붙고, static만 빈 줄로 떨어진다.
규칙 하나가 도구 둘에 나뉘어 산다. formatter.xml을 아무리 뒤져도 import 규칙은 없고 build.gradle에 있다. 이걸 안 적어두면 6개월 뒤에 XML만 보고 “import 규칙이 없네” 한다.
원칙 6. 빌드가 검사하고 실패시킨다. 고치는 건 부르는 쪽이 한다
포맷터를 언제 걸 것인가 — 이 질문의 실제 형태는 “AI가 쓴 코드가 어느 시점에 교정되는가” 다. IDE 저장 시점은 앞에서 지워졌다.
안 ㄱ — 어시스턴트 훅에 얹는다. Codex Stop 훅이 이미 자바 파일 변경을 감지하므로 거기에 spotlessApply를 붙인다. 얻는 것은 생산자에게 가장 가까운 지점이라는 것이다. 버린 이유는 그 도구를 쓸 때만 돈다는 것 — 손으로 고친 코드는 안 잡힌다.
안 ㄴ — Git pre-commit 훅. 얻는 것은 도구와 무관하게 커밋되는 모든 코드를 잡는 것이다. 버린 이유는 .git/hooks가 clone에 안 따라와서 안 심으면 조용히 안 돈다는 것이다. 신호 없이 죽는 검문은 없는 검문과 같다.
안 ㄷ — 빌드가 자동으로 고친다. spotlessApply를 compileJava 앞에 건다. 버린 이유는 빌드가 소스를 몰래 고친다는 것이다. 코드를 안 건드렸는데 diff가 생긴다.
안 ㄹ — 빌드가 검사만 하고 실패시킨다. 어긋나면 죽고, 사람이 spotlessApply를 부른다.
골랐다 — 안 ㄹ. 도구·사람·CI를 가리지 않고 같은 판정을 내는 건 이것뿐이다. 안 ㄱ은 도구를 쓸 때만 돌고, 안 ㄴ은 안 심으면 죽고, 안 ㄷ은 소스를 몰래 고친다. 안 ㄹ만 빠져나갈 구멍이 없다.
배선할 것도 없었다. check가 spotlessCheck를 이미 부른다.
> Task :spotlessJavaCheck FAILED
> The following files had format violations:
src\main\java\com\example\Bad.java
Run 'gradlew.bat spotlessApply' to fix all violations.
실패 메시지가 어느 파일인지와 무엇을 돌릴지를 다 알려준다. “AI가 빌드 실패를 보고 고친다”는 전제가 여기서 성립한다.
그 위에 안 ㄱ을 편의로 얹을 수 있다. 다만 이건 표준이 아니다 — 훅이 안 돌아도 안 ㄹ이 잡으므로 정합성이 안 깨진다. 이 구분을 적어두지 않으면 나중에 훅을 표준으로 착각한다.
대가는 로직과 무관한 이유로 빌드가 죽는 것이다. 그게 이 안의 기능이다.
원칙 7. 포맷터를 끄는 주석을 열지 않는다
Spotless에는 toggleOffOn()이 있다. 켜면 주석으로 특정 구간을 포맷터에서 뺄 수 있다.
// spotless:off
// 주문상태 결제 기대 결과
// ORDERED 완료 배송대기
// spotless:on
테스트 데이터를 표처럼 정렬하거나 SQL 문자열을 다룰 때 쓰고 싶어진다. 켜지 않는다.
켜는 순간 “여기는 포맷터가 안 걸린다”가 사람 판단으로 돌아오기 때문이다. 원칙 1이 없앤 게 정확히 그 판단이다. 그리고 판단이 돌아오면 “이건 좀 지저분하니까”로 새어나가고, 새어나간 걸 되돌릴 계기가 없다 — 안 B가 깨지던 경로와 같은 모양이다.
진짜로 필요한 자리가 나오면 그때 열고 그 자리를 근거로 적는다. 미리 열어두지 않는다.
원칙 8. 이 표준은 1단에 넣지 않는다
지금까지 표준은 대부분 CLAUDE.md(1단)에 한 줄씩 올라갔다. 이건 안 올린다.
1단의 기준은 “말하지 않으면 기본값으로 어기게 되는 것” 이다. 포맷은 그 기준을 통과하지 못한다 — 어겨도 되기 때문이다. 어기면 빌드가 고친다.
오히려 반대다. 1단에 포맷 규칙을 적으면 AI가 그걸 지키려고 애쓰는데, 그 노력은 전부 낭비다. 도구가 이미 결정론적으로 처리하는 것을 확률적으로 다시 하는 셈이다.
그래서 스킬에만 둔다. 스킬이 필요한 시점은 코드를 쓸 때가 아니라 프로젝트를 세팅할 때와 “포맷이 왜 이래?”를 물을 때다. 스킬 설명이 그 두 시점을 가리킨다.
이 표준은 AI에게 알려주는 규칙이 아니라 프로젝트에 심는 설정이다. 그래서 지금까지의 표준과 배포 방식도 다르다 — 설치기가 config/formatter.xml을 심지만 build.gradle은 못 건드리므로, Gradle 설정은 손으로 붙여야 한다.
판단 기준 정리
| 질문 | 답 | 왜 |
|---|---|---|
| 포맷을 누가 지키나 | 도구가 강제한다 | 문서 규칙은 어겨지는 게 아니라 조용히 대체된다 |
| 규칙을 어디서 가져오나 | 내가 정한 XML | 취향의 영역이라 근거를 요구하지 않기로 정했다 |
| 항목마다 근거를 적나 | 적지 않는다 | 근거는 영역 단위로 한 번 붙는다 |
| XML에 없는 항목은 | 발견할 때마다 채운다 | 기본값이 본 적 없는 모양을 만든다 |
| import 규칙은 어디 있나 | build.gradle |
Eclipse 포맷터는 import를 안 건드린다 |
| 언제 걸리나 | check 실패 |
도구·사람·CI가 같은 판정을 낸다 |
| 포맷터를 끌 수 있나 | 없다 | 끄는 판단이 돌아오면 새어나간다 |
CLAUDE.md에 올리나 |
안 올린다 | 1단은 “어기면 안 되는 것”인데 포맷은 어겨도 된다 |
이 표준을 정하기까지
시작은 “포맷터를 쓸까”가 아니라 “포맷 얘기를 하자”였다. 그래서 첫 일은 질문을 셋으로 가르는 것이었다 — 누가 강제하는가, 어떤 규칙인가, 언제 걸리는가. 한 번에 답하면 근거가 엉킨다는 걸 알았고, 실제로 첫 질문의 답이 나머지 둘을 거의 결정했다.
이 저장소만의 조건을 먼저 찾았다. 일반적인 포맷터 논의는 “팀원끼리 diff로 싸우지 말자”인데 여기엔 팀원이 없다. 대신 AI가 코드를 쓴다. 그러자 근거가 통째로 바뀌었다 — 포맷터는 스타일 문제가 아니라 3단 구조의 토큰 예산 문제가 됐고, “IDE 저장 시 자동 포맷”은 시작부터 지워졌다. AI는 저장 이벤트를 안 거치니까.
안 A가 정해진 건 과거의 실패 때문이었다. .claude/skills와 .agents/skills가 4개 어긋난 채 배포된 적이 있다. 그때 “사람이 기억해야만 지켜지는 규칙은 기계로 옮긴다”를 세웠는데, 포맷은 그 규칙의 극단이었다. 새 판단이 아니라 이미 있던 판단을 적용한 것이다.
두 번째 질문에서 처음으로 부딪혔다. 나는 기성품(안 가)을 권했다. 근거는 “항목마다 근거를 못 적는다”였다. 그런데 되물어 왔다 — “각 항목의 근거를 적을 수 없는 거야?”
되물음이 내 주장을 반쯤 무너뜨렸다. 다시 세어보니 근거가 붙는 항목이 실제로 있었다. 들여쓰기 폭은 AI 산출물과의 거리로 설명되고, 줄 길이는 글에 코드가 실리므로 폭이 제약이라는 이 저장소만의 근거가 있고, import 그룹과 정렬 여부도 근거가 선다. “하나도 못 적는다”는 과장이었다.
그런데 다시 세어보니 그 넷은 안 가에서도 우리가 정하고 있었다. 들여쓰기는 AOSP 변형으로 골랐고, import는 별도 스텝으로 남겨뒀고, 정렬은 기본값이 이미 우리가 원하는 답이었다. 그러니 안 다가 추가로 여는 건 근거를 못 적는 항목들뿐이었다. 이 대비를 만들고 나서야 갈림길의 모양이 정확해졌다.
그리고 판단이 뒤집혔다 — “이건 근거 없이 가는 게 맞다. 취향이니까.” 나는 이걸 저장소 규칙 위반으로 보고 있었는데, 아니었다. 규칙은 “근거 없는 결정을 박제하지 마라”였고, “이 영역은 근거를 요구하지 않기로 한다”는 그 자체로 근거가 있는 결정이었다. 포맷의 목적이 “옳은 모양”이 아니라 “한 가지 모양”이라는 것. 근거가 항목이 아니라 영역 단위로 붙는다는 걸 여기서 처음 언어로 꺼냈다.
값은 목록이 아니라 코드로 뽑았다. “빈 줄 최대 개수는요?”를 수십 번 묻는 대신 기준선 코드를 하나 놓고 “여기서 바꾸고 싶은 것”을 지목받았다. 취향은 항목으로 물으면 답이 안 나오고 모양을 보면 즉시 나온다. 여덟 개가 몇 분 만에 정해졌다.
글을 쓰기 전에 실제로 돌려보기로 했다. 이게 결정적이었다. Gradle 프로젝트를 하나 만들어 두 번 돌렸는데, 돌리지 않았으면 몰랐을 게 셋 나왔다.
첫째, importOrder가 그룹 사이에 빈 줄을 넣어서 “한 덩어리로”가 안 됐다. 문서를 뒤져 | 문법을 찾았다. 둘째, 메서드 사이에 빈 줄이 없었다 — 명시 안 한 항목의 기본값이었다. 고쳤더니 세 번째가 나왔다. 필드와 생성자가 붙어 있었다.
두 번 돌려 두 개가 나온 것이 원칙 4가 됐다. “설정 파일은 한 번에 완성되지 않는다”는 추측이 아니라 관측이다. 그리고 이게 안 가와 안 다의 진짜 차이라는 것도 여기서 알았다 — 자유가 아니라 노동이었다.
마지막으로 이 표준이 지금까지와 다른 종류라는 걸 알았다. SKILL.md에 적을 게 거의 없다. AI에게 알려줄 규칙이 아니라 프로젝트에 심는 설정이기 때문이다. 그래서 1단에 올리지 않기로 했고, 그 판단을 원칙 8로 남겼다. 1단의 기준이 “어기면 안 되는 것”이라면 포맷은 정확히 반대쪽에 있다.
정리
- 포맷은 도구가 강제한다. 문서에 적은 포맷 규칙은 어겨지는 게 아니라 주변 코드에 조용히 대체된다
- 값은 취향으로 정한다. 항목마다 근거를 적지 않고, 근거를 요구하지 않기로 정한 것 자체를 근거로 남긴다
- 근거는 영역 단위로 붙는다. 근거 없음을 명시하는 것이 근거를 지어내는 것보다 낫다
- 설정 파일은 한 번에 완성되지 않는다. 이상한 모양은 취향이 어긋난 게 아니라 그 항목이 XML에 없다는 신호다
- import 규칙은
formatter.xml이 아니라build.gradle에 산다. Eclipse 포맷터는 import를 안 건드린다 check가 검사하고 실패시킨다. 훅은 편의일 뿐 표준이 아니다 — 안 돌아도 정합성이 안 깨진다- 포맷터를 끄는 주석을 열지 않는다. 끄는 판단이 돌아오면 새어나가고 되돌릴 계기가 없다
- 이 표준은
CLAUDE.md에 올리지 않는다. 1단은 어기면 안 되는 것을 담는데 포맷은 어겨도 된다
AI 코드 어시스턴트에 바로 적용하기
Claude Code — .claude/skills/code-formatter-standard/SKILL.md
---
name: code-formatter-standard
description: 자바 코드 포맷 규칙과 Spotless 설정. 프로젝트를 새로 만들거나 build.gradle·formatter.xml을 손볼 때, 그리고 들여쓰기·줄바꿈·import 순서를 묻거나 리뷰할 때 적용한다.
---
# 코드 포맷터 표준
포맷은 사람도 AI도 판단하지 않는다. Spotless가 강제하고 빌드가 검사한다.
## 코드를 쓸 때
- 포맷을 맞추려고 공백·줄바꿈·들여쓰기를 손으로 조정하지 않는다.
- import를 손으로 정렬하거나 그룹을 나누지 않는다.
- 코드 리뷰에서 포맷을 지적하지 않는다.
- 포맷 때문에 빌드가 실패하면 `./gradlew spotlessApply`를 실행한다.
- `// spotless:off`를 쓰지 않는다.
## 프로젝트에 거는 방법
`build.gradle`에 그대로 넣는다.
```gradle
plugins {
id 'com.diffplug.spotless' version '8.9.0'
}
spotless {
java {
target 'src/**/*.java'
eclipse().configFile('config/formatter.xml')
importOrder('java|org|com|', '\\#')
removeUnusedImports()
trimTrailingWhitespace()
endWithNewline()
}
}
```
- `check`가 `spotlessCheck`를 자동으로 부른다. 따로 배선하지 않는다.
- IDE가 같은 `config/formatter.xml`을 읽게 한다. IDE 설정을 따로 만들지 않는다.
- `google-java-format`이나 `palantir-java-format`으로 바꾸지 않는다. 설정이 없어 아래 값을 지킬 수 없다.
## 정한 값
| 항목 | 값 |
|---|---|
| 들여쓰기 | 4칸 |
| 이어지는 줄 | 8칸 |
| 줄 길이 | 180 |
| 중괄호 | 줄 끝에 붙인다 |
| import 순서 | `java` → `org` → `com` → 나머지, 그룹 사이 빈 줄 없음 |
| `static` import | 맨 아래, 앞에 빈 줄 하나 |
| 빈 줄 | 클래스 첫 줄·메서드 사이·멤버 종류가 바뀌는 자리에 하나 |
| 파라미터 | 한 줄에 안 들어가면 전부 다음 줄로 내려 하나씩 나열 |
## 값을 바꿔야 할 때
- `config/formatter.xml`을 프로젝트에서 고치지 않는다. 설치기가 덮어쓴다.
- 포맷터가 이상한 모양을 만들면 그 항목이 `formatter.xml`에 없는 것이다. 표준 저장소에 항목을 추가하고 배포한다.
- `import`는 `formatter.xml`이 아니라 `importOrder` 스텝이 정한다. Eclipse 포맷터는 import를 건드리지 않는다.
GitHub Copilot — .github/instructions/code-formatter-standard.instructions.md
---
description: 자바 코드 포맷 규칙과 Spotless 설정
applyTo: "**/*.java"
---
# 코드 포맷터
- 포맷을 맞추려고 공백·줄바꿈·들여쓰기를 손으로 조정하지 않는다. Spotless가 고친다.
- import를 손으로 정렬하거나 그룹을 나누지 않는다.
- `// spotless:off`를 쓰지 않는다.
- 포맷 때문에 빌드가 실패하면 `./gradlew spotlessApply`를 실행한다.
- 들여쓰기는 4칸, 이어지는 줄은 8칸, 줄 길이는 180이다.
- 중괄호는 줄 끝에 붙인다.
- import는 `java` → `org` → `com` → 나머지 순서에 그룹 사이 빈 줄이 없고, `static`이 맨 아래다.
- 클래스 첫 줄, 메서드 사이, 멤버 종류가 바뀌는 자리에 빈 줄을 하나 둔다.
- 메서드 파라미터가 한 줄에 안 들어가면 전부 다음 줄로 내려 하나씩 나열한다.
- `config/formatter.xml`을 프로젝트에서 고치지 않는다. 표준 저장소가 소유한다.
- `google-java-format`이나 `palantir-java-format`으로 바꾸지 않는다.
자신만의 철학을 만들어가는 중입니다.
댓글남기기