@agentsmd/preset-java 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +11 -2
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/files/.githooks/pre-commit.d/spotless +14 -0
- package/files/.github/workflows/ci.yml +33 -0
- package/files/docs/clean-code-examples.md +179 -0
- package/files/docs/clean-code.md +48 -0
- package/files/docs/code-style.md +62 -0
- package/files/docs/java-checklist.md +113 -0
- package/files/docs/lint-and-ci.md +52 -0
- package/files/docs/project-direction.md +54 -0
- package/package.json +43 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 이태경
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @agentsmd/preset-java
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[`@agentsmd/cli`](https://www.npmjs.com/package/@agentsmd/cli)의 Java·Gradle 프리셋입니다.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx @agentsmd/cli init --preset git,java # Git 작업 규칙과 함께
|
|
7
|
+
npx @agentsmd/cli init --preset java # Java 규칙만
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
- Java 코드 규칙 문서: 코드 스타일, 클린 코드 원칙과 예시, Java 체크리스트
|
|
11
|
+
- palantir-java-format(Spotless) 커밋 검사, Gradle CI
|
|
12
|
+
- Git 작업 규칙은 [Git 프리셋](https://www.npmjs.com/package/@agentsmd/preset-git)을 함께 고르면 더해집니다. `AGENTS.md`는 고른 프리셋에 맞춰 만들어집니다.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Java·Gradle 프로젝트용 프리셋.
|
|
3
|
+
*
|
|
4
|
+
* Java 코드 규칙 문서, Spotless(palantir-java-format) 커밋 검사, Gradle CI를 담는다.
|
|
5
|
+
* Git 작업 규칙은 Git 프리셋을 함께 고르면 더해진다.
|
|
6
|
+
* `files/`는 빌드 결과(dist/)와 소스(src/) 어디서 실행해도 패키지 루트 기준으로 같은 위치다.
|
|
7
|
+
*/
|
|
8
|
+
export declare const javaPreset: {
|
|
9
|
+
name: string;
|
|
10
|
+
description: string;
|
|
11
|
+
filesDir: string;
|
|
12
|
+
hooksPath: string;
|
|
13
|
+
docs: {
|
|
14
|
+
path: string;
|
|
15
|
+
when: string;
|
|
16
|
+
}[];
|
|
17
|
+
rules: string[];
|
|
18
|
+
verify: string;
|
|
19
|
+
nextSteps: string[];
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA;;;;;;GAMG;AACH,eAAO,MAAM,UAAU;;;;;;;;;;;;CA0BL,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
/**
|
|
3
|
+
* Java·Gradle 프로젝트용 프리셋.
|
|
4
|
+
*
|
|
5
|
+
* Java 코드 규칙 문서, Spotless(palantir-java-format) 커밋 검사, Gradle CI를 담는다.
|
|
6
|
+
* Git 작업 규칙은 Git 프리셋을 함께 고르면 더해진다.
|
|
7
|
+
* `files/`는 빌드 결과(dist/)와 소스(src/) 어디서 실행해도 패키지 루트 기준으로 같은 위치다.
|
|
8
|
+
*/
|
|
9
|
+
export const javaPreset = {
|
|
10
|
+
name: "java",
|
|
11
|
+
description: "Java·Gradle 프로젝트용 지침, 커밋 훅, Spotless·Gradle CI",
|
|
12
|
+
filesDir: fileURLToPath(new URL("../files", import.meta.url)),
|
|
13
|
+
// Spotless 검사(pre-commit.d/spotless)를 연결한다.
|
|
14
|
+
hooksPath: ".githooks",
|
|
15
|
+
docs: [
|
|
16
|
+
{
|
|
17
|
+
path: "docs/project-direction.md",
|
|
18
|
+
when: "모든 작업 시작 전 (필수). 범위 밖 작업이면 먼저 질문",
|
|
19
|
+
},
|
|
20
|
+
{ path: "docs/code-style.md", when: "코드 작성, 주석" },
|
|
21
|
+
{ path: "docs/clean-code.md", when: "코드 작성, 설계" },
|
|
22
|
+
{ path: "docs/clean-code-examples.md", when: "코드 작성 시 참고 예시" },
|
|
23
|
+
{ path: "docs/java-checklist.md", when: "Java 코드 작성·리뷰" },
|
|
24
|
+
{ path: "docs/lint-and-ci.md", when: "훅·린트·CI 설정" },
|
|
25
|
+
],
|
|
26
|
+
rules: [
|
|
27
|
+
"코드에는 의도를 설명하는 주석을 적극적으로 단다.",
|
|
28
|
+
"포맷은 손으로 맞추지 않는다. 커밋 전 `./gradlew spotlessApply`.",
|
|
29
|
+
],
|
|
30
|
+
verify: "./gradlew spotlessCheck build",
|
|
31
|
+
nextSteps: [
|
|
32
|
+
"docs/project-direction.md 에 프로젝트 목표와 범위를 채우세요.",
|
|
33
|
+
"Gradle 프로젝트라면 docs/lint-and-ci.md 의 Spotless 설정을 build.gradle.kts 에 추가하세요.",
|
|
34
|
+
],
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAGzC;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,IAAI,EAAE,MAAM;IACZ,WAAW,EAAE,gDAAgD;IAC7D,QAAQ,EAAE,aAAa,CAAC,IAAI,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC7D,4CAA4C;IAC5C,SAAS,EAAE,WAAW;IACtB,IAAI,EAAE;QACJ;YACE,IAAI,EAAE,2BAA2B;YACjC,IAAI,EAAE,kCAAkC;SACzC;QACD,EAAE,IAAI,EAAE,oBAAoB,EAAE,IAAI,EAAE,WAAW,EAAE;QACjD,EAAE,IAAI,EAAE,oBAAoB,EAAE,IAAI,EAAE,WAAW,EAAE;QACjD,EAAE,IAAI,EAAE,6BAA6B,EAAE,IAAI,EAAE,eAAe,EAAE;QAC9D,EAAE,IAAI,EAAE,wBAAwB,EAAE,IAAI,EAAE,eAAe,EAAE;QACzD,EAAE,IAAI,EAAE,qBAAqB,EAAE,IAAI,EAAE,YAAY,EAAE;KACpD;IACD,KAAK,EAAE;QACL,6BAA6B;QAC7B,kDAAkD;KACnD;IACD,MAAM,EAAE,+BAA+B;IACvC,SAAS,EAAE;QACT,gDAAgD;QAChD,4EAA4E;KAC7E;CACe,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# Java 포맷 검사. 공통 pre-commit 훅이 실행한다.
|
|
4
|
+
# Gradle 프로젝트가 붙어 있고 Java 파일이 스테이징됐을 때만 spotlessCheck 를 돌린다.
|
|
5
|
+
# 규칙: docs/lint-and-ci.md
|
|
6
|
+
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
if [ -x ./gradlew ] && git diff --cached --name-only | grep -q '\.java$'; then
|
|
10
|
+
if ! ./gradlew -q spotlessCheck; then
|
|
11
|
+
echo "pre-commit: 포맷 위반. ./gradlew spotlessApply 후 다시 스테이징하세요." >&2
|
|
12
|
+
exit 1
|
|
13
|
+
fi
|
|
14
|
+
fi
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# 규칙: docs/lint-and-ci.md
|
|
2
|
+
name: CI
|
|
3
|
+
|
|
4
|
+
on:
|
|
5
|
+
pull_request:
|
|
6
|
+
push:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
# 커밋 규칙(파일 수, 메시지 형식)은 CI 에서 막지 않는다. 로컬 훅만 검사한다.
|
|
11
|
+
build:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v7
|
|
15
|
+
|
|
16
|
+
# 지침 전용 저장소일 때는 Gradle 프로젝트가 없으므로 빌드를 건너뛴다.
|
|
17
|
+
- name: Gradle 프로젝트 확인
|
|
18
|
+
id: detect
|
|
19
|
+
run: |
|
|
20
|
+
if [ -f gradlew ]; then echo "gradle=true" >> "$GITHUB_OUTPUT"; fi
|
|
21
|
+
|
|
22
|
+
- uses: actions/setup-java@v6
|
|
23
|
+
if: steps.detect.outputs.gradle == 'true'
|
|
24
|
+
with:
|
|
25
|
+
distribution: temurin
|
|
26
|
+
java-version: 21
|
|
27
|
+
|
|
28
|
+
- uses: gradle/actions/setup-gradle@v6
|
|
29
|
+
if: steps.detect.outputs.gradle == 'true'
|
|
30
|
+
|
|
31
|
+
- name: 포맷 검사 + 빌드
|
|
32
|
+
if: steps.detect.outputs.gradle == 'true'
|
|
33
|
+
run: ./gradlew spotlessCheck build
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# 클린 코드 예시
|
|
2
|
+
|
|
3
|
+
원칙은 [clean-code.md](clean-code.md).
|
|
4
|
+
|
|
5
|
+
## 조기 반환
|
|
6
|
+
|
|
7
|
+
```java
|
|
8
|
+
// Bad
|
|
9
|
+
public int discount(Member member) {
|
|
10
|
+
if (member != null) {
|
|
11
|
+
if (member.isActive()) {
|
|
12
|
+
return member.isVip() ? 20 : 10;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Good
|
|
19
|
+
public int discount(Member member) {
|
|
20
|
+
if (member == null || !member.isActive()) {
|
|
21
|
+
return 0;
|
|
22
|
+
}
|
|
23
|
+
return member.isVip() ? VIP_RATE : DEFAULT_RATE;
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## 매직 넘버
|
|
28
|
+
|
|
29
|
+
```java
|
|
30
|
+
// Bad
|
|
31
|
+
if (password.length() < 8) { ... }
|
|
32
|
+
|
|
33
|
+
// Good
|
|
34
|
+
/** 보안 정책 v2 기준 최소 길이. */
|
|
35
|
+
private static final int MIN_PASSWORD_LENGTH = 8;
|
|
36
|
+
|
|
37
|
+
if (password.length() < MIN_PASSWORD_LENGTH) { ... }
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## boolean 플래그 파라미터
|
|
41
|
+
|
|
42
|
+
```java
|
|
43
|
+
// Bad
|
|
44
|
+
sendMail(user, true);
|
|
45
|
+
|
|
46
|
+
// Good
|
|
47
|
+
sendWelcomeMail(user);
|
|
48
|
+
sendReminderMail(user);
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Tell, Don't Ask
|
|
52
|
+
|
|
53
|
+
```java
|
|
54
|
+
// Bad
|
|
55
|
+
if (order.getStatus() == Status.PAID && order.getPaidAt().isAfter(limit)) {
|
|
56
|
+
order.setStatus(Status.CANCELED);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Good
|
|
60
|
+
order.cancel(limit);
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## 원시 값 포장
|
|
64
|
+
|
|
65
|
+
```java
|
|
66
|
+
// Bad
|
|
67
|
+
public void register(String email) {
|
|
68
|
+
if (!email.contains("@")) throw new IllegalArgumentException();
|
|
69
|
+
...
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Good
|
|
73
|
+
public record Email(String value) {
|
|
74
|
+
public Email {
|
|
75
|
+
if (value == null || !value.contains("@")) {
|
|
76
|
+
throw new InvalidEmailException(value);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## 예외 처리
|
|
83
|
+
|
|
84
|
+
```java
|
|
85
|
+
// Bad
|
|
86
|
+
try {
|
|
87
|
+
payment.approve();
|
|
88
|
+
} catch (Exception e) {
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Good
|
|
92
|
+
try {
|
|
93
|
+
payment.approve();
|
|
94
|
+
} catch (PgTimeoutException e) {
|
|
95
|
+
// PG 타임아웃은 승인 여부가 불확실하므로 조회 후 재처리 대상에 넣는다.
|
|
96
|
+
throw new PaymentPendingException(payment.id(), e);
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## SRP
|
|
101
|
+
|
|
102
|
+
```java
|
|
103
|
+
// Bad: 주문 저장, 메일 발송, 포인트 적립이 한 클래스에
|
|
104
|
+
class OrderManager {
|
|
105
|
+
void order(...) { save(); sendMail(); addPoint(); }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Good: 책임별로 나누고 조합
|
|
109
|
+
class OrderService {
|
|
110
|
+
private final OrderRepository orders;
|
|
111
|
+
private final OrderNotifier notifier;
|
|
112
|
+
private final PointRewarder rewarder;
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## OCP (타입별 분기 제거)
|
|
117
|
+
|
|
118
|
+
```java
|
|
119
|
+
// Bad: 결제 수단 추가마다 switch 수정
|
|
120
|
+
int fee(PayType type, int amount) {
|
|
121
|
+
switch (type) {
|
|
122
|
+
case CARD: return amount * 3 / 100;
|
|
123
|
+
case BANK: return 500;
|
|
124
|
+
default: throw new IllegalArgumentException();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Good: 새 수단은 구현체만 추가
|
|
129
|
+
interface FeePolicy {
|
|
130
|
+
int fee(int amount);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
class CardFeePolicy implements FeePolicy {
|
|
134
|
+
public int fee(int amount) { return amount * 3 / 100; }
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## LSP
|
|
139
|
+
|
|
140
|
+
```java
|
|
141
|
+
// Bad: 상위 계약을 깨는 하위 타입
|
|
142
|
+
class ReadOnlyList<E> extends ArrayList<E> {
|
|
143
|
+
@Override public boolean add(E e) { throw new UnsupportedOperationException(); }
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Good: 필요한 계약만 가진 타입을 쓴다
|
|
147
|
+
List<E> items = List.copyOf(source);
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## ISP
|
|
151
|
+
|
|
152
|
+
```java
|
|
153
|
+
// Bad
|
|
154
|
+
interface Worker { void work(); void eat(); }
|
|
155
|
+
class Robot implements Worker { public void eat() { } } // 빈 구현
|
|
156
|
+
|
|
157
|
+
// Good
|
|
158
|
+
interface Workable { void work(); }
|
|
159
|
+
interface Eatable { void eat(); }
|
|
160
|
+
class Robot implements Workable { ... }
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## DIP
|
|
164
|
+
|
|
165
|
+
```java
|
|
166
|
+
// Bad: 구체 클래스를 직접 생성
|
|
167
|
+
class OrderService {
|
|
168
|
+
private final MySqlOrderRepository repository = new MySqlOrderRepository();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Good: 추상에 의존, 생성자 주입
|
|
172
|
+
class OrderService {
|
|
173
|
+
private final OrderRepository repository;
|
|
174
|
+
|
|
175
|
+
OrderService(OrderRepository repository) {
|
|
176
|
+
this.repository = repository;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
```
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# 클린 코드 + SOLID
|
|
2
|
+
|
|
3
|
+
코드 작성·리뷰 시 체크리스트. 예시는 [clean-code-examples.md](clean-code-examples.md).
|
|
4
|
+
Java 는 [java-checklist.md](java-checklist.md) 를 함께 본다. 겹치면 그쪽이 우선한다.
|
|
5
|
+
|
|
6
|
+
## 함수
|
|
7
|
+
|
|
8
|
+
- 한 가지 일만 한다. 이름으로 설명이 안 되면 쪼갠다.
|
|
9
|
+
- 20줄 이내 권장. 들여쓰기 2단계 이내.
|
|
10
|
+
- 파라미터 3개 이하. 넘으면 객체로 묶는다.
|
|
11
|
+
- 조기 반환(guard clause)으로 중첩을 줄인다.
|
|
12
|
+
- 부수효과를 숨기지 않는다. 조회 메서드는 상태를 바꾸지 않는다.
|
|
13
|
+
- boolean 플래그 파라미터 금지. 메서드를 나눈다.
|
|
14
|
+
|
|
15
|
+
## 값과 상태
|
|
16
|
+
|
|
17
|
+
- 매직 넘버·문자열은 상수나 enum 으로.
|
|
18
|
+
- 불변을 기본으로. 필드는 `final`, 컬렉션은 방어적 복사.
|
|
19
|
+
- null 반환 대신 `Optional` 또는 빈 컬렉션.
|
|
20
|
+
- 원시 값 대신 의미 있는 타입. (`String email` → `Email`)
|
|
21
|
+
|
|
22
|
+
## 객체
|
|
23
|
+
|
|
24
|
+
- 데이터를 꺼내서 판단하지 말고 객체에 시킨다. (Tell, Don't Ask)
|
|
25
|
+
- 디미터 법칙: `a.getB().getC().do()` 금지.
|
|
26
|
+
- getter/setter 기계적 생성 금지. 필요한 것만.
|
|
27
|
+
|
|
28
|
+
## 예외
|
|
29
|
+
|
|
30
|
+
- 예외를 삼키지 않는다. 빈 catch 금지.
|
|
31
|
+
- 의미 있는 커스텀 예외로 감싼다. 원인(cause)을 유지한다.
|
|
32
|
+
- 에러 코드 반환 대신 예외.
|
|
33
|
+
|
|
34
|
+
## SOLID
|
|
35
|
+
|
|
36
|
+
| 원칙 | 규칙 | 신호 |
|
|
37
|
+
| --- | --- | --- |
|
|
38
|
+
| SRP 단일 책임 | 클래스가 바뀌는 이유는 하나 | 클래스 이름에 `And`, `Manager`, `Util` |
|
|
39
|
+
| OCP 개방-폐쇄 | 기능 추가 시 기존 코드 수정 없이 확장 | 타입별 `if/switch` 가 여러 곳에 반복 |
|
|
40
|
+
| LSP 리스코프 치환 | 하위 타입은 상위 타입 계약을 지킨다 | 오버라이드에서 `UnsupportedOperationException` |
|
|
41
|
+
| ISP 인터페이스 분리 | 클라이언트가 안 쓰는 메서드에 의존하지 않게 | 빈 구현 메서드 |
|
|
42
|
+
| DIP 의존 역전 | 구체 클래스가 아니라 추상에 의존 | 내부에서 `new` 로 협력 객체 생성 |
|
|
43
|
+
|
|
44
|
+
## 과설계 방지
|
|
45
|
+
|
|
46
|
+
- 구현체가 하나뿐이고 교체 계획이 없으면 인터페이스를 만들지 않는다.
|
|
47
|
+
- 패턴은 문제가 보일 때 적용한다. 미리 넣지 않는다.
|
|
48
|
+
- 세 번 반복되면 추출한다. 두 번은 참는다.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# 코드 스타일
|
|
2
|
+
|
|
3
|
+
## 포맷
|
|
4
|
+
|
|
5
|
+
- palantir-java-format (Spotless). 들여쓰기 공백 4칸, 줄 길이 120. 수동 정렬하지 않는다.
|
|
6
|
+
- 커밋 전 `./gradlew spotlessApply`. pre-commit 훅이 `spotlessCheck` 를 돌린다.
|
|
7
|
+
- 설정은 [lint-and-ci.md](lint-and-ci.md).
|
|
8
|
+
|
|
9
|
+
## 네이밍
|
|
10
|
+
|
|
11
|
+
| 대상 | 규칙 | 예 |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| 클래스 | 명사, PascalCase | `OrderCanceler` |
|
|
14
|
+
| 메서드 | 동사, camelCase | `cancelOrder()` |
|
|
15
|
+
| boolean | `is/has/can` 접두 | `isCancelable()` |
|
|
16
|
+
| 상수 | UPPER_SNAKE_CASE | `MAX_RETRY_COUNT` |
|
|
17
|
+
| 컬렉션 | 복수형 | `orders` |
|
|
18
|
+
|
|
19
|
+
- 축약어 금지. (`ord`, `mgr`, `tmp`)
|
|
20
|
+
- 타입을 이름에 넣지 않는다. (`orderList` → `orders`)
|
|
21
|
+
|
|
22
|
+
## 주석
|
|
23
|
+
|
|
24
|
+
주석은 적극적으로 단다. 단, 코드를 반복하지 말고 **의도와 이유**를 쓴다.
|
|
25
|
+
|
|
26
|
+
필수:
|
|
27
|
+
|
|
28
|
+
- public 클래스·메서드: Javadoc. 역할, 파라미터 제약, 반환값, 예외.
|
|
29
|
+
- 비즈니스 규칙·정책 값: 출처나 이유.
|
|
30
|
+
- 비자명한 로직: 왜 이렇게 했는지.
|
|
31
|
+
- 우회 코드: 원인과 제거 조건.
|
|
32
|
+
|
|
33
|
+
형식:
|
|
34
|
+
|
|
35
|
+
- TODO: `// TODO(작성자): 내용 #이슈번호`
|
|
36
|
+
- 한 줄 주석은 대상 코드 바로 위에.
|
|
37
|
+
|
|
38
|
+
금지:
|
|
39
|
+
|
|
40
|
+
- 코드를 그대로 읽는 주석 (`// i 증가`)
|
|
41
|
+
- 주석 처리된 코드
|
|
42
|
+
- 변경 이력 주석 (git 이 한다)
|
|
43
|
+
|
|
44
|
+
```java
|
|
45
|
+
/**
|
|
46
|
+
* 주문을 취소한다.
|
|
47
|
+
*
|
|
48
|
+
* @param orderId 취소할 주문 ID. null 불가
|
|
49
|
+
* @throws OrderNotCancelableException 결제 후 24시간이 지난 경우
|
|
50
|
+
*/
|
|
51
|
+
public void cancel(Long orderId) {
|
|
52
|
+
Order order = orderRepository.getById(orderId);
|
|
53
|
+
|
|
54
|
+
// 정산이 결제 후 24시간 시점에 확정되므로 그 이후 취소는 환불 절차로 처리한다.
|
|
55
|
+
if (order.isPaidBefore(CANCEL_LIMIT)) {
|
|
56
|
+
throw new OrderNotCancelableException(orderId);
|
|
57
|
+
}
|
|
58
|
+
order.cancel();
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
설계 원칙은 [clean-code.md](clean-code.md), 예시는 [clean-code-examples.md](clean-code-examples.md).
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# 자바 클린 코드 체크리스트
|
|
2
|
+
|
|
3
|
+
Java 코드 작성·리뷰 시 확인한다. [clean-code.md](clean-code.md) 와 겹치면 이 문서가 우선한다.
|
|
4
|
+
|
|
5
|
+
| # | 체크 항목 |
|
|
6
|
+
| --- | --- |
|
|
7
|
+
| 1 | 자바 코드 컨벤션을 지켰는가 |
|
|
8
|
+
| 2 | 메서드 들여쓰기가 1단계인가 |
|
|
9
|
+
| 3 | `else` 를 쓰지 않았는가 |
|
|
10
|
+
| 4 | 원시값과 문자열을 포장했는가 |
|
|
11
|
+
| 5 | 컬렉션에 일급 컬렉션을 적용했는가 |
|
|
12
|
+
| 6 | 인스턴스 변수가 2개 이하인가 |
|
|
13
|
+
| 7 | 도메인 객체에 getter/setter 가 없는가 |
|
|
14
|
+
| 8 | 메서드 인자가 3개 이하인가 |
|
|
15
|
+
| 9 | 한 줄에 점(`.`)이 하나인가 |
|
|
16
|
+
| 10 | 메서드가 한 가지 일만 하는가 |
|
|
17
|
+
| 11 | 클래스가 작은가 |
|
|
18
|
+
|
|
19
|
+
## 1. 자바 코드 컨벤션
|
|
20
|
+
|
|
21
|
+
- [palantir-java-format](https://github.com/palantir/palantir-java-format) 스타일을 따른다. Google Java Style 기반이고 들여쓰기 4칸, 줄 길이 120 이다.
|
|
22
|
+
- IntelliJ 는 palantir-java-format 플러그인으로 포맷팅한다.
|
|
23
|
+
- 포맷은 손으로 맞추지 않는다. 최종 판정은 `spotlessCheck`. → [lint-and-ci.md](lint-and-ci.md)
|
|
24
|
+
|
|
25
|
+
## 2. 들여쓰기 1단계
|
|
26
|
+
|
|
27
|
+
- 메서드 안의 들여쓰기는 1단계만 허용한다.
|
|
28
|
+
- 중첩이 생기면 안쪽 블록을 메서드로 추출한다.
|
|
29
|
+
|
|
30
|
+
## 3. else 금지
|
|
31
|
+
|
|
32
|
+
- `else` 를 쓰지 않는다. 조기 반환으로 바꾼다.
|
|
33
|
+
|
|
34
|
+
```java
|
|
35
|
+
// 나쁨
|
|
36
|
+
if (score >= 60) {
|
|
37
|
+
return PASS;
|
|
38
|
+
} else {
|
|
39
|
+
return FAIL;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 좋음
|
|
43
|
+
if (score >= 60) {
|
|
44
|
+
return PASS;
|
|
45
|
+
}
|
|
46
|
+
return FAIL;
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## 4. 원시값·문자열 포장
|
|
50
|
+
|
|
51
|
+
- 의미 있는 원시값과 문자열은 클래스로 포장한다.
|
|
52
|
+
- 검증 로직은 포장 클래스 생성자에 둔다.
|
|
53
|
+
|
|
54
|
+
```java
|
|
55
|
+
public class Money {
|
|
56
|
+
private final int amount;
|
|
57
|
+
|
|
58
|
+
public Money(int amount) {
|
|
59
|
+
// 음수 금액은 도메인상 존재할 수 없으므로 생성 시점에 막는다.
|
|
60
|
+
if (amount < 0) {
|
|
61
|
+
throw new IllegalArgumentException("금액은 0 이상이어야 한다.");
|
|
62
|
+
}
|
|
63
|
+
this.amount = amount;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## 5. 일급 컬렉션
|
|
69
|
+
|
|
70
|
+
- 컬렉션은 그 컬렉션만 필드로 가진 클래스로 감싼다.
|
|
71
|
+
- 컬렉션 관련 로직(검증, 합계, 필터)은 일급 컬렉션 안에 둔다.
|
|
72
|
+
|
|
73
|
+
```java
|
|
74
|
+
public class Lottos {
|
|
75
|
+
private final List<Lotto> lottos;
|
|
76
|
+
|
|
77
|
+
public Lottos(List<Lotto> lottos) {
|
|
78
|
+
this.lottos = List.copyOf(lottos);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
public int countWinning(WinningNumbers winning) { ... }
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## 6. 인스턴스 변수 2개 이하
|
|
86
|
+
|
|
87
|
+
- 3개 이상의 인스턴스 변수를 가진 클래스를 만들지 않는다.
|
|
88
|
+
- 어렵다면 최소한 줄이려고 시도한다. 관련 있는 필드끼리 객체로 묶는다.
|
|
89
|
+
|
|
90
|
+
## 7. getter/setter 금지
|
|
91
|
+
|
|
92
|
+
- 핵심 로직을 담은 도메인 객체에는 getter/setter 를 쓰지 않는다.
|
|
93
|
+
- 값을 꺼내 판단하지 않고 객체에 메시지를 보낸다.
|
|
94
|
+
- DTO 는 허용한다.
|
|
95
|
+
|
|
96
|
+
## 8. 메서드 인자 3개 이하
|
|
97
|
+
|
|
98
|
+
- 4개 이상의 인자는 허용하지 않는다.
|
|
99
|
+
- 3개도 줄일 수 있는지 먼저 검토한다.
|
|
100
|
+
|
|
101
|
+
## 9. 한 줄에 점 하나
|
|
102
|
+
|
|
103
|
+
- 디미터 법칙("친구하고만 대화하라")을 지킨다.
|
|
104
|
+
- `location.current.representation.substring(0, 1)` 처럼 점이 여러 개면 리팩터링 대상을 찾는다.
|
|
105
|
+
|
|
106
|
+
## 10. 메서드는 한 가지 일만
|
|
107
|
+
|
|
108
|
+
- 메서드 이름으로 하는 일이 다 설명되지 않으면 나눈다.
|
|
109
|
+
|
|
110
|
+
## 11. 작은 클래스
|
|
111
|
+
|
|
112
|
+
- 클래스를 작게 유지한다.
|
|
113
|
+
- 클래스가 커지면 책임을 나눌 수 있는지 먼저 본다.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# 훅 · 린트 · CI
|
|
2
|
+
|
|
3
|
+
## Git 훅
|
|
4
|
+
|
|
5
|
+
`.githooks/pre-commit` 이 `.githooks/pre-commit.d/` 의 검사를 이름 순서로 실행한다.
|
|
6
|
+
Java 프리셋은 포맷 검사를 넣는다. Git 프리셋을 함께 설치했다면 커밋 형식·파일 수 검사도 같이 돈다.
|
|
7
|
+
|
|
8
|
+
| 파일 | 검사 |
|
|
9
|
+
| --- | --- |
|
|
10
|
+
| `.githooks/pre-commit.d/spotless` | `gradlew` 가 있고 `.java` 가 스테이징됐으면 `spotlessCheck` |
|
|
11
|
+
|
|
12
|
+
- 클론한 뒤 훅이 동작하지 않으면 `git config core.hooksPath .githooks` 로 연결한다.
|
|
13
|
+
- `--no-verify` 금지.
|
|
14
|
+
|
|
15
|
+
## 린트 (Spotless + palantir-java-format)
|
|
16
|
+
|
|
17
|
+
프로젝트에 Gradle 을 붙일 때 `build.gradle.kts` 에 추가한다.
|
|
18
|
+
|
|
19
|
+
palantir-java-format 은 Google Java Style 을 바탕으로 들여쓰기 4칸, 줄 길이 120 을 쓴다.
|
|
20
|
+
|
|
21
|
+
```kotlin
|
|
22
|
+
plugins {
|
|
23
|
+
java
|
|
24
|
+
id("com.diffplug.spotless") version "8.10.4"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
spotless {
|
|
28
|
+
java {
|
|
29
|
+
target("src/**/*.java")
|
|
30
|
+
palantirJavaFormat("2.103.0")
|
|
31
|
+
removeUnusedImports()
|
|
32
|
+
trimTrailingWhitespace()
|
|
33
|
+
endWithNewline()
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
| 명령 | 용도 |
|
|
39
|
+
| --- | --- |
|
|
40
|
+
| `./gradlew spotlessApply` | 포맷 적용 |
|
|
41
|
+
| `./gradlew spotlessCheck` | 포맷 검사 (훅, CI) |
|
|
42
|
+
|
|
43
|
+
## CI
|
|
44
|
+
|
|
45
|
+
[.github/workflows/ci.yml](../.github/workflows/ci.yml)
|
|
46
|
+
|
|
47
|
+
| job | 실행 | 검사 |
|
|
48
|
+
| --- | --- | --- |
|
|
49
|
+
| `build` | PR, main push | `spotlessCheck build`. `gradlew` 없으면 건너뜀 |
|
|
50
|
+
|
|
51
|
+
- 커밋 규칙(파일 수, 메시지 형식)은 CI 에서 검사하지 않는다. 로컬 훅만 검사한다.
|
|
52
|
+
- 버전을 올릴 때는 이 문서의 스니펫과 CI 를 같이 고친다.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# 프로젝트 방향
|
|
2
|
+
|
|
3
|
+
<!--
|
|
4
|
+
작성 규칙:
|
|
5
|
+
- 사람이 채운다. 에이전트는 사용자 요청 없이 이 문서를 고치지 않는다.
|
|
6
|
+
- 각 섹션은 3~5줄 불릿. 채울 내용이 없으면 섹션을 지운다.
|
|
7
|
+
- 방향이 바뀌면 이 문서부터 고친다.
|
|
8
|
+
-->
|
|
9
|
+
|
|
10
|
+
## 목표
|
|
11
|
+
|
|
12
|
+
<!-- 이 프로젝트가 해결하는 문제. 한 줄 요약 + 핵심 목표 -->
|
|
13
|
+
|
|
14
|
+
-
|
|
15
|
+
|
|
16
|
+
## 범위
|
|
17
|
+
|
|
18
|
+
<!-- 이번 단계에서 만드는 것 -->
|
|
19
|
+
|
|
20
|
+
-
|
|
21
|
+
|
|
22
|
+
## 하지 않는 것
|
|
23
|
+
|
|
24
|
+
<!-- 범위 밖. 에이전트가 임의로 추가하지 않도록 명시한다 -->
|
|
25
|
+
|
|
26
|
+
-
|
|
27
|
+
|
|
28
|
+
## 기술 스택
|
|
29
|
+
|
|
30
|
+
| 구분 | 선택 | 이유 |
|
|
31
|
+
| --- | --- | --- |
|
|
32
|
+
| 언어 | | |
|
|
33
|
+
| 프레임워크 | | |
|
|
34
|
+
| 빌드 | | |
|
|
35
|
+
| DB | | |
|
|
36
|
+
|
|
37
|
+
## 설계 원칙
|
|
38
|
+
|
|
39
|
+
<!-- 판단이 갈릴 때 따르는 우선순위. 예: 성능보다 가독성 -->
|
|
40
|
+
|
|
41
|
+
-
|
|
42
|
+
|
|
43
|
+
## 진행 단계
|
|
44
|
+
|
|
45
|
+
| 단계 | 내용 | 상태 |
|
|
46
|
+
| --- | --- | --- |
|
|
47
|
+
| 1 | | 진행 중 |
|
|
48
|
+
|
|
49
|
+
## 결정 기록
|
|
50
|
+
|
|
51
|
+
<!-- 방향에 영향을 준 결정. 날짜와 이유를 한 줄로 -->
|
|
52
|
+
|
|
53
|
+
| 날짜 | 결정 | 이유 |
|
|
54
|
+
| --- | --- | --- |
|
package/package.json
CHANGED
|
@@ -1,6 +1,46 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentsmd/preset-java",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Java·Gradle 프로젝트용 AI 작업 지침 프리셋 (코드 규칙 문서, Spotless 커밋 검사, Gradle CI)",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"agents.md",
|
|
7
|
+
"ai-agent",
|
|
8
|
+
"java",
|
|
9
|
+
"gradle",
|
|
10
|
+
"spotless"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://github.com/xoruddl/agentsmd-kit#readme",
|
|
13
|
+
"bugs": "https://github.com/xoruddl/agentsmd-kit/issues",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/xoruddl/agentsmd-kit.git",
|
|
17
|
+
"directory": "packages/preset-java"
|
|
18
|
+
},
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"author": "이태경 (https://github.com/xoruddl)",
|
|
21
|
+
"type": "module",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"default": "./dist/index.js"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"files"
|
|
31
|
+
],
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=22"
|
|
34
|
+
},
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@agentsmd/core": "0.2.0"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "tsc -p tsconfig.build.json",
|
|
43
|
+
"typecheck": "tsc -p tsconfig.json",
|
|
44
|
+
"test": "vitest run"
|
|
45
|
+
}
|
|
6
46
|
}
|