Masteries

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

他チームの振り返りを支援するという行い

このエントリは, 「はてなエンジニア Advent Calender 2020」の16日目のエントリです.

qiita.com

昨日の担当は, id:astj さんでした.

blog.astj.space

他チームの振り返りを支援するという行い

はてな社内には「すくすく開発会」という有志にのチームがあります. 「社内の炎上プロジェクトをゼロにする」を合言葉に, ソフトウェア開発におけるマネジメントを中心とした様々な領域について, 知見の共有をしています.

また, 参加メンバー同士で知見を共有するだけでなく, 実際に開発チームの手助けをするという活動も行っています. その中の1つが「振り返りのファシリテーションを手伝う」というもので, 自分も何度か他チームの振り返りを支援させてもらったことがあります.

「振り返り」は, これまでの出来事を振り返り, 次に活かすための大事なイベントです. そういうイベントのファシリテーションをするというのは, 大変に責任を感じますし, 正直難しいです.

そういうとき, 自分は「まあ, チーム外の自分がファシリテーションするだけで, だいぶメリットは提供できてるということで...」と, 半ば開き直るようにしています.

「振り返りの参加者」と「ファシリテーター」が同一人物であるときの弊害

前述のように, 「振り返り」という場でファシリテーションをするのは難しいです. そのため, 逆にチーム外からファシリテーターを呼ばないと, チーム内の誰かが「振り返りをしながら, ファシリテーションもする」という事態に陥ります. そうなると, 「振り返り」と「ファシリテーション」のどちらかが疎かになってしまう可能性があります.

チーム内で振り返りを実施するとき, チーム歴の長い人や, リーダー的な役割の人がファシリテーターを務める事が多いのではないでしょうか. もし, その人がファシリテーターとしての役割に意識を使いすぎて振り返りに集中できなくなると, 振り返りの中でその人の意見や経験が露見せず, 一切が失われてしまい, 次に繋げることが出来なくなってしまいます.

また一方で, 振り返りに意識を使いすぎると, 当然ファシリテーションが疎かになります. 自分自身うまく出来ているとは思っていないのですが, ファシリテーターは「振り返り」という場の中で, そのゴールを達成するために, 参加者の様子を観察したり, それを踏まえて式次第などを柔軟に組み立てる必要があります.

例えば, 最近読んだ「SCRUMMASTER THE BOOK」には, ファシリテーターの態度や振る舞いを次のように記しています:

聞き上手で、全員の声を聞き、ポジティブさを高め、柔軟性があり、直感を使いますが、1つのアイデアに執着しすぎません。ファシリテーターは場の熱量を把握し、それに応じてフォーマットを調整する必要があります。

...正直, 「振り返り」に参加しながらファシリテーションもする, というのは, よほど経験豊富でない限り難しいと思います.

さらに言えば, 「振り返りの参加者」と「ファシリテーター」という役割を切り替えるのが大変難しい, という話題もあります. その発言は「振り返りの参加者」としてのものなのか? 「ファシリテーター」としてのものなのか? ...これを常に明白にしないと, 2つの立場を利用して, 振り返り会の方向を捻じ曲げてしまう可能性もあるでしょう.

まとめ

...というわけで, 自分たちのチームが(特に大きな施策が完了した時の)振り返り会を実施する時は, なるべく他チームのメンバーにファシリテーションをお願いしています. また, 逆にファシリテーターをお願いした時に(当然, その人やその人が所属するチームの作業時間を奪う事になるので)「いいですよ!」と快く言ってもらえるように, 逆に他チームが振り返り会を実施する時には, 依頼があって余裕や元気があれば, ファシリテーターを引き受けるようにしています.

単純に, ファシリテーターとしての経験を積めるのもありがたいですし, 他チームの様子や工夫を振り返りを通じて知ることができる, というのもファシリテーターの役得なのかなと思います. 逆に, (振り返りの流れや方向性を誘導しない範囲で)自分が知っている知見を, 振り返り会の中で披露するという事もあります.

