1. 들어가며
parameter와 return value는 어떻게 다루는 것이 좋은가?
method signature는 어떻게 설계할 것인가?
method 문서는 어떻게 만드는 것이 바람직한가?
에 대해서 다룹니다.
2. Method
규칙38: 인자의 유효성을 검사하라.
- index는 음수가 될 수 없다거나 객체 참조는 null이 될 수 없다는 등의 유효성 검사는 반드시 문서로 남기고 메소드 시작 부분에서 검사해야 한다.
- public 메소드라면 인자 유효성이 위반되었을 때 경우에 발생하는 예외를 Javadoc의 @throws 태그를 사용해 문서화해라.보통 IllegalArgumentException이나 IndexOutOfBoundsException, NullPointerException이 이용된다.
- public이 아닌 메소드라면 package 개발자가 메소드 호출이 이루어지는 상황을 통제할 수 있으므로 항상 유효한 인자가 전달될 것으로 생각할 수 있다. 따라서 일반적으로 assertion을 이용한다.
Warning! “인자에 제약을 두는 것이 바람직하다”로 이해해서는 안 된다. 반대로 메소드는 가능하면 일반적으로 적용될 수 있도록 설계헤야 한다. 단 제한이 있다면 확실히 검사해야 된다는 뜻이다.
규칙39: 필요하다면 방어적 복사본을 만들라.
- class를 사용하는 이들이 불변성을 망가뜨리기 위해 최선을 다할 것이라는 가정하에 방어적으로 프로그래밍해야 한다. 어떤 객체의 내부 상태를 그 객체의 도움 없이 변경하는 것은 불가능하다. 하지만 외부 클래스가 객체 상태를 마음대로 변경할 수 있는 실수를 저지르는 것도 놀랄 만큼 쉽다.
- 인자의 유효성을 검사하기 전에 방어적 복사본을 만들자. 그렇지 않으면 변경 가능한 내부 필드에 대해 수정 공격이 들어올 수 있다.
- 근본적으로, 객체의 컴포넌트로는 가능하다면 변경 불가능 객체를 사용하는 것이 좋다는 것이다. 그렇게 한다면 방어적 복사본에 대해서는 신경 쓸 필요가 없어진다.
// Period는 불변 클래스로 보인다.
public final class Period {
private final Date start;
private final Date end;
public Period(Date start, Date end) {
if(start.compareTo(end) > 0) {
throw new IllegalArgumentException(start + " after " + end);
}
this.start = start;
this.end = end;
}
public Date getStart() {
return start;
}
public Date getEnd() {
return end;
}
// 하지만 Date가 변경 가능하다면 충분히 변경 가능하다는 위험성이 있다.
public static void main(String[] args) {
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
end.setYear(78);
}
}
규칙40: method signature는 신중하게 설계하라.
- 메소드 이름은 신중하게 고르자
- 편의 메소드 제공하는데 너무 열 올리지 마라.
- parameter list는 가능한 한 짧게 하자. 특히 자료형이 같은 인자들이 길게 연결된 경우 더욱 그렇다.
짧게 줄이는 방법은 크게 세 가지이다.
a) 여러 메소드로 나눈다.
b) helper class를 만들어 인자들을 그룹별로 나눈다.
c) builder pattern을 메소드 호출에 적용한다. - 인자의 자료형으로는 class보다 interface가 좋다.
- 인자 자료형으로 boolean을 쓰는 것보다 원소개 2개인 enum 자료형을 쓰는 게 낫다.
규칙41: overloading 시 주의하라.
- 오버로딩된 메소드 가운데 어떤 것이 호출될지는 컴파일 시점에 결정된다.
public String classify(Set<?> s) // ---- 1
public String classify(List<?> lst) // ---- 2
public String classify(Collection<?> c) // ---- 3
// 이 세가지가 있으면
Collections<?> collections = {
new HashSet<>()
new ArrayList<>(),
new HashMap<>()
}
// 일 때 실제로 3이 모두 실행된다.
컴파일 시점의 자료형이 모두 Collection<?>으로 동일하기 때문이다. runtime 자료형은 다르지만 영향을 미치지 못한다.
overload 메소드는 정적으로 선택되지만 override 메소드는 동적으로 선택된다.
즉 override에서는 해당 객체의 컴파일 시점 자료형과 상관 없이 항상 하위 클래스의 override 메소드가 호출된다.
보수적인 해결법: 같은 수의 인자를 갖는 두 개 이상의 overload 메소드를 만들지 않는다.
메소드는 이름을 바꾸면 되기 때문에 그리 어렵지 않다. 하지만 생성자는?
타협안: 형변환만 추가하면 같은 인자가 되는 것들로 여러 overload 메소드를 호출하는 것은 가능한 피하자
규칙 42: varargs는 신중히 사용하라.
//간단한 사용예
static int num(int... args){
int sum = 0;
for(int arg: args)
sum += arg;
return sum;
}
- 이처럼 varargs는 임의 개수의 인자를 처리하는 메서드를 만들어야 할 때 효과적이다.
- 즉 임의의 개수가 아닌 필수 인자를 처리하려면 다른 인자로 빼는 것이 좋다.
- 그리고 마지막 인자가 배열이라고 해서 무조건 뜯어고칠 생각을 하지 말자. 정말로 임의의 개수의 인자를 처리할 수 있는 메소드를 만들어야 할 때 사용해야 한다.
규칙 43: null 대신 빈 배열이나 collection을 반환하라.
아래와 코드를 자주 본 기억이 있을 것이다.
public Cheese[] getCheeses() {
if(cheesesInStock.size() == 0)
return null;
...
}
- 하지만 치즈 재고가 없는 경우 null을 반환하는, 특별한 처리를 강제하는 코드는 바람직하지 않다. 이 메소드를 사용하는 모든 클라이언트가 후처리가 필요하다.
- 프로파일링해서 비용 문제가 심각하게 드러나지 않는 이상 빈 배열이나 collection을 반환하자.
private static final Cheese[] EMPTY_CHEESE_ARRAY = new CHEESE[0]
과 같이 만들어놓고 사용하면 의미도 명확하고 좋다.
규칙 44: 모든 API 요소에 문서화 주석을 달라.
- 좋은 API 문서륾 만들려면 API에 포함된 모든 클래스, 인터페이스, 생성자, 메소드, 그리고 필드 선언에 문서화 주석을 달아야 한다.
- 제네릭 자료형이나 메서드에 주석을 달 때는 모든 자료형 인자들을 설명해야 한다.
- enum 자료형에 주석을 달 때는 자료형, public 메소드뿐만 아니라 상수 각각에도 주석을 달자.
- annotation 자료형에 주석을 달 때는 모든 멤버에도 주석을 달자.
'DevBook' 카테고리의 다른 글
Effective Java 2판 - 9장 정리 (Exception, 예외) (1) | 2024.06.04 |
---|---|
Effective Java 2판 - 8장 정리 (일반적인 원칙) (0) | 2024.06.04 |
Effective Java 2판 - 6장 정리 (Annotation) (2) | 2024.05.14 |
Effective Java 2판 - 5장 정리 (Generic) (2) | 2024.05.14 |
Effective Java 2판 - 4장 정리 (Class, Interface) (0) | 2024.05.14 |