본문 바로가기
Language/Effective java

메서드

by y.j 2022. 8. 1.
728x90

매개변수가 유효한지 검사하라.

메서드와 생성자 대부부은 입력 매개변수의 값이 특정 조건을 만족하여야 한다. 오류를 발생한 즉시 잡지 못하면 해당 오류를 감지하기 어려워지고, 감지하더라도 오류의 발생 지점을 찾기 어려워진다.

 

매개변수가 잘못되었을 때 문제점

  • 메서드가 수행되는 중간에 모호한 예외를 던지며 실패 할 수 있다.
  • 메서드가 잘 수행되지만 잘못된 결과를 반환할 때다.
  • 어떤 객체를 이상한 상태로 만들어놓아서 미래에 알 수 이 없는 시점에 이 메서드와 관련 없는 오류를 낸다.

 

public과 protected 메서드는 매개변수 값이 잘못됐을 때 던지는 예외를 문서화해야 한다. ( @throws 자바독 태그를 사용하면 된다. )

/**
 * ( 현재 값 mod m ) 값을 반환한다. 이 메서드
 * 항상 음이 아닌 BigInteger를 반환한다는 점에서 remainder 메서드와 다르다.
 * 
 * @param m 계수 (양수여야 한다.)
 * @return 현재 값 mod m
 * @throws ArithmeticException m이 0보다 작거나 같으면 발생한다.
 */
public BigInteger mod(BigInteger m) {
    if(m.signum() <= 0)
        throw new ArithmeticException("계수(m)는 양수여야 합니다." + m);
}

 

java.util.Objects.requireNonNull 메서드는 유연하고 사용하기도 편해 더 이상 null 검사를 수동으로 하지 않아도 된다.

this.strategy = Objects.requireNonNull(strategy, "전략");

 

public이 아닌 메서드라면 assert를 통해 매개변수 유효성을 검증 할 수 있다. 아래 조건이 참이 아니면 AssertionError가 발생하고 런타임에 아무런 효과도, 성능저하도 없다.

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;
}

 

매개변수 유효성을 검사해야 한다는 규칙의 예외

  • 유효성 검사 비용이 지나치게 높다
  • 계산 과정에서 암묵적으로 검사가 수행될 때

 

적시에 방어적 복사본을 만들라

클라이언트가 불변식을 깨뜨린다고 가정하고 방어적으로 프로그래밍해야 한다. 

 

Period 클래스는 불변식이 쉽게 깨지는 예제이다.

public class Period {
    private final Date start;
    private final Date end;

    public Period(Date start, Date end) {
        this.start = start;
        this.end = end;
    }
    
    public Date start() {
        return start;
    }
    
    public Date end() {
        return end;
    }
}

end.setYear에서 Period의 내부 값을 수정되도록 바뀌었다.

public static void main(String[] args) {
    Date start = new Date();
    Date end = new Date();
    Period p = new Period(start, end);
    end.setYear(780);
}

 

이것을 해결하기 위해 생성자에서 받은 가변 매개변수 각각을 방어적 복사를 해야 한다. 방어적 복사본을 만들고 나서 유효성을 검사하였다.

public class Period {
    private final Date start;
    private final Date end;

    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);
    }

    public Date start() {
        return start;
    }

    public Date end() {
        return end;
    }
}

* 매개변수가 제 3자에 의해 확장 될 수 있는 타입이라면 clone을 사용해서는 안된다.

 

하지만, 여전히 end()메서드를 호출하여 p의 내부를 바꿀 수 있다.

public static void main(String[] args) {
    Date start = new Date();
    Date end = new Date();
    Period p = new Period(start, end);
    p.end().setYear(780);
}

 

메서드들도 방어적 복사본을 반환해주자.

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

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

 

방어적 복사를 해야 하는 이유 중 하나는 클라이언트가 제공한 객체의 참조를 내부의 자료구조에 보관해야 할 때면 항시 그 객체가 잠재적으로 변경 될 수 있는지를 생각해야 한다. Set이나 Map같은 자료구조에 보관하는 동안 객체가 변경된다면 불변식이 깨질 것이다.

 

방어적 복사에는 성능 저하가 따르고, 또 항상 쓸 수 있는 것도 아니다. 

  • 호출자가 컴포넌트 내부를 수정하지 않으라 확신하면 방어적 복사를 하지 않아도 되지만, 문서화를 해놓자
  • 다른 패키지에서 넘겨받은 가변 매개변수에 경우 이전하는 클라이언트가 더 이상 객체를 직접 수정하는 일이 없다면 방어적 복사는 필요없다.
  • 방어벅 복사를 생략해도 되는 상황에서 불변식이 깨지더라도 영향도는 클라이언트로 국한되어야만 한다. 래퍼클래스의 경우에는 래퍼에 넘긴 객체는 접근 할 수 있지만 클라이언트에만 영향을 받는다.

 

 

 

 

728x90

'Language > Effective java' 카테고리의 다른 글

메서드  (0) 2022.08.04
메서드  (0) 2022.08.01
람다와 스트림  (0) 2022.07.29
람다와 스트림  (0) 2022.07.26
람다와 스트림  (0) 2022.07.24

댓글