もし, 日々の定期的な振り返り会はともかくとして, 数ヶ月に渡る施策が終わった時の振り返り会も, ファシリテーター含めてチームで完結している... という状況にあるのなら, 一度他チームの人(その施策に関わりが薄かった人)に, ファシリテーターをお願いしてみてはいかがでしょう. きっと, より多くのものを次に活かすことが出来るようになると思います.


明日の担当は id:YaaMaa さんです. よろしくおねがいします.

PerlでスナップショットテストをするTest::Snapshotのご紹介

このエントリは, 「Perl Advent Calendar 2020」の9日目の記事です.

qiita.com

昨日のエントリは, id:xtetsuji さんの「xargs や find と合わせて使う・代わりに使う Perl」でした.

qiita.com


実は最近異動をしていた id:papix です. 異動後もPerlをモリモリ書いている日々ですが, 移動先のチームのプロダクトで同僚の id:mizdra が導入していた Test::Snapshot が便利だったので紹介します.

metacpan.org

Test::Snapshot

Test::Snapshotは, その名の通り「スナップショットテスト」を提供するモジュールです. スナップショットテストとは, 予め「スナップショット」と呼ばれる期待値を生成しておき, テストを実行する際には実行結果とスナップショットを比較してテストをする手法です.

使い方

まず初めに, Test::Snapshotを利用したテストの実例を見てみましょう:

use Test2::V0;
use Test::Snapshot;

sub method {
    return {
        a => 1,
        b => 2,
        c => [3, 4, 5],
    };
};

is_deeply_snapshot method(), 'hashref';

done_testing;

これは, method() の返り値をスナップショットテストで検証するコードになります. なおここでは, 単純にするためにテストスクリプトに直接テスト対象となる実装(method)を書いています.

このコードを, snapshot.t という名前で保存し, prove で実行してみます.

$ prove -v snapshot.t
snapshot.t ..
# Seeded srand with seed '20201209' from local date.
# No snapshot filename '/path/to/dir/snapshots/snapshot_t/hashref' found
not ok 1 - hashref

#   Failed test 'hashref'
#   at snapshot.t line 12.
# @@ -1 +1,9 @@
# -undef
# +{
# +  'a' => 1,
# +  'b' => 2,
# +  'c' => [
# +    3,
# +    4,
# +    5
# +  ]
# +}
1..1
# Looks like you failed 1 test of 1.
Dubious, test returned 1 (wstat 256, 0x100)
Failed 1/1 subtests

Test Summary Report
-------------------
snapshot.t (Wstat: 256 Tests: 1 Failed: 1)
  Failed test:  1
  Non-zero exit status: 1
Files=1, Tests=1,  0 wallclock secs ( 0.02 usr  0.00 sys +  0.07 cusr  0.02 csys =  0.11 CPU)
Result: FAIL

まだスナップショットを生成していないため, 当然ですがテストは失敗しますね.

# -undef
# +{
# +  'a' => 1,
# +  'b' => 2,
# +  'c' => [
# +    3,
# +    4,
# +    5
# +  ]
# +}

Test::Snapshotの便利なところとして, このようにスナップショットと実行結果の差をいい感じにダンプしてくれる機能があります. ここでは, スナップショットがまだ存在しないため undef となっていることがわかります.

スナップショットの生成

さて, 続いてスナップショットを生成してみましょう. スナップショットを生成するには, TEST_SNAPSHOT_UPDATE という環境変数に真値をセットした上でテストを実行すればよいです.

$ TEST_SNAPSHOT_UPDATE=1 prove -v snapshot.t
snapshot.t ..
# Seeded srand with seed '20201209' from local date.
# No snapshot filename '/path/to/dir/snapshots/snapshot_t/hashref' found
not ok 1 - hashref

#   Failed test 'hashref'
#   at snapshot.t line 12.
# @@ -1 +1,12 @@
# -undef
# +{
# +  'a' => 1,
# +  'b' => 2,
# +  'c' => [
# +    3,
# +    4,
# +    5,
# +    {
# +      'd' => 10
# +    }
# +  ]
# +}
1..1
# Looks like you failed 1 test of 1.
Dubious, test returned 1 (wstat 256, 0x100)
Failed 1/1 subtests

Test Summary Report
-------------------
snapshot.t (Wstat: 256 Tests: 1 Failed: 1)
  Failed test:  1
  Non-zero exit status: 1
