Masteries

技術的なことや仕事に関することを書いていきます.

Kichijoji.pmで2回くらい話していました

...が, そういえばブログエントリ書いてなかったので, 発表資料を紹介しておきます.

Kichijoji.pm #28

Kichijoji.pm #29


特に「Kichijoji.pm #28」の「ワーケーションに関する考察」については, 自分らしい(旅行が趣味なエンジニアらしい)発表になったんじゃないかな, と思っていて, ワーケーションに関する知見をうまくまとめられたと思っています. コロナ禍の影響でだいぶ「ワーケーション」という文化が普及してきた感じがあって, 「試してみたい!」という人に届いたらいいなと思います.

Kichijoji.pm, 自分にとっては実家のような場所で, いろいろなネタを持っていっても暖かく受け止めてくれる(?)ので, いい勉強会だなって思います. これからも, Kichijoji.pmを通して技術に限らないエンジニアとしてやっていく上でのいろいろな話題を共有していけたらいいなって思います.

小ネタ: Perlのsplitの第3引数

超〜〜〜小ネタです. Perlにはsplitという関数がありまして, 次のように使えます:

my @list = split(/,/, "a,b,c"); # @list = ('a', 'b', 'c');

第1引数に正規表現を, 第2引数に文字列を渡すと, 第2引数の文字列を第1引数の正規表現にマッチした部分で分割してくれます.

で, 実はsplitには第3引数が渡せるのはご存知ですか? ご存知でしたら, このエントリで語りたいことの本題はそれなので, これ以上読み進める必要はありません(?).

さて, splitに第3引数を渡すと, 次のような挙動になります:

my @list = split(/,/, "a,b,c", 2); # @list = ('a', 'bc');

要するに, 第3引数で渡した数まで分割してくれる, という挙動をします.

なので, 例えば hogehoge.jpg といった文字列を hogehogejpg に分割したい, という時は, split(/\./, 'hogehoge.jpg') と書いてもいいのですが, split(/\./, 'hogehoge.jpg', 2) のように書いてあげると, 「2つに分割したいんだな」というのが明示できてお得かもしれません.

更に, 「分割した結果のうち, 片方しか必要ない」という場合は, 次のようにして受けるのが個人的には好みです:

# $basename = hogehoge となる
# jpg の部分は不要なので, undef で受ける
my ($basename, undef) = split(/\./, 'hogehoge.jpg', 2);

こうしておけば, 「splitで文字列を2つに分割したい」, 「分割した2つのうち, $basenameの部分(分割後の1つ目)だけ必要」ということを伝えられていいんじゃないかな, と思っています.

もっのすごい小ネタですが, まあたまにはこういうネタでもブログ書いてもいいじゃん, ということで...

Class::Accessor::Typed 0.03をリリースしました

Class::Accessor::Typedの0.03をリリースしました. 前回の0.02は2019年のリリースなので約2年ぶりですね...

papix.hatenablog.com

今回の0.03では, optional オプションの追加をしました. 次のように使えます:

use N;

# optional なので, `rw2` は指定しなくてもよい
my $obj = N->new(rw1 => 'RW1');
is $obj->rw1, 'RW1';
is $obj->rw2, undef;

package N;

use Class::Accessor::Typed (
    rw => {
        rw1 => 'Str',
        rw2 => { isa => 'Int', optional => 1 },
    },
);

Smart::Argsのように, optional が真の場合, new するときにそのパラメータを渡さない... ということができます(その場合, undefになります).

metacpan.org

宜しければ使ってみてください.

Class::Accessor::Typedで独自の型を使う

Class::Accessor::LiteやClass::Accessor::Lite::Lazyのように使えて, 型の恩恵を受けられる, Class::Accessor::Typedというモジュールを昔書きました.

metacpan.org

詳しくは, 昔Kichijoji.pmで紹介した時に使った資料をまとめたものがあるので, こちらをご覧頂ければと思います.

papix.hatenablog.com

このモジュールは大変便利なんですが, 1つ課題がありました. それは独自の型をうまくいい感じに使う方法が当時の自分には思い浮かばなかった... ということです.

例えば, 次のような Types.pm があり, ここで「正の整数」であるPositiveIntという型を定義していたとします.

package Types;
use strict;
use warnings;

use Mouse::Util::TypeConstraints;

subtype 'PositiveInt',
    as 'Int',
    where { $_ > 0 },
    message { '0以上の整数でなければなりません' };

Class::Accessor::TypedでこのPositiveIntを使うには, 常にTypesを一緒にuseしないといけません(そうしなければ, Class::Accessor::TypedがPositiveIntの定義を見つけられないからです).

package Pkg;
use strict;
use warnings;

# これがないと, 次のようなエラーが発生する:
# 'baz': Validation failed for 'PositiveInt' with value 1 at
# ...
use Types;

use Class::Accessor::Typed (
    rw => {
        baz => 'PositiveInt',
    },
    new => 1,
);

1;

ちょっと面倒ですね. この辺りで悩んでいて, プロダクト開発に導入するにあたって悩んでいたのですが, ふと思いつきました. それは, 次のようなパッケージを用意して, これを使うようにするというアイデアです:

