해당 포스트는 "Effective Java" 책의 내용을 요약한 것이다.



인자의 유효성을 검사하라

: 메서드나 생성자를 구현할 때는 받을 수 있는 인자에 제한이 있는지 따져봐야 한다. 제한이 있으면 그 사실을 Javadoc과 같은 문서에 남기고 오류를 최대한 빨리 발견하기 위해서 메서드의 윗 부분에 검사해야한다. 문서에 남기는 것은 public 메서드에 해당한다. public 메서드가 아닌 패키지 개발자라면 유효한 인자를 전달하도록 통제가 스스로 가능하다. 따라서 Javadoc에 문서를 남기거나 유효성 검사를 하지않고 일반적으로 확증문 assertion을 이용한다. 물론 인자에 제약이 두는 것이 무조건 바람직 한 건 아니다. 인자에 제약이 적을수록 좋기 때문이다. 또한 유효성 검사가 자동적으로 이루어지는 경우가 있다. 예로 Collections.sort(List); 인데 sort 메서드는 리스트 내 모든 객체에 대해서 비교연산을 수행한다. 만약 리스트 내에 비교 불가능 객체가 포함되어 있다면 자동적으로 ClassCastException이 발생할 것이다. 따라서 sort 메서드 실행 전에 리스트 내 모든 객체에 대해서 비교 가능한지 검사하는 것은 의미가 없다.

/**
 * (this mod m)의 값을 가지는 BigInteger를 리턴한다.
 * 이 메소드는 항상 음수가 아닌 BigInteger를 리턴한다는 점이 
 * remainder 메소드와 다르다
 * 
 * @param m 나누는 수, 반드시 양수이다.
 * @return this BigInteger를 m으로 나눈 몫, 항상 양수이다.
 * @throw ArithmeticException m <= 0 일 때 발생한다.
 */

public BigInteger mod(BigInteger m){
    if(m.signum() <= 0)
        throw new ArithmeticException("Modulus not positive");
        
    ...// 실제 계산
}
private static void sort (long a[], int offset, int length) {
    assert a != null;
    assert offset >= 0 && offset <= a.length;
    assert length >= 0 && length <= a.length - offset;
    // 계산 수행
}

 필요하면 방어적 복사본을 만들어라

: 자바는 버퍼 오버플로, 배열 오버런, 와일드 포인터 같은 메모리 훼손 오류가 발생하지 않아 안전하다. 하지만 악의적인 클라이언트나 실수로 인해 프로그램이 망가지지 않도록 방어적으로 프로그래밍 해야 한다. 다음은 클라이언트가 변경 불가능 클래스를 변경하는 예이다.

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 start(){
        return start;
    }
    public Date end(){
        return end;
    }
    ... // 이하 생략
}

위 Period 클래스는 변경 불가능한 클래스이다. 얼핏 보면 변경이 불가능할 것처럼 보인다. 하지만 Date가 변경 가능 클래스라는 점을 이용하면 이를 깨뜨릴 수 있다.

Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
end.setYear(78); // p의 내부를 변경

따라서 Period 객체의 내부를 보호하려면 생성자로 전달되는 변경 가능 객체를 반드시 방어적으로 복사해야한다.

public Period(Date start, Date end) {
    this.start = new Date(start.getTime());
    this.end = new Date(end.getTime());
    if ( this.start.compareTo(this.end) > 0 ) 
        throw new IllegalArgumentException(this.start + " after " + this.end);
}

방어적 복사할 때 주의할 것이 인자 유효성을 검사하기 전에 복사를 해야 한다. 인자를 검사한 후 복사하기 전까지의 시간동안 공격을 받을 수 있기 때문이다. 위와 같이 생성자를 통해서 공격을 막을 수 있지만 아직까지 접근자를 통한 공격은 막을 수 없다.

Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
p.end().setYear(78);  // p 의 내부를 변경 

위와 같이 하면 접근자를 통해서 변경 불가능 클래스의 객체값을 바꿀 수 있다. 따라서 접근자를 다음과 같이 수정해 방어적 복사가 일어나도록 해야 한다.

public Date start(){
   return new Date(start.getTime());
}

publick Date end(){
   return new Date(end.getTime());
}

이렇게 생성자와 접근자를 수정하면 Period는 안전하고 진정한 변경 불가능 클래스가 된다. 

방어적인 복사본을 만들면 안전한 프로그램이 되지만 성능에서 손해를 보기 때문에 적절치 못할 때가 있다. 따라서 변경 가능한 클래스나 변경 불가능한 클래스나 내부 필드 값이 클라이언트에 의해서 변경될 가능성이 없다고 확실시 될 때는 방어적인 복사본을 만들지 않아도 된다. 대신 문서에 필드 값을 변경하면 안 된다는 사실을 명시하는 게 좋다. 

