設定の誤字を既定値として扱わない:設計原理
一言でいうと
厳密な設定のパース・秘密情報の除去・アプリごとのスナップショットをテストします。
なぜ必要なのか
文字列のfalseをboolに変換したら、Trueになりました。本番でデバッグのレスポンスが有効になり、ステータスページには、設定の辞書全体が出力されました。環境変数は文字列なので、型の宣言だけでは、安全な値になりません。設定を読む時点と、公開する範囲も、アプリケーションの契約です。
どう動くのか
ポート・タイムアウト・真偽値・必須のトークンを、それぞれ検証します。設定は、与えられた辞書から一度だけ読んでコピーし、不正な値を、黙って既定値に変えません。欠落に対してだけ、既定値を適用します。アプリを作成したあとで、入力の辞書を変更しても、すでに作成されたアプリの動作は、変わってはいけません。公開するステータスには、サービス名とdebugの値だけを残します。
문자열 사전 → 개별 타입/범위 검증 → 설정 스냅샷 → 공개 허용 필드
契約を読んで失敗を予測するワークシート
以下は、実装をまるごと暗記するための答案ではなく、ステップごとのコードレビューです。各変更の断片は、意図的に契約を壊しています。変更したあとでも、正常な例は通ることがある点に注意してください。実行する前に、どの入力・例外・状態を観測すれば違いが表に出るかを予想し、実装したあとで、その予想と結果を比べます。
1. 真偽値を明示的にパースする
parse_bool(value)は、大文字と小文字を区別しないtrueまたはfalseだけを、boolとして返します。空白が付いているか、文字列でなければ、ValueErrorです。
判断の根拠: bool('false')はTrueです。許可する2つの文字列を、直接比較してください。
レビューする誤った変更の断片:
bool(value)
この断片が入った関数の公開契約と比べてみてください。成功例1つでは区別できないなら、拒否されるべき入力や、失敗したあとの状態を観測の対象に選びます。
2. ポートの範囲を検査する
parse_port(value)は、ASCIIの数字だけがある文字列をintに変換して、1–65535なら返します。それ以外は、ValueErrorです。
判断の根拠: 整数への変換が成功しても、有効なポートの範囲であるという意味ではありません。
レビューする誤った変更の断片:
<= 65536
この断片が入った関数の公開契約と比べてみてください。成功例1つでは区別できないなら、拒否されるべき入力や、失敗したあとの状態を観測の対象に選びます。
3. タイムアウトを有限の値にする
parse_timeout(value)は、文字列をfloatに変換して、0より大きく30以下の有限の値だけを返します。それ以外は、ValueErrorです。
判断の根拠: NaNは、通常の比較で予想と違う動作をするので、isfiniteを確認します。
レビューする誤った変更の断片:
0 <= number <= 30
この断片が入った関数の公開契約と比べてみてください。成功例1つでは区別できないなら、拒否されるべき入力や、失敗したあとの状態を観測の対象に選びます。
4. 必須の秘密情報の欠落を拒否する
required_token(env)は、TOKENが文字列で、stripしたあとに空でないときに、stripした値を返します。ないか、空の値は、ValueErrorです。
判断の根拠: 欠落した必須の秘密情報を、例の既定値で置き換えません。
レビューする誤った変更の断片:
return value
この断片が入った関数の公開契約と比べてみてください。成功例1つでは区別できないなら、拒否されるべき入力や、失敗したあとの状態を観測の対象に選びます。
5. 欠落に対してだけ既定値を適用する
load_settings(env)は、service=envのSERVICE、または欠落時は'api'、debug=parse_bool(DEBUGの欠落時は'false')、port=parse_port(PORTの欠落時は'8000')、timeout=parse_timeout(TIMEOUTの欠落時は'5')、token=required_tokenの辞書です。空のSERVICEは、ValueErrorです。
判断の根拠: getの既定値と、orで既定値に切り替える書き方は、空文字列の扱いが違います。
レビューする誤った変更の断片:
env.get("DEBUG","true")
この断片が入った関数の公開契約と比べてみてください。成功例1つでは区別できないなら、拒否されるべき入力や、失敗したあとの状態を観測の対象に選びます。
6. 公開する設定から秘密情報を取り除く
public_settings(settings)は、serviceとdebugだけを持つ新しい辞書です。元のデータは変更しません。
判断の根拠: トークンの値の一部をマスキングするよりも、フィールドそのものを公開しない契約を使います。
レビューする誤った変更の断片:
"debug":settings["debug"],"token":settings["token"]}
この断片が入った関数の公開契約と比べてみてください。成功例1つでは区別できないなら、拒否されるべき入力や、失敗したあとの状態を観測の対象に選びます。
7. 外部の変更と設定を分離する
snapshot(env)は、load_settingsの結果を返します。呼び出したあとにenvを変更しても、返された設定は変わりません。
判断の根拠: アプリの起動時点の設定を、あとで変わる入力の辞書から分離します。
レビューする誤った変更の断片:
return env
この断片が入った関数の公開契約と比べてみてください。成功例1つでは区別できないなら、拒否されるべき入力や、失敗したあとの状態を観測の対象に選びます。
8. 起動の失敗と公開レスポンスを確認する
create_app(env)は、snapshotをすぐに読み、不正な設定ならValueErrorでアプリの作成を失敗させます。GET /infoは、public_settingsだけを返します。別のenvで作ったアプリ同士で、設定を共有しません。
判断の根拠: サーバーが起動したあと、最初のリクエストで設定エラーが表に出ないように、作成時に検証します。
レビューする誤った変更の断片:
return settings
この断片が入った関数の公開契約と比べてみてください。成功例1つでは区別できないなら、拒否されるべき入力や、失敗したあとの状態を観測の対象に選びます。
現場での姿
通常の辞書で環境を注入するので、実際のプロセスのグローバルな環境には依存しません。暗号化された秘密情報のストア、キーのローテーション、動的な再読み込みまで実装する例ではありません。トークンの文字列は学習用の入力で、実際の本番のキーを、ラボのPodに入れてはいけません。
次のラボですること
8つのステップが、1つの実行可能な成果物につながります。真偽値を明示的にパースする → ポートの範囲を検査する → タイムアウトを有限の値にする → 必須の秘密情報の欠落を拒否する → 欠落に対してだけ既定値を適用する → 公開する設定から秘密情報を取り除く → 外部の変更と設定を分離する → 起動の失敗と公開レスポンスを確認する、という流れです。
各ステップは、関数やファイルが存在するという事実ではなく、実際の戻り値・例外・状態の変化を検査します。正解を見たあとには、わざと境界の比較や後始末のコードを変えて、どのテストが失敗するかを確認してください。前のテストが次のステップでも維持される理由を説明し、このラボが保証しない本番の条件を1つ書いてみてください。