Enum을 사용하지 않는 이유

루비·2024년 7월 23일

Typescript

목록 보기
7/8

서론

Admin 페이지를 작성하면서 ErrorCode를 관리해야 하는 상황이 있었습니다. 기존 프로덕션 코드에서는 Enum을 활용하여 ErrorCode를 관리하고 있었습니다. 이를 참고하여 코드를 작성했지만, 사수님께서 Enum 사용을 지양하고 있다고 말씀하셨습니다. 이에 궁금증이 생겨 찾아보게 되었습니다.

Enum을 사용하지 않는 이유

1. 유연성 부족

Enum은 정해진 값의 집합을 나타내기 때문에, 새로운 값이나 옵션을 추가하는 데 제한이 있습니다. 코드가 진화하면서 새로운 요구 사항이 생길 때 Enum은 유연하게 대응하기 어렵습니다.

2. 타입스크립트에서의 불편함

TypeScript에서 Enum은 JavaScript로 컴파일될 때 특정 방식으로 변환됩니다. 이 과정에서 일부 개발자들은 예상치 못한 결과나 불필요한 복잡성을 경험할 수 있습니다. 예를 들어, TypeScript의 const enum은 런타임 비용을 줄이기 위해 사용되지만, 이는 IDE 지원이나 디버깅에서 불편을 초래할 수 있습니다.

3. 문자열 리터럴 타입의 대두

TypeScript에서는 문자열 리터럴 타입(string literal type)이 더 선호됩니다. 문자열 리터럴 타입은 타입 안전성을 제공하면서도 유연하게 사용할 수 있습니다. 예를 들어:
type Status = 'success' | 'error' | 'loading';

function handleStatus(status: Status) {
// status가 'success', 'error', 'loading' 중 하나임을 보장
}

4. 코드 가독성 및 유지보수

Enum은 때로는 코드의 가독성을 떨어뜨릴 수 있습니다. 특히, 값이 많아질수록 Enum을 정의하고 사용하는 부분이 복잡해질 수 있습니다. 반면, 문자열 리터럴 타입은 단순하고 직관적입니다.

5. 코드 크기

Enum을 사용할 경우, Enum의 각 멤버가 실제로는 객체로 변환되어 코드 크기가 커질 수 있습니다. 이는 특히 작은 코드베이스나 성능 최적화가 중요한 프로젝트에서 문제가 될 수 있습니다.

예제 비교

Enum 사용:

enum Status {
    Success = 'success',
    Error = 'error',
    Loading = 'loading'
}

function handleStatus(status: Status) {
    if (status === Status.Success) {
        // 성공 처리
    }
}

문자열 리터럴 타입 사용:

type Status = 'success' | 'error' | 'loading';

function handleStatus(status: Status) {
    if (status === 'success') {
        // 성공 처리
    }
}

어떤 상황에서 Enum은 유용하게 사용되는가?

1. 고정된 값의 집합이 자주 사용될 때

값의 집합이 고정되어 있고, 자주 참조되거나 변경될 가능성이 적은 경우 Enum은 이상적입니다. 예를 들어, 애플리케이션 상태, HTTP 상태 코드, 특정 설정 값 등이 이에 해당합니다.

2. 값의 의미를 명확히 할 때

숫자나 문자열 값이 특정 의미를 갖는 경우, Enum을 사용하면 해당 값의 의미를 명확히 할 수 있습니다. 이는 코드의 가독성을 높이고, 의미를 직관적으로 파악할 수 있게 해줍니다.

enum HttpStatus {
    OK = 200,
    BadRequest = 400,
    Unauthorized = 401,
    NotFound = 404
}

function handleResponse(status: HttpStatus) {
    if (status === HttpStatus.OK) {
        // 성공 처리
    }
}

3. 컴파일 타임 안전성 확보

Enum은 TypeScript에서 컴파일 타임에 값의 유효성을 검증하는 데 도움이 됩니다. 이를 통해 런타임 오류를 줄일 수 있습니다.

enum UserRole {
    Admin = 'ADMIN',
    User = 'USER',
    Guest = 'GUEST'
}

function assignRole(role: UserRole) {
    // role이 UserRole의 값 중 하나임을 보장
}