클라이언트로부터 구했거나, 클라이언트에게 반환되는 변경 가능 컴포넌트가 있는 경우 해당 클래스는 그 컴포넌트를 방어적으로 복사하는 게 좋다. 다만 복사 오버헤드가 크고 사용자가 내부 컴포넌트를 부적절하게 변경하지 않는다는 보장이 있을 때는 방어적 복사 대신 문서에 변경하면 안 된다고 명시하고 넘어갈 수 있다.


- 인자 리스트를 길게 만들지 마라.

- 인자의 자료형으로는 클래스보다 인터페이스가 더 좋다.

- 인자 자료형으로 boolean을 쓰는 것보다는 원소가 2개인 enum 자료형을 쓰는 것이 낫다.


 오버로딩할 때는 주의하라.

public class CollectionClassifier {
   public static String classify(Set<?> s) {
     return "Set";
   }
   public static String classify(List<?> s) {
     return "List";
   }
   public static String classify(Collection<?> s) {
     return "Unknown Collection";
   }

   public static void main(String[] args) { 
     Collection<?> collections = {
       new HashSet<String>(), 
       new ArrayList<BigInteger>(),
       new HashMap<String, String>().values()
     };
     for (Collection<?> c : collections) {
       System.out.println(classify(c));
     }
   }
}

위 코드에서 println 결과값을 보면 Set,List,Unknown Collection이라고 출력될 것 같지만 UnKnown Collection을 3번 출력한다. 오버로딩된 메서드들은 어떤 것을 호출할 지는 컴파일 시점에 호출되기 때문이다. 위에서 변수들은 Collection<?> 자료형으로 선언되 컴파일 시점에는 Collection으로 인식된다. 반면, 재정의된 오버라이딩 메서드들은 실행 시점에 어떤 메서드를 호출할지 결정해 다형성이 보장된다. 따라서 위 코드를 고치는 방법은 다음 코드와 같이 오버로딩된 메서드 3개를 합치고 그 안에서 instanceof 연산자로 자료형을 검사하는 것이다. 

	public static String classify(Collection<?> c){
		return c instanceof Set ? "Set" :
			c instanceof List ? "List" : "Unknown Collection";
	}

위에서 본 것 같이 오버로딩을 사용할 때는 조심해야 한다. 가장 안전한 전략은 같은 수의 인자를 갖는 두 개의 오버로딩 메서드를 API에 포함시키지 않는 것이다. 어쩔 수 없이 같은 수의 인자를 낮는 메서드를 구현해야 할 시에는 인자 중 적어도 하나라도 서로 형변환 할 수 없다고 확신할 정도로 다른 인자를 가져야 한다. 


- varargs는 인자 개수가 가변적인 메서드를 정의할 때 편리하다. 성능상 오버헤드가 많이 발생할 가능성이 높다. 따라서 인자 개수가 4개 이상일 때 인자 3개는 varargs를 사용하지 말고 일반 인자를 사용하고 4번 째부터 varargs를 사용하는 게 좋다. 


- null 대신 빈 배열이나 컬렉션을 반환해라.



 모든 API 요소에 주석을 달아라

: javadoc을 활용해 주석을 달아라. 아래는 javadoc을 활용한 예이다.

    /**
    * Returns the element at the specified position in this list.
    *
    * <p>This method is <i>not</i> guaranteed to run in constant
    * time. In some implementations it may run in time proportional
    * to the element position.
    *
    * @param index index of element to return; must be
    * non-negative and less than the size of this list
    * @return the element at the specified position in this list
    * @throws IndexOutOfBoundsException if the index is out of range
    * ({@code index < 0 || index >= this.size()})
    */
먼저 제일 첫 줄에 메서드가 무엇을 하는 지를 요약해서 설명한다. 메서드나 생성자의 경우 위와 같이 동사구로 표현한다. 클래스, 인터페이스, 필드의 요약문은 명사구로 표현한다. Javadoc은 문서화 주석을 HTML 문서로 변환하기 때문에 html 태그가 사용된다. 따라서 <연산자를 그대로 사용할 수 없다. 위의 예처럼 {@code }를 사용하면 <을 코드로 인식하게 된다. 여러 줄의 코드를 사용할 시 <pre>{@code }</pre>를 사용하면 된다. 또한 왠만하면 "."도 사용하지 않는 게 좋다.  '<'나 '&'를 코드가 아닌 주석의 일부로 표현하고 싶다면 {@literal} 태그를 사용하면 된다. @throws는 선행 조건을 나타낸다. 따라서 if문가 자주 사용된다. 제네릭을 주석으로 표현할 때는 모든 자료형 인자들을 설명해야 하고 enum의 경우 enum 안에 선언된 상수에 대해서 설명해야 한다.


+ Recent posts