動的なモジュールロードで発生する問題とその対策
さて、
(3)
可読性が低くなる──動的にモジュール名を作らない
モジュールロードをする際に文字列連結や正規表現などで動的にロードするモジュール名を作っていると、
また、
可読性が低くならないようにするため、
例外として、
循環する依存関係が作られる──依存関係をチェックする
循環するモジュールの依存関係があると、
しかし、
そうならないように、
静的にモジュールロードしている場合
静的にモジュールロードしている場合は、
- 循環している依存関係があればサブルーチン再定義の警告が出る
- モジュールロードするコードはファイル上部にまとめて書かれていることが多いので、
どのモジュールに依存しているかが視認しやすい Perl::PrereqScanner
など静的解析を利用したツールによって依存しているモジュールの検出がしやすいため、依存関係のチェックがしやすい
動的にモジュールロードしている場合
しかし、
- 動的に依存関係を解決するため、
循環する依存関係があってもサブルーチン再定義の警告が出ない - あらゆる箇所から自由に別モジュールの処理を呼べるため、
どのモジュールがどのモジュールに依存しているかが視認しにくくなり、 混沌とした依存関係になる - 静的解析を利用したツールによって依存しているモジュールの検出ができないか、
できたとしても独自に静的解析する処理を書かないと依存しているモジュールの検出ができないため、 精度の高い依存関係のチェックがしにくい
循環する依存関係を作らないようにするには
循環する依存関係を作らないようにするには、caller
で呼び出しもとを調べて記録、
しかし、
実行までモジュールロードできるかわからない──すべてのモジュールがロード可能かテストする
依存しているモジュールが文法エラーなどでモジュールロードに失敗する状態になっていても、
最近ではIDE
すべてのモジュールがロード可能であることを保証するには、all_
関数でlib
以下のすべてのモジュールがロード可能かをテストします。
保守性が下がる使い方ができる──静的なモジュールロードをする方法で代替できないか検討する
ここまでで、
静的にモジュールロードする場合は逆に柔軟性がない分、
実は動的なモジュールロードでしか実現できないと思われることでも、
モジュール名を短く記述したい場合──定数かaliasedでエイリアスを作る
モジュール名を短く記述してクラスのメソッドを呼び出したい場合、Catalyst
風のコンテナやモジュールローダを使うのではなく、
コンテナやモジュールローダを利用する場合と違ってuse
忘れは防ぐことができませんが、aliased
でモジュール名のエイリアスを作る場合はエイリアスの中身のモジュール名文字列がコンパイルフェーズにインライン展開されるため、
use constant Hoge => 'MyApp::Foo::Hoge';
# 'MyApp::Foo::Hoge'->do_something() と同じ
Hoge->do_something();
use aliased 'MyApp::Bar::Fuga';
# 'MyApp::Bar::Fuga'->do_something() と同じ
Fuga->do_something();
拡張性のあるアーキテクチャを作りたい場合──プラグインを外部で静的にロードし渡す
プラガブルなモジュールなど拡張性のあるアーキテクチャを作る場合、
この実装方法で
package Module;
sub add_plugin {
my ($class, $plugin) = @_;
no strict 'refs';
for my $method ( @{"${plugin}::EXPORT"} ){
*{$class . '::' . $method} =
$plugin->can($method);
}
}
このプラグイン機構でプラグインを使うには、use
し、add_
メソッドにプラグインモジュールのモジュール名をそのまま渡します。
package My::Module;
use parent 'Module';
use Module::Plugin::Hoge;
__PACKAGE__->add_plugin('Module::Plugin::Hoge');
動的にディスパッチさせたい場合──利用するモジュールとの対応を自動生成する
動的にディスパッチしている箇所は、use
しておき、
たとえば、
use MyApp::ItemEffect::Potion;
use MyApp::ItemEffect::Ether;
use MyApp::ItemEffect::Elixir;
sub use {
my ($self, $user) = @_;
state %effect_module_of = (
potion => 'MyApp::ItemEffect::Potion',
ether => 'MyApp::ItemEffect::Ether',
elixir => 'MyApp::ItemEffect::Elixir',
);
my $module = $effect_module_of{ $self->name };
my $effect = $module->new(@_);
$effect->execute($user);
}
アイテム名とアイテム効果モジュールの対応を%effect_
で定義し、%effect_
からアイテム効果モジュールのモジュール名を取得しています。
依存関係を動的に解決したい場合
依存関係を動的に解決したい場合も、
特定の条件でのみモジュールロードをしたい場合はif、Some::Module
をロードする処理は次のように書けます。
use if $] < 5.030000, 'Some::Module';
また、BEGIN
コードブロックや、use
されたときによばれるimport
メソッドに処理を書くことで、BEGIN
コードブロック内でModule::Find
を利用すると、
BEGIN {
use Module::Find qw( usesub );
usesub('MyApp');
}
しかし、BEGIN
コードブロック内やimport
メソッド内で複雑な処理が実行されたり、
まとめ
動的なモジュールロードは実行時にモジュールロードをするため柔軟性があります。そのためフレームワークやプラガブルなアーキテクチャの実装に向いており、
しかし注意して利用しなければ保守性が下がります。動的なモジュールロードをしたくなった場合は、
さて、
本誌最新号をチェック!
WEB+DB PRESS Vol.130
2022年8月24日発売
B5判/
定価1,628円
ISBN978-4-297-13000-8
- 特集1
イミュータブルデータモデルで始める
実践データモデリング
業務の複雑さをシンプルに表現! - 特集2
いまはじめるFlutter
iOS/Android両対応アプリを開発してみよう - 特集3
作って学ぶWeb3
ブロックチェーン、スマートコントラクト、 NFT