Dev

Enum을 사용할 때 생각해봐야할 점

친환경사과 2024. 9. 26. 16:57

🍎 Enum을 "잘" 사용하는 나름의 방법을 예를 통해 정리한 글입니다.


🍏 Enum에 관한 정의

- Wiki에서 정의한 열거형(Enumeration) 타입은 멤버라 불리는 명명된 값의 집합을 이루는 자료형입니다. 열거자 이름들은 일반적으로 해당 언어의 상수 역할을 하는 식별자입니다.

 

- 위의 내용을 풀어 설명하면, 열거형은 여러 이름이 있는 값들을 하나의 그룹으로 묶은 자료형을 뜻하고 여기서 각각의 값은 ‘멤버’라고 하며, Enum Class는 이러한 멤버들을 정의하는 클래스입니다.

e.g) 일주일을 나타낼 때, '월, 화, 수, 목, 금, 토, 일'과 같이 이름을 붙인 값들을 열거형으로 정의할 수 있습니다.


📝 생각해 볼 것

✓ 어떤 값을 Enum으로 사용해야 할까?

 

1. 동일한 위상의 값

Enum의 멤버로 오는 모든 값들은 같은 위상을 만족해야 한다는 것입니다. 

 

❓ 같은 위상을 가져야 한다는 말은 무슨 이야기일까요?

같은 위상을 갖는 말은 열거형 내의 모든 멤버들이 서로 동등한 역할을 수행해야 한다는 것을 의미합니다.

 

- 예를 들어 학생들이 수강할 과목 Enum Class가 있다고 가정해 보겠습니다.

enum class Subject {
    MATHEMATICS,         // 수학
    ENGLISH,             // 영어
    HISTORY,             // 역사
    SCIENCE,             // 과학
    PHYSICAL_EDUCATION,  // 체육
    ALL,                 // 모든 과목
    NOTHING              // 어떤 과목도 선택하지 않음
}

수강 과목 Enum Class

 

- 위 Enum Class에선 위상이 다른 두 가지 멤버 ALL, NOTHING이 속해있습니다.

 

위상이 다른 멤버 사용을 지양해야 하는 이유는 무엇일까요?

- 과목이 Subject Enum Class에 추가되는 것은 ENGLISH, SCIENCE와 같은 레벨이기 때문에 추가해 사용할 수 있습니다. 하지만 모든 과목을 나타내는 ALL, 어떤 과목도 선택하지 않은 NOTHING은 다른 과목들과 다른 범주에 속하는 값입니다. 즉 위상이 다릅니다.

 

- 이와 같이 Enum에 상태적인 값을 추가하게 되면, 해당 클래스의 정체성이 모호해집니다. 예를 들어, 시간표를 구성하는 상황에서 아직 과목이 결정되지 않았다면 어떤 멤버를 넣어야 할까요? 이를 해결하기 위해 ‘UNKNOWN’이라는 값을 추가해야 할까요?

- 그러나, ‘UNKNOWN’과 같은 상태값을 추가하고 나서 다시 이 Enum 클래스를 본다면, 과연 이 클래스가 ‘과목’이라는 명확한 정체성을 제대로 나타내고 있을까요? 그렇지 않다고 생각됩니다. 따라서 위상이 다른 멤버 사용은 지양해야 합니다.

 

💭 한 단계 더 생각해 보기

- ALL과 NOTHING은 과목을 나타내는 것이 아닌 "모든 과목을 갖는 상태", "어떠한 과목도 갖지 않은 상태"입니다.


2. 확장 가능성

여기서 이야기하는 "확장 가능성"은 확장될 가능성이 높은 값들에 대해선 열거형 사용을 지양한다는 이야기입니다.

 

- 예를 들어 서비스 개발에서 Event라는 Enum Class가 정의되어 사용되고 있다고 가정해 보겠습니다.

enum class Event {
    ROULETTE, // 룰렛 이벤트
    LOTTERY, // 추첨 이벤트
    LOTTO // 로또 이벤트
}

 

- 서비스 초기 진행하는 Event가 많지 않기 때문에 클래스에 멤버를 하나 추가하고 사용해도 큰 부담 없이 사용할 수 있습니다.

