Yamato DaiwaE(CMA)S(cript) extensions

RawObjectDataProcessor : 問題考察

事前に不明で、外部から来るデータの扱いは プログラミングの基本的な課題の一つ。 データの事例は、

  • クライアント・—サーバーモデルに於けるクライアント側からの来るデータ
  • 逆に、クライアント—・サーバーモデルに於けるサーバー側から来るのデータ
  • データベースからの取得したデータ
  • ファイル(JSONYAML等)からの読み取ったデータ

其の様なデータはTypeScriptに届けられない源から来るので、 最初unknownか、更に悪いと anyになっている。

一般的には此の様な状況に於いては何がされるだろうか? 残念なら、高品質のコードだと言えるやり方ではないない。 クライアント—・サーバー間の取引に於いて、クライアント側から受け取るデータの妥当性確認セキュリティ上の必須対策と見做されているので、行われるが、其れ以外の多くの別の場合、例えば下記の様にサーバーから受け取る データを其の儘信用して望む型が付けられてしまう。

「サーバーからデータを取得する時、何の為データをバリデーションする必用が有るのか?」と聞きたいなら、我が逆質問は「サーバーからデータを取得する時、 データをバリデーションする必用が無い理由は何?」。 「サーバーに100%正しいデータしかない」という回答は実戦から遠すぎると思わないだろうか? 実戦から見る、大抵の中規模以上のプロジェクトには期待データと実データの食い違いが起きがち。 特にクライアント側とサーバーが違う言語で実装されたか、別の団体で開発されている場合によく起きる事。 其の食い違いの数は数十から数百、場合に依っては千単位にも成る事が有る。 原因は単純な人為ミスから、データ変更の通知が関係エンジニアに適時届かない事迄様々。 尚、データベースへのデータ保存が複数の方法で行われ、即ちアプリケーションのGUIを介した方法や、データベースマネージャーの利用や、SQLクエリの 実行や、データのインポート等。 上記の方法に依って、データが完全なバリデーションを通っていない事が有る。 此の食い違いを可能な限り早く検出する必用が有る。

更に、ファイルに依る宣言的な設定を持っている(普通はJSONYAML等) docker composeの様なユーティリティを作るなら、 誤った設定は発生しがちな流れなので、此の場合の妥当性確認必須

ライブラリ無しの方法

型ガードTypeScriptネイティブ機能。 型ガードブーリアンを返す関数だが、 戻る値のアノテーションはbooleanではなくx is Tの原形通り。 此処でxは型を検査しなければいけない対象引数で、 Tは望む

公式のTypeScriptドキュメントの他、型ガードはフロントエンドエンジニアの Marius Schulz記事で丁寧に解説されている。 今我々にとって重要な事は、

下記は態と間違っている例。 此れに於いてisUserガード引数Userになっているか、検問するべきはずだが、事実上 Userに関係無いプロパティーを確認している。

potentialUserという引数の値はUser 完全に関係無い のに、isUsertrueを返し、 TypeScript何か可笑しいとは気付かない。 更に下記の例通り、ガード関数本体で何も検査していなくても、 TypeScriptは不満を言わない。

何故事情が其処迄悪いだろうか。 結論から言うと、原因はTypeScript根本的な制約に在る。 上記の型ガードを含めて、データの妥当性確認JavaScriptの実行時に行われ、其の時点元の TypeScriptコード最早存在していない出力されたJavaScriptコードでは型ガード既に 只の真偽値を返すJavaScript関数過ぎない

型エイリアス(typeキーワード)やインターフェースの様な TypeScriptの機能は元のTypeScriptコード内に しか存在せず、出力JavaScriptコードには無いので、 実行時にこれ等に参照する手段も無い理論上は、TypeScriptコードJavaScriptコードへのトランスパイリングの際、 元のTypeScriptコードに有る型エイリアスインターフェースを元に補助関数オブジェクトの自動生成機能を実装すると、毎回態々手動で妥当性確認コードを書かなくても、妥当性確認が行われる。 残念ながら、TypeScript開発チームが近い将来に此の様な機能を実装する可能性は低い

上述の問題に加えて、型ガードには幾つか重要な問題が有る。

  • 型ガード設計上単に引数が妥当かどうかを答える   だけで(然も正解・不正解は実装次第)、具体的に何処で違反が起きているか、そして違反数は報告 されない
  • 型ガード最初の違反でfalseを返すが、 他に違反がいくら有っても当然。

厳密には、上記問題は型ガードの概念に有る問題ではなく実践的なの問題になっている。 上記通り、型ガードの実装でTypeScriptが要求するのは真偽値を返す事 戻り型の特別な注釈という二つだけで、 任意にロギングや全プロパティの完全検問を含めて何でも実装可能。 とはいえ、実際にはそういった事が滅多にされない事であり、其れに十分な理由が有る。 初心者専用の練習問題の話しではなく実際のプロジェクトだと、上記の機能を丁寧に実装 し始めるとボイラープレートコードが急増に膨らんで、所々ほぼ変わらない。 特に高品質のロギングに当たる事であり、同種のメッセージが大量に発生し、毎回丁寧な本文を考えて書くか、メッセージを別オブジェクトやファイルに 切り出し、やがてはライブラリ化を検討する事になる。 尚、プロジェクトの場合、オブジェクトは上の例に出たUserの様に単純ではなく、 2030程度のプロパティを持つ事も普通で、より多くのも珍しくない。 更に入れ子オブジェクト配列(オブジェクト型 要素配列の場合を含めて)も頻出。 ボイラープレートの大量は疲労に依る間違いを伴いがち。 故にネイティブ解決は存在してはいるが、実用的ではない

