Coding-Village-Protector / woowahan-ts

[우아한 타입스크립트 with 리액트] 북 스터디 📚
14 stars 2 forks source link

3.1.6_enum의 특징인 직접 할당되지 않은 멤버의 값을 추론하는 방식이 안전하지 이유는 무엇인가요? #7

Closed sryung1225 closed 9 months ago

sryung1225 commented 9 months ago

📝 p97

❓ enum의 특징인 직접 할당되지 않은 멤버의 값을 추론하는 방식이 안전하지 이유는 무엇인가요? enum 타입은 타입스크립트에서 지원하는 특수한 타입입니다. 각 멤버에 명시적으로 값을 할당할 수도 있고, 아래와 같이 값을 스스로 추론하도록 할 수도 있습니다.

enum HttpStatusType {
  Forbidden = 403,
  NotFound,
  BadGateway = 502,
}
console.log(HttpStatusType[404]); // ? "NotFound" // 역방향 접근

하지만 이런 타입스크립트가 자동으로 추론한 enum은 안전하지 않다는 이유로 책에서 권장하지 않고 있습니다. 안전하지 않다고 평가한 근거는 무엇인가요? 그리고 대신에 어떤 방식으로 선언된 enum을 권장하는 지 설명하시오.

lulla-by commented 9 months ago

자동으로 추론한 enum의 경우 의도하지 않은 결과가 나올 수 있습니다. 자동 추론을 사용하여 멤버를 변경하는 등의 경우 다른 enum 멤버들에게도 영향이 미쳐 의도하지 않은 값의 변경이 생길 수 있습니다.

const enum으로 열거형을 선언하여 역방향 접근을 허용하지 않는 방법을 권장하며 숫자 상수 방식은 선언하지 않은 멤버로 접근 및 할당이 가능하기에 이를 막아주는 문자열 상수 방식으로 선언하는 것을 권장합니다.

Stilllee commented 9 months ago

안전하지 않은 이유

1. 값의 중복과 예상치 못한 결과

타입스크립트에서 enum을 사용할 때, 명시적으로 값을 할당하지 않으면 이전 멤버의 값을 기반으로 다음 멤버의 값을 자동으로 할당하게 됩니다.

enum HttpStatusType {
  Forbidden = 403,
  NotFound, // 자동으로 404가 할당됨
  BadGateway = 502,
}

이는 명시적으로 선언되지 않았기 때문에 코드를 읽는 사람이나 컴파일러가 예측하기 어렵습니다.

2. 명시적 재할당으로 인한 중복 값

enum 내에서 값이 명시적으로 재할당되면, 다른 멤버가 같은 값을 갖게 되어 예상치 못한 중복 값이 발생할 수 있습니다.

enum HttpStatusType {
  Forbidden = 403,
  NotFound, // 자동으로 404가 할당됨
  BadGateway = 404, // BadGateway에 404라는 값을 명시적으로 재할당
}

예를들어 위 코드의 경우 NotFoundBadGateway 두 멤버 모두 같은 값인 404를 갖게 됩니다.

이는 enum의 기본 목적인 각 멤버의 고유성을 해치며, 런타임에 예상치 못한 결과를 초래할 수 있습니다.

3. 역방향 접근의 문제

enum은 역방향으로도 접근할 수 있습니다. 즉, 숫자를 사용하여 해당 숫자에 할당된 enum 멤버의 이름을 얻을 수 있습니다.

이는 할당된 값의 범위를 넘어서는 접근을 시도할 때도 막지 않으므로, 예상치 못한 동작을 유발할 수 있습니다.

// 정상적인 역방향 접근
console.log(HttpStatusType[404]); // "NotFound"

// 범위를 넘어서는 역방향 접근
console.log(HttpStatusType[405]); // undefined 또는 예상치 못한 결과

대신 권장되는 방식

1. const enum 사용

const enum은 역방향으로의 접근을 허용하지 않으며, 컴파일 시 enum을 인라인 상수로 변환하여 성능상의 이점을 제공합니다. 이는 자바스크립트에서 객체에 접근하는 것과 유사한 동작을 보장하며, 불필요한 런타임 오버헤드를 줄여줍니다.

2. 명시적 값 할당

모든 enum 멤버에 대해 명시적으로 값을 할당하면 자동 값 추론으로 인한 혼란을 방지할 수 있으며, 코드의 가독성과 예측 가능성이 높아집니다.

eeeyooon commented 9 months ago

자동으로 추론한 열거형은 안전하지 않은 결과를 낳을 수도 있습니다. 만약 할당된 값을 넘어서는 범위로 역방향으로 접근하더라도 타입스크립트는 막지 않습니다. 또한 선언되지 않은 key값의 접근을 허용하기도 합니다.

