Language/Java

JAVA - Enum 파헤치기

DuL2 2022. 7. 26. 11:12

Enum이 궁금한 이유.

-> 과제 프로젝트를 하면서 각 기능들에 대한 테스트를 진행 중이었다. 그러던 중 postman의 너저분한 trace 보고는 `와.. 이건 아니지..`라는 생각이 들었고, exceptionHandling을 하고 싶어 졌다. Exception 발생에 대한 핸들링을 하기 위해서 나만의 에러코드와 메세지 그리고 어떻게 리턴해줄 것인가에 대한 생각을 해봐야겠다고 생각을 했고, 에러 코드와 같이 정해져있어야 하는 상수같은 값을 Enum 열거형으로 만들어야 겠다?(사용하시는걸 보고 따라해봐야겠다)라고 생각했다.

 

 자바 공부할 때 간단하게 보고 넘어갔지만 더 좋은 활용을 위해서는 공부를 왜 쓰는지에 대해 하는 것이 좋겠다고 판단하여 포스팅을 해본다.

 

Enum

Enum은 Enumerated Type 으로 `서로 연관된 상수 값들의 집합`을 말한다.

 

예를들어 요일 - {`월`, `화, `수`, `목`, `금`, `토`, `일`} 이나 회원 등급 - {`VIP`, `BASIC`} 등과 같은 예시가 있을 것이다.

 

Enum은 static과 final을 내장하고 있는데, Enum 이전에는 final을 통한 변수를 상수처리화 하여 사용하였다. 하지만, 이 상수 값에 있어서 다른 연관 관계를 가지지만 같은 int 타입이라면 비교가 가능하고, 또한 사용도중 오타로 인한 문제가 생기게 되었다. 또, 이런 문제는 컴파일 단계까지 가야 확인할 수 있는 문제도 있었다.

 

Enum 탄생의 역사

1. 주석을 사용하여 의미 표현. ->  주석이 삭제되어 버리면 알 수가 없다.

/*
 * 1 = LOW
 * 2 = MEDIUM
 * 3 = HIGH
 */

2. 변하지 않는 클래스 변수(상수-final)로 표현 -> 상수명의 중복이라는 문제가 발생했다.

	final static int LOW = -1;
	final static int MEDIUM = 0;	// 과일
	final static int HIGH = 1;
    
	final static int NAVER = 1;
	final static int MEDIUM = 2;	// 블로그 플랫폼
	final static int VELOG = 3;
	final static int TISTORY = 4;
	final static int GITHUB = 5;
    ...
MEDIUM 변수명 중복!

3. Interface로 상수명을 구체화. -> 상수간 비교가 가능하다.

interface Level {
	final static int LOW = -1;
	final static int MEDIUM = 0;
	final static int HIGH = 1;
}

interface BlogPlatform {
	final static int NAVER = 1;
	final static int MEDIUM = 2;
	final static int VELOG = 3;
	final static int TISTORY = 4;
	final static int GITHUB = 5;
    ...
}
int 타입을 공유하여 비교가 가능함! 컴파일 에러도 발생하지 않음!

4. 인스턴스를 생성하여 사용. -> switch 조건문에는 사용자 정의 클래스를 사용할 수가 없다.

class Level {
	public final static Level LOW = new Level();
	public final static Level MEDIUM = new Level();
	public final static Level HIGH = new Level();
}

class BlogPlatform {
	public final static BlogPlatform NAVER = new BlogPlatform();
	public final static BlogPlatform MEDIUM = new BlogPlatform();
	public final static BlogPlatform VELOG = new BlogPlatform();
	public final static BlogPlatform TISTORY = new BlogPlatform();
	public final static BlogPlatform GITHUB = new BlogPlatform();
    ...
}
But, switch 조건문에 사용자 정의 클래스가 못들어감.

5. 자바 1.5부터 enum 등장

 

 

Enum을 사용함으로써 얻는 장점

  1. 반복적으로 작성되는 코드의 양이 줄어듬.
  2. 인스턴스 생성과 상속 시도 시, 컴파일 에러로 인하여 조기 처리가 가능함.
  3. enum 이라는 키워드로 열거형의 의도를 명확히 드러내고 사용할 수 있음.
  4. 문자열과 비교해, IDE의 적극적인 지원 받을 수 있음
    • 자동완성, 오타검증, 텍스트 리팩토리 등등
  5. 허용 가능한 값들을 제한할 수 있음.
  6. 리팩토링시 변경 범위가 최소화됨.
    • 내용의 추가가 필요하더라도, Enum 코드외에 수정할 필요가 없다.

 

자바 Enum만의 장점을 설명할 상황들

우아한형제들 기술 블로그를 통해 실무에서 겪을 수 있는 enum 사용의 예시를 통해 enum의 유용성을 확인해보고자 한다.

 

1. 데이터들 간의 연관관계 표현

  • 예시 상황
    • 매일 배치를 돌며 테이블 origin에 있는 내용을 2개의 테이블1과 테이블 2로 등록하는 기능이 있다.
    • 그런데 origin 테이블의 값을 테이블 1과 테이블 2을 넣을 때 받는 값이 달랐다.
    • 테이블 1은 `0/1`로 받았으며 테이블 2는 `true/false` boolean 값으로 받았다. origin은 `Y/N`으로 이루어져 있다.
    • 테이블마다 넣어주는 값을 변경하는 로직을 짜서 사용 중이었다.
  • 문제점
    • "Y", "1", true는 모두 의미적으로는 같지만 표현이 다르다.
    • 코드상에서 의미가 같은 것인지 명확히 식별하려면 위에서 선언된 클래스와 메소드를 봐야지만 이해가 쉽게 된다.
  • 해결 방법
    • 각 테이블 1과 2에 대해 넣어주어야 하는 값을 TableStatus Enum에 담아 저장한다.
public enum TableStatus {
	Y("1", true),
 	N("0", false);
    
    private String table1Value;
    private boolean table2Value;
  
  ... getter, isStatus 메소드
  }

 각 테이블에는 Enum에서 테이블에 맞는 인자를 뽑아서 준다.

 

2. 상태와 행위를 한곳에서 관리

 

  • 예시 상황
    • 한 코드 값에 대해서 로직 A, B, C가 다르게 처리되어야 한다.
    • 하지만 만약 단순히 if 문으로 해결하는 메소드를 만들면 코드 조회와 계산 메소드는 분리되어 작동한다.
  • 문제 상황 
    • 메소드와 code 사이의 연관성을 코드로 표현할 수 없다.(문맥을 알수없다?)
    • 다른 개발자가 메소드의 존재를 모르고 똑같은 기능을 중복 생성할 수도 있다.
    • 개발 히스토리를 모르면 계산 메소드와 코드 간의 의미를 알 수 없어 계산 과정을 생략할 수도 있다.
      • "DB의 테이블에서 뽑은 특정 값은 지정된 메소드와 관계가 있다."
  • 해결 방법
    • Code를 각각 본인만의 계산식을 가지도록 지정한다.

https://techblog.woowahan.com/2527/

  • 부가적 사용법
    • Entity 클래스에는 String 값이 아닌 enum 타입을 선언하여 사용할 수 있다.
    • `@Enumerated(EnumType.STRING)` 과 같이 사용할 수 있다.
      • 숫자형을 사용할 때 중간에 값이 하나가 추가되면 DB의 값이 밀려 큰 참사가 일어날 수 있으므로 `@Enumerated`를 함께 사용하는 것을 추천.

 

 

3. 데이터 그룹 관리

  • 예시 상황
    • 결제라는 데이터는 결제 종류와 결제 수단이라는 2가지 형태로 표현된다.
    • 현금, 카드, 기타 그룹에는 계좌이체, 무통장입금, 토스, 페이코, 신용카드, 카카오페이, 배민패이, 포인트, 쿠폰 등이 분리되어 들어있을 것이다.
    • 각각의 그룹을 구분해야하고 어떤 방식이 어떤 그룹에 속하는지 if문을 통해 구현되어있었다.
  • 문제 상황 
    • 그룹과 방식의 관계를 알기가 힘들다. (2번과 비슷한 상황이라고 이해했다.)
    • 입력값과 결과값이 예측 불가능하다.
      • 문자열로 들어오므로 어떤 문자열이든 들어올 수 있다.
    • 그룹별로 기능을 추가하기가 어렵다.
      • 추가 기능을 개발하려면 if문으로 내부에 구분을 해야할 것 이고, 복잡한 코드가 될 확률이 높다.
  • 해결 방법
    • PayGroup 이라는 enum을 만들고 String 결제 방식(그룹), List<String> 결제 수단(요소) 의 두 필드를 가지도록 하여 각 enum 상수에서 list를 확인하고 처리하도록 한다.
    • 또한, 그룹의 요소인 결제 수단들도 enum으로 만들어 List에 넣는다.
  • 결과
    • enum을 중복으로 사용함으로써, 쉬운 조회, 확장성, 타입 안전성까지 챙겼다

 

 

4. 관리 주체를 DB에서 객체로

  • 예시 상황
    • 새로 참여하게 된 프로젝트에서 사용하던 코드 테이블을 익혀야 한다.
    • 코드 테이블을 위한 DB가 따로 존재했다.
  • 문제점(내가 이해한대로 의역)
    • 코드명만 봐서는 이게 무엇을 의미하는지 알기 힘들다.
    • 단순히 UI를 그리는 작업에도 코드 테이블의 쿼리가 진행됨으로써 자원 낭비가 발생된다.
    • 서비스 로직을 추가할 때 위치가 애매했음.(1~3번과 비슷한 이야기)
  • 해결 방법
    • 카테고리 및 코드는 6개월에 1 ~ 2개가 추가될지 안될지 모르는 변동성이 적은 영역이다.
    • 그렇기 때문에 테이블로 관리하는 것보다는 enum으로 관리하는 것이 좋을 수도 있다.

 

상황과 해결 방법을 읽고 느낀점.

  1. enum은 열거형의 의미처럼 상수들을 정리하여 사용하기 좋다.
  2. enum은 단순 로직들의 집합을 분해하고 다시 조립하여 코드들의 문맥에 맞게 의미를 합쳐준다.
    • 이는 다른 개발자들과의 협업에서 팀의 생산성을 높여줄 수도 있다.
  3. enum은 변경이 어렵다는 단점이 있지만, 변경이 크게 없는 상황에서는 성능적으로 혹은 의미적으로 효율이 좋다.
    • 상황과 사용법에 맞게 enum을 적절하게 사용해야 한다.

 

 enum이 사용되는 의미와 용도를 이해하고 내부에 메소드를 생성함으로써 내가 왜 이 코드를 짰는지에 대한 명확성? 명료성을 줄 수 있다는 점이 실감났다. 머리속에 생각을 해두었다가 개인 프로젝트에 사용해야겠다.

 

 

 

 

참조 글

 

[2017.08.21] 12. 왜 Enum을 사용할까?

Enum은 Enumerated Type으로 '서로 연관된 상수 값들의 집합'이다. (cf. 배열은 서로 연관된 변수 값들의 집합 / 왜 배열을 사용할까? - http://heepie.tistory.com/2) Enum의 등장 배경 1. 복잡한 값 -> 단순한..

heepie.me

 

Java Enum 활용기 | 우아한형제들 기술블로그

{{item.name}} 안녕하세요? 우아한 형제들에서 결제/정산 시스템을 개발하고 있는 이동욱입니다. 이번 사내 블로그 포스팅 주제로 저는 Java Enum 활용 경험을 선택하였습니다. 이전에 개인 블로그에 E

techblog.woowahan.com