とはいえ型ガードが無用な訳ではなく多数のプロパティを持つ オブジェクトの検証には良くないだけだ。 他の値の型(文字列数値等)なら適しており、実践に大量に使われる。 YDEE型ガードを提供しており、多くはライブラリ内部でも利用されている。

RawObjectDataProcessor方法論

TypeScriptからJavaScriptへのトランスパイル際に インターフェース型エイリアス(typeキーワード)は 消滅する為、JavaScriptコードの実行時にそれらを参照する方法は全く 無いので、特定のオブジェクトが具体的なである事を 完全に証明するのも不可能。 たとえ専用の型ガードが有ったとしても、其の内部のロジックが必要なプロパティ検査と 全く関係ない迄有り得る。 然し、「だからasを使おう」という結論な訳ではなくasを使う前に、其の使用に論証が必要、即ち妥当性確認 (バリデーション)。 但し保守性の為にバリデータは実際のデータの期待のデータとの不一致を、一件目だけではなくをログしなければいけません。 此処迄対策を取ると、たとえ疲労等に依るミスでバリデーション規則に間違ったとしても、実践上其の間違いが発見させる事が早い場合は殆ど。

此の様にRawObjectDataProcessoras使用の過失を 引き受ける代わりに、妥当なデータの仕様をほぼ宣言的に与えることを要求する。 先述の理論の観点で デモ をもう一度考察しておこう。

  1. 先ずRawObjectDataProcessorexternalDataオブジェクトかどうかを確認する。 オブジェクトになっていなければ、今後検証・処理するものは無い
  2. 次にRawObjectDataProcessorexternalData オブジェクトの中で妥当なデータ仕様validDataSpecificationに挙げた 全てプロパティ(入れ子のオブジェクト及び ECMAScript視点上オブジェクト特別な場合になっている 配列を含めて)を確認する。

    • デモ目的上、例の妥当なデータ仕様ではの確認に加えて追加の制約も指定してある。 たとえばbarプロパティ文字列であるだけでなく、 文字数が5以上でなければならない。
    • 仕様に書いた制約に対してRawObjectDataProcessorは個別の検査を行い、 実データと期待の不一致を見つけても直ぐに検査を止めたりはしない(入力自体がオブジェクトでない場合を除いて)。 代わりに不一致のメッセージを配列に蓄積し、其れはprocessメソッド の戻り値経由で参照出来る。
  3. 検証で実データが定めた制約に一つも違反しなければ、RawObjectDataProcessorが 入力データにasキーワードジェネリック引数で指定された を(上の例ではSampleType)付ける 過失を引き受ける。

RawObjectDataProcessor型ガード(但し概念通り実装されたが、追加機能無し)の 共通点は次の通り。

  1. 生データが期待に合うかを確認する
  2. TypeScript根本的な制約に依り、特定のへの完全一致を 保証不可能

相違点だと、より多く有る。

  1. RawObjectDataProcessorオブジェクト(特に添字配列)に 限定して動作する。 但しプロパティ要素JSONと互換であれば何でも良い。
  2. 戻り値真偽値ではなく多態の オブジェクト。 データが不正かどうかはisRawDataInvalidプロパティが表す。 此れがfalseの時、望むにキャストされたオブジェクトprocessedDataで参照出来、さもなくば validationErrorsMessagesプロパティに 期待データと実データの全不一について致メッセージが入る。
  3. 殆どのAPIは宣言的だが、必要に応じて追加で検証や処理を命令的に定義出来る。
  4. プロパティー要素だけではなく、必要に応じて其の他の制限に満たしているか 検証可能。
  5. 必要な時検証に加えて元のオブジェクトに変更も加えられる。 例えば文字列として保存されている数値JSONと未だ 非互換BigIntに変換する場合や、 プロパティ名を変更する等の場合に非常に役に立っているので、此の機能自体が非推奨なってはいないが、 妥当性確認を壊すか、妥当として指示されたデータを不正にする事も出来るので、利用の際に注意を払わなければいけない。 重要な点としてRawObjectDataProcessorには2つの戦略が有り、 一つ目は元のオブジェクトを操作する(規定)、二つ目は元から新しいオブジェクトを構築する流れ。 元のオブジェクトを変更したい時、特に此の戦略の選択を考えなければいけない。

最後にRawObjectDataProcessorは実データと期待の不一致を説明する高品質なメッセージの 原形を備えている。 此れらは 文書化済み だが、ドキュメント無しでも内容が理解出来る様に書かれている。此のメッセージ原形のソースコードを御自身で確認可能で(ところで、日本語化も有る)、仮に御自身で此の様なメッセージを用意し、複数のプロジェクトに於いて流用を確保するには時間がどれぐらい必要が、見極めてもらいたい。