はじめに
Pythonで定数を整理するときに enum.Enum
を使うことは多いと思います。
Enumに表示文字列を直書きすると、UIのちょっとした表記変更のたびにEnumまで直さなければならず、想定以上に影響範囲が広がってしまいます。
本記事では、それを防ぐための「識別子は安定した英語、表示は外出し」という整理方法を解説します。
よくある書き方(落とし穴)
例えば、食事区分をEnumで管理したとします。
from enum import Enum
class Meal(Enum):
朝ごはん = "朝ごはん"
昼ごはん = "昼ごはん"
夜ごはん = "夜ごはん"
「夜ごはん → 晩ごはん」と表記を変えたいときにどうなるでしょうか?
- Enumの値を変える必要がある
- 識別子と値がずれて混乱する
- 値に合わせて識別子も変更する
- DBやAPIへの影響が広がる
表示を直したいだけなのに、システムの識別子まで変える必要が出てしまい、必要以上の修正の必要が発生します。
解決策:識別子と表示文字列を分ける
識別子は業務ロジックに依存しない安定した英語にして、表示用の文言は外部に持たせます。
from enum import Enum
class Meal(Enum):
BREAKFAST = "BREAKFAST"
LUNCH = "LUNCH"
DINNER = "DINNER"
# 表示文字列(日本語)
MEAL_LABELS = {
Meal.BREAKFAST: "朝ごはん",
Meal.LUNCH: "昼ごはん",
Meal.DINNER: "夜ごはん", # → 晩ごはんに変えてもEnumはそのまま
}
UI側はマッピングから表示するための値を取るだけのため無影響となります。
print(MEAL_LABELS[Meal.DINNER]) # 夜ごはん
この方法のメリット
- 識別子は安定:Enumは「安定コード」として残る
- 表示は自由に変更可能:UXの都合で好きに直せる
- 識別子の変更は「後方互換性注意」、ラベル変更は「UI改善」と、レビュー観点が明確に分かれます。
注意点
- 識別子は英語でフルスペルに(略字は混乱を生む)
- DBや外部APIと整合性が崩れる、値の変更はNG
- ラベルは自由に変更OK(UI改善の範囲に閉じる)
設計原則との関係
単一責任の原則(SRP)
- Enumの識別子の責務:システム内部で安定したIDを持つこと
- 表示文字列の責務:ユーザーに見せる文言を管理すること
両方に同じ文字を持たせると、「表示変更」という理由でも識別子を変更せざるを得なくなります。これはSRP違反になります。
関心の分離(Separation of Concerns)
- 表示(ユーザーにどう見せるかの関心)と識別子(システム内部で定して管理するための関心)は異なる関心ごとである
- 「表現の変更」と「内部識別子の変更」という異なる変更理由が混じっていると、UI側の要求がシステム内部へ波及する(= 変更に弱い設計となる)
DRYの観点
- 識別子と表示文字列を同一にすると「朝ごはん」という情報が識別子とUI表示の両方に埋め込まれ、同じ情報を二重に管理することとなる。
- 意味が二箇所に閉じ込められていることで将来の変更が二重になる危険がある。
識別子と表示文字列を分けておくことで、表示の変更は「識別子は変えずにラベルだけ変更」で済むため、DRY(重複排除)にも適います。
まとめ
- Enumに文言を直書きすると変更に弱い
- 識別子=安定コード、表示=外出し
- SRP/SoC/DRYに沿う設計でUI変更に強くなる
UIの表記変更をシステム内部に波及させない。シンプルな工夫が長期的な保守性を高めます。
Appendix: 既存DBの数値IDに合わせる場合
既存DBの数値コードに合わせたいケースも多いと思います。
その場合はIntEnum
を使うと自然に表現できます。
from enum import IntEnum
class MealCode(IntEnum):
BREAKFAST = 1
LUNCH = 2
DINNER = 3
MEAL_LABELS = {
MealCode.BREAKFAST: "朝ごはん",
MealCode.LUNCH: "昼ごはん",
MealCode.DINNER: "夜ごはん",
}