Files=1, Tests=1,  0 wallclock secs ( 0.02 usr  0.01 sys +  0.08 cusr  0.02 csys =  0.13 CPU)
Result: FAIL

実行すると, テストは失敗しますが, snapshot.t があるディレクトリに snapshots というディレクトリが設置されます.

$ tree
.
├── snapshot.t
└── snapshots
    └── snapshot_t
        └── hashref

2 directories, 2 files

tree するとこんな感じ. shapshots ディレクトリの中に, テストファイルに基づいたディレクトリ(テストがsnapshot.tなので, snapshot_t)が設置され, 更にその中にスナップショットの実態であるhashref というファイルが設置されます. ちなみにこのファイル名は, is_deeply_snapshot の第2引数(description)から生成されます.

注意点として, ファイル名はdescriptionに含まれる文字から, aからzまでのアルファベット(大文字小文字問わず)と0から9までの数字, そして-以外を全てまとめて_に置換したものになります.

そのため, 次のようなテストを書いてしまうと, method()other_method()のスナップショットがどちらも_というファイルに書き込まれてしまいます(!?).

is_deeply_snapshot method(), 'ひとつめのテストです';
is_deeply_snapshot other_method(), 'ふたつめのテストです';

スナップショット生成後のテスト

さて, スナップショットが生成できたので, 改めてテストを実行してみましょう.

$ prove -v snapshot.t
snapshot.t ..
# Seeded srand with seed '20201209' from local date.
ok 1 - hashref
1..1
ok
All tests successful.
Files=1, Tests=1,  0 wallclock secs ( 0.02 usr  0.00 sys +  0.08 cusr  0.02 csys =  0.12 CPU)
Result: PASS

method()の実行結果とスナップショットは等しいので, 当然テストは成功します.

ここで改めて, methodの実装を変更してテストを実行してみましょう.

use Test2::V0;
use Test::Snapshot;

sub method {
    return {
        a => 1,
        b => 2,
        c => [3, 4, 5, { d => 10 }],
    };
};

is_deeply_snapshot method(), 'hashref';

done_testing;

意図したようにテストは失敗し, その差分を表示してくれます.

$ prove -v snapshot.t
snapshot.t ..
# Seeded srand with seed '20201209' from local date.
not ok 1 - hashref

#   Failed test 'hashref'
#   at snapshot.t line 12.
# @@ -4,6 +4,9 @@
#    'c' => [
#      3,
#      4,
# -    5
# +    5,
# +    {
# +      'd' => 10
# +    }
#    ]
#  }
1..1
# Looks like you failed 1 test of 1.
Dubious, test returned 1 (wstat 256, 0x100)
Failed 1/1 subtests

Test Summary Report
-------------------
snapshot.t (Wstat: 256 Tests: 1 Failed: 1)
  Failed test:  1
  Non-zero exit status: 1
Files=1, Tests=1,  1 wallclock secs ( 0.02 usr  0.00 sys +  0.08 cusr  0.02 csys =  0.12 CPU)
Result: FAIL

まとめ

さくっとではありますが, Perlでスナップショットテストを実現する, Test::Snapshotを紹介しました. スナップショットを配置するディレクトリやファイル名に若干クセがある(変更はできなさそうでした)ものの, シンプルで便利なモジュールでした. 依存モジュールがかなり少なく, 基本的な(よく使う)モジュールが多いところも嬉しいです.

明日の担当は, @Taroupho さんです. よろしくおねがいします.

間接オブジェクト記法とPerl 7 (追記あり)

コードレビューする時に軽く調べたので, 備忘録として軽くまとめておきます.

2020年11月29日追記: 当初はPerl 7で間接オブジェクト記法は非推奨になる予定でしたが, 状況が変わって非推奨にしない方針となったようです.

github.com

@argrath さん情報ありがとうございました!


間接オブジェクト記法とは, こういう記法です:

my $obj = new Object;

これは, 以下のコードと同じです:

my $obj = Object->new();

「初めてみた!」, 「使ったことない!」という方もいるかもしれませんが, 実は標準エラー出力をする際の,

print STDERR 'foobar';

