기본적이지만 놓치기 쉬운 부분을 기록해보려 한다.
이해하기 쉽게 예시를 들어, 코드와 함께 상황극으로 설명하려 한다.
아래와 같은 코드가 있다.
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로 자신이 포함된 크루 가입 신청서를 만들었다.
이 테스트는 제대로 잘 돌까?
결과는 아래와 같았다.
java.lang.UnsupportedOperationException 이 발생하고 말았다.
무엇이 문제일까? 이 학생이 클라이밍을 할 수 있도록 문제점을 찾아주자!
(이렇게 작성하니까 무슨 학교 수학문제 같은 느낌이 든다;;)
원인
원인은 바로 여기다.
무심코 빈 리스트를 만들고자 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에서 해당 케이스를 놓쳤다면?
- 운영환경에서 위 에러가 발생할 수 있다. 심지어 로그까지 확인이 어려운 상황이라면? 원인찾기는 숨바꼭질처럼 어려워질 것이다.
근데 웬만해선 테스트코드에서 잡아낼 수 있을 것이다. (테스트코드가 잘 있다면 말이지.)
위 내용을 기억해서 테스트코드가 한큐에 통과되도록, 터졌다 하더라도 원인을 바로 잡아낼 수 있도록 하여 칼퇴를 하도록 하자!!
(칼퇴하고 싶은 마음이 매우 드러나는 것 같다면 정답이다.)
'JAVA > JAVA | Spring 학습기록' 카테고리의 다른 글
[241030] 우아콘 2024 참여 후기 (7) | 2024.11.03 |
---|---|
[240831] 유스콘 2024 컨퍼런스 후기-이제는 발표자 신분으로! (다중 서버에서 똑똑하게 캐싱하기) (5) | 2024.09.02 |
[Spring] Spring Boot + Shedlock 이용한 API 응답 캐싱 폴링 (4) | 2024.06.12 |
[240525] Spring Camp 2024 갔다온 후기 (49) | 2024.05.28 |
[Spring] JUnit5 에서 OutputCapture를 이용한 로그 테스트 해보기 (6) | 2024.04.30 |