Skip to content

グローバル変数を使わずにグローバルな設定をする(Dart)

グローバル変数について

プログラミング言語における変数という機能は、私は slot(スロット) と抽象化して呼んでいます。これは、final や const 修飾子 を付けた 定数も変数と呼ぶことは混乱を招くからです。 slot は3種類に分けることができます。

  • 変数(variable)
  • 実行時定数(run time constant)
  • コンパイル時定数(compile time constant)

dart で上記の slot を宣言する際、使われる修飾子はそれぞれ、var, final, const, です。 表題の、「グローバル変数を使わずに」とは、「どこからでもアクセス可能な var で宣言した slot(変数) を使わずに」という意味になります。

実際の code

今実際に書いている log class から一部抜粋します。 グローバルに設定したい内容は、yaml の インデント数、json か yaml どちらの形式で出力するか、などです。

class Log
{
    static late final bool shortHand;
    // immutatble なのかわからない外部 library の class は
    // 適宜 スコープを private(「_」を付ける)にして不用意なアクセスによる
    // 思いがけない変更がされないようにする。
    static late final YAMLWriter _yamlWriter;
    static late final bool isJsonFormat;

    static void setFormat({
        bool shortHand = true,
        int indentSize = 4,
        bool useJsonFormat = false,
    }) {
    // 名前の競合を避けるために 頭に Log と付けています。
    // Log をつけてそろえることで、ぱっと見で、
    // static slot に代入していることがわかりやすくなります。
        Log.isJsonFormat = useJsonFormat;
        Log.shortHand = shortHand;
        Log._yamlWriter = YAMLWriter(indentSize: indentSize, allowUnquotedStrings: true);
    }

    String toYamlString() => 省略;
    String toJsonString() => 省略;

    @override
    String toString() {
        if (isJsonFormat) return toJsonString();
        return toYamlString();
    }

}

static late final

グローバルに設定したい項目を static late final で定義します。 * static late final bool shortHand; * static late final YAMLWriter _yamlWriter; * static late final bool isJsonFormat;

この3つです。 Log instance すべてで共通した値を参照したいので static を使います。 宣言時代入(slot を宣言したと同時に値を代入すること。造語です。)ではなく、後から代入したいので late を使います。 一度代入したら、そこから変更はされないようにしたいので final を使います。

setFormat

static な setFormat method を使って、上記で定義した slot たちに値を設定する処理を行います。今回は Log の形式を決めるので setFormat という名前になっています。

toString

toString の部分で isJsonFormat を参照し、yaml で返すか json で返すかの分岐をしています。

実際に使う

void main() {

    Log.setFormat(
        shortHand: false,
        indentSize: 4,
        useJsonFormat: false,
    );

    final logA = Log(); // Log instance の生成
    final logB = Log();

    print(logA);
    print(logB);

}

処理の開始に Log.setFormat を一度だけ呼ぶ

Log instance を生成する前に Log.setFormat を一度だけ呼ぶ必要があります。 実行時に static late final で定義した slot に値が代入されていないのに、これらを参照する処理が発生すると エラーになります。 なので、参照する前に値を代入する必要があります。 また、一度 setFormat を呼んだ後に、さらにsetFormat を呼び、再代入しようとすると、エラーになります。 * 値が代入される前に参照しようとするとエラー * 再代入しようとするとエラー

なので、処理の一番初めに Log.setFormat を一度だけ呼びましょう。

おわりに

より堅牢な書き方をするなら、static late final で宣言した slot のスコープを private にした方がよさそうです。 複数人での開発や、ライブラリとして公開するといった場合など。

    static late final bool _shortHand;
    static late final YAMLWriter _yamlWriter;
    static late final bool _isJsonFormat;

今回は、自分の一人の開発で、setFormat を呼ぶ以外の方法で代入など、するわけないのでやってませんが。 private にした slot を外部から参照しようと思った場合 別途 getter を定義しなければならないので、その分、手数が増えます。 これらは全部 private にして getter も定義せず Log instance 以外からのアクセスが無い(Log class 以外からの依存が無い)という状態を保証する方が良さげですね。 つまり、この設定を変更しても、直接的に影響が及ぶのは、Log class だけということを保証します。

そういえば、そもそも論として、なぜ、グローバル変数が推奨されないのかというと。 一言でいうなら、人間の脳の認知能力の限界、とでもいえばいいのでしょうか。 人は何か巨大な物事を管理しようとしたとき、それぞれに単位をつけたりして、グループ分けして、小さなものの集合体として、管理します。グローバル変数はその定石をぶち破り、真逆をいく存在だからです。 例えば、バグが発生し、その原因を追究するときに、小さく分けていれば、原因の当てをつけやすくなり、調べる範囲も小さくなります。 グローバル変数が存在し、それによる影響範囲がどこまであるのか定かでない場合、当てがわからず、調べる範囲が全体になってしまったりします。 規模が大きくなればなるほど、悪影響は甚大になります。