JAVA/JAVA | Spring 학습기록

[Java] 불변 객체와 UnsupportedOperationException

kth990303 2024. 7. 19. 20:38
반응형

기본적이지만 놓치기 쉬운 부분을 기록해보려 한다. 

이해하기 쉽게 예시를 들어, 코드와 함께 상황극으로 설명하려 한다.

 

아래와 같은 코드가 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Getter
public class BoulderingCrewSignupRequest {
 
    private List<Long> memberId;
    private String crewName;
 
    public BoulderingCrewSignupRequest(List<Long> memberId, String crewName) {
        this.memberId = memberId;
        this.crewName = crewName;
    }
 
    public static BoulderingCrewSignupRequest generateNewCrew(String crewName) {
        return new BoulderingCrewSignupRequest(Collections.emptyList(), crewName);
    }
 
    public void addMember(Long memberId) {
        this.memberId.add(memberId);
    }
}
 
cs

 

클라이밍 종목 중 하나인 볼더링에 관심이 있는 학생이 있다.

이 학생은 혼자 클라이밍하기는 아직 무서웠는지, 크루를 만들어서 자신이 포함된 클라이밍 크루를 만들려 한다.

그래서 BoulderingCrewSignupRequest 클래스를 만들되, 자신은 이미 속해있도록 정적 팩터리 메서드 BoulderingCrewSignupRequest.generateNewCrew() 를 이용하려 한다.

 

 

이 상황을 이해하기 귀찮다면, 아래 테스트 코드를 보자.

1
2
3
4
5
6
7
8
9
10
11
@Test
@DisplayName("새로운 크루를 만들고 회원을 가입시키는 요청을 생성한다")
void addMember() {
    Long memberId = 1L;
 
    BoulderingCrewSignupRequest request = BoulderingCrewSignupRequest.generateNewCrew("전완근을키울래");
    request.addMember(memberId);
 
    assertThat(request.getMemberId()).hasSize(1);
}
 
cs

 

테스트코드로 위 상황과 똑같은 경우를 만들어보았다.

정적 팩터리 메서드 BoulderingCrewSignupRequest.generateNewCrew() 를 이용하되, 자신은 이미 가입되도록 addMember로 자신이 포함된 크루 가입 신청서를 만들었다.

이 테스트는 제대로 잘 돌까?

 

 

결과는 아래와 같았다.

UnsupportedOperationException

 

java.lang.UnsupportedOperationException 이 발생하고 말았다.

 

무엇이 문제일까? 이 학생이 클라이밍을 할 수 있도록 문제점을 찾아주자!

(이렇게 작성하니까 무슨 학교 수학문제 같은 느낌이 든다;;)

 

원인

Collections.emptyList() 로 불변객체를 만든 후 add

원인은 바로 여기다.

무심코 빈 리스트를 만들고자 Collections.emptyList() 를 만들어 불변객체로 생성 후 add 하려고 하니 UnsupportedOperationException 이 발생한 것.

 

공식문서를 살펴보면 Collections.emptyList  에 대해 아래와 같이 설명이 되어있다.

immutable list, 즉 불변객체를 만든 것이다.

new ArrayList<>(); 로 만들어주거나, Arrays.asList() 를 사용했다면 위와 같은 문제는 발생하지 않았을 것.

 

Java 9 의 List.of(),

Java 10 의 Collections.toUnmodifiableList(),

Java 16 의 stream.toList()를 쓸 때에도 조심해야 한다.

immutable 객체이므로 수정 시 UnsupportedOperationException이 발생할 수 있다.

 

에이, 우리가 바보도 아니고!
당연히 add, remove 안하면 되는 거 아냐~

 

맞는 말이다!

어려운 내용은 아니고 불변객체에 대한 Java 문법의 기본기에 해당되는 내용이다.

 

하지만 이런 경우가 있을 수 있다.

  • 어떠한 비즈니스 로직 및 요구사항으로 인해, A 개발자는 어떠한 필드를 immutable Collections 객체로 만들었다. 이 객체는 수정되어선 안되는 요구사항이 존재한다. 
  • 하지만, 몇달 후 요구사항이 변경됐다. B 개발자는 요구사항 변경으로 add 및 remove 하는 코드를 작성했다. B 개발자는 몇달 전 요구사항 및 비즈니스로직까진 알기 어려운 상황이라 가정하자. 타입이 List<Long> 이어서 list타입이므로 무심코 add, remove로 작성한 것.
    • 여기서 런타임에러로 QA 또는 테스트코드에서 문제를 잡아낼 수 있다. 이 때 의문사하지 말고 위 내용을 기억하면 좋다.
    • 하지만 테스트코드 미작성 또는 QA에서 해당 케이스를 놓쳤다면?
      • 운영환경에서 위 에러가 발생할 수 있다. 심지어 로그까지 확인이 어려운 상황이라면? 원인찾기는 숨바꼭질처럼 어려워질 것이다.

 

 

근데 웬만해선 테스트코드에서 잡아낼 수 있을 것이다. (테스트코드가 잘 있다면 말이지.)

위 내용을 기억해서 테스트코드가 한큐에 통과되도록, 터졌다 하더라도 원인을 바로 잡아낼 수 있도록 하여 칼퇴를 하도록 하자!!

(칼퇴하고 싶은 마음이 매우 드러나는 것 같다면 정답이다.)

반응형