(1)はこちら、(2)はこちらから。
動的な型制約の応用例
前節までで、Perlで動的な型制約を導入する方法の基本は紹介し終えました。本節では、動的な型制約をアプリケーションで応用した例を紹介します。
アプリケーションの仕様を型として定義する
アプリケーションには、満たすべき仕様があります。たとえばユーザー名は3文字以上100文字以内である必要がある、ブログの状態は公開か非公開のどちらかであるなどです。Webアプリケーションとしてユーザーから入力を受け付けるときも、開発者がコマンドラインを使いデータを入れるときも、どんなときでも仕様を満たさない不整合なデータを作りたくありません。そのためには、たとえばデータベースレベルで制限をかけるなど、さまざまな方法が考えられます。ただ、簡単な仕様なら、これまで紹介した動的な型制約を利用して実現できます。
リスト3は、ブログアプリケーションの仕様を型として定義し、制約をかけることでチェックしている例です。(1)でブログのタイトルは3文字以上100文字以内という仕様を型として定義しています。(2)はブログの状態は公開を表すpublic
か非公開を表すprivate
のどちらかであると定義しています。(3)はブログデータを更新するメソッドで、定義したBlog::TitleやBlog::Statusを利用して引数に制約をかけています。これで、update_blog
に変なデータを渡したとしても、仕様を満たさないデータが作られることはありません。
この例のように、定義した型とSmart::Argsを使ってロジックの関数の引数に型制約をかけておけば、単純な仕様であれば引数を間違えて不整合なデータを作ってしまうことを防げます。もちろんユーザーの入力を受け付ける場合、そのまま関数に渡すとユーザーにとって意味のないSmart::Argsのエラーが表示されるため、ユーザーを混乱させてしまいます。そのため使いやすさの観点からは、関数に渡す前にユーザーの入力が正しいかどうかをチェックし、誤っていた場合は適切なフィードバックを返すということを別に行う必要があります。
型制約でユーザーの入力を制限する
先ほど、使いやすさの観点からは、関数に渡す前にユーザーの入力が正しいかどうかをチェックし、誤っていた場合は適切なフィードバックを返すということを別に行う必要があると説明しました。もし返すべきフィードバックが単純なものであれば、型制約を用いてチェックする方法もあります。ユーザーから受け取った入力が型制約を満たすかをチェックし、満たさない場合にフィードバックを返すという方法です。
型制約を満たすかどうかを真偽値で取得できれば、このようなチェックを行えるはずです。この方法を実現するには、Mouse::Util::TypeConstraintsを利用します。Mouse::Util::TypeConstraintsにはfind_type_constraint
という型を取得するユーティリティがあり、これを用いると型のオブジェクトを取得できます。さらにそのオブジェクトからcheckメソッドを呼ぶと、型制約を満たすかを真偽値で返してくれます。
次のコードは、前項で定義したBlog::Title
型とfind_type_constraint
を利用して、ユーザーの入力をチェックする例です。
(1)のように呼び出せば、$input
がBlog::Title
型を満たすなら真が、満たさないなら偽が$is_valid
に代入されます。偽の場合にユーザーにフィードバックを返せば、最低限のユーザー入力のチェックを行えます。
型制約で不正なリクエストパラメータを制限する
Webアプリケーションを作っていると、悪意のあるユーザーが不正なリクエストパラメータを送ってくる場合があります。不正なものをそのまま受け入れるとアプリケーションに不具合を起こすこともあるため、チェックが必要です。
たとえばブログの状態を公開(public
)にするか、非公開(private
)にするかをユーザーがWeb上で選べ、それがHTMLのセレクトボックスで実装されているとします。
このとき、普通に利用しているユーザーからは、blog_status
としてpublic
かprivate
のどちらかしか送られません。しかし悪意のあるユーザーの場合、別の文字列を送ってくる可能性があります。
このような悪意のあるパラメータをいちいちチェックするのは大変です。チェックをする方法はいろいろありますが、型制約を応用して簡単にチェックすることもできます。
リスト4は、HTTPリクエストを処理するモジュールのPlack::Requestを継承し、typed_param
というメソッドを定義したものです。typed_param
メソッドは、パラメータ名と型名を渡せば、型制約を満たすならその値を、そうでなければundef
を返します。
このtyped_param
とリスト3で定義したBlog::Status
型を利用すれば、普通のユーザーの利用なら正しくpublicかprivateの値を受け取り、悪意のあるユーザーがevilのような値を送ってきたらundef
を受け取れます。undef
が返ってきている場合は不正なリクエストであると判断できるため、エラー処理を単純化できます。typed_param
メソッドの利用例は次のとおりです。
(1)のように、受け取りたいリクエストパラメータ名blog_status
を第1引数に、型Blog::Status
を第2引数に渡します。Blog::Status
型がpublic
かprivate
しか受け付けないため、悪意のあるユーザーがevil
という値を送信してきたら、変数$status
にはundef
が代入されます。undef
の場合にBad Request
を返すなどの処理をすれば、不正なパラメータを防げます。
動的な型制約を導入してみて
筆者は、所属するはてなの最近のプロジェクトで今回紹介した動的な型制約を導入し、半年間ほど運用しました。運用してみると、だんだん導入のメリットやデメリットが見えてきました。そこで本節では、筆者が感じたメリットやデメリットを紹介します。
メリット
動的な型制約の導入によって感じたメリットは以下の3つです。
関数の使い方の間違いで不整合が起こらない
最初に紹介したとおり、関数を間違って使っても、それだけではデータの不整合が起こらないことは大きなメリットです。
長くアプリケーションを運用していると、関数を定義する人と関数を呼び出す人は別であることも多くなります。自分が定義した関数でも、長く触っていない部分であれば、どんな関数であったか忘れてしまいます。結果として、呼び出し方を間違えることは頻繁に起こります。
引数に型制約をかければ、呼び出しの間違いで関数が中途半端に実行されることはなく、呼び出し間違いによるデータの不整合は起こらなくなりました。
関数に何を渡せばよいかが明確になる
関数の引数に何を渡せばよいかわかりやすくなるメリットも感じました。
動的な型制約を導入していない状態では、引数にどんなオブジェクトを渡すのか、どのパラメータが必須なのかをPerlのシンタックスから判断できません。そのため引数の変数名か、引数に何を渡すか書かれたコメントから判断するしかありませんでした。
Smart::Argsで型制約をかけておけば、Smart::Argsのシンタックスによって渡すべき型が明示され、何を渡せばよいかすぐに判断できるようになりました。
誰が書いても厳しくチェックされる
Smart::Argsを導入する前も、必須の引数がない場合は例外を出し、関数が中途半端に実行されないようにする対策は行っていました。しかし、このやり方は人によってどこまでチェックするかがまちまちで、場合によってはまったくチェックしていないこともありました。
Smart::Argsは、簡単な記法で引数の型が正しいことのチェックを行えます。そのため、プロジェクトメンバーの誰もが型制約を書くようになり、関数の呼び出し間違いのチェックが必ず行われるようになりました。
デメリット
型制約を導入するデメリットはほとんどありませんでした。1つだけ挙げるとすると、呼び出しの回数が多い場合に遅くなることです。
呼び出しの回数が多いと遅くなる
静的言語での型チェックはコンパイル時に行われ、型チェックで実行が遅くなることはありません。しかし動的な型チェックはプログラムの実行時に行われるため、関数を実行するたびにチェックも実行されます。
Smart::ArgsやMouseの型制約のしくみは、実行時に動作することに配慮して高速に動作します。それでも数万回も呼び出すと、体感できるほどに実行が遅くなります。
ベンチマークのコードはリスト5で、実行結果は以下です。
チェックをしていないno_args_func
は変数への代入と加算しかしていないので当然速く、秒間333万回も実行できます。しかしargs_func
のようにNum
型だけを使ったチェックを入れるだけで、だいたい秒間8万回ほどしか実行できなくなります。
秒間数万回実行できるのは十分速いですが、非常に多く呼ばれる関数への型制約の導入は慎重に検討したほうがよいでしょう。
まとめ
本稿では、Perlで動的な型制約を導入する動機や、Smart::Argsを使って型制約を導入する方法、型制約の応用など、Perlの動的な型制約について紹介しました。Smart::Argsの導入は簡単ですし、少しの手間でアプリケーションの堅牢性を高めることができるので、ぜひ使ってみてください。
さて、次回の執筆者は岡林大さんで、テーマはPlack::Middlewareです。お楽しみに。
- 特集1
イミュータブルデータモデルで始める
実践データモデリング
業務の複雑さをシンプルに表現!
- 特集2
いまはじめるFlutter
iOS/Android両対応アプリを開発してみよう
- 特集3
作って学ぶWeb3
ブロックチェーン、スマートコントラクト、NFT