Effective Java 2판 - 7장 정리 (Method)

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는 신중하게 설계하라.

  1. 메소드 이름은 신중하게 고르자
  2. 편의 메소드 제공하는데 너무 열 올리지 마라.
  3. parameter list는 가능한 한 짧게 하자. 특히 자료형이 같은 인자들이 길게 연결된 경우 더욱 그렇다.
    짧게 줄이는 방법은 크게 세 가지이다.
    a) 여러 메소드로 나눈다.
    b) helper class를 만들어 인자들을 그룹별로 나눈다.
    c) builder pattern을 메소드 호출에 적용한다.
  4. 인자의 자료형으로는 class보다 interface가 좋다.
  5. 인자 자료형으로 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 자료형에 주석을 달 때는 모든 멤버에도 주석을 달자.