| name | java-coding-standards |
| description | Spring Boot 서비스를 위한 Java 코딩 표준입니다. 네이밍, 불변성, Optional 사용, stream, exception, generics, 프로젝트 레이아웃을 다룹니다. |
| origin | ECC |
Java Coding Standards
Spring Boot 서비스에서 읽기 쉽고 유지보수 가능한 Java 17+ 코드를 위한 표준입니다.
활성화 시점
- Spring Boot 프로젝트에서 Java 코드를 작성하거나 리뷰할 때
- 네이밍, 불변성, 예외 처리 규칙을 강제할 때
- records, sealed classes, pattern matching(Java 17+)을 다룰 때
- Optional, streams, generics 사용 방식을 점검할 때
- 패키지와 프로젝트 구조를 잡을 때
핵심 원칙
- 영리함보다 명확성을 우선합니다
- 기본은 불변으로 두고 공유 가변 상태를 최소화합니다
- 의미 있는 예외로 빠르게 실패합니다
- 일관된 네이밍과 패키지 구조를 유지합니다
네이밍
public class MarketService {}
public record Money(BigDecimal amount, Currency currency) {}
private final MarketRepository marketRepository;
public Market findBySlug(String slug) {}
private static final int MAX_PAGE_SIZE = 100;
불변성
public record MarketDto(Long id, String name, MarketStatus status) {}
public class Market {
private final Long id;
private final String name;
}
Optional 사용
Optional<Market> market = marketRepository.findBySlug(slug);
return market
.map(MarketResponse::from)
.orElseThrow(() -> new EntityNotFoundException("Market not found"));
Streams 모범 사례
List<String> names = markets.stream()
.map(Market::name)
.filter(Objects::nonNull)
.toList();
변환에는 streams를 쓰되, 너무 복잡한 중첩은 루프로 바꿔 명확성을 유지합니다.
예외
- 도메인 오류에는 unchecked exception을 사용하고 기술적 예외는 문맥과 함께 감쌉니다
MarketNotFoundException 같은 도메인 전용 예외를 만듭니다
- 중앙 처리 없이 넓은
catch (Exception ex)를 남발하지 않습니다
Generics와 타입 안전성
- raw type을 피하고 제네릭 파라미터를 명시합니다
- 재사용 유틸리티에는 bounded generic을 선호합니다
public <T extends Identifiable> Map<Long, T> indexById(Collection<T> items) { ... }
프로젝트 구조
src/main/java/com/example/app/
config/
controller/
service/
repository/
domain/
dto/
util/
src/main/resources/
application.yml
src/test/java/... (main을 미러링)
포맷과 스타일
- 프로젝트 기준에 맞춰 2 또는 4칸 들여쓰기를 일관되게 유지합니다
- 파일당 public top-level type은 하나만 둡니다
- 메서드는 짧고 집중되게 유지하고, 필요하면 helper로 분리합니다
- 멤버 순서는 constants, fields, constructors, public, protected, private를 권장합니다
피해야 할 냄새
- 긴 파라미터 목록: DTO나 builder 사용
- 깊은 중첩: early return 사용
- 매직 넘버: 이름 있는 상수 사용
- static mutable state: 의존성 주입 선호
- silent catch block: 로그 후 처리하거나 재던짐
로깅
private static final Logger log = LoggerFactory.getLogger(MarketService.class);
log.info("fetch_market slug={}", slug);
log.error("failed_fetch_market slug={}", slug, ex);
Null 처리
- 피할 수 없을 때만
@Nullable을 받습니다
- 입력은
@NotNull, @NotBlank 같은 Bean Validation으로 검증합니다
테스트 기대치
- JUnit 5 + AssertJ
- Mockito 기반 mocking
- partial mock은 가능하면 피합니다
- 숨은 sleep 없는 결정론적 테스트를 선호합니다
코드는 의도적이고, 타입이 분명하며, 관측 가능해야 합니다. 마이크로 최적화보다 유지보수성을 우선합니다.