inflearn logo
강의

강의

N
챌린지

챌린지

멘토링

멘토링

N
클립

클립

로드맵

로드맵

지식공유

[오픈소스] if (name == null) 방어 코드, 타입으로 없앨 수 있습니다 — java-refined

90

jg k
0

안녕하세요, Java 개발자분들께 만든 오픈소스 라이브러리를 소개합니다.

이런 코드, 매일 쓰고 계시죠?

public void createUser(String name, int age, List<String> roles) {
    if (name == null || name.trim().isEmpty()) {
        throw new IllegalArgumentException("name must not be blank");
    }
    if (age <= 0) {
        throw new IllegalArgumentException("age must be positive");
    }
    if (roles == null || roles.isEmpty()) {
        throw new IllegalArgumentException("roles must not be empty");
    }
    // 여기서부터 진짜 비즈니스 로직...
}

서비스 메서드마다 반복되는 방어적 검증 코드. 하나 빠뜨리면 3계층 아래에서 NPE가 터집니다.

java-refined로 바꾸면

public void createUser(NonBlankString name, PositiveInt age, NonEmptyList<String> roles) {
    // 검증 코드 불필요 — 타입이 보장합니다
}

Refinement type은 기존 타입에 제약 조건을 결합한 타입입니다.

경계(Controller)에서 한 번 생성하면, 이후 모든 레이어에서 유효성이 보장됩니다.

Spring Boot에서 Bean Validation과 함께 쓰기

java-refined는 Bean Validation을 대체하는 게 아니라 보완합니다.

검증 종류 도구 위치 단일 값 제약 java-refined Service/Domain 파라미터 DTO 어노테이션 @Valid + Bean Validation Controller DTO 교차 필드 검증 커스텀 Validator 복합 규칙

점진적 도입: 새로 만드는 Service 메서드부터 적용하고, 기존 @Valid는 그대로 유지하면 됩니다.

에러 처리

// 1줄로 검증 + 안전한 기본값
PositiveInt safeAge = PositiveInt.ofOrElse(userInput, 1);

// fold()로 성공/실패 분기
EmailString.of("invalid").fold(
    v -> log.warn("에러: {} - {}", v.code(), v.message()),
    email -> sendMail(email)
);

// 여러 필드 에러를 한 번에 수집
Validated<String, Integer> a = Validated.invalid(Arrays.asList("age 오류"));
Validated<String, Integer> b = Validated.invalid(Arrays.asList("name 오류"));
List<String> errors = a.zip(b, Integer::sum).getErrors();
// ["age 오류", "name 오류"] — 첫 번째에서 멈추지 않고 전부 수집

핵심 수치

설치

// Gradle
implementation("io.github.junggikim:java-refined:1.1.0")
<!-- Maven -->
<dependency>
    <groupId>io.github.junggikim</groupId>
    <artifactId>java-refined</artifactId>
    <version>1.1.0</version>
</dependency>

링크

피드백이나 궁금한 점 편하게 남겨주세요!안녕하세요, Java 개발자분들께 만든 오픈소스 라이브러리를 소개합니다.

 

이런 코드, 매일 쓰고 계시죠?

 

public void createUser(String name, int age, List<String> roles) {

if (name == null || name.trim().isEmpty()) {

throw new IllegalArgumentException("name must not be blank");

}

if (age <= 0) {

throw new IllegalArgumentException("age must be positive");

}

// 여기서부터 진짜 비즈니스 로직...

}

 

java-refined로 바꾸면:

 

public void createUser(NonBlankString name, PositiveInt age, NonEmptyList<String> roles) {

// 검증 코드 불필요 — 타입이 보장합니다

}

 

세 가지 생성 패턴:

 

1. of(value) → Validation 반환 (예외 없음). fold()로 성공/실패 분기

2. unsafeOf(value) → 유효하지 않으면 예외. 신뢰할 수 있는 데이터용

3. ofOrElse(value, default) → 검증 + 안전한 기본값. 1줄로 처리

 

// 1줄로 검증 + 안전한 기본값

PositiveInt safeAge = PositiveInt.ofOrElse(userInput, 1);

 

// fold()로 성공/실패 분기

EmailString.of("invalid").fold(

v -> log.warn("에러: {} - {}", v.code(), v.message()),

email -> sendMail(email)

);

 

Spring Boot에서 Bean Validation과 함께 쓸 수 있습니다:

- 단일 값 제약 → java-refined (Service/Domain 파라미터)

- DTO 어노테이션 → @Valid + Bean Validation (Controller)

- 교차 필드 검증 → 커스텀 Validator

 

핵심 수치:

- 204개 정제 타입 (46 numeric, 48 string, 10 collection...)

- 제로 런타임 의존성

- Java 8+ 호환

- 100% 테스트 커버리지, 95%+ mutation testing

- MIT 라이선스

 

GitHub: https://github.com/JunggiKim/java-refined

Maven Central: io.github.junggikim:java-refined:1.1.0

Dev.to 튜토리얼: https://dev.to/junggikim/stop-writing-if-checks-refinement-types-in-java-8-14ap

 

피드백이나 궁금한 점 편하게 남겨주세요!

답변 0