@nqounet です。
前回は、外部の設定ファイルを読み込む機能を追加しました。
今回は、複数の場所から設定を使う方法を考えます。
今回のゴール メインスクリプトと別のモジュールから、同じ設定を参照できるようにすることです。
ミキさんの新しい悩み ミキさんのアプリが大きくなり、機能を別のモジュールに分割することになりました。
「ログ出力を担当するLoggerモジュールを作ったんだけど、そこからも設定(デバッグモードとか)を参照したいんだよね」
なるほど、それならLoggerモジュールでもConfigを使えばいいですね。
Loggerモジュールを作る デバッグモードの設定を参照して、デバッグログを出力するかどうかを判断するLoggerモジュールを作ります。
1
2
3
4
5
6
7
8
9
10
11
12
13
package Logger {
use Moo ;
use v5 .36 ;
sub debug ($self, $message) {
my $config = Config -> new ();
$config -> load_config ( 'config.ini' );
if ( $config -> get ( 'debug' )) {
say "[DEBUG] $message" ;
}
}
};
debugメソッドの中でConfigをnewして、設定ファイルを読み込んでいます。
メインスクリプトから使ってみる メインスクリプトで設定を変更し、Loggerを使ってみましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
use v5 .36 ;
use Moo ;
package Config {
use Moo ;
has _settings => ( is => 'ro' , default => sub { {} });
sub load_config ($self, $file) {
open my $fh , '<' , $file or die "Cannot open $file: $!" ;
while ( my $line = <$fh> ) {
chomp $line ;
next if $line =~ /^\s*$/ ;
next if $line =~ /^\s*#/ ;
if ( $line =~ /^\s*(\w+)\s*=\s*(.+?)\s*$/ ) {
my ( $key , $value ) = ( $1 , $2 );
$self -> set ( $key , $value );
}
}
close $fh ;
}
sub set ($self, $key, $value) {
$self -> _settings -> { $key } = $value ;
}
sub get ($self, $key) {
return $self -> _settings -> { $key };
}
};
package Logger {
use Moo ;
use v5 .36 ;
sub debug ($self, $message) {
my $config = Config -> new ();
$config -> load_config ( 'config.ini' );
if ( $config -> get ( 'debug' )) {
say "[DEBUG] $message" ;
}
}
};
package main ;
# メインスクリプトで設定を読み込む
my $config = Config -> new ();
$config -> load_config ( 'config.ini' );
say "アプリ名: " . $config -> get ( 'app_name' );
# ここでデバッグモードをOFFに変更
$config -> set ( 'debug' , 0 );
say "デバッグモード(メイン側): " . ( $config -> get ( 'debug' ) ? 'ON' : 'OFF' );
# Loggerを使ってデバッグログを出力
my $logger = Logger -> new ();
$logger -> debug ( "処理を開始します" );
config.iniは前回と同じものを使います。
1
2
3
4
# アプリケーション設定
app_name = MyApp
version = 1.0.0
debug = 1
実行してみよう 1
2
3
アプリ名: MyApp
デバッグモード(メイン側): OFF
[DEBUG] 処理を開始します
あれ?おかしいですね。
メイン側ではデバッグモードをOFFに設定したのに、Loggerはデバッグログを出力しています。
何が起きているのか? 図で見てみましょう。
flowchart TB
subgraph メインスクリプト
A[Config->new] --> B[インスタンス1]
B --> C[debug = 0 に設定]
end
subgraph Loggerモジュール
D[Config->new] --> E[インスタンス2]
E --> F[debug = 1 のまま]
end
B -.->|別々のインスタンス| E
問題は、メインスクリプトとLoggerで、それぞれ別のConfigインスタンスを作っていることです。
メインスクリプトのConfigインスタンス: debug = 0に変更 LoggerのConfigインスタンス: debug = 1のまま(設定ファイルから再読み込み) newするたびに新しいインスタンスが作られるので、設定の変更が他の場所に反映されないのです。
ミキさんの反応 「え、設定を変更したのに反映されないって、これじゃ困る!どうすればいいの?」
確かに、これは困りますね。
ちなみに、今の実装ではdebug()を呼ぶたびに設定ファイルを読み込んでいるため、非常に非効率です。でも、これは次回で解決します。
考えられる解決策 いくつかの方法が考えられます。
方法1: インスタンスを引数で渡す 1
2
my $config = Config -> new ();
my $logger = Logger -> new ( config => $config );
動きますが、すべての場所で$configを渡し続ける必要があり、コードが煩雑になります。
方法2: グローバル変数を使う 1
our $CONFIG = Config -> new ();
シンプルですが、グローバル変数は予期しない副作用を起こしやすく、推奨されません。
方法3: ??? もっと良い方法があるはずです…。
第3回 完成コード 今回は「問題が発覚する」ことがゴールなので、問題のあるコードが完成コードです。
ファイル構成
1
2
3
.
├── app.pl
└── config.ini
app.pl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
use v5 .36 ;
use Moo ;
package Config {
use Moo ;
has _settings => ( is => 'ro' , default => sub { {} });
sub load_config ($self, $file) {
open my $fh , '<' , $file or die "Cannot open $file: $!" ;
while ( my $line = <$fh> ) {
chomp $line ;
next if $line =~ /^\s*$/ ;
next if $line =~ /^\s*#/ ;
if ( $line =~ /^\s*(\w+)\s*=\s*(.+?)\s*$/ ) {
my ( $key , $value ) = ( $1 , $2 );
$self -> set ( $key , $value );
}
}
close $fh ;
}
sub set ($self, $key, $value) {
$self -> _settings -> { $key } = $value ;
}
sub get ($self, $key) {
return $self -> _settings -> { $key };
}
};
package Logger {
use Moo ;
use v5 .36 ;
sub debug ($self, $message) {
my $config = Config -> new ();
$config -> load_config ( 'config.ini' );
if ( $config -> get ( 'debug' )) {
say "[DEBUG] $message" ;
}
}
};
package main ;
my $config = Config -> new ();
$config -> load_config ( 'config.ini' );
say "アプリ名: " . $config -> get ( 'app_name' );
$config -> set ( 'debug' , 0 );
say "デバッグモード(メイン側): " . ( $config -> get ( 'debug' ) ? 'ON' : 'OFF' );
my $logger = Logger -> new ();
$logger -> debug ( "処理を開始します" );
# => デバッグログが出力されてしまう!(期待はOFFなのに)
config.ini
1
2
3
4
# アプリケーション設定
app_name = MyApp
version = 1.0.0
debug = 1
まとめ 複数の場所から設定を使おうとして、問題が発覚した newするたびに新しいインスタンスが作られる設定の変更が他の場所に反映されない インスタンスを渡し続けるのは煩雑 グローバル変数は副作用が心配 次回予告 この問題を解決する方法を考えます。
「インスタンスを1つだけにできれば、どこからでも同じ設定が使えるんじゃない?」
次回は、クラス変数とinstance()メソッドを使って、インスタンスを1つに統一する方法を学びます。