つい先日、プロジェクト内に点在するハードコーディングを滅ぼすタスクを行っていました。
滅ぼしながらも「ハードコーディングが許されるパターンはないのか?」ということが気になり、調べてみました。
簡単にいうと、大事なのは、ハードコーディングされた値の意味・変更理由・影響範囲がコードから読み取れるか であり、場合によってはハードコーディングを許容してもいいのかもしれないと思えました。
ハードコーディングとは
ハードコーディングとは、値をコードの中に直接書くことです。
マジックナンバー
例えば、以下の 80 は意味が分かりにくいです。
if (score >= 80) {
return "passed";
}
このような値は、いわゆる マジックナンバー と呼ばれます。
この場合は、80が何の数字なのかを表す名前を付けると、意図が伝わりやすくなります。
const PASSING_SCORE = 80;
if (score >= PASSING_SCORE) {
return "passed";
}
こうすることで、80 が単なる数値ではなく、合格点を意味していることが分かります。
このように名前をつけることは、1回しか使わない値だとしても、業務上の意味が重要なら価値があると思います。
const MAX_LOGIN_ATTEMPTS = 5;
この場合、大事なのは 5 を再利用することではありません。
5 が「最大ログイン試行回数」を意味しているとコード上で分かることです。
つまり、定数化は 値に意味を与えるための手段 でもあります。
環境によって変わる値
以下のような値もコードに直接書かない方がよいです。
const apiUrl = "https://prod.example.com";
API URLのような値は、開発環境や検証環境などの環境によって変わる可能性があります。
それぞれで値が変わるなら、環境変数や設定ファイルに切り出す方が自然です。
const apiUrl = process.env.API_BASE_URL;
特に、以下のような値はハードコーディングを避けるべきです。
- API URL
- DB接続情報
- APIキー
- パスワード
- 外部サービスのエンドポイント
- 環境ごとに変わる設定値
これらはコードに直接書くと、変更しづらいだけでなく、セキュリティ上の問題にもつながります。
ハードコーディングが許容される(かもしれない)場合
局所的で意図が明確なもの
一方で、すべてのリテラルを定数化すればよいわけでもありません。
例えば、テストコードで以下のように書く場合、この 201 はHTTPステータスコードとして比較的意味が明確です。
expect(response.status).toBe(201);
これを毎回次のように書くと、かえって読みにくくなることもあります。
const CREATED_STATUS_CODE = 201;
expect(response.status).toBe(CREATED_STATUS_CODE);
もちろん、プロジェクトの方針や文脈によります。
ただ、局所的で意味が明確な値まで何でも定数化すると、読む人が定義元を追う手間が増えます。
テストコードの一部
テストコードでは、期待値を直接書いた方が読みやすい場合があります。
expect(user.name).toBe("Taro");
expect(user.age).toBe(20);
このような値は、そのテストケースの入力・期待値としてその場で読める方が分かりやすいことがあります。
一方で、テストでも業務ルールに関わる値は定数化した方がよいです。
const RESERVATION_CAPACITY_LIMIT = 10;
expect(result.capacity).toBe(RESERVATION_CAPACITY_LIMIT);
おまけ: 定数の置き場所にも注意する
定数化するときは、置き場所も重要です。
定数置き場として、 constants.ts を作成しこれに集約する、というやり方があるかと思います。
最初は便利に見えます。しかし、プロジェクトが大きくなると、何のための定数なのか分かりにくくなります。
// constants.ts
export const ADMIN = "admin";
export const LIMIT = 10;
export const DEFAULT_NAME = "guest";
このような巨大な共通定数ファイルは、かえって依存関係を分かりにくくすることがあります。
まずは、使う場所の近くに置くのがよいです。
const MAX_LOGIN_ATTEMPTS = 5;
function canRetryLogin(attemptCount: number): boolean {
return attemptCount < MAX_LOGIN_ATTEMPTS;
}
そして、本当に複数箇所で共有する必要が出てきたら、共通化を検討します。
特定のドメインでしか使わない定数なら、それが分かるような場所にconstants.tsを作るのもいいと思います。
domain/
├─ user/
└─ post/
├─ index.ts
└─ constants.ts // postでしか使わない定数置き場
判断基準
ハードコーディングしてよいか迷ったら、以下を考えると判断しやすいです。
- その値は変更される可能性があるか?
- 定数化すると、変更箇所を減らすことができる
- 同じ値を複数箇所で使っているか?
- 定数化すると、変更箇所を減らすことができる
- その値に業務上の意味があるか?
- 文字列や数値の意味を明示できる
1つでも当てはまるなら、定数化・設定化・環境変数化を検討する価値があります。
まとめ
ハードコーディングは、常に滅ぶべき悪だと考えていました。今回、逆にどのような時なら許されるのかを考えることで、頭の体操になりました。
今回は項目として立てることはやめましたが、とにかく早く作って検証したい場合、超緊急で修正&リリースしないといけない場合(後の修正は必須)なども、場合によっては許されるかもしれないです。
実際の運用はチームやプロジェクトの方針に従いましょうね。