package MyClassAccessorTyped;

use Types;
use Class::Accessor::Typed;

sub import {
    goto \&Class::Accessor::Typed::import
}

1;

こうすると, MyClassAccessorTyped をuseするだけで事足りるようになります(use Types;しなくても意図通りに動作する).

package Pkg;
use strict;
use warnings;

use MyClassAccessorTyped (
    rw => {
        baz => 'PositiveInt',
    },
    new => 1,
);

1;

本来はClass::Accessor::Typed単体で綺麗に扱えるようにするべきなのでしょうが, 今のところかっこいい解決策が思いついていないので, この手で回避するのがいいのではないか, と思っています. ちょっとダサい(?)感じもしますが, Class::Accessor::LiteやClass::Accessor::Lite::Lazyで型の恩恵を受けられるのはそのダサさ(?)を超えるメリットがある... と思っています. この機会に是非Class::Accessor::Typedをお試し頂ければ幸いです.

また, Class::Accessor::Typedから独自の型をいい感じに使えるようにする問題について, 良いソリューションがありましたら, TwitterやPull Requestでご意見をお寄せ頂けると幸いです. よろしくおねがいします.

小ネタ: Perlのメソッド呼び出しをModule::Spy風にモックする

Perlでテストを書いているとき, 例えば外部のAPIを叩くメソッドをモックしたい, という気持ちになることがあります. 選択肢としてはTest::Mock::GuardやModule::Spy, 最近ならTest2::Tools::Mockあたりが選択肢になるでしょうか.

metacpan.org

metacpan.org

metacpan.org

「単純にモックしたい」という用途なら, 上記に挙げた3つのモジュール全てがその望みを叶えてくます. ただ, 「モックしたメソッドに渡った引数もチェックしたい」のであれば, Module::Spyが最適でしょう.

use DDP;
use Module::Spy;

my $spy = spy_on('Hogehoge', 'hoge_method')->and_returns(undef);

Hogehoge->hoge_method(args1 => 'a', args2 => 'b');

p $spy->calls_most_recent;
# \ [
#     [0] "Hogehoge",
#     [1] "args1",
#     [2] "a",
#     [3] "args2",
#     [4] "b"
# ]

package Hogehoge;

sub hoge_method { ... }

一方で, この時に hoge_method に渡った args1 をテストしたい... という場合は, 次のように書く必要があります.

use DDP;
use Module::Spy;

my $spy = spy_on('Hogehoge', 'hoge_method')->and_returns(undef);

Hogehoge->hoge_method(args1 => 'a', args2 => 'b');

my (undef, %args) = $spy->calls_most_recent->@*;
is $args{args1}, 'a';

Module::Spyは配列リファレンスを返し, かつその最初にはオブジェクト or パッケージ名の文字列が格納されているため, その手当も必要です. 数度ならいいですが, 都度都度こういった手当をするのは少々手間です. というわけで次のような, Module::Spy風のモジュールを使って処理するのはどうでしょうか.

package Test::Mock::Hogehoge;
use strict;
use warnings;
use utf8;

use Exporter 'import';

use Test2::Tools::Mock qw(mock);

our @EXPORT_OK = qw(mock_hogehoge);

sub mock_hogehoge {
    my $guard = Test::Mock::Hogehoge->new();
    $guard->{guard} = mock 'Hogehoge' => (
        override => [
            hoge_method => sub {
                my (undef, %args) = @_;
                $guard->_push_args(\%args);
            },
        ]
    );

    return $guard;
}

sub new {
    my ($class) = @_;

    bless { args => [] }, $class;
}

sub _push_args {
    my ($self, $args) = @_;

    push $self->{args}->@*, $args;
}

sub calls_all {
    my ($self) = @_;
    return $self->{args};
}

sub called {
    my ($self) = @_;
    return scalar $self->calls_all->@* == 0 ? !!0 : !!1;
}

sub calls_most_recent {
    my ($self) = @_;
    return $self->called ? $self->calls_all->[-1] : undef;
}

sub calls_first {
    my ($self) = @_;
    return $self->called ? $self->calls_all->[0] : undef;
}

sub calls_reset {
    my ($self) = @_;
    $self->{args} = [];
}

1;

これなら, 次のように書けて比較的ラクではないでしょうか. インターフェイスや使い方もModule::Spyに準拠しているので, 覚えることを増やさずにテストを簡単に書けて嬉しくなりそうです.

use Test::Mock::Hogehoge qw(mock_hogehoge);
use Test::More;

my $mock = mock_hogehoge;

Hogehoge->hoge_method(args1 => 'a', args2 => 'b');

is $mock->calls_most_recent->{args1}, 'bb';

テストを書くのは大事, とわかりつつ, ダラダラ書くのは大変という気持ちもあると思っていて, 言語を問わずこういう感じで少しでも短く, 簡単に書けるような取り組みをしていくのが大事かな, と思っています. 小粒すぎる技ではありますが, 今後もおもしろいアイデアが浮かんだらブログに書いていこうと思いました.