간펴니
간편 자바프로그래밍
간펴니
전체 방문자
오늘
어제
  • 전체보기 (185)
    • 알고리즘 (2)
    • JAVA (69)
      • 이펙티브 자바 (47)
      • JAVA 병렬프로그래밍 (5)
      • 자바 (17)
    • SPRING (60)
      • Spring (12)
      • IceWater Community (37)
      • Homme Shop (10)
      • 토비의 스프링 (1)
    • SPRING BOOT (4)
      • WhiteRecord (7)
    • 오류 (9)
    • DB (10)
      • ORACLE (5)
      • MYSQL (1)
      • MYBATIS (4)
      • JPA (0)
      • 대용량 데이터 베이스 (0)
      • SQL (0)
    • FRONT (8)
      • JSP (2)
      • JavaScript (2)
      • Jquery (3)
      • Thymeleaf (1)
    • AWS (6)
    • JNI (10)
    • 회고 (0)
    • MQ (0)
    • Radis (0)
    • Git (0)
    • Docker (0)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

  • 블로그 컨셉 변경

인기 글

태그

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
간펴니

간편 자바프로그래밍

JAVA/이펙티브 자바

Java. 불변 사용 시 적시에 방어적 복사본을 만들자

2024. 3. 6. 22:12
728x90

 

알다시피 자바는 가비지컬렉터로 인해 메모리 충돌 오류에서 안전하다.

 

하지만 클라이언트가 불변식을 깨뜨리려 혈안이 되어있다고 가정하고 방어적으로 프로그래밍 해야한다.

 

어떤 객체던 그 객체의 허락 없이는 외부에서 내부를 수정하는 일은 불가능하다.

 

하지만 주의를 기울이지 않으면 허락되는 경우가 생길 수 있다.

 

자바에서 불변식이 깨진다는 것은, 객체의 상태가 예상치 못한 방식으로 변경되어 객체가 의도한 동작만을 수행할 수 없게 된다는 것이다.

- 데이터 무결성이 손실 될 수 있다 : 객체가 가지고 있는 데이터가 더이상 유효하지 않게 된다. 예를 들어, 금융 애플리케이션에서 계좌잔액이 음수가 되지 않도록 하는 불변식이 깨질 경우, 잘못된 잔액 정보가 발생할 수 있다.

- 예기치 않은 예외 발생 : 객체의 상태가 불안정하다면, 객체를 사용하는 메서드 호출 시 예기치 않은 동작이 발생할 수 있다.
NullPointerException이 발생하면 다행이고 객체가 변경된 시점에서 한참 로직이 진행되어 어디가 문제점인지 찾기 힘들어질 수 있다.
코드의 규모가 커질 수록 에러가 발생했을 때 행복도 지수가 크게 상승 할 수 있다...^^ 이게 개발이지~
또한 협업 시에 범인으로 특정되면 호감작과 명예작을 할 수도 있겠다. 

- 멀티스레드 환경에서 동기화 문제 : 멀티스레드 환경에서 불변 객체는 동기화 걱정 없이 안전하게 공유될 수 있다. 그러나 불변식이 깨질 경우, 여러 스레드가 객체의 상태를 동시에 변경할 수 있어 경쟁 상태나 데이터 일관성 문제가 발생 할 수 있다.
진행했던 프로젝트에서는 모델을 만들 때 불변 객체로 생성하는 것이 코드 컨벤션 중 하나였기 때문에,
후에 멀티스레드를 사용하는 부분에서 일관성에 대한 안전성을 높일 수 있었다.

 

/ 코드 50-1 기간을 표현하는 클래스 - 불변식을 지키지 못했다. (302-305쪽)
public final class Period {
    private final Date start;
    private final Date end;

    /**
     * @param  start 시작 시각
     * @param  end 종료 시각. 시작 시각보다 뒤여야 한다.
     * @throws IllegalArgumentException 시작 시각이 종료 시각보다 늦을 때 발생한다.
     * @throws NullPointerException start나 end가 null이면 발생한다.
     */
    public Period(Date start, Date end) {
        if (start.compareTo(end) > 0)
            throw new IllegalArgumentException(
                    start + "가 " + end + "보다 늦다.");
        this.start = start;
        this.end   = end;
    }

    public Date start() {
        return start;
    }
    public Date end() {
        return end;
    }

    public String toString() {
        return start + " - " + end;
    }

 

필드가 final로 선언되어 있고, setter 가 존재하지 않으니 불변으로 보이지만 Date는 가변이다.

 

Date 클래스의 인스턴스는 수정이 가능하고, 이 클래스의 메서드를 통해 내부 상태를 변경할 수 있다.

 

final 키워드는 참조 자체의 불변성만을 보장할뿐, 참조된 객체의 불변성은 보장하지 않는다.

 

Java8 이후로는 Date 대신 불변인 Instant/LocalDateTime/ZonedDateTime 를 사용하자.

 

그러나 문제는 오래된 API에 Date가 많이 사용되어 있다는 것이다.

 

외부 공격으로부터 내부를 보호하려면 생성자에서 받은 기본 매개변수 각각을 방어적으로 복사해야 한다.

 

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

 

방법은 간단하다. 기존 값을 생성자를 이용해 새로 생성하자.

 

매개변수의 유효성을 검사하기 전에 방어적 복사본을 만들고, 이 복사본으로 유효성을 검사해야 한다.

 

멀티스레딩 환경이라면 원본 객체의 유효성을 검사한 후 검사본을 만드는 찰나의 순간에 다른 스레드가 원본 객체를 수정할 위험이 있기 때문이다.

 

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

 

Date는 final이 아니므로 clone이 Date가 정의한게 아닐 수도 있다.

 

즉 clone이 악의를 가진 하위 클래스의 인스턴스를 반환할 수 있다. 

 

// 수정한 접근자 - 필드의 방어적 복사본 반환
public Date start() {
    return new Date(start.getTime());
}

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

 

때문에 접근자에 가변 필드의 방어적 복사본을 반환해야 한다.

 

Date와 같은 객체, 길이가 1이상인 배열 등 항상 가변의 특징을 가진 필드를 사용할 때는 주의를 기울이자.

 

또한 방어적 복사는 성능 저하가 따르고 항상 사용할 수 있는 것도 아니기 때문에,

 

생략할 때에는 해당 매개변수의 반환값을 수정하지 말아야 함을 명확히 문서화 하도록 하자.

728x90
저작자표시 (새창열림)

'JAVA > 이펙티브 자바' 카테고리의 다른 글

Java. 오버로딩, 오버라이딩은 신중히 사용하자 ( 다중 정의 )  (0) 2024.03.06
Java. 명명규칙은 통용되는 것으로 지키자.  (1) 2024.03.06
Java. 스트림이란?  (2) 2024.01.02
43. 람다보다는 메서드 참조를 사용하라 ( 람다 표현식과 메서드 참조 )  (0) 2023.08.02
42. 익명 클래스보다는 람다를 사용하라 ( 람다 표현식 )  (0) 2023.07.31
    'JAVA/이펙티브 자바' 카테고리의 다른 글
    • Java. 오버로딩, 오버라이딩은 신중히 사용하자 ( 다중 정의 )
    • Java. 명명규칙은 통용되는 것으로 지키자.
    • Java. 스트림이란?
    • 43. 람다보다는 메서드 참조를 사용하라 ( 람다 표현식과 메서드 참조 )
    간펴니
    간펴니
    개발공부 기록하는 곳

    티스토리툴바