49. 매개변수가 유효한지 검사하라
매개변수가 유효한지 검사하라
메서드와 생성자가 기대하는 제약사항을 문서화하고 명시적으로 검사하기
매개변수 검사가 중요한 이유
메서드 본문 실행 전에 오류를 빠르게 감지할 수 있다
오류 원인과 위치를 정확히 파악할 수 있다
실패 원자성(failure atomicity)을 지킬 수 있다
검사하지 않으면 발생할 수 있는 문제
메서드 중간에 모호한 예외가 발생한다
메서드가 잘못된 결과를 반환할 수 있다
객체가 불안정한 상태가 되어 미래에 예상치 못한 오류가 발생할 수 있다
매개변수 검사 방법
일반적인 검사 방법
/**
* (현재 값 mod m) 값을 반환한다.
* 항상 음이 아닌 BigInteger를 반환한다는 점에서 remainder와 다르다.
*
* @param m 계수(양수여야 함)
* @return 현재 값 mod m
* @throws ArithmeticException m이 0 이하일 경우
*/
public BigInteger mod(BigInteger m) {
if (m.signum() <= 0)
throw new ArithmeticException("계수(m)는 양수여야 합니다: " + m);
return ... // 계산 수행
}
Java 7부터 도입된 유틸리티 메서드
// Objects.requireNonNull - null 검사
this.strategy = Objects.requireNonNull(strategy, "전략이 null일 수 없습니다");
// Java 9: requireNonNullElse - null일 경우 기본값 제공
this.timeout = Objects.requireNonNullElse(timeout, DEFAULT_TIMEOUT);
// Java 9: 인덱스 검사 유틸리티
this.subList = Objects.checkFromIndexSize(fromIndex, size, list.length);
this.subList = Objects.checkFromToIndex(fromIndex, toIndex, list.length);
this.element = Objects.checkIndex(index, list.length);
private 메서드에서는 assert 활용
private static void sort(int[] a, int fromIndex, int toIndex) {
assert fromIndex >= 0;
assert toIndex <= a.length;
assert fromIndex <= toIndex;
// 정렬 로직...
}
✔️ 특징
assert는 개발 중에만 활성화된다 (
-ea
또는--enableassertions
옵션 필요)런타임에는 성능 저하가 없다
private 메서드는 호출되는 상황을 통제할 수 있으므로 assert로 충분하다
특별한 상황들
📌 생성자 매개변수 검사
public Period(Date start, Date end) {
// null 검사와 방어적 복사를 함께 수행
this.start = new Date(Objects.requireNonNull(start, "시작 날짜가 null일 수 없습니다").getTime());
this.end = new Date(Objects.requireNonNull(end, "종료 날짜가 null일 수 없습니다").getTime());
// 날짜 관계 검증
if (this.end.before(this.start)) {
throw new IllegalArgumentException(
"종료 날짜가 시작 날짜보다 이전일 수 없습니다");
}
}
✔️ 중요성
생성자는 객체의 불변식을 보장하는 첫 번째 방어선이다
잘못된 객체가 생성되는 것을 막을 수 있다
📌 필드로 저장하는 매개변수
public void setBackgroundColor(Color color) {
this.backgroundColor = Objects.requireNonNull(color);
}
✔️ 특징
나중에 사용하려고 저장하는 매개변수는 특히 더 신경 써서 검사해야 한다
null이 저장되면 나중에 NullPointerException이 발생할 수 있다
📌 고비용 검사와 암묵적 검사
고비용 검사
// 큰 배열이 정렬되어 있는지 검사하는 것은 비용이 높음
public void processSortedArray(int[] hugeArray) {
// 정렬 검사를 하지 않음 (문서화로 대체)
}
암묵적 검사
// Collections.sort는 내부적으로 타입 검사를 수행
Collections.sort(list);
✔️ 주의사항
예외적인 상황에서만 검사를 생략한다
문서화를 통해 제약조건을 명확히 한다
암묵적 검사에 과도하게 의존하면 실패 원자성이 깨질 수 있다
실패 원자성 보장하기
public void transfer(Account to, double amount) {
// 모든 검사를 먼저 수행
Objects.requireNonNull(to, "대상 계좌가 null일 수 없습니다");
if (amount <= 0)
throw new IllegalArgumentException("양수 금액이어야 합니다: " + amount);
if (amount > balance)
throw new IllegalArgumentException("잔액 부족");
// 모든 검사 통과 후 상태 변경
balance -= amount;
to.balance += amount;
}
✔️ 실패 원자성이란?
메서드 호출이 실패하더라도 객체는 호출 전 상태를 유지해야 함
매개변수 검사는 상태 변경 전에 수행해야 함
표준 예외 사용하기
IllegalArgumentException
인수 값이 부적절함
IndexOutOfBoundsException
인덱스가 범위를 벗어남
NullPointerException
null 값이 전달됨
IllegalStateException
객체 상태가 메서드 호출에 부적절함
ConcurrentModificationException
허용하지 않는 동시 수정 감지
UnsupportedOperationException
호출한 메서드를 지원하지 않음
🧩 어려웠던 점
너무 엄격하면 유연성이 떨어지고, 너무 느슨하면 안정성이 떨어진다
성능이 중요한 상황에서 매개변수 검사와 성능 사이의 균형을 잡는 것
여러 매개변수 간의 복잡한 관계를 검증하는 코드를 깔끔하게 작성하는 것
💭 느낀 점
매개변수 검사는 API의 사용성을 높이고 오류 메시지를 명확하게 해준다
제약조건을 명시적으로 검사하는 것은 코드의 의도를 분명히 드러내준다
유틸리티 메서드(Objects.requireNonNull 등)를 활용하면 코드가 더 간결해진다
Last updated