실전! 코틀린과 스프링 부트로 도서관리 애플리케이션 개발하기

Ch03. 요구사항 추가(type, 대출현황) - Enum Class 를 활용해 type 리펙토링 하기

webmaster 2022. 11. 5. 23:07
728x90

String 클래스 단점

  • 클라이언트에서 들어오는 type 필드에 무엇이 들어올지 모른다(서버에서 검증 로직을 작성해 줄 수 있지만, 코드를 추가 작성해야 하는 부담이 있다)
  • 코드만 보았을 때, Book 테이블의 type 필드에 어떤 값들이 있는지 알 수 없다.
  • type과 관련한 새로운 로직을 작성해야 할 때, 분기 로직이 들어간다.

예를 들어 type에 따라 점수를 주는 로직을 작성해야 한다면 아래와 같은 로직이 들어갈 것이다.

fun getEventScore(): Int {
    return when (type) {
        "COMPUTER" -> 10
        "ECONOMY" -> 8
        "SOCIETY", "LANGUAGE", "SCIENCE" -> 5
        else -> throw IllegalArgumentException("잘못된 타입입니다")
    }
}
  • switch을 분기문이 들어가므로 타입이 추가될 때 실수로 추가하지 않거나,  누락되는 문제가 발생할 수 있다.
  • else 문 같은경우 절대 동작될 수 없지만, 추가해야 되는 단점이 있다.

이러한 단점을 해결하기 위해서는 Enum 클래스를 적극 활용하자

Enum 클래스

BookType 클래스

enum class BookType {
    COMPUTER,
    ECONOMY,
    SOCIETY,
    LANGUAGE,
    SCIENCE,
}

Book

@Entity
class Book(
    val name: String,
    @Enumerated(EnumType.STRING)
    val type: BookType, //책 타입 추가
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long? = null,
) {
    //기본 생성자가 없는 오류가 발생(플러그인을 받으면 오류 해결)

    init { // 초기화 블록
        if (name.isBlank()) {
            throw IllegalArgumentException("이름은 비어 있을 수 없습니다")
        }
    }

    companion object{ //동반객체가 가장 아래 들어가는게 컨벤션
        //Object Mother 패턴
        fun fixture(
            name: String="책 이름",
            type: BookType = BookType.COMPUTER,
            id:Long? = null,
        ): Book{
            return Book(
                name = name,
                type = type,
                id = id,
            )
        }
    }
}

BookRequest

data class BookRequest(
    val name: String,
    val type: BookType,
)

BookServiceTest

@Test
@DisplayName("책 등록이 정상 동작한다")
fun saveBookTest() {
    //given
    val request = BookRequest("이상한 나라의 엘리스", BookType.COMPUTER)

    //when
    bookService.saveBook(request)

    //then
    val books = bookRepository.findAll()
    assertThat(books).hasSize(1)
    assertThat(books[0].name).isEqualTo("이상한 나라의 엘리스")
    assertThat(books[0].type).isEqualTo(BookType.COMPUTER)
}
  • Enum 클래스 같은 경우 패키지 위치를 3가지에 둘 수 있다 -> 큰 장단점이 있지 않으므로, 팀 컨벤션을 지키자.
    • 엔티티 파일에 같이 두기
    • 도메인 하위 패키지에 두기
    • type/도메인 패키지에 두기
  • fixture를 호출하여 테스트 코드를 작성했기 때문에 오류가 발생하지 않는다.
  • 기존에 발생한 문제들이 모두 해결이 되었다.
    1. 클라이언트에서 들어오는 type 필드에 무엇이 들어올지 모른다 -> 존재하지 않는 값을 넣으면, 400 에러로 서버에서 Enum을 확인해 자동으로 막아준다.
    2. 코드만 보았을때 Book 테이블의 type 필드에 어떤 값들이 있는지 알 수 없다 -> Enum 코드만 보면, 바로 알 수 있으며, 주석을 활용하면 코드의 유지보수를 용이하게 할 수도 있다.
    3. type과 관련한 새로운 로직을 작성할 때, 분기로직이 발생한다 -> 더 이상 분기 로직이 아닌 Enum 타입을 생성자에 점수를 미리 넣어 프로퍼티처럼 바로 가지고 올 수 있다(유지보수성 용이)
enum class BookType(val score: Int) {
    COMPUTER(10),
    ECONOMY(8),
    SOCIETY(5),
    LANGUAGE(5),
    SCIENCE(5) }
fun getEventScore(): Int {
    return this.type.score
}
  • @Enumerated(EnumType.STRING) 애노테이션을 사용해 DB에도 숫자가 아닌 String이 들어가도록해야한다.
    • 숫자가 들어가면 값이 추가, 삭제되면 데이터의 적합성이 깨지는 단점이 있기 때문에 반드시 String값을 저장해야 한다.
728x90