# 49. 매개변수가 유효한지 검사하라

## 매개변수가 유효한지 검사하라

메서드와 생성자가 기대하는 제약사항을 문서화하고 명시적으로 검사하기

<br>

### 매개변수 검사가 중요한 이유

* 메서드 본문 실행 전에 오류를 빠르게 감지할 수 있다
* 오류 원인과 위치를 정확히 파악할 수 있다
* 실패 원자성(failure atomicity)을 지킬 수 있다

<br>

### 검사하지 않으면 발생할 수 있는 문제

* 메서드 중간에 모호한 예외가 발생한다
* 메서드가 잘못된 결과를 반환할 수 있다
* 객체가 불안정한 상태가 되어 미래에 예상치 못한 오류가 발생할 수 있다

\
![Image](https://github.com/user-attachments/assets/989dcf7d-1a8d-405d-b9ea-a8d14105ad0e)<br>

## 매개변수 검사 방법

### 일반적인 검사 방법

```java
/**
 * (현재 값 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 ... // 계산 수행
}
```

<br>

### Java 7부터 도입된 유틸리티 메서드

```java
// 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);
```

<br>

### private 메서드에서는 assert 활용

```java
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로 충분하다

<br>

## 특별한 상황들

### 📌 생성자 매개변수 검사

```java
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(
            "종료 날짜가 시작 날짜보다 이전일 수 없습니다");
    }
}
```

✔️ 중요성

* 생성자는 객체의 불변식을 보장하는 첫 번째 방어선이다
* 잘못된 객체가 생성되는 것을 막을 수 있다

<br>

### 📌 필드로 저장하는 매개변수

```java
public void setBackgroundColor(Color color) {
    this.backgroundColor = Objects.requireNonNull(color);
}
```

✔️ 특징

* 나중에 사용하려고 저장하는 매개변수는 특히 더 신경 써서 검사해야 한다
* null이 저장되면 나중에 NullPointerException이 발생할 수 있다

<br>

### 📌 고비용 검사와 암묵적 검사

#### 고비용 검사

```java
// 큰 배열이 정렬되어 있는지 검사하는 것은 비용이 높음
public void processSortedArray(int[] hugeArray) {
    // 정렬 검사를 하지 않음 (문서화로 대체)
}
```

#### 암묵적 검사

```java
// Collections.sort는 내부적으로 타입 검사를 수행
Collections.sort(list);
```

✔️ 주의사항

* 예외적인 상황에서만 검사를 생략한다
* 문서화를 통해 제약조건을 명확히 한다
* 암묵적 검사에 과도하게 의존하면 실패 원자성이 깨질 수 있다

<br>

## 실패 원자성 보장하기

```java
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;
}
```

✔️ 실패 원자성이란?

* 메서드 호출이 실패하더라도 객체는 호출 전 상태를 유지해야 함
* 매개변수 검사는 상태 변경 전에 수행해야 함

<br>

## 표준 예외 사용하기

| 예외 타입                           | 사용 시기               |
| ------------------------------- | ------------------- |
| IllegalArgumentException        | 인수 값이 부적절함          |
| IndexOutOfBoundsException       | 인덱스가 범위를 벗어남        |
| NullPointerException            | null 값이 전달됨         |
| IllegalStateException           | 객체 상태가 메서드 호출에 부적절함 |
| ConcurrentModificationException | 허용하지 않는 동시 수정 감지    |
| UnsupportedOperationException   | 호출한 메서드를 지원하지 않음    |

<br>

***

### 🧩 어려웠던 점

* 너무 엄격하면 유연성이 떨어지고, 너무 느슨하면 안정성이 떨어진다
* 성능이 중요한 상황에서 매개변수 검사와 성능 사이의 균형을 잡는 것
* 여러 매개변수 간의 복잡한 관계를 검증하는 코드를 깔끔하게 작성하는 것

<br>

### 💭 느낀 점

* 매개변수 검사는 API의 사용성을 높이고 오류 메시지를 명확하게 해준다
* 제약조건을 명시적으로 검사하는 것은 코드의 의도를 분명히 드러내준다
* 유틸리티 메서드(Objects.requireNonNull 등)를 활용하면 코드가 더 간결해진다
