-
Notifications
You must be signed in to change notification settings - Fork 0
Coding Convention
이프, If edited this page Oct 3, 2025
·
25 revisions
프로덕션 코드는 루트 패키지 com.example.green 하위에 도메인/글로벌/인프라 계층으로 구성한다.
src/main/java/com/example/green/
├── domain/
│ ├── common/
│ │ ├── idempotency/
│ │ ├── lock/
│ │ └── ...
│ ├── point/
│ │ ├── controller/
│ │ ├── dto/
│ │ ├── entity/
│ │ ├── exception/
│ │ ├── repository/
│ │ └── service/
│ ├── product/
│ │ └── ...
│ └── ...
│
├── global/
│ ├── config/
│ │ └── WebConfig.java
│ ├── error/
│ │ ├── GlobalExceptionHandler.java
│ │ └── ErrorResponse.java
│ └── util/
│ └── DateUtils.java
│
└── infra/
├── storage/
│ ├── S3Client.java
│ └── S3Config.java
├── mail/
│ └── EmailSender.java
└── security/
├── JwtProvider.java
└── SecurityConfig.java
-
패키지 규칙
- 기능(도메인) 중심 패키징. 내부 구성은 layered, clean 등 자유 아키텍처로 분리
- 외부 연동, 보안, 스토리지 등 인프라 의존은
infra/로 분리하고 도메인에서 활용 - 각 도메인에서 사용할 공통 AOP/락 등의 로직은
domain/common/에서 관리 - spring boot starter 및 spring 관련 설정은
/global에서 관리
기본 옵션
%d{yyyy-MM-dd HH:mm:ss.SSS} - 날짜와 시간을 밀리초까지 표시 (문제 발생 시점 정확히 파악)
[%thread] - 로그를 생성한 스레드 이름 (멀티스레드 환경에서 병렬 실행 추적)
%-5level - 로그 레벨 (INFO, ERROR 등, 5자리로 정렬)
%logger{36} - 로거 이름 (최대 36자, 패키지 이름 축약)
[%class][%method][%line] - 클래스명, 메서드명, 라인번호 (정확한 코드 위치 추적)
%msg%n - 실제 로그 메시지와 줄바꿈
[${server-name}] - 로그를 생성한 서버 이름 (다중 서버 환경에서 출처 식별)
[%X{traceId:-}] - 분산 추적 ID (마이크로서비스 간 요청 흐름 추적, '-'는 ID가 없을 때의 기본값)
테스트는 JUnit 5, AssertJ, Mockito, Testcontainers를 사용한다. 패키지로 "단위/통합"을 명확히 구분한다.
src/test/java/com/example/
├── unit/ # 순수/슬라이스 단위 테스트
│ ├── point/
│ ├── product/
│ └── ...
└── integration/ # 스프링 컨텍스트/실제 인프라 통합 테스트
├── order/
├── pointtransaction/
└── ...
-
단위 테스트 (unit)
- 대상: 순수 비즈니스 로직, 유틸, 리포지토리 mock, 서비스 로직의 경계 등.
- 금지: 실제 DB/네트워크, 컨테이너, 스프링 컨텍스트 로딩.
- 권장:
@ExtendWith(MockitoExtension.class), Mockito, AssertJ. 경우에 따라 스프링 슬라이스(@WebMvcTest,@DataJpaTest) 사용 가능하나 외부 I/O는 mock. - 네이밍:
ClassNameTest(접미사Test). - 위치:
src/test/java/com/example/unit/...
-
통합 테스트 (integration)
- 대상: 스프링 빈 조합, 트랜잭션, JPA, AOP, 보안, 메시징/스토리지 등 실제 흐름 검증.
- 환경:
@SpringBootTest(+@AutoConfigureMockMvc),@Testcontainers와org.testcontainers:postgresql,localstack사용. - 데이터: 마이그레이션/seed 반영 후 시나리오 검증. 가능하면 테스트 간 독립성 보장.
- 네이밍:
FeatureNameTest(접미사Test). - 위치:
src/test/java/com/example/integration/...
-
수락 테스트 (선택)
- 필요 시
acceptance/하위에서 사용자 시나리오 관점 E2E 정의.
- 필요 시
캠퍼스 핵데이 Java 코딩 컨벤션 ➡️ 링크
선택한 이유1. Asciidoc 으로 매우 잘 정리되어 있음. 선택한 이유2. 컨벤션 XML이 제공됨.
-
Checkstyle 실행
- 전체 스타일 및 정적 검사:
./gradlew clean check- 현재 설정상
checkstyleMain활성화,checkstyleTest비활성화.
- 현재 설정상
- 스타일만 확인:
./gradlew checkstyleMain - 리포트 위치:
build/reports/checkstyle/
- 전체 스타일 및 정적 검사:
-
객체지향 생활체조 원칙
- 한 메서드에 오직 한 단계의 들여쓰기(indent)만 한다.
- 들여쓰기가 많다면 책임 분리를 고려. private 메서드 난립 시 객체 분리 고려.
- else 예약어를 쓰지 않는다. (switch 포함)
- 모든 원시 값과 문자열을 포장한다. (도메인 VO 적용)
- 한 줄에 점을 하나만 찍는다. (체이닝은 가독성 고려)
- 줄여 쓰지 않는다. (의도 명확성)
- 모든 엔티티를 작게 유지한다. (도메인 vs 서비스 책임 분리)
- getter/setter 남용 금지. 의미 있는 동작 메서드 제공.
- 한 메서드에 오직 한 단계의 들여쓰기(indent)만 한다.
-
우리는 글로벌 템플릿을 사용함:
global/api/ApiTemplate -
성공 응답 JSON 예시
{
"success": true,
"message": "OK",
"result": { /* 도메인별 payload */ }
}-
에러 응답:
global/error/GlobalExceptionHandler가ExceptionResponse/DetailedExceptionResponse기반으로 반환. -
권장 헤더
Content-Type: application/json; charset=UTF-8Accept: application/json
-
버전 경로:
/api/v1/... -
마이그레이션: 신규 버전 배포 후 구버전은 일정 기간 유지. 폐기 시 Swagger에
deprecated표기 및 공지.
- 기본 BDD 포맷
void test() {
// given
// when
// then
}- 상황에 따른 변형 허용
void test() {
// when
// then
// when & then
}-
테스트 메서드 명명 규칙
- 영어: BDD 의도가 드러나게. 길어져도 OK. 필요한 경우
@DisplayName으로 한글 설명 추가. - 한글 가능: 의도가 명확하면 OK.
- 영어: BDD 의도가 드러나게. 길어져도 OK. 필요한 경우
void givenExistingUserIdWhenExistsCheckingThenReturnTrue() { /* ... */ }
@DisplayName("존재하는 userId로 존재하는지 검증하면 true를 반환한다.")
void given...() { /* ... */ }
void 존재하는_userId로_존재하는지_검증하면_true를_반환한다() { /* ... */ }-
단위/통합 테스트 실행 가이드
- 전체 테스트:
./gradlew clean test - 커버리지 리포트:
./gradlew clean jacocoTestReport-
jacocoTestReport는test에 의존하며, HTML 리포트는build/reports/jacoco/html/index.html에 생성. - 커버리지 기준: 클래스 단위 LINE/BRANCH/METHOD 각 60% 이상. 미달 시
jacocoTestCoverageVerification에서 실패.
-
- 통합 테스트만 또는 태그 기반 실행이 필요하면 JUnit5
@Tag활용을 권장하며, Gradle 태그 필터링은 필요 시build.gradle에 별도 설정을 추가한다.
- 전체 테스트:
- 예외는 도메인/애플리케이션 계층에서 의미 있는 커스텀 예외로 표현한다.
- 글로벌 처리:
global/error/GlobalExceptionHandler에서 공통 포맷(ErrorResponse)으로 변환. - API 스펙(에러 코드/메시지/필드 오류)은 별도 합의 문서에 따르며, Swagger 문서화와 동기화한다.
-
중요: Spring Data
Pageable/Page를 사용하지 않는다. 커스텀 1-based 규약을 사용. -
구성요소
- 요청 조건:
global/api/page/PageSearchCondition(메서드:page(),size()) - 계산기:
global/api/page/Pagination - 응답 래퍼:
global/api/page/PageTemplate<T>
- 요청 조건:
-
요청 파라미터 (1-based)
-
page: 1부터 시작. 기본값 1 -
size: 기본 10, 최대 100 (초과 시 100으로 캡)
-
-
응답 형태 (
PageTemplate)
{
"success": true,
"message": "OK",
"result": {
"totalElements": 123,
"totalPages": 13,
"currentPage": 1,
"pageSize": 10,
"hasNext": true,
"content": [ /* 아이템 배열 */ ]
}
}-
오프셋 계산 (
Pagination):(currentPage - 1) * pageSize -
hasNext 계산:
currentPage < totalPages -
커서 기반 페이징 (
CursorTemplate<R, T>)- 요청 파라미터 예:
cursor,size - 응답 형태:
- 요청 파라미터 예:
{
"success": true,
"message": "OK",
"result": {
"hasNext": true,
"nextCursor": "...",
"content": [ /* 아이템 배열 */ ]
}
}-
CursorTemplate.from(list, size, cursorExtractor)사용 시: list를size + 1개 로딩하여 nextCursor 계산 후 한 개 제거하여 반환하는 패턴을 따른다.