(Effective Java) 8장 메서드 ( null 체크, Optional )
[!info] 개인 의견 추가 되어 있음.
아이템 49. 매개변수가 유효한지 검사하라
- 매개변수 유효성 체크는 메서드 바디 시작 전에 수행해야 한다. (‘오류는 가능한 한 (발생지점에서 가까운 곳에서) 빨리 잡아야 한다’ 원칙 )
- 보통 파라미터 체크에 assert를 사용하는 것은 걸맞지 않지만, private 메서드라면 파라미터로 넘어오는 값을 통제할 수 있으니 assert를 사용할 수 있다. [assert 에 대해서 ]
- 그렇긴 하지만 assert 보단 파라미터 체크에는 require, 내부 상태 체크에는 check 라는 메서드를 만들어 사용하는 것이 좋은 듯. (kotlin의 require, check 참고)
- 유효성 검사 비용이 지나치게 높거나 실용적이지 않을 때, 혹은 계산 과정에서 암묵적으로 검사가 수행될 때는 유효성 체크를 생략할 수도 있다.
- e.g., 정렬을 돌리기 전에 리스트 안의 아이템들이 모두 상호 비교 가능한지를 검사하는 것은 너무 코스트가 크다. 그냥 정렬 돌려보고 Exception 나는지를 잡는게 낫다.
null 체크
1
2
3
4
5
6
7
8
9
10
// null일 경우 Exception 발생이 필요할 때
1. this.something = Objects.requireNonNull(something, "CHECK FAIL"); // NPE
2. if (something == null) new MyException("CHECK FAIL"); // 정의한 Exception
3. Optional.ofNullable(User).orElseThrow(() -> new MyException("CHECK FAIL")); // 정의한 Exception
4. assert something != null; // AssertionError
5. require(), check() ( kotlin style )
1
2
3
4
5
6
7
// Optional의 장점은 아래와 같은 상황에서
// 어느 지점에서 null이건 간에 모두 종단 처리(orElse)로 가기 때문에, 처리가 깔끔해진다는 점.
ofNullable(responseEntity)
.map(entity -> entity.getBody())
.map(body -> body.getResult())
.orElse(DEFAULT_RESULT);
// if { if { if ... 보다 확실히 가독성에 이점이 있다.
아이템 50. 적시에 방어적 복사본을 만들라
- getter 등에서 리턴해줄 때 객체 레퍼런스를 넘기면 외부에서 이 레퍼런스를 통해 객체 내부 값을 변경할 수 있으므로, copy본을 리턴 하라는 얘기 (= 불변으로 간주하는데 도움이 된다.)
아이템 51. 메서드 시그니처를 신중히 설계하라
- 메서드 매개변수는 4개 이하가 좋다.
- 매개변수가 너무 길 때 사용할 수 있는 방법 세가지?
- 여러 메서드로 쪼갠다. 이 때 직교성이 높은 방향으로 쪼갠다.(중복 기능이 없도록 쪼갠다)
- 매개변수를 여러 개 묶어서 넘길 수 있게 DataClass를 만든다. (static nested class로 둔다.)
- DataClass를 큰 단위로 묶어서 정의하고, 빌더 패턴을 사용해 값을 세팅한다.
- 참/거짓이 딱 맞는 상황이라면 boolean을, 그게 아니라면 원소 2개 짜리 enum 타입을 쓰는 것이 좋다
아이템 52. 다중정의(Overloading)는 신중히 사용하라
- Overloading 메서드는 정적으로 선택된다.(정적 타입)
- 즉, 객체를 넘길 때 그 객체의 실제 타입이 아니라 그 객체가 어떤 타입으로 넘어가는지를 보고 어떤 메서드가 호출될지 결정된다.
- item52/CollectionClassifier.java
- 이 경우 그 객체의 실제 타입을 알아내려면
instanceof
를 쓰면 되긴 한다.
- Override 메서드는 동적으로 선택된다.(동적 타입)
- 즉, 객체를 넘길 때 그 객체의 실제 타입을 보고 어떤 메서드가 호출될지 결정된다.
- item52/Overriding.java
- 안전하게 가려면 매개변수 수가 같은 Overloading은 하지 않는게 좋다.
- 위 예제에서 보다시피 형변환이 가능한 타입 끼리 Overloading이 되어 있는 경우 예상과 다르게 동작할 수 있다.
- 타입이 근본적으로 달라 이런 애매한 상황이 발생할 가능성이 없다면 Overloading도 괜찮다.
아이템 53. 가변인수는 신중히 사용하라
1
2
3
4
5
// 이렇게 쓰면 0개 넣고 호출할 시 런타임에 실패하므로, 이렇게 쓰지말고
int min(int... args)
// 이렇게 써야 0개 호출 시 컴파일 타임에 실패한다. 이걸 권장
int min(int firstArg, int... args)
아이템 54. null이 아닌, 빈 컬렉션이나 배열을 반환하라
- null 대신,
Collections.emptyList()
또는java .toArray()
를 사용해 반환하라.
1
2
3
4
5
6
7
8
// 컬렉션
return cheesesInStock.isEmpty() ? Collections.emptyList()
: new ArrayList<>(cheesesInStock);
// 배열
return cheesesInStock.toArray(new Cheese[0]);
// toArray는 다음 표현을 메서드로 만든 것이다.
// return cheesesInStock.size() > 0 ? cheesesInStock 배열 : new Cheese[0]
아이템 55. 옵셔널 반환은 신중히 하라
- Optional 똑바로 쓰기 -https://dzone.com/articles/using-optional-correctly-is-not-optional
- Optional이 항상 옳은 것은 아닌게, 단순히 값을 받아오는 목적이면 삼항 연산자를 쓰라고 권장하고 있음.(12번)
- 하지만 if나 삼항 연산자 대신 사용했을 때 가독성 이 월등히 나은 경우가 있어서(map, filter, orElseThrow), 가독성 목적 만으로도 사용할 가치가 충분해보임.
- 메서드 파라미터에 사용 금지.(14번)
- 호출당하는 쪽에서 옵셔널이든 일반필드이든 항상 validation check는 해줘야 하기 때문에, 굳이 옵셔널을 받겠다고 하여 호출할 때 한 번 더 래핑하는 번거로움을 만들 필요가 없다.
- return 값으로 쓰는건 권장함. 원래 null 리턴하기 싫어서 나온게 Optional이니까. 호출하는 쪽으로 null 처리를 강제 할 수 있다.
- 필드에 사용 금지≈파라미터 타입으로 사용 금지 (13번)
- Optional이 Serializable을 impl한 클래스가 아니기도 하고, 애초에 필드로 쓰는 용도가 아니라서.
- 파라미터 타입으로 들어가게 되면 필드가 될 수도 있으므로 주의.
- 이런 케이스는 그냥 null이나 정확한 값으로 풀던가 해서 전달해야 함
- Optional이 항상 옳은 것은 아닌게, 단순히 값을 받아오는 목적이면 삼항 연산자를 쓰라고 권장하고 있음.(12번)
- 컬렉션, 스트림, 배열 등 컨테이너 타입은 옵셔널로 반환하지 말고 그냥 empty() 반환해라.
- 박싱된 옵셔널 대신 기본타입 옵셔널인
OptionalLong
등을 사용해라. - getOrDefault 같은 유틸 메서드가 지원된다면 이를 사용하는게 더 깔끔한 경우가 종종 있다.
참고 ) Optional.orElse(new User())
와 Optional.orElseGet(User::new)
의 차이?
- orElse는 거기까지 도달하지 않는 상황이어도, 그 안에 있는 객체 User가 생성이 된다.
- 반면 orElseGet은 람다를 넘기므로, 거기까지 도달하는 상황에만 코드가 실행되어 User가 생성된다.
- 그래서 위와 같은 상황에서는
orElseGet
을 쓰는 것이 좋고, 별도 생성이 필요 없는 고정 값을 넘길 때는orElse("string")
을 쓰는 것이 좋다.
아이템 56. 공개된 API 요소에는 항상 문서화 주석을 작성하라
- 책 참고
개인 의견 추가 ) Optional 전달, Optional 리턴
위에서 언급했듯, “메서드 파라미터에 Optional 사용 금지”는 Optional이 아닌 일반 파라미터도 null일 가능성이 충분히 존재하기 때문에 Optional이든 아니든 모두 메서드 내부에서 검증을 해주어야 한다. 따라서 Optional을 굳이 쓸 필요가 없다. 라는 의미다.
반면 return 타입에는 Optional 사용이 권장되는데 이를 위와 같은 논리로 생각하면 어떤 함수가 Optional, non-Optional 타입의 결과를 각각 반환한다고 했을 때, non-Optional 타입의 값은 null이 아닐거라는 것을 확신하고 쓸 수 있다는 의미이다.
요약하면, 아래와 같다.
- 파라미터 전달 시에는 Optional이든 아니든 모두 nullable임. (외부에서 전달받는 것. 컨트롤 불가)
- 반환 시에는 nullable은 모두 Optional, 나머지는 무조건 null이 아님. (내가 외부로 반환하는 것. 컨트롤 가능)
1 2 3
* {@code Optional} is primarily intended for use as a method return type where * there is a clear need to represent "no result," and where using {@code null} * is likely to cause errors.
그렇다면 만약, “전체 시스템에서 nullable은 무조건 Optional이다.” 라는 컨벤션이 있고 잘 지켜진다고 가정한다면?
“전체 시스템에서 nullable은 무조건 Optional이다.” 라는 컨벤션이 있고 잘 지켜진다고 가정한다면, 메서드 파라미터에 넘길 때도 non-Optional 파라미터에 대해 검증없이 얘는 null이 아닐거야. 라고 처리해도 되지 않나? 라는 의문이 있을 수 있는데
하지만 현실적으로 이게 불가능하기 때문에 그냥 외부에서 전달 받는건 모두 nullable일거야 라고 가정하고 코딩 해라. 라는 것이다.
특히 라이브러리를 만들 때는 외부에서 뭘 넘길지, 컨벤션이 어떨지 알 수가 없기 때문에 단순히 우리가 코딩하면서 무조건 nullable은 optional로 하자~라고 했다고 지켜질거란 보장이 없다는 것.
이는 Java가 기본적으로 모든 타입에 대해 nullable이기 때문에 그렇다. Optional로 어느 정도 보완이 되었지만, 언어 자체에서 오는 한계는 극복 불가능하다.
따라서 이런 것들을 제한하려면 언어 차원의 제약이 필수적인거고, 코틀린이 nullability를 언어 차원에서 제약하는 이유가 이것이다.