ìë íì¸ì, 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ì 기존 íì ì ì ì½ ì¡°ê±´ì ê²°í©í íì ì ëë¤.
NonBlankString= String + "ë¹ì´ìì§ ìì" ì ì½PositiveInt= int + "ìì" ì ì½EmailString= String + "ì´ë©ì¼ íì" ì ì½
ê²½ê³(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 ì¤ë¥"] â 첫 ë²ì§¸ìì ë©ì¶ì§ ìê³ ì ë¶ ìì§
íµì¬ ìì¹
204ê° ì ì íì (46 numeric, 48 string, 10 collection, 7 character...)
ì ë¡ ë°íì ìì¡´ì± â classpath ì¶©ë ìì
Java 8+ í¸í â ë ê±°ì íë¡ì í¸ë OK
100% í ì¤í¸ 커ë²ë¦¬ì§, 95%+ mutation testing
MIT ë¼ì´ì ì¤
Kotlin íì¥ ëª¨ëë ì ê³µ
ì¤ì¹
// 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>
ë§í¬
Dev.to íí 리ì¼: https://dev.to/junggikim/stop-writing-if-checks-refinement-types-in-java-8-14ap
í¼ëë°±ì´ë ê¶ê¸í ì í¸íê² ë¨ê²¨ì£¼ì¸ì!안녕하세요, 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
피드백이나 궁금한 점 편하게 남겨주세요!