4. 코드의 일관성 유지

프로젝트 전반에 걸쳐 동일한 값 집합을 일관되게 사용할 때 Enum을 사용하면 코드의 일관성을 유지하는 데 도움이 됩니다. 값의 오타나 불일치를 방지할 수 있습니다.

enum Environment {
    Development = 'development',
    Staging = 'staging',
    Production = 'production'
}

function getApiUrl(env: Environment): string {
    switch (env) {
        case Environment.Development:
            return 'https://dev.api.example.com';
        case Environment.Staging:
            return 'https://staging.api.example.com';
        case Environment.Production:
            return 'https://api.example.com';
    }
}

5. 자동완성 및 IDE 지원

Enum을 사용하면 IDE에서 자동완성 기능을 통해 값을 쉽게 선택할 수 있습니다. 이는 코드를 작성하는 데 있어 편의성을 높이고, 실수를 줄이는 데 도움이 됩니다.

ErrorCode는 Enum을 사용하는 것이 맞는가?

Enum 사용의 장점

1. 명확한 의미 부여
Enum을 사용하면 각 ErrorCode의 의미를 명확히 할 수 있습니다. 이를 통해 코드의 가독성을 높이고, 오류 코드의 의미를 쉽게 이해할 수 있습니다.

enum ErrorCode {
    UserNotFound = 'USER_NOT_FOUND',
    InvalidCredentials = 'INVALID_CREDENTIALS',
    PaymentFailed = 'PAYMENT_FAILED',
    InternalError = 'INTERNAL_ERROR'
}

2. 컴파일 타임 타입 안전성
Enum을 사용하면 컴파일 타임에 오류 코드의 유효성을 검증할 수 있어, 잘못된 값이 사용되는 것을 방지할 수 있습니다.

function handleError(code: ErrorCode) {
    switch (code) {
        case ErrorCode.UserNotFound:
            console.log('User not found');
            break;
        case ErrorCode.InvalidCredentials:
            console.log('Invalid credentials');
            break;
        case ErrorCode.PaymentFailed:
            console.log('Payment failed');
            break;
        case ErrorCode.InternalError:
            console.log('Internal server error');
            break;
    }
}

Enum 사용의 단점

1. 유연성 부족
ErrorCode가 자주 변경되거나 새로운 코드가 자주 추가되는 경우, Enum은 유연성 측면에서 한계가 있습니다. 변경이 있을 때마다 Enum을 업데이트해야 하므로 유지보수 비용이 증가할 수 있습니다.
2. 추가된 복잡성
TypeScript에서 Enum은 특정 방식으로 변환되므로, 예상치 못한 런타임 동작이나 디버깅의 어려움이 있을 수 있습니다.

대안: 문자열 리터럴 타입 사용

문자열 리터럴 타입을 사용하면 유연성을 유지하면서도 타입 안전성을 어느 정도 확보할 수 있습니다.

type ErrorCode = 'USER_NOT_FOUND' | 'INVALID_CREDENTIALS' | 'PAYMENT_FAILED' | 'INTERNAL_ERROR';

function handleError(code: ErrorCode) {
    switch (code) {
        case 'USER_NOT_FOUND':
            console.log('User not found');
            break;
        case 'INVALID_CREDENTIALS':
            console.log('Invalid credentials');
            break;
        case 'PAYMENT_FAILED':
            console.log('Payment failed');
            break;
        case 'INTERNAL_ERROR':
            console.log('Internal server error');
            break;
    }
}

ErrorCode는 40개 이상이라면?

ErrorCode가 40개 이상으로 많은 경우, Enum을 사용하는 것과 문자열 리터럴 타입을 사용하는 것 모두 장단점이 있습니다. 이를 효율적으로 관리하기 위해 몇 가지 접근 방식을 고려해볼 수 있습니다.

Enum 사용의 장단점

장점:
1. 명확한 구조: 모든 오류 코드를 한 곳에서 정의하여 체계적으로 관리할 수 있습니다.
2. 타입 안전성: TypeScript의 타입 검사를 통해 컴파일 타임에 오류를 잡아낼 수 있습니다.
3. IDE 지원: 자동 완성 기능을 통해 오류 코드 입력 시 편리합니다.
단점:
1. 유연성 부족: 새로운 코드 추가나 변경이 잦은 경우 Enum을 지속적으로 업데이트해야 합니다.
2. 코드 길이 증가: Enum 정의가 길어지면 코드의 가독성이 떨어질 수 있습니다.

