@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つに統一する方法を学びます。