이러한 enum의 문제점 때문에 책에서는 const enum으로 열거형을 선언하는 방법을 권장합니다. 다만 const enum 으로 열거형을 선언하더라도 숫자 상수로 관리되는 열거형은 선언한 값 이외의 값을 할당하거나 접근할 때 이를 방지하지 못합니다. 반면 문자열 상수 방식으로 선언한 열거형은 미리 선언하지 않은 멤버로 접근을 방지하기 때문에 숫자 상수 방식보다 더 안전하며 의도하지 않은 값의 할당이나 접근을 방지하는 데 도움이 되는 문자열 상수 방식을 권장합니다.

sryung1225 commented 9 months ago

☑ 예시 코드와 같이 enum은 직접 할당한 값 또는 추론 된 값을 이용해 멤버를 알아내는 역방향 접근이 가능합니다. 문제는 할당한 값을 넘어서는 범위로도 역방향 접근이 가능하다는 것입니다.

console.log(HttpStatusType[404]); // ? "NotFound"
console.log(HttpStatusType[476]); // ? undefined

위에서 자동으로 추론된 NotFound = 404BadGateway = 502 사이 임의의 값으로 역방향 접근을 하게 될 경우 undefined를 출력해 냅니다.


여기에서 핵심은 실행 과정에서 막히는 부분이 없다는 사실입니다. 에러가 발생하지 않는 이 결과를 안전하지 않은 결과라고 평가합니다.


때문에 더 안전한 방식상수 enum을 사용하는 것입니다. const enum 을 이용해 enum을 선언하는 경우 역방향 접근을 아예 허용하지 않게 됩니다.

const enum HttpStatusType {
  Forbidden = 403,
  NotFound,
  BadGateway = 502,
}
console.log(HttpStatusType[404]); // 🚨 A const enum member can only be accessed using a string literal.

image

sryung1225 commented 9 months ago

+) 사실 책에서 내린 결론은 const enum으로는 충분하지 않고 문자열 상수 방식을 사용하는 것이 숫자 상수 방식보다 더 안전하다 입니다.

그러나 const enum으로 열거형을 선언하더라도 숫자 상수로 관리되는 열거형은 선언한 값 이외의 값을 할당하거나 접근할 때 이를 방지하지 못한다. 반면 문자열 상수 방식으로 선언한 열거형은 미리 선언하지 않은 멤버로 접근을 방지한다. 따라서 문자열 상수 방식으로 열거형을 사용하는 것이 숫자 상수 방식보다 더 안전하며 의도하지 않은 값의 할당이나 접근을 방지하는 데 도움이 된다. -p97

하지만 이 구문의 근거를 찾지 못해 위 문제의 답으로 작성하지 않았습니다.


p97 하단 예제 3.1.6-5는 숫자 상수 방식과 문자열 상수 방식를 비교하는 예제입니다.

const enum NUMBER {
  ONE = 1,
  TWO = 2,
}
const myNumber: NUMBER = 100; // NUMBER enum에서 100을 관리하고 있지 않지만 이는 에러를 발생시키지 않는다

const enum STRING_NUMBER {
  ONE = "ONE",
  TWO = "TWO",
}
const myStringNumber: STRING_NUMBER = "THREE"; // Error

숫자 상수 방식(Number)은 에러를 반환하지 않고, 문자열 상수 방식(STRING_NUMBER)은 에러를 반환하기 때문에 더 안전함을 근거로 들었습니다.


하지만 실제로는 두 방식 모두 동일한 에러를 반환했습니다.

const enum NUMBER {
  ONE = 1,
  TWO = 2,
}
const myNumber: NUMBER = 100; // 🚨 Type '100' is not assignable to type 'NUMBER'.

const enum STRING_NUMBER {
  ONE = "ONE",
  TWO = "TWO",
}
const myStringNumber: STRING_NUMBER = "THREE"; // 🚨 Type '"THREE"' is not assignable to type 'STRING_NUMBER'.

TS playground 실행 결과나 직접 만든 예시도 상황은 동일합니다.

const enum HttpStatusType {
  Forbidden = 403,
  BadGateway = 502,
}
const myStatus: HttpStatusType = 430; // 🚨 Type '430' is not assignable to type 'HttpStatusType'.

image


그 외 chatGPT와 검색을 통해서도 명확한 정답을 찾아내기 어려움이 있었습니다. 해당 사유로 인하여 이 문제의 답에 문자열 상수 방식은 포함하지 않기로 정리합니다.