も間接オブジェクト記法で, 以下のコードと同じだったりします:

STDERR->print('foobar');

さて, この間接オブジェクト記法ですが, 下記エントリによるとPerl 7においては非推奨になりそうです.

www.perl.com

Perl 5.32.0以降では, no feature qw(indirect);とすることで間接オブジェクト記法の利用を制限することができ, これはPerl7においてデフォルトで有効になるようです.

Perl 7のリリース後も, Perl 5は引き続きメンテナンスされ続けます. そのため, 直ちに間接オブジェクト記法を撤廃する必要はなさそうですが, とはいえ将来的にPerl 7への以降を見据えているのであれば, 早い段階から間接オブジェクト記法を使わないようにしておく必要がありそうです.

※ 以下 2020年11月29日追記

...が, 後にPerl 7において間接オブジェクト記法の非推奨化は見送る, という方針になったようです.

github.com

理由として,

Initial testing shows a high amount of breakage on CPAN.

と書かれており, 要するに実際に非推奨にして試してみたところ, たくさんのCPANモジュールが動作しなくなったため, 断念した... ようです.

何れにせよ, Perl 7において非推奨にしたい! という声が挙がった程度には微妙な記法であることには間違いと言えるのではないでしょうか. なので, (print STDERR ...のような例を除いて)今後間接オブジェクト記法を利用するケースはないと思ってよい/既に使っている場合はなるべく使わないように書き換えていった方が良い... という温度感で良いのではないか, と思っています.

あわせてよみたい (参考文献)

kfly8.hatenablog.com

余談

Perl 5.32.0で no feature qw(indirect); とすると,

my $obj = new Object;

というコードは次のようなエラーになります:

Bareword found where operator expected at indirect.pl line x, near "new Object"
        (Do you need to predeclare new?)

一方で, 以下のコードは no feature qw(indirect); とされていても問題なく動作します:

print STDERR 'foobar';

恐らくですが, こういった形での間接オブジェクト記法の利用はかなり頻繁に使われているので, 一律で利用禁止にする訳ではなく, 例外(?)を設けているのではないかと思います.

ISUCON10の予選に参加しました(そして無事予選敗退しました)

「ISUCON参加したことないんで参加してみたいんすよね〜」という友人と一緒に, 「イスイスユカイ」で出場しました. Go実装で結果は1695点, 無事に予選落ち. 例年, ISUCONに出る時はだいたい昼過ぎくらいに何もわからなくなって, 終盤は「もうだめぽ...」となり, 何も出来ずにお通夜状態になっていることが多かったのですが, 今年はなんとか最後まで戦意喪失せずに走り切ることができました(が, 予選は敗退しました).

最後まで走りきれたのもそうですが, Goで意外と(予想以上に)スルスルと読めた/実装出来たことも良かったですね. まあ, もっと複雑なアプリケーションになってくると苦戦するのでしょうが, 今回のISUCON10の予選くらいの規模感であれば普通にやっていける, と知れたのはほんのちょっと自信になりました.

やったこと

DB分割, schema変更, 後はnginxの設定書き換えたりとかをやりました. うち, 自分が手を動かしたのが以下2つ.

DB分割

「なんかこれ, estate(物件)とchair(椅子)でJOINしているクエリなさそうじゃん?」と気づいたので, 適当にコードを書き換えて, ホストのうち1台をproxy/appに, 1台を物件用/1台を椅子用のDBサーバにする, という構成に変更しました. 2台のサーバをMySQL用にするのはチームメンバーにおまかせ. やってみたら思ったよりスコアが上がって, みんなで「ウケるw」という感じになっていました.

schema変更

なぞって検索する機能で, 緯度経度を使っていたので, そのあたりの処理をGeometry型のカラムを追加してMySQLでいい感じにできないか? ということで, fixtureの変更とGoの実装をやりました.

あとは, ある物件について椅子が入るか? というのを判定する処理がだいぶ複雑だったので, door_long, door_shortカラムを追加して, イッパツで引けるようにしたりしました. 椅子のwidth/depthについて, 短い方が物件のdoor_short/長い方が物件のdoor_longより小さかったら入る, という感じでスマートに実装できます.

