Featured image of post 第2回:コマンドを実行したい【PerlでSlackボット指令センターを作る】

第2回:コマンドを実行したい【PerlでSlackボット指令センターを作る】

前回はSlackからのWebhookを受け取るだけの「オウム返しボット」を作りました。 第2回となる今回は、受け取ったメッセージを「コマンド」として解釈し、実際の処理を実行させる部分を作っていきます。

まだデザインパターンは使いません。まずは直感的な実装を行い、そこから生まれる課題を共有しましょう。

コマンド解析

コマンドフォーマットを決める

ボットに対する命令は、CLI(コマンドラインインターフェース)のようにスペース区切りで引数を渡す形式が一般的です。

1
2
3
/deploy production
/restart web01
/status

先頭の / はSlackの「スラッシュコマンド」機能もありますが、今回は通常のメッセージとして「メンション + コマンド」または「ボットへのDM」でこの形式を受け取ることを想定します。

正規表現で解析する(ナイーブな実装)

一番手っ取り早いのは正規表現です。メッセージ本文を受け取り、先頭のキーワードで処理を分岐させます。

 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
sub handle_message {
    my ($text) = @_;

    # 不要な空白除去
    $text =~ s/^\s+|\s+$//g;

    if ($text =~ m{^/deploy\s+(\w+)}) {
        my $target = $1;
        cmd_deploy($target);
    }
    elsif ($text =~ m{^/restart\s+(\w+)}) {
        my $server = $1;
        cmd_restart($server);
    }
    elsif ($text eq '/status') {
        cmd_status();
    }
    else {
        return "不明なコマンドです: $text";
    }
}

sub cmd_deploy {
    my $target = shift;
    # 実際はここでデプロイスクリプトを叩く
    return "🚀 $target 環境へのデプロイを開始しました...";
}

sub cmd_restart {
    my $server = shift;
    return "🔄 サーバー $server を再起動しています...";
}

sub cmd_status {
    return "✅ 現在のシステム稼働状況: オールグリーン";
}

これで、/deploy production と打てばデプロイ処理が走り、/status と打てば状況が返ってくるようになりました。シンプルですね。

引数の検証

実際の運用では、引数のチェックも必要です。例えば production 以外への本番デプロイは防ぎたいですし、存在しないサーバー名の指定もエラーにすべきです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
sub cmd_deploy {
    my $target = shift;
    
    my @allowed_envs = qw(production staging development);
    unless (grep { $_ eq $target } @allowed_envs) {
        return "⚠️ エラー: 指定可能な環境は @allowed_envs のみです。";
    }

    # ...
}

このように、コマンドごとのバリデーションロジックも追加していくことになります。

早くも漂う「コードスメル」

さて、この実装を見てどう思いましたか? 「まあ、わかりやすいし、いいんじゃない?」と思ったかもしれません。確かに3〜4個のコマンドならこれで十分です。

しかし、もしコマンドが50個あったら? 各コマンドに複雑なオプション(--force とか --dry-run)が必要になったら? 権限チェック(production は管理者のみ)が必要になったら?

handle_message 関数は巨大な if-elsif-else の塊になり、視認性は最悪になります。新しいコマンドを追加するたびにこの巨大な関数を編集しなければならず、うっかり別の行を壊してしまうリスクも高まります。

これはまさに 「Open/Closed Principle(開放閉鎖の原則)」への違反 です。 本来、機能追加(Open)は容易であるべきで、既存コードの修正(Modification)は最小限(Closed)であるべきです。

次回は、この「if文地獄」が具体的にどのように破綻していくのか、もう少し複雑な要件を追加してシミュレーションしてみます。そして、そこからデザインパターンの光を見つけ出しましょう。

今回の完成コード

 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
# simple_bot.pl
use strict;
use warnings;
use utf8;
use feature qw(say);

# コマンド実行シミュレーション(コンソールで動作確認)
while (my $line = <STDIN>) {
    chomp $line;
    say handle_message($line);
}

sub handle_message {
    my ($text) = @_;
    $text =~ s/^\s+|\s+$//g;

    if ($text =~ m{^/deploy\s+(\w+)}) {
        my $target = $1;
        return cmd_deploy($target);
    }
    elsif ($text =~ m{^/restart\s+(\w+)}) {
        my $server = $1;
        return cmd_restart($server);
    }
    elsif ($text eq '/status') {
        return cmd_status();
    }
    else {
        return "不明なコマンドです: $text";
    }
}

sub cmd_deploy {
    my $target = shift;
    my @allowed_envs = qw(production staging development);
    unless (grep { $_ eq $target } @allowed_envs) {
        return "⚠️ エラー: 指定可能な環境は @allowed_envs のみです。";
    }
    return "🚀 $target 環境へのデプロイを開始しました...";
}

sub cmd_restart {
    my $server = shift;
    return "🔄 サーバー $server を再起動しています...";
}

sub cmd_status {
    return "✅ 現在のシステム稼働状況: オールグリーン";
}
comments powered by Disqus
Hugo で構築されています。
テーマ StackJimmy によって設計されています。