【入門編】Dartのグローバル変数とstatic定数の管理:メモリリークを防ぐための依存関係設計 – Dart コア文法・オブジェクト指向・Null安全解析バイブル

こんにちは!FlutterやDartを使った開発、楽しんでいますか?
今日は、多くの開発者が何気なく使いがちでありながら、実はアプリケーションのパフォーマンスやメモリ管理の運命を大きく左右する「変数宣言(`var`、`final`、`const`)」と「グローバル状態・静的定数の設計」について、少し踏み込んでお話ししていきますね。

他のプログラミング言語からDartの世界に入ってきた方ほど、「とりあえずどこからでもアクセスできるグローバル変数を作っちゃえばラクだな」と思い込みがちです。ですが、ここを誤ると、アプリが重くなったり、最悪の場合はメモリリークの原因になったりします。

ここをしっかりとクリアすれば、あなたの書くDartコードは一気にプロダクション品質に近づきますよ。さあ、一緒に本質をマスターしていきましょう!

—

1. 変数宣言の三兄弟:`var`、`final`、`const` の正体を知る

まずは基本の復習からですが、単なる「書き方の違い」として覚えていませんか?
Dartのコンパイラとメモリの動きを意識すると、これらは全く異なる生き物です。

void main() {
var name = “Dart”; // 1. 再代入可能(型推論)
final score = 100; // 2. 実行時イミュータブル(一度だけ代入可能)
const maxRetry = 3; // 3. コンパイル時定数(完全な不変)
}

  • `var`: 値が変わることを許す、通常の変数です。型はコンパイル時に安全に推論されます。
  • `final`: 「一度だけ値を代入できる」変数です。代入される値は実行時(Runtime)に決まります。例えば、アプリが起動した瞬間の現在時刻や、ネットワークから取得したIDなどを入れるのに最適です。
  • `const`: コンパイル時(Compile-time)に値が完全に確定していなければならない「真の定数」です。Dart VM(またはAOTコンパイラ)は、コードが実行されるはるか前にこの値をメモリ上の定数領域に焼き付けます。

🧠 アーキテクトからの深掘り知見:`const` のメモリ効率

`const` で宣言されたオブジェクト(特にリストやカスタムオブジェクト)は、「カノニカル化(Canonicalization)」という最適化を受けます。
アプリ内のどこで何回 `const [1, 2, 3]` と書いても、Dart VMはメモリ上にたった1つのインスタンスしか生成しません。一方、`final` は実行時に毎回新しいメモリ領域を消費します。ここが、パフォーマンスを極限まで高めるための第一歩です。

—

2. なぜ「グローバル変数」は危険なのか?

さて、本題のグローバル変数(関数の外、クラスの外、あるいはファイルの最上位に書く変数)についてです。

「どこからでもアクセスできて便利!」と、以下のようなコードを書いたことはありませんか?

// ❌ 危険なグローバル変数の例
String globalUserName = “Guest”;
List globalCacheLog = [];

void main() {
globalUserName = “Alice”;
// どこからでも書き換え可能で、依存関係がカオスになる…
}

依存関係の「見えない糸」がアプリを壊す

グローバル変数がなぜ悪者扱いされるかというと、「誰が、いつ、どこでその値を書き換えたのか」をコードの静的構造から追跡できなくなるからです。

これを図解するとこうなります。

[UI画面A] ──(勝手に書き換え)──> 📈 [グローバル変数] <──(参照)── [バックグラウンド処理] ↑ (誰も責任を持たない) 特定の画面でバグが起きたとき、原因を探ると「別の画面の非同期処理が勝手にグローバル変数を書き換えていた」ということがよく起きます。また、Flutterのようなフレームワークでは、Isolate(Dartの並行処理単位)をまたいだデータの共有や、テスト時のモック化(差し替え)が極めて困難になります。 ---

3. 安全な定数管理:`static const` を使いこなす

「じゃあ、アプリ全体で共有したい設定値やAPIのエンドポイントはどう書けばいいの?」という疑問が湧きますよね。
ここで登場するのが、クラスのスコープに閉じ込めた `static const` です。

以下のように、インスタンス化を必要としない「定数専用のクラス」を作るのが、Dartにおけるベストプラクティスの一つです。

// 🎯 安全で美しい定数管理の例
class AppConfig {
// プライベートコンストラクタにして、インスタンス化を完全に防ぐ
AppConfig._();

static const String appName = “SuperDartApp”;
static const int timeoutSeconds = 30;
static const List supportedLanguages = [‘ja’, ‘en’, ‘es’];
}

void main() {
// クラス名経由で安全にアクセス(どこから書き換えることもできない!)
print(AppConfig.appName);
}

なぜ `static const` は安全なのか?

1. 名前空間(Namespace)の汚染を防ぐ: グローバル空間にポツンと変数が置かれるのではなく、`AppConfig`という文脈の中にきれいに収まります。
2. 不変性の保証: `const` がついているため、実行時に値が書き換えられる心配が100%ありません。
3. ツリーシェイキング(Tree Shaking)の恩恵: 使われなかった定数は、最終的なコンパイル結果から綺麗に削ぎ落とされます。

—

4. 陥りやすい罠:`static final` と `const` の混同

初学者がよくやってしまうコンパイルエラーがこちらです。

class Database {
// ❌ コンパイルエラーになる例
// DateTime.now() は「実行時」に値が決まるため、コンパイル時には分からない!
static const DateTime launchTime = DateTime.now();
}

このコードを書くと、Dartのコンパイラは次のようなエラーを吐きます。
> Const variables must be initialized with a constant value.
> (Const変数は、コンパイル時定数で初期化されなければなりません)

💡 解決策:実行時に決まるなら `static final`

「アプリが起動した瞬間の時間」などを保持したい場合は、`const` ではなく `static final` を使います。

class Database {
// ✅ 正しいアプローチ:実行時イミュータブルな静的変数
static final DateTime launchTime = DateTime.now();
}

これで、「クラスに紐づく、一度だけ初期化される安全な共有値」を作ることができます。メモリリークを防ぐためにも、ミュータブル(変更可能)なグローバル変数を減らし、どうしても必要な共有状態は `final` やRiverbloc等の状態管理ライブラリのスコープ管理下に置くのが鉄則です。

—

まとめ

いかがでしたでしょうか? 今回のポイントをギュッと凝縮します。

  • `var` / `final` / `const` の違いを意識し、可能な限り `const` を選んでメモリ効率と安全性を高める。
  • ミュータブルなグローバル変数は依存関係をカオスにするため、極力避ける。
  • アプリ全体で共有したい定数は、インスタンス化できないクラスに `static const` として閉じ込める。
  • 実行時に決まる値は `static final` を活用する。

ここをクリアできれば、あなたの書くDartコードの美しさと堅牢性は一段と跳ね上がります。
明日からのコーディングで、ぜひ意識して使い分けてみてくださいね。それではまた!

タイトルとURLをコピーしました