메서드가 건네 받은 값으로 무언가 제대로 된 일을 할 수 있다면 매개변수 제약은 적을수록 좋다.
하지만 구현 하려는 개념 자체가 특정한 제약을 내재한 경우도 드물지 않다.
메서드나 생성자를 작성할 때면 그 매개변수들에 어떤 제약이 있을지 생각해야 한다.
그 제약들을 문서화하고 메서드 코드 시작 부분에서 명시적으로 검사해야 한다.
이런 습관을 반드시 기르도록 하자.
그 노력은 유효성 검사가 실제 오류를 처음 걸러낼 때 충분히 보상받을 것이다.
매개변수 검사 못했을 시 문제
- 메서드가 수행되는 중간에 모호한 예외를 던지며 실패할 수 있음
- 더 나쁜 상황은 메서드가 잘 수행되지만 잘못된 결과를 반환할 때
- 한층 더 나쁜 상황은 메서드는 문제없이 수행 됐지만 어떤 객체를 이상한 상태로 만들어 놓아서 미래의 알 수 없는 시점에 이 메서드 와는 관련 없는 오류를 낼 때 ⇒ 실패 원자성을 어기는 결과를 낳을 수 있음
가이드
- 메서드와 생성자 대부분은 입력 매개변수의 값이 특정 조건을 만족하기를 바란다.
- 이런 제약은 반드시 문서화해야 하며 메서드 몸체가 시작되기 전에 검사해야 한다.
- 오류는 가능한 한 빨리 (발생한 곳에서) 잡아야 한다는 일반 원칙의 한 사례임
⇒ public과 protected 메서드는 매개변수 값이 잘못됐을 때 던지는 예외를 문서화해야 함
공개되지 않은 private 메서드라면 패키지 제작자인 여러분이 메서드가 호출되는 상황을 통제할 수 있음 ⇒ assert 사용
assert는 몇 가지 면에서 일반적인 유효성 검사와 다르다.
1. 실패하면 AssertionError를 던짐
2. 런타임에 아무런 효과도, 아무런 성능 저하도 없다.
- 메서드가 직접 사용하지는 않으나 나중에 쓰기 위해 저장하는 매개 변수는 특히 더 신경 써서 검사해야 한다.
static List<Integer> intArrayAsList(int[] a) { Objects.requireNonNull(a); // The diamond operator is only legal here in Java 9 and later // If you're using an earlier release, specify <Integer> return new AbstractList<>() { @Override public Integer get(int i) { return a[i]; // Autoboxing (Item 6) } @Override public Integer set(int i, Integer val) { int oldVal = a[i]; a[i] = val; // Auto-unboxing return oldVal; // Autoboxing } @Override public int size() { return a.length; } };
- 위의 코드에서 intArrayAsList에 int[] 를 requireNonNull을 하지 않으면 List가 반환되고 나서 나중에 사용하려고 할때 NullPointerException이 발생하게 된다. 이렇게 되면 해당 List를 어디에서 가져왔는지 추적하기가 어려워 디버깅이 상당히 괴로워질 수 있음
예외
- 유효성 검사 비용이 지나치게 높거나 실용적이지 않을 때, 혹은 계산 과정에서 암묵적으로 검사가 수행될 때
- 예) Collections.sort(List) 처럼 객체 리스트를 정렬하는 메서드를 생각해보면, 리스트 안의 객체들은 모두 상호 비교될 수 있어야 하며 정렬과정에서 비교가 이뤄지게 됨. 만약 상호 비교될 수 없는 타입의 객체가 들어 있다면 그 객체와 비교할 때
ClassCastException
을 던짐 - 하지만 암묵적 유효성 검사에 너무 의존 했다가는 실패 원자성을 해칠 수 있으니 주의하기