[Go] Go에서 Enum처럼 구현하기

Seyeon_CHOI·2023년 12월 9일
0

Go

목록 보기
3/5
post-thumbnail

Enum(열거형)은 명명된 상수 값의 집합을 나타내는 데이터 형식이다. 유용한 이름과 간단하며 고유한 값으로 구성된 복잡한 상수 집합을 생성하는 데 도움이 되는 강력한 도구로써 활용될 수 있다.

Enum을 대부분의 다른 언어에서 자주 활용하는 걸 볼 수 있지만 제목에서 알 수 있다시피 Go에는 Enum 타입은 존재하지 않는다. 그렇지만 Go에서는 명시적인 Enum 타입이 없어도 Enum과 유사한 패턴을 구현하여 코드의 일관성을 유지할 수 있다.

현재 진행하고 있는 프로젝트에서 고양이/개 처럼 타입을 enum 형태로 선언하여 코드의 일관성 유지할 필요성이 있었다. 그렇기에 Enum과 유사한 패턴을 구현하기로 결정하였다.

문자열 Enum

현재 구현하고 있는 내용은 다음과 같다.

  1. CCTV, 펫캠 촬영 동의
  2. 사전 통화 가능 여부
  3. 반려 동물 등록 여부

이렇게 3가지 경우를 조건 테이블에 적재를 할 예정이다. 그러기 위해서 해당 조건들을 Enum 형태로 선언하여 사용하기로 하였다.

type SosCondition string

const (
	CCTVPermission  SosCondition = "CCTV, 펫캠 촬영 동의"
	PhonePermission SosCondition = "사전 통화 가능 여부"
	PetRegistration SosCondition = "반려 동물 등록 여부"
)

var ConditionName = []SosCondition{CCTVPermission, PhonePermission, PetRegistration}

SosCondition이라는 새로운 문자열 타입을 정의하였다. 이 타입은 나중에 Enum 값들을 나타내는 데 사용될 것이다. 여기서는 세 가지 조건을 나타내는 Enum 값들을 정의하여 각각 SosCondition 타입으로 정의해 문자열로 표현되어 있다. 슬라이스 ConditionName을 통해 Enum의 가능한 값들을 나열하고, 이를 통해 반복문에서 유용하게 사용하도록 하였다.

row 쿼리로 조건 이름을 적재할 예정이었기에 서브쿼리 내에 name = $2::VARCHAR(50)처럼 해당 타입을 명시하여 조회하도록 구현하였다. 추가로 v(조건 이름) 타입은 SosCondition으로 선언되어있지만 tx.Exec이 자동으로 string 형태로 변환해주기에 따로 타입변환은 해주지 않았다.

func (s *ConditionPostgresStore) InitConditions(conditions []sos_post.SosCondition) (string, error) {
	tx, err := s.db.Begin()
	if err != nil {
		return "", err
	}

	for n, v := range conditions {
		_, err := tx.Exec(`
			INSERT INTO sos_conditions 
				(
				 	id,
					name, 
					created_at, 
					updated_at
				)
				SELECT $1, $2, now(), now()
				WHERE NOT EXISTS (
					SELECT 
						1 
					FROM 
						sos_conditions 
					WHERE 
						name = $2::VARCHAR(50)
				);
		`, n+1, v)
		if err != nil {
			tx.Rollback()
			return "", err
		}
	}

	err = tx.Commit()
	if err != nil {
		return "", err
	}

	return "condition init success", nil
}

여기서 각 조건의 타입이 SosCondition인 것을 알 수 있다. 위 상황에서는 굳이 필요하지 않지만 많은 케이스들에서는 string 타입으로 변환이 필요하다.

문자열 배열을 넣고 인덱스로 접근

Fruit 타입에 String 메서드를 추가하여 해당 상수가 문자열로 어떻게 표현되어야 하는지 정의해서 사용할 수 있다. main 함수에서 AppleBanana를 출력하면, 해당 상수가 문자열로 표현되는 것을 확인할 수 있다. 이때 String 메서드가 호출되어 배열에서 문자열을 가져오게 된다.

