Skip to content

Coding Convention

이프, If edited this page Oct 3, 2025 · 25 revisions

프로젝트 구조 - Product

프로덕션 코드는 루트 패키지 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가 없을 때의 기본값)

프로젝트 구조 - Test

테스트는 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), @Testcontainersorg.testcontainers:postgresql, localstack 사용.
    • 데이터: 마이그레이션/seed 반영 후 시나리오 검증. 가능하면 테스트 간 독립성 보장.
    • 네이밍: FeatureNameTest (접미사 Test).
    • 위치: src/test/java/com/example/integration/...
  • 수락 테스트 (선택)

    • 필요 시 acceptance/ 하위에서 사용자 시나리오 관점 E2E 정의.

Code Convention

캠퍼스 핵데이 Java 코딩 컨벤션 ➡️ 링크

선택한 이유1. Asciidoc 으로 매우 잘 정리되어 있음. 선택한 이유2. 컨벤션 XML이 제공됨.

IntelliJ 네이버 코딩 컨벤션 설정

  • Checkstyle 실행

    • 전체 스타일 및 정적 검사: ./gradlew clean check
      • 현재 설정상 checkstyleMain 활성화, checkstyleTest 비활성화.
    • 스타일만 확인: ./gradlew checkstyleMain
    • 리포트 위치: build/reports/checkstyle/
  • 객체지향 생활체조 원칙

    1. 한 메서드에 오직 한 단계의 들여쓰기(indent)만 한다.
      • 들여쓰기가 많다면 책임 분리를 고려. private 메서드 난립 시 객체 분리 고려.
    2. else 예약어를 쓰지 않는다. (switch 포함)
    3. 모든 원시 값과 문자열을 포장한다. (도메인 VO 적용)
    4. 한 줄에 점을 하나만 찍는다. (체이닝은 가독성 고려)
    5. 줄여 쓰지 않는다. (의도 명확성)
    6. 모든 엔티티를 작게 유지한다. (도메인 vs 서비스 책임 분리)
    7. getter/setter 남용 금지. 의미 있는 동작 메서드 제공.

swagger

api 명세

{
  "success": true,
  "message": "OK",
  "result": { /* 도메인별 payload */ }
}
  • 에러 응답: global/error/GlobalExceptionHandlerExceptionResponse/DetailedExceptionResponse 기반으로 반환.
  • 권장 헤더
    • Content-Type: application/json; charset=UTF-8
    • Accept: application/json

API 버저닝 & 폐기 정책

  • 버전 경로: /api/v1/...
  • 마이그레이션: 신규 버전 배포 후 구버전은 일정 기간 유지. 폐기 시 Swagger에 deprecated 표기 및 공지.

Test

  • 기본 BDD 포맷
void test() {
    // given
    // when
    // then
}
  • 상황에 따른 변형 허용
void test() {
   // when
   // then
   // when & then
}
  • 테스트 메서드 명명 규칙
    • 영어: BDD 의도가 드러나게. 길어져도 OK. 필요한 경우 @DisplayName으로 한글 설명 추가.
    • 한글 가능: 의도가 명확하면 OK.
void givenExistingUserIdWhenExistsCheckingThenReturnTrue() { /* ... */ }

@DisplayName("존재하는 userId로 존재하는지 검증하면 true를 반환한다.")
void given...() { /* ... */ }

void 존재하는_userId로_존재하는지_검증하면_true를_반환한다() { /* ... */ }
  • 단위/통합 테스트 실행 가이드
    • 전체 테스트: ./gradlew clean test
    • 커버리지 리포트: ./gradlew clean jacocoTestReport
      • jacocoTestReporttest에 의존하며, 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 계산 후 한 개 제거하여 반환하는 패턴을 따른다.

Clone this wiki locally