문자열 리터럴 타입 사용의 장단점

장점:
1. 유연성: 새로운 오류 코드 추가가 상대적으로 용이합니다.
2. 간결성: 코드가 비교적 간결하게 유지될 수 있습니다.
단점:
1. 타입 안전성 저하: 오류 코드가 많아질수록 문자열 리터럴 타입의 관리가 번거로울 수 있습니다.
2. IDE 지원 부족: 자동 완성 기능이 Enum에 비해 덜 직관적일 수 있습니다.

대안: 객체 기반 관리

객체를 활용하여 오류 코드를 관리하는 방법도 고려해볼 수 있습니다. 이는 유연성과 타입 안전성을 어느 정도 절충할 수 있는 방법입니다.

const ErrorCodes = {
    USER_NOT_FOUND: 'USER_NOT_FOUND',
    INVALID_CREDENTIALS: 'INVALID_CREDENTIALS',
    PAYMENT_FAILED: 'PAYMENT_FAILED',
    INTERNAL_ERROR: 'INTERNAL_ERROR',
    // ...더 많은 오류 코드
} as const;

type ErrorCode = keyof typeof ErrorCodes;

function handleError(code: ErrorCode) {
    switch (code) {
        case 'USER_NOT_FOUND':
            console.log('User not found');
            break;
        case 'INVALID_CREDENTIALS':
            console.log('Invalid credentials');
            break;
        case 'PAYMENT_FAILED':
            console.log('Payment failed');
            break;
        case 'INTERNAL_ERROR':
            console.log('Internal server error');
            break;
        // ...더 많은 오류 처리
    }
}

// 사용 예시handleError(ErrorCodes.USER_NOT_FOUND);

결론

  • 프론트엔드 개발자들이 Enum을 지양하는 이유는 주로 유연성 부족, TypeScript에서의 불편함, 문자열 리터럴 타입의 편리함, 코드 가독성 및 유지보수성, 그리고 코드 크기와 관련이 있습니다. 프로젝트의 요구 사항과 상황에 따라 적절한 방법을 선택하는 것이 중요합니다.
  • Enum은 고정된 값의 집합이 자주 사용되거나 값의 의미를 명확히 해야 하는 상황에서 매우 유용합니다. 또한, 컴파일 타임 안전성을 확보하고 코드의 일관성을 유지하는 데 도움을 줍니다. 프로젝트의 요구 사항과 상황에 따라 적절히 활용하면 효과적인 코드 작성에 기여할 수 있습니다.
  • ErrorCode에 값이나 텍스트가 담겨 있는 경우, Enum을 사용하면 명확한 의미 부여와 컴파일 타임 타입 안전성을 확보할 수 있습니다. 그러나, 자주 변경되거나 새로운 코드가 추가되는 경우 문자열 리터럴 타입을 사용하는 것이 더 유연하고 유지보수가 쉬울 수 있습니다. 프로젝트의 특성과 요구 사항에 따라 적절한 방식을 선택하는 것이 중요합니다.
  • ErrorCode가 40개 이상인 경우, 객체 기반 관리 방법을 추천합니다. 이 방법은 Enum의 장점(타입 안전성, 명확한 구조)과 문자열 리터럴 타입의 장점(유연성, 간결성)을 적절히 결합할 수 있습니다. 프로젝트의 특성과 유지보수 편의성을 고려하여 적절한 방식을 선택하는 것이 중요합니다.

느낀점

상황에 따라 해당 기능이 원활하고 적합한지를 판단하게 되었습니다. 이를 통해 프로젝트의 이해도와 구조를 잘 파악하는 것이 중요하다고 느꼈습니다. 또한, 다양한 접근 방식을 비교하고 적절한 방법을 선택하는 과정에서 코드의 유연성과 유지보수성을 고려해야 한다는 점을 깨달았습니다.

profile
개발훠훠

0개의 댓글