package main

import (

    "fmt"
)

type Fruit int

const (
    Apple Fruit = iota
    Banana
    Mongo
)

func (f Fruit) String() string {
   return []string{"Apple", "Banana", "Mongo"}[f]
}

func main() {
   fmt.Println(Apple) // 출력: Apple
   fmt.Println(Banana) // 출력: Banana
}


사용자 타입 정의해서 사용하기

StringToFruit 함수를 통해 문자열을 받아서 해당하는 Fruit 타입으로 변환한다. switch 문을 사용하여 유효한 Fruit 상수인지 확인하고, 유효하지 않다면 Unknown을 반환한다.

package main

import (

    "fmt"
)

type Fruit int

const (
    Apple Fruit = "Apple"
    Banana Fruit = "Banana"
    Mongo Fruit = "Mongo"
)

func StringToFruit(fruitStr string) Fruit {
   switch Fruit(fruitStr) {
   case Apple, Banana, Mongo:
      return Fruit(fruitStr)
   default:
      return Unknown
   }
}

func main() {
   fmt.Println(StringToFruit("Apple")) // 출력: Apple
   fmt.Println(StringToFruit("PineApple")) // 출력: Unknown
}

여기서 문자열 Enum인 아닌 상수로 Enum을 선언해서 사용하려면 어떻게 해야할까?



상수 Enum


const (
    Apple  = iota
    Banana
    Mongo
)

이렇게 선언을 하게 된다면

const (
    Apple  = iota // 0
    Banana // 1
    Mongo // 2
)

각각 Apple, Banana, Mongo는 0, 1, 2가 된다.

package main

import "fmt"

const (
    Apple  = iota // 0
    Banana // 1
    Mongo // 2
)

func main() {
    fruit := Banana
    fmt.Println(fruit) // 출력은 2가 된다.
}

이를 활용하여 Banana를 출력한다고 가정하면 출력 값은 2가 된다.

아래처럼 새로운 타입을 선언하여 사용할 수도 있다.

package main

import "fmt"

type Fruit int

const (
    Apple Fruit = iota // 0
    Banana // 1
    Mongo // 2
)

func main() {
    fruit := Banana
    fmt.Println(fruit) // 출력은 2가 된다.
}

Fruit이라는 새로운 타입을 정의하고, 그 타입을 기반으로 상수들을 선언하고 있다.

Fruit 타입에 iota를 사용하여 상수를 정의하면 각 상수는 Fruit 타입을 가지게 된다.


++ 특수 케이스

1부터 시작하는 Enum 생성

const (
    Apple  = iota + 1 // 0
    Banana // 1
    Mongo // 2
)

곱셈을 활용한 Enum 생성

const (
    Apple  = iota + 1 // 0 + 1 = 1
    Banana = iota * 10 // 1 * 10 = 10
    Mongo = iota * 100 // 2 * 100 = 200
)

Enum에서 값 건너뛰기

const (
    Apple  = iota // 0
    _
    Mongo // 2
)


왜 Go에서는 Enum 타입이 존재하지 않을까?


Go는 프로그래밍 언어의 발전과정 중에 있는 여러 가지 이로운 것들을 의도적으로 배제하는 것들이 있다. 설계 철학에서 간결하고 명확한 코드를 중시하기 때문이다.

이러한 간결성과 명확성을 위해 복잡한 기능과 패턴을 최소화 하려고 노력했고, 이러한 접근방식으로 코드의 예측 가능성과 유지 보수성을 높혔다.

2017년부터 enum이 필요하다는 의견이 나오고 있지만 위에서 말했듯이 Go의 설계 철학과 맞지않아 나오지 않는 중인 듯 하다. 개인적인 생각으로는 generic type이 추가된 것처럼 enum 또한 추가되었으면 하는 바램이 있다.


profile
오물쪼물 코딩생활 ๑•‿•๑

0개의 댓글