“釘付け”や“ハードコード”は、設定や構成に値を外出しせずに、プログラムコードに直接値を固定することを意味する業界用語です。ハードコードは開発において最もよく知られたアンチパターンの一つであり、コードの柔軟性と再利用性を低下させます。Refactoring Guruによると、ハードコードはテスト、保守、および異なる環境へのアプリケーションの適応を困難にします。ハードコードの代わりに意識的に定数を使用することは、成熟したアーキテクチャの証です。
重要なポイント
ハードコードする(釘付けにする) — プログラムコードに特定の値を、変更するにはソースコードの編集とアプリケーションの再コンパイルが必要となるように組み込むこと。“釘付け”というメタファーは本質を正確に反映しています。値は永久に固定され、コードから引き剥がすには努力が必要です。
ハードコードの例 — 関数の本体に文字列として直接書かれたサーバーURL。サーバーが別のアドレスに移行した場合、開発者はコード内の文字列を見つけ、変更し、アプリケーションを再ビルドしてリリースする必要があります。適切なアーキテクチャを持つアプリケーションでは、そのURLは設定ファイル、環境変数、または設定サービスに外部化されているでしょう。
“釘付け”という用語はより感情的なニュアンスを持ちます。値が恒久的に挿入され、迅速な交換が不可能であることを強調しています。ロシア語圏では、両方の表現が否定的なニュアンスを持つ完全な同義語として使用されます。ハードコードは皮肉を込めて“定数から取り出した別の定数の中の定数”と呼ばれることもあります。
ハードコードは、コードの保守性、テスタビリティ、拡張性の原則に違反するため、アンチパターンです。値が“釘付け”されたコードでは、環境、デザイン、ロジックの変更ごとに、ソース内での手動検索と置換が必要になります。これによりエラーのリスクが高まり、開発が遅れます。
典型的なモバイルアプリケーションを例に、ハードコードの具体的な結果を考えてみましょう。すべてのボタンのマージンがコード内の数値で指定され、リソース経由ではない場合 — デザイン変更にはすべての出現箇所の検索と置換が必要になります。エンドポイントURLが固定されている場合 — 環境(dev、stage、prod)の切り替えは再ビルドなしでは不可能です。
| 結果 | 説明 | 重大度 |
|---|---|---|
| 保守の複雑さ | 変更にはコード全体の検索が必要 | 高 |
| コピー時のエラー | すべての出現箇所が見つかり置換されるとは限らない | 高 |
| テスティング不能 | テストデータを差し替えられない | 中 |
| ローカライゼーションの問題 | コード内のテキストが翻訳されない | 中 |
| コードレビューの複雑化 | レビュアーがすべてのコンテキストを把握する必要がある | 低 |
マジックナンバーと固定文字列を使用する関数は、ハードコードの典型です。1ヶ月後には、作者でさえ18、0.07、2.5が何を意味するか覚えていないでしょう。1年後には — チームの誰もロジックを壊すことを恐れてこれらの数値を変更しようとしません。値を名前付き定数に抽出することで、コードは自己文書化されます。
// 悪い例:マジックナンバーと文字列
fun calculatePrice(base: Double): Double {
val tax = base * 0.07
val tip = base * 0.15
val discount = if (base > 100) 10 else 0
return base + tax + tip - discount
}
ハードコードされたデータベースURLでは、ローカルのインメモリデータベースでテストを実行できません。開発者はテスト前に本格的なサーバーを起動するかコードを編集する必要があります。コードから設定を外部化することで問題が解決します。テストはテストパラメータを、本番環境は実際のパラメータを使用し、コードは変更されません。
ハードコードはアンチパターンですが、固定値が許容されるだけでなく推奨される正当な例外が存在します。境界線は可変性の軸に沿っています。値がアプリケーションのライフサイクル内で決して、またはほとんど決して変更されない場合、ハードコードしても問題ありません。変更される可能性がある場合は — 設定に外部化してください。
数学定数や物理定数 — 円周率、重力加速度、1秒あたりのミリ秒数 — はハードコードしても安全です。これらは自然または標準によって定義され、変更されることはありません。仕様で定義された定数配列のサイズも固定できますが、数値の出所に関するコメントを付けてください。
1秒あたりのミリ秒数は、時間標準によって定義された安定した定数です。これは決して変更されないため、設定に外部化する意味はありません。ただし、そのような定数でも、コードに“マジックナンバー”が含まれないよう、意味のある名前で宣言する方が良いです。1000ではなくMILLISECONDS_IN_SECONDと書きましょう。
// 正当化されるハードコード:安定した定数
private const val MILLIS_IN_SECOND = 1000
private const val LOGIN_TIMEOUT_SECONDS = 30
fun formatDuration(ms: Long): String {
val seconds = ms / MILLIS_IN_SECOND
return "${seconds} sec."
}
ハードコードを回避する実証済みの方法がいくつかあり、それぞれ値のタイプに適しています。代替手段の選択は、値が変更される頻度と、誰が変更するか(開発者、デブオプス、エンドユーザー)によって異なります。
サーバーURL、APIキー、フィーチャーフラグには、JSON、YAML、TOML形式の設定ファイルを使用してください。Androidではbuild.gradleのbuildConfigFieldやres/values/config.xmlを使用します。iOSでは — Info.plistやxcconfig。設定ファイルはアプリケーションと一緒にビルドされますが、ビルドスキームごとに異なるものにできます。
シークレット(トークン、パスワード)や環境パラメータには環境変数を使用してください。これらはリポジトリに入らず、dev、stage、prodサーバーで異なる値にできます。モバイル開発では、環境変数はXcodeのビルドスキームやGradleのビルドフレーバーを通じてエミュレートされることがよくあります。
文字列、色、サイズ、画像はリソースファイルに外部化する必要があります。Androidではstrings.xml、iOSではLocalizable.strings、FlutterではARBファイル。これにより、ローカライゼーション、異なる画面への適応、ダークテーマが容易になります。リソース内の文字列を変更しても、コードを書き換える必要はありません。
<!-- Android: res/values/strings.xml -->
<resources>
<string name="app_name">MyApp</string>
<string name="api_base_url">https://api.example.com</string>
</resources>
サービスやプロバイダーには、AndroidではDagger、Hilt、Koin、iOSではSwinjectを使用した依存性注入を利用してください。DIフレームワークは — テスト用、異なる環境用、異なるユーザー用に — 実装を動的に差し替えることを可能にします。これは抽象化の最高レベルであり、値の“固定”が外部からの注入に置き換えられます。
ハードコードのリファクタリングは、固定された値を設定やリソースに抽出するプロセスです。これは、系統立てて行えば、最も安全なリファクタリング操作の一つです。以下に説明する手順は、あらゆる言語とプラットフォームに適しています。
IDE(Search in Project)やスクリプトを使用して検索できます。文字列、URL、数値リテラル、サイズ、タイムアウトを探します。特に注意が必要なのは — 繰り返し出現する値です。同じ数値が5箇所に出現する場合、それは定数に抽出する候補です。grepやIDEA / Xcodeの内蔵検索を使用してください。
見つかった各値に対して、意味のある名前を持つ定数を作成します。定数をモジュールやクラスごとにグループ化します。名前は値の意味を説明するものであり、使用方法を説明するものではありません。API_TIMEOUTであってTIMEOUT_30ではありません。置き換え後、コード内に説明なしの数値が残ってはいけません。
// 以前:マジックナンバー 0.4
let cardHeight = screenHeight * 0.4
// 以後:名前付き定数
private let cardHeightRatio: CGFloat = 0.4
let cardHeight = screenHeight * cardHeightRatio
値がビルド間や環境間で変わる可能性がある場合は — 設定ファイルまたはアプリケーションリソースに抽出します。文字列にはローカライゼーションファイルを使用します。URLには — build configまたはxcconfig。サイズには — リソースファイル(Androidのdimens.xml)。抽出後もアプリケーションが正しくビルドされ動作することを確認してください。
リファクタリング後、設定が正しく読み込まれ、値が期待通りであることを確認するテストを書きます。将来誰かが設定を変更した場合、テストが不一致を指摘します。設定のテストは — リグレッションを防ぐ迅速で信頼性の高い方法です。
設定に抽出した後、古い値を使用していたすべての箇所が単一のソースを参照していることを確認します。コメントアウトされたコードや使用されなくなった古い定数を削除します。リファクタリングを、どの値がどこに抽出されたかを説明するコミットメッセージで完了します。
よくある質問
ハードコードする — 設定やリソースに抽出せずに、ソースコードに直接値を書き込むこと。これによりコードの柔軟性が低下し、保守が難しくなります。
ハードコードはアプリケーションの動作変更を困難にし、テストを妨げ、重複を生み出し、コピー時のエラーリスクを高めます。ハードコードされた値を変更するには、アプリケーションの再ビルドと再リリースが必要です。
許容されるのは、数学定数、アプリケーションのライフサイクル内で変更されない安定した値、および一時的なプロトタイプの場合です。本番環境では、定数でさえ名前付き変数に抽出するべきです。
検索ですべてのマジックナンバーを見つけ、名前付き定数に置き換えるか、設定ファイルに抽出します。設定の読み込みを確認するテストを書き、重複を削除し、変更内容を説明するコミットメッセージでコミットします。
定数 — コード内の名前付きの値で、一箇所で変更可能です。ハードコード — コード中に散在する名前のない値。良い慣行:常に意味のある名前の付いた名前付き定数を使用すること。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。