enum class Event {
    ROULETTE, // 룰렛 이벤트
    LOTTERY, // 추첨 이벤트
    LOTTO, // 로또 이벤트
    COMMENT, // 댓글 이벤트
    ... // 추가될 이벤트 맴버들
}

서비스에 새로운 이벤트가 추가될 때마다 변경되는 Enum Class 예제

 

- 그러나, 서비스가 커져감에 따라 여러 Event가 추가될 수 있고 이에 따라 Enum 값을 사용하는 곳에서 처리해야 할 부담이 커지게 됩니다. 대략적으로 생각해 보면, 확장 가능성이 큰 값들에 Enum을 사용할 경우, 그로 인한 부담(=비용)은 최소 y=x 이상의 증가를 보일 것입니다.

Big-O Graph

 

 그렇다면 부담을 어떤 방식으로 해결할 수 있을까요?

- 생각해 볼 수 있는 방식은 이벤트의 상태(때에 따라선 행위)를 Enum으로 정의하는 것입니다.

enum class EventAction {
    CREATED,        // 이벤트가 생성됨
    PENDING,        // 이벤트가 대기 중 (시작 전)
    STARTED,        // 이벤트가 시작됨
    IN_PROGRESS,    // 이벤트가 진행 중
    COMPLETED,      // 이벤트가 성공적으로 완료됨
    FAILED,         // 이벤트가 실패함
    CANCELED        // 이벤트가 취소됨
}

Event 상태를 멤버로 갖는 EventStatus 모습

 

- 상태나 행위를 멤버로 포함시키면, 새로운 이벤트 멤버를 추가할 때의 부담이 줄어듭니다. 이벤트의 상태는 자주 변경되지 않기 때문에, 추가되는 멤버와 비교할 때 관리 비용이 적게 듭니다.

 


🍏 나아가 생각해 보기

Enum Class의 멤버로 존재하는 값들의 네이밍은 어떻게 작성하는 것이 좋을까?

 

 Humna Readable, Machine Readable 두 가지 양분된 시선에서 Enum 값을 정의

(여기서 'Readable'이란 단어는 "친화적인"으로 해석해 이해해도 무방함.)

 

- Human Readable(사람 친화적)이란 사람이 읽기 쉬운 형식으로 구성된 것을 의미합니다.

enum class HumanReadableTime(
    override val value: Long
): Second {
    MINUTE_1(60),
    MINUTE_5(60 * 5),
    MINUTE_10(60 * 10),
    HOUR_1(60 * 60 * 1),
    DAY_1(60 * 60 * 24),
}

HumanReadable Time Enum Class

 

- Machine Readable(기계 친화적)이란 기계가 읽기 쉬운 형식으로 구성된 것을 의미합니다.

enum class MachineReadableTime(
    override val value: Long
): Second {
    ONE_MINUTE(60),
    FIVE_MINUTES(60 * 5),
    TEN_MINUTES(60 * 10),
    HOUR(60 * 60 * 1),
    DAY(60 * 60 * 24),
}

MachineReadable Time Enum Class

 

"사람 친화적인” Enum Value를 사용하는 것은 사용자가 평소 익숙한 용어를 기준을 사용해 편의를 제공하는 것을 의미합니다. 이를 통해 얻을 수 있는 장점으로는 일상생활과 다르지 않은 언어를 사용해 어색함이 없다는 것입니다.

 

“기계에 친화적인” Enum Value를 사용한다면 내부에서 만든 규칙을 사용할 수 있습니다. 만약 값을 파싱 할 경우, 다른 곳에서 해당 Enum 값을 사용할 경우 규칙이 깨지지 않아 일관성을 유지할 수 있는 장점이 있습니다.


🍎 정리

Enum 정리 글을 통해 Enum을 사용할 때 주의 점(동일한 위상 값, 확장 가능성)을 알아봤고 잘 사용하기 위해 어떻게 멤버 네이밍을 사용해야 하는지 정리해 봤습니다.

이 내용이 개발 과정에서 도움이 되길 바랍니다.


📝 Reference

Enumeration Type Wiki