141 lines
5.0 KiB
Markdown
141 lines
5.0 KiB
Markdown
# enum / constants 기준
|
|
|
|
## 목적
|
|
|
|
고정된 의미 집합은 문자열/정수 상수 남발 대신 enum으로 표현한다.
|
|
상수는 단순히 `static final`인 값이 아니라, 실제로 변경 불가능하고 의미가 분명한 값으로 관리한다.
|
|
|
|
## 공식 의미
|
|
|
|
- enum은 미리 정의된 상수 집합을 표현하는 특별한 클래스다.
|
|
- 각 enum constant는 해당 enum 타입의 고유 인스턴스다.
|
|
- enum은 필드, 메서드, 생성자를 가질 수 있다.
|
|
- `ordinal()`은 대부분의 개발자가 사용할 용도가 아니며 `EnumSet`, `EnumMap` 같은 저수준 구조를 위한 값이다.
|
|
- `name()`은 선언된 정확한 이름을 반환한다.
|
|
- `toString()`은 더 사람이 읽기 좋은 문자열이 필요할 때만 재정의한다.
|
|
- enum key/set에는 `EnumMap`, `EnumSet` 같은 특화 구현을 우선 검토한다.
|
|
|
|
## 기본 규칙
|
|
|
|
### 1. 고정된 값 집합이면 enum 우선
|
|
다음과 같은 경우는 enum을 우선 검토한다.
|
|
|
|
- 상태
|
|
- provider 종류
|
|
- 역할 종류
|
|
- 모드
|
|
- 정책 선택지
|
|
- 결과 코드의 제한된 집합
|
|
|
|
금지 예:
|
|
- `"LOCAL"`, `"GOOGLE"`, `"KAKAO"` 같은 문자열 분기
|
|
- `1`, `2`, `3` 같은 매직 넘버 상태값 분기
|
|
|
|
### 2. 값 집합이 열려 있으면 enum을 쓰지 않는다
|
|
외부 시스템이 임의 값을 추가할 수 있거나, 런타임에 확장 가능한 값이면 enum보다 다른 모델을 검토한다.
|
|
|
|
예:
|
|
- 자유 입력 태그
|
|
- 외부 설정으로 계속 늘어나는 코드값
|
|
- tenant별 커스텀 상태
|
|
|
|
### 3. `ordinal()` 의존 금지
|
|
다음 용도로 `ordinal()`을 사용하지 않는다.
|
|
|
|
- DB 저장값
|
|
- 외부 API/JSON 값
|
|
- 비즈니스 분기
|
|
- 배열 인덱스 계약
|
|
- stable code/wire number
|
|
|
|
안정적인 숫자/문자 코드가 필요하면 enum 필드로 명시한다.
|
|
|
|
### 4. `name()`은 내부 안정 식별자, 사용자 표시값은 별도 필드/메서드
|
|
- 내부 고정 식별이 필요하면 `name()`
|
|
- 사용자에게 보이는 문자열은 별도 필드나 메서드
|
|
- API/DB/wire 값도 별도 필드로 분리
|
|
|
|
즉:
|
|
- internal name
|
|
- display label
|
|
- external code
|
|
를 섞지 않는다.
|
|
|
|
### 5. `toString()`은 신중하게
|
|
공식 문서상 `toString()`은 필요하면 더 programmer-friendly 한 문자열로 재정의할 수 있다.
|
|
하지만 프로젝트에서는 다음 기준을 따른다.
|
|
|
|
- 로깅/디버깅/개발자용 표현만 바꿀 때 제한적으로 허용
|
|
- 외부 계약, JSON, DB 저장값을 `toString()`에 의존하지 않는다
|
|
- 외부 계약값은 명시적 getter/field를 사용한다
|
|
|
|
### 6. enum 비교는 enum끼리 직접 비교
|
|
동일 enum 타입 비교는 enum 값 자체로 직접 비교한다.
|
|
비즈니스 의미 비교를 위해 문자열로 변환해서 비교하지 않는다.
|
|
|
|
금지 예:
|
|
- `status.name().equals("ACTIVE")`
|
|
- `provider.toString().equals(input)`
|
|
|
|
### 7. enum set/map은 `EnumSet` / `EnumMap` 우선 검토
|
|
enum을 key/set element로 사용할 때는 다음을 우선 검토한다.
|
|
|
|
- 집합 -> `EnumSet`
|
|
- 맵 -> `EnumMap`
|
|
|
|
비트 플래그용 `int`나 일반 `HashSet`/`HashMap`을 습관적으로 쓰지 않는다.
|
|
|
|
### 8. switch/분기는 enum 의미 중심으로 작성
|
|
enum 분기는 문자열/정수 코드가 아니라 enum 값 자체를 기준으로 작성한다.
|
|
|
|
또한:
|
|
- default로 조용히 삼키지 않는다
|
|
- 새로운 enum 값 추가 시 분기 누락이 드러나도록 작성한다
|
|
|
|
### 9. 상수는 진짜 immutable일 때만 상수로 취급
|
|
상수는 다음을 만족해야 한다.
|
|
|
|
- `static final`
|
|
- 값 자체가 immutable
|
|
- 내부 원소도 변경으로 의미가 흔들리지 않음
|
|
|
|
즉:
|
|
- mutable collection
|
|
- mutable object reference
|
|
- 변경 가능한 배열
|
|
은 상수처럼 다루지 않는다.
|
|
|
|
### 10. 관련 상수는 “상수 클래스”보다 소유 타입 근처에 둔다
|
|
다음 순서를 우선한다.
|
|
|
|
1. enum으로 승격 가능한 값이면 enum
|
|
2. 특정 클래스 책임이면 해당 클래스 내부 상수
|
|
3. 여러 모듈에 걸친 진짜 전역 상수만 별도 위치
|
|
|
|
의미 없는 `Constants`, `CommonConstants`, `AppConstants` 집합소는 기본 금지다.
|
|
|
|
### 11. null enum 기본 금지
|
|
enum 값은 가능한 한 non-null로 다룬다.
|
|
부재 표현이 필요하면:
|
|
- Optional 반환
|
|
- nullable boundary 입력
|
|
- 명시적 `UNKNOWN` / `UNSPECIFIED` 도입 여부를 신중히 검토
|
|
|
|
단, `UNKNOWN`이 실제 비즈니스 상태가 아닐 경우 null 회피용으로 남용하지 않는다.
|
|
|
|
### 12. persistence / serialization 계약은 별도 기준을 따른다
|
|
enum을 DB나 JSON에 노출할 때는 language 차원 기본 동작에 기대지 않고,
|
|
- 어떤 값을 저장/전송할지
|
|
- 호환성을 어떻게 유지할지
|
|
를 명시적 기준으로 정의한다.
|
|
|
|
## 프로젝트 기준 요약
|
|
|
|
- 고정된 의미 집합은 enum 우선
|
|
- 열린 값 집합은 enum 지양
|
|
- `ordinal()` 의존 금지
|
|
- internal name / display label / external code 분리
|
|
- enum set/map은 `EnumSet` / `EnumMap` 우선
|
|
- mutable 값은 상수처럼 다루지 않음
|
|
- 잡다한 constants class 기본 금지
|