はじめに
対象読者
- Claris FileMaker Pro ( 以下 FMP と略す ) でグローバルフィールドを扱っている人、もしくは、これから扱おうかなと思っている人
- 他プログラミング言語にあまり触れておらず「グローバル汚染」などと聞いてもピンとこない人
- 以下の姉妹記事から飛んできた人
- 過去の私
要旨
グローバル変数は便利だけれど無闇矢鱈に使うとバグの元になるので、適切に管理してあげると黒歴史を作らずに済むようになります。という記事を書きました(上記、対象読者の項を参照)
そこで、
グローバルフィールドをグローバル変数代わりに使ってはなりません!
と記したところ、コメントにて理由を問われました。
本記事では、上記のテーゼ「グローバルフィールドをグローバル変数代わりに使わない方が良いよね」について、十全な理解が得られやすくなるように、グローバルフィールドそのものを再度見直してみようと思い、書かれたものとなります。
記事内では、以下の三点を順に見ていくこととなります。
- グローバルフィールドの特徴
- その特徴が故の危険性
- 危険性を回避した上で、便利な使い方の例
つまるところ、グローバルフィールドは便利だけれど無闇矢鱈に使うとバグの元になるので、適材適所で用いれば黒歴史を作らずに済むようになります。
前置き
スコープの原則
本記事では、
- スコープは、狭く済ませられるなら狭い方が良い
という原則に従うことを前提とします。(時によりこの原則から逸脱すべき場面も、プログラミングにおいては有り得るということも知った上で、ひとまずは)
グローバル変数とは
グローバルフィールドと比較されることとなるグローバル変数については、以下の記事をご参照ください。
本題
そもそもグローバルフィールドとは
グローバルフィールドの設定方法
フィールド定義における「データの格納」というタブ内にある「グローバル格納」のチェックボックスにチェックを入れることで設定されるフィールドとなります。
グローバルフィールドの公式説明を噛み砕く
公式でなされている説明は上キャプチャ画像の通りですが、分かりやすく噛み砕いた特徴は、以下のようになります。
- グローバル変数以上に広域な、超広域グローバル変数的なものとして取り扱える。
- FileMaker においてローカル変数/グローバル変数は「テキスト」型でしか格納されないが、グローバルフィールドは、以下の通り「フィールド」と全く同じ型で値を格納することができる。
- テキスト
- 数字
- 日付
- 時刻
- タイムスタンプ
- オブジェクト
- (グローバルな計算と集計については闇が深くなるので今回は割愛します……)
また、ここには書かれていない重要な特徴として、次のものがあります。
- ただし変数と異なり、メモリ空間ではなく、ファイルに書き込まれたデータとなる。
これはパフォーマンスに影響するものなので、後に詳しく見ることとします。
超広域ってどれくらいの範囲?
比較するために、以下、スコープの範囲を表にしてみました。(関数内変数とは Let や While などの計算式中における関数内で用いられる局所的な変数です)
関数内変数 | ローカル変数 | グローバル変数 | グローバルフィールド | |
---|---|---|---|---|
単一関数 | ○ | ○ | ○ | ○ |
単一スクリプト | ○ | ○ | ○ | |
単一ファイル | ○ | ○ | ||
FMP クライアント | ○ |
「FMP クライアント」って何です……? ってなりますよね。フツーなると思います。
そこで、以下、パターン別に、この場合はグローバルフィールドへアクセスできるのか? というのも表にしてみました。
FileMaker Server | ファイル | リレーション | 値の取得/上書可能 |
---|---|---|---|
同 | 同 | 組んでいる別テーブル | ○ |
組んでいない別テーブル | ○ | ||
別 | 組んでいる別テーブル | ○ | |
組んでいない別テーブル | ○ | ||
別 | 別 | 組んでいる別テーブル | ○ |
組んでいない別テーブル | ○ |
いわゆる「もう全部あいつ一人でいいんじゃないかな」ってなくらいの広さです。とにかく FMP でファイルを開きさえいれば、どこまででも値を取りに行ったり上書きしたりできます。
なお、別ファイルへのアクセスは「外部データソース」に登録されていることが必須です。が、通常、アクセスしようとしたときに、勝手に追加されてくれるので(良くも悪くも)そこまで自覚的になる必要がないところです。
また、さすがに別クライアントでのグローバルフィールドと競合することはありません。そこまでいってしまったら、もうレコードの実値なので……
グローバルフィールド取り扱いの要注意点
この「超広域」という特徴を、便利と取るか、厄介と取るかは、ケースバイケースとなるでしょう。
ちなみに筆者は 厄介となることの方が多い と考えています。その理由について、述べていきます。
大半のユースケースにおいては、スコープ原則に従い、グローバルフィールドを使う必要がない
まず前置きで述べたスコープの原則からして、
- グローバル変数よりも、グローバルフィールドの方がスコープが広い
ため、 グローバル変数で済ませられるなら、あえてグローバルフィールドを用いるべきではない ということになります。
では、グローバル変数で済ませられる範囲とは何か? というのを考えると、大きく言って以下の二点になるでしょう。
- レイアウト上に表示する(入力なし。表示だけ)
- 現在開いているファイルにおいてメタ的に情報を保持する(前掲記事内の IP アドレス等が一例)
もっと言えば、 通常、スクリプトや計算式によりロジック処理するにあたっては、ローカル変数や関数内変数で十分なもの です。
グローバルフィールドのトリッキーな特徴:ローカルファイルの場合
さらにいうと、トリッキーとしか言いようがない特徴があります。グローバルフィールドの初期値問題です。
関数内/ローカル/グローバル変数においては、当然、初期値というのは、宣言して初めて格納されるものです。しかし、グローバルフィールドは違います。
重要なのでもう一度。 グローバルフィールドは違います。宣言もせずに、初期値が格納され得るもの です。
グローバルフィールドは、メモリではなくファイルに値が格納される、と先述しました。これにより、 ファイルを閉じてもグローバルフィールドの値は保持され続ける のです。
「ファイルを閉じてもグローバルフィールドの値が保持される」……? どういうこと? 何を言っているの?
いえもう、字面通りです。
例えば apps というファイルに persons というテーブルがあり、そこに g_txt というグローバルフィールドがあったとしましょう。そこに「こんにちは」という値を入力した後で apps ファイルを閉じます。
通常、メモリに保存されているグローバル変数であれば、ファイルが閉じられた時点で値は消えます。が、グローバルフィールドの場合は消えません。再度 apps というファイルを開くと persons テーブルの g_txt グローバルフィールドには「こんにちは」という値が格納されたままになっています。
まあそれくらいなら……という方へ、ここからもう一段階アクロバティックな仕様をお伝えします。
グローバルフィールドのトリッキーな特徴:ホストされたファイルの場合
先の例は FileMakerServer ( 以下 FMS ) にホストされていない、ローカルファイルの場合の挙動です。では、ホストされたファイルだと、どうなるのか……?
何と、ホストした時点で格納されている値が、永続保持されます。
重要なことなのでもう一度。**ホストされたファイルにおけるグローバルフィールドの初期値は、そのファイルがホストされた時点で格納されていた値となる。**です。恐ろしくないですか?
「何を言っているんだ……」となるのがフツーの反応だと思います。「当たり前じゃん」って感じられる方は、もう「おそらく何年もの地獄を見てきた者達だ 面構えが違う」のです。
実感が湧かないかもしれないので、画像つきで例示しましょう。先ほどと同じく apps というファイルに persons というテーブルがあり、そこに g_txt というグローバルフィールドがあったとしましょう。そこに「こんばんは」という値が入っていたので、「こんにちは」という値へ上書きした後で apps ファイルを閉じます。
再度 apps というファイルを開くと persons テーブルの g_txt グローバルフィールドには「こんばんは」という値が格納されていました。ホラー。
トリッキーな特徴から得られる教訓
このグローバルフィールドの初期値問題からは、以下の教訓が得られます。
- ローカルファイルでテスト実行していた挙動と、ホストした後のファイルで本番実行した挙動とは、異なり得るものである。
- くれぐれも、グローバルフィールドの初期値を前提としたシステムを組もうとしてはならない。それはあっさりと壊れ得る。
壊れ得る例としては、メンテナンス等の理由でファイルを非ホスト状態にしてローカルファイルとして取り扱った際に、うっかりグローバルフィールドの値が書き換わってしまい、それに気づかずそのまま再度ホストしてしまう……とか。
もしどうしてもグローバルフィールドに初期値を格納したいというのであれば、その処理をおこなうスクリプトを作成しておくのが無難でしょう。
パフォーマンス上の問題
これまで散々、グローバルフィールドはメモリ空間ではなくファイルへ値を書き込む……ということを述べてきました。これにより、パフォーマンスとしても各変数より劣るものとなってしまっています。
検証として、以下の条件の実験をおこなってみます。
- 9,400 文字程度ある『方丈記』全文をテキストとする
- 変数/グローバルフィールド a から 変数/グローバルフィールド b へと書き込んだ後で a, b 両者を Exact 関数で比較して一致していたら変数 result に 1 を格納する、という処理
- 処理を繰り返し 1,000 回おこなう
- 繰り返し中、毎回 b は初期化を行う
- かかった時間を計測・比較する
- 実行環境
- Windows 10 Pro
- FMP Advance 18.0.3
- FMP 19.x 以後でも大して変わらないと思います
ローカル変数の場合
以下のようなスクリプトを実行し、
結果は、
111 ミリ秒 = 0.1 秒 でした。複数回まわしてみましたがこのあたりの値に収束してます。
また、グローバル変数でも同じように実行してみましたが、ローカル変数との差はありませんでした。
グローバルフィールドの場合
グローバルフィールドを用意し、(簡略化のため、慣例的な接頭辞の g_ を省略しています)
以下のようなスクリプトを実行し、
結果は、
2,485 ミリ秒 = 2.5 秒 でした。こちらも複数回まわしてみましたが 2,500 台前半になるなど、およそ 2.5 秒に収束します。
検証結果
ということで、結果を表にしました。
ミリ秒 | |
---|---|
変数 | 0.1 |
グローバルフィールド | 2.5 |
パフォーマンス差 25 倍 ってなかなかですよね。
これだけ、ファイルアクセスを発生させることは負荷となり得るものです。一つ一つだと大したことがなくとも、チリツモの合計値で数秒のラグは容易に発生します。 WEB 界隈で、どれだけ離脱率を下げるためにパフォーマンスチューニングを必死におこなっているかを知れば、 FileMaker においても謙虚な気持ちでパフォーマンス問題に取り組めるようになるのではと思っています。
メモリ上の処理だけで済ませられる変数を頼りにすべき でしょう。
よりみち
『方丈記』全文をテキストとする比較検証の過去記事は、以下のようなものがありますので、よろしければあわせてお読みください。
-
FileMakerのフィールド同士の完全一致比較の速度検証(= vs PatternCount関数)
- コメント:これ Exact 関数でも同じような結果になります。何であえて PatternCount でやったのか、覚えてないけれど……
- FileMakerのフィールド定義「索引>デフォルト言語」設定による検索速度の差異比較検証
- FileMaker-Let関数でフィールド内容を変数に入れておくとどれくらい計算結果が速くなるか?_テキストフィールド編
- FileMaker-Let関数でフィールド内容を変数に入れておくとどれくらい計算結果が速くなるか?_計算フィールド編
また、マージ変数のレンダリング速度を検証する記事も、以下の通り以前に書いていました。
が、見直してみると、ループ処理中に毎回フィールドアクセスをおこなっているので、そりゃ遅くて当たり前だわ、と自己ツッコミを入れてしまいました。そのうち最新の FMP ver. で記事書き直そうと思います。
2023/03/27 時点で書き直せていません。ニーズありますかね……🤔
グローバルフィールドの利用シーン
ここまで長々と、グローバルフィールドを使わない方が良い理由ばかり述べてきてしまったように思います。が、グローバルフィールドでなくては実装できないものというのは、もちろんあります。
喩えるなら、大木を切り倒したいのであればチェーンソーを用意すべきだけれど、魚を捌くのにチェーンソーを持ち出すのは狂気以外のなにものでもない、ということです。道具はそれぞれに相応しい用途があります。
では、グローバルフィールドを用いるべきところというのは?
汎用的な入力欄としての使用
一番に挙げられるのは、ユーザからの任意入力を受け付ける汎用的な入力欄。これに尽きると思います。具体的な活用例として、以下の二つを挙げておきます。
- 一時的な入力画面
- 動的な値一覧/ポータル
それぞれについて、次にもう少し詳しく述べていきます。
一時的な入力画面
例えば users というテーブルが以下のようにあったとして、
このテーブルへレコードを追加するための入力画面を作る、という案件とします。
特に何の工夫もせずに作るとなれば users テーブルを直接参照するレイアウトを作成し、そこに ctrl + n で新規レコードを作って入力していく、という流れになると思います。
ただ、これだと、無闇に新規レコードが作られてしまって空レコードができたり id が飛び飛びになったりしてしまうから、入力を確定したときに初めてレコードが作成されるような仕様にしたい。そう言われたときに活躍するのがグローバルフィールドです。
テーブル名などはデファクトスタンダードがあるわけではないので、分かりやすいものをつければ良いと思いますが、ここでは global というストレートな名前をつけておき、そこに対応するグローバルフィールドを 3 つ作りました。
それに対応するレイアウトを作成し、
レイアウトモードで見てみると、
こんな感じで、作成ボタンには以下のスクリプトを割り当てています。
まず name を必須項目としたいので、空欄の場合は早期終了されます。
次のように入力されていると、
作成するかどうか訊かれるので OK を押して進めると、
このように users 側へレコードが作成されました。
最後にグローバルフィールドに一時的に入力されたテキスト情報は、消去しておきましょう。
入力担当者さんが不慣れだったりする場合にも、実際の users テーブルを触らせることがないつくりにできるので、色々なうっかり事故防止に役立てられます。
また、テーブルのレコード数が増えていくこともないので、レイアウトを開くのも軽量のまま運用できます。
続・一時的な入力画面:応用編
このまま続けて、一時的な入力画面における応用編です。
今は users 側のレイアウトに切り替わったままになっていますが、事務フロー的には、レイアウト切り替えてまた global 入力画面へ戻したいかもしれません。
その場合、スクリプトの末尾に以下を付け加えてあげて、
このようにカスタムダイアログで、戻るかどうかを問うようにしても良いかもしれません。
ただ、毎回毎回、このように訊かれると鬱陶しい、という人もいそうですね。その場合、またグローバルフィールドが役に立ちます。
global テーブルに一つ g_is_direct_return を追加しましょう。
レイアウトにも設置します。ここに 1 を入力してもらうような想定です。(チェックボックスの方が便利そうですが、うっかり触れるミスというのがあるので、私はこうしたフラグについては数字の 1 を入力させるつくりにする派です)
スクリプトにも、追加します。
この状態でスクリプトを実行させると、ダイアログ無しに同じ画面へ戻ってきて、入力情報が空になっていますが、
users を見に行くと、しっかりレコードは作成されていました。
このように、ユーザ任意設定のスイッチングとしてグローバルフィールドを活用してあげるというのも、便利な使い方の一例です。
動的な値一覧/ポータル
ユーザ任意設定、という方向性でのさらなる応用に、動的な値一覧/ポータルがあります。
これは、グローバルフィールドにユーザが入力した値をキーとしたリレーションにより、関連テーブルのレコードを取得してくる、というものになります。
百聞は一見に如かずですね、次の例を。
店舗の売上を動的なポータルで見たい、という案件があったとしましょう。
shops, sales, items という 3 つのテーブルを用意します。
なお、ここで sales テーブルの item_id がテキスト型になってるのは、億劫がって中間テーブルを使わずに改行区切りでのリレーションによって sales ⇔ items の一対多を表現しているからです。これは一般的な RDBMS においてはアンチパタンですので、本番環境での用いるのはオススメしません。
例えば shops を参照する次のようなレイアウトを作っておき、
g_date のフィールドに日付を入力すると、
その日の売上がポータルに表示されます。日付を変えると、
別の日の売上へと切り替わりました。
これは、以下のようにグローバルフィールドを含んだリレーションを組むことで実装できます。
実際にこうして店舗ごとの売上を確認するとしたら、一日だけでなく範囲指定できる実装にしておくべきところですね。今回は簡易版ということで。
さらに、ポータルのフィルタ機能を使って、ここから任意の金額範囲のレコードへと絞り込むこともできます。
フィルタの計算式は次のような感じで、ポイントは上限が空欄の場合のデフォルト値を想定の最大値にしておく必要があります。
Let (
[
amount = shops⇔sales◎g_date::amount;
under = shops::g_amount_under;
under = If (
IsEmpty ( under );
0;
under
);
top = shops::g_amount_top;
top = If (
IsEmpty ( top );
99999999;
top
)
];
under ≤ amount and
top ≥ amount
)
ここで、ポータルフィルタが確実にレンダリングされるよう、金額範囲の数値を入力した後に、画面のリロードが走るようスクリプトトリガを噛ませましょう。
リロードスクリプトは単純一行のもので OK です。
これで、指定範囲の金額のもののみを抽出することができました。(ただし、ポータルのフィルタ機能は、リレーションで取得するよりもレイアウト負荷の大きいものとなりますので、実運用で多用するのは非推奨です)
おおむね以上となります。
このあと、もうちょっと応用編で値一覧に突っ込んでいこうと思いましたが、さすがに長くなりすぎて力尽きてきたので、このあたりで切り上げたいと思います。
あまり実用性のない使い方の例だけ示しておきますと、
こういう条件で値一覧を作っておいた上で、
こういう計算フィールドを用意しておくと、以下のような感じに、その日に売上のあった item_id を改行区切りで返すことができたりします。
値一覧の良いところは、手軽に重複排除できるのが便利ですね。
まあ、グローバルフィールドを用いた動的値一覧の使い道はもっと他にありますので、またの機会に……
おわりに
ここまで長らくお読みいただいた方には、冒頭に挙げました、
- グローバルフィールドの特徴
- その特徴が故の危険性
- 危険性を回避した上で、便利な使い方の例
という三点について、ご理解いただけたのではないかと思います。
繰り返しになりますが、 大半のユースケースにおいては、スコープ原則に従い、グローバルフィールドを使う必要がありません。
グローバルフィールドを用いるのは、 ユーザの任意な入力に応じて、挙動の変化を生じさせたいときのみに留めておく のが、一般的には無難な使い所であると考えます。
もちろん、危険を承知した上であればこの限りでないかもしれませんし、こういう便利な使い方だってある、というご意見もあることと思います。何かありましたら、コメントや他記事でご意見いただけると嬉しいです。
余談
グローバルフィールドも、グローバル変数と同じように、値セットを個別スクリプトでおこなった方が良いのだろうか? という疑問を持たれた方もいらっしゃるかもしれません。
が、私は、 そこまでするのはファットな実装になるからやる必要はないだろう という考えです。これには、そもそもが、 グローバルフィールドに変数的な振る舞いをさせるべきでない = グローバルフィールドへの値の格納はユーザの任意入力によるというユースケースに留めるべき という考えが根底にあるからです。
ただ、一時入力画面の例で見せたように、一括でグローバルフィールドの値を空にする処理が必要になることはありますので、こうした際には汎用的なスクリプトとして用意しておいても良いと思います。