fixtureの修正は, 思考停止しておもむろにPerlのスクリプトを書き始めて(Go実装でやっているのに!!!)ガッとやったのですが, 感想戦で「初期データを突っ込んで, MySQLにカラム追加して, よしなにデータ書き込んで, dumpした」という話を聞いて「それでよかったわ...」と思ったりしました.

感想

最初にも書きましたが, 今回は最後の1時間前まで予選通過ギリギリラインに踏みとどまれたのが良かったですね(しかし, そこからのラストスパートが全然出来なかったので, そこは完全に実力不足). 今回は事前に(1回だけど)過去問で練習したので, やっぱり場数踏んでおくの大事だなと思ったので, もし来年もあるのならもうちっとちゃんと練習してから望みたいですね... 特にMySQLやnginxなど, ミドルウェアのチューニングが弱すぎるので, 感覚つかんでおけると良さそうと思いました. あとは, 今回はISUCON初メンバーと一緒にやった, という所で, 一応(毎回予選敗退ではあるものの)経験者である自分がいい感じにリードする必要がある, と思うのですがそういった所あまり配慮出来なかったのも反省点です... 申し訳ない.

あと, ポータル周りは毎回進化していて凄い! と思うのですが, 今回は特に, めちゃくちゃいい感じで大変感動しました. 質問をポータルで完結出来るのは大変体験が良かったです. 途中, 1台サーバーが動かなくなってみんなで青ざめたのですが, 「動かないんスけど...」という問い合わせを投げたらシュッと対応してもらえて助かったりしていました. あと, ベンチマークも実行したらじわじわスコアが上昇していくのが見えて, これがまた脳汁が出てきて... 最高...

例年, 運営非常に大変と思いますが, その甲斐もあってありがたい事に今回もとても楽しませて頂きました. 最後まで頑張れると感想戦も楽しいんだな, ということをようやく知ることが出来ました(?). 学びも多かったので, もし来年も開催されるなら次はちょっと力を入れつつ, 絶対参加したい!!! と思いました. 運営の皆様, 参加者の皆様, お疲れ様でした!!!

最近読んだ本

最近ちょっと仕事が忙しく, 疲弊気味だったので, 「業務に活きそう重点」ではなく「面白そう重点」で, Kindle Unlimitedで配信されている本をいろいろ読んでいました. 軽く感想を書きます.

情報なき国家の悲劇 大本営参謀の情報戦記

第二次世界大戦において, 陸軍の情報参謀を務め, 後に陸上自衛隊の陸将補になった堀栄三氏の著書. 第二次世界大戦において, 突然情報参謀に任じられた筆者が, 如何にして情報分析に従事し, 米軍の侵攻パターンを予測するに至ったかについて振り返っている本です. 単純に読み物としても面白いし(戦後の, ドイツ駐在武官としてキューバ危機に対峙した時のエピソードが面白かった), 今に当てはめれば「内情が見えない競合他社の情報分析」と言えるので, そういった視点でも示唆がある本だと思いました.

血と汗とピクセル: 大ヒットゲーム開発者たちの激戦記

「ディアブロⅢ」, 「アンチャーテッド4」など, 著名なゲームの開発の様子を関係者のインタビューを元に綴った1冊です. 個人的には数年前にハマって遊びまくった「スターデューバレー」も取り上げられていたのが良かったです. ゲーム開発も, 自分が従事するウェブサービス開発のようにエンジニア/プログラマーの働きが必要不可欠な職種だけれど, その働き方, 取り組み方などは違っている部分もあって, 「そういう世界なのか...」と感じることが出来ました. 「スターデューバレー」のように1人で作ったゲームもあれば, 大人数のチームで作ったゲーム, 成功したパターン, うまくいかなかったパターンなど, いろいろな事例が取り上げられていて, 日本の作品は1つもないけれど, 「ゲーム業界の内情」というか, その雰囲気を知ることが出来る良い本だと思いました. ゲーム好きには確実にオススメ出来る1冊だと思います.

他...

現代語訳 信長公記 (新人物文庫)

現代語訳 信長公記 (新人物文庫)

...なんというか, かなり雑多に読んでいますね. 引き続き, ちまちま積ん読を消化していきたいと思います.