Rosie Pattern Languageに入門(3)

正直第2回で終わる予定だったんですが Rosie Pattern Languageに入門(2) - だいたいよくわからないブログ の続きです。

前回サンプルファイルのsemverをパースしてみたりしたと思うんですが、そんなにしっくり行く例でもないなと感じていたり、 かといってがっつりパースしたくなるログも特にないなーと思っていたりしました。

ところが、さっき何気なく自宅macbrew upgrade コマンドでいろいろなライブラリをがっつりアプデしていたところ ちょうどよい感じのログが出ていました。*1

==> Upgrading 100 outdated packages:
llvm@6 6.0.1 -> 6.0.1_1, ocaml 4.07.0_1 -> 4.07.1, git-lfs 2.5.2 -> 2.7.2, pyenv 1.2.8 -> 1.2.11, tig 2.4.1 -> 2.4.1_1, plantuml 1.2018.12 -> 1.2019.6, tree 1.7.0 -> 1.8.0, terraform 0.11.10 -> 0.12.1, php@7.1 7.1.23 -> 7.1.30, coreutils 8.30 -> 8.31, cppcheck 1.85 -> 1.87, wget 1.19.5 -> 1.20.3_1, ocaml-num 1.1_3 -> 1.1_4, mycli 1.16.0 -> 1.19.0, python@2 2.7.15_1 -> 2.7.16, go 1.11.2 -> 1.12.5, docker-machine 0.15.0 -> 0.16.1, ponyc 0.25.0 -> 0.28.1, gnu-getopt 1.1.6 -> 2.33.2, cmake 3.12.3 -> 3.14.5, opus 1.3 -> 1.3.1, freetype 2.9.1 -> 2.10.0, python 3.7.0 -> 3.7.3, elixir 1.7.4 -> 1.8.2, redis 5.0.0 -> 5.0.5, highlight 3.47 -> 3.52, boost 1.67.0_1 -> 1.69.0_2, cscope 15.8b -> 15.9, libyaml 0.2.1 -> 0.2.2, docker-completion 18.06.1 -> 18.09.6, jemalloc 5.1.0 -> 5.2.0, maven 3.5.4 -> 3.6.1, jo 1.1 -> 1.2, wata727/tflint/tflint 0.7.2 -> 0.8.2, shared-mime-info 1.10 -> 1.12, unzip 6.0_3 -> 6.0_4, zeromq 4.2.5 -> 4.3.1_1, composer 1.7.3 -> 1.8.5, perl 5.28.0 -> 5.30.0, nkf 2.1.4 -> 2.1.5, rust 1.30.0 -> 1.35.0, zsh-completions 0.29.0 -> 0.30.0, readline 7.0.5 -> 8.0.0_1, erlang 21.1.1 -> 22.0.2, llvm 7.0.0 -> 8.0.0_1, awscli 1.16.40 -> 1.16.170, teleport 3.0.0 -> 3.2.6, binutils 2.31.1_1 -> 2.32, gcc 8.2.0 -> 9.1.0, crystal 0.26.1_1 -> 0.29.0, webp 1.0.0 -> 1.0.2, ruby-build 20181019 -> 20190423, sqlite 3.25.2 -> 3.28.0, gobject-introspection 1.58.0 -> 1.60.1, eigen 3.3.5 -> 3.3.7, yarn 1.12.1 -> 1.16.0, numpy 1.15.3 -> 1.16.4, grep 3.1 -> 3.3, emacs 26.1_1 -> 26.2, docker-compose-completion 1.23.0 -> 1.24.0, moreutils 0.62 -> 0.63, thrift 0.11.0 -> 0.12.0, gradle 4.10.2 -> 5.4.1, llvm@3.9 3.9.1_1 -> 3.9.1_2, ansible 2.7.1 -> 2.8.1, llvm@5 5.0.2 -> 5.0.2_1, leiningen 2.8.1 -> 2.9.1, openblas 0.3.3 -> 0.3.6_1, curl 7.62.0 -> 7.65.1, ghc 8.4.4 -> 8.6.5, opencv 3.4.3 -> 4.1.0_2, gnu-sed 4.5 -> 4.7, consul 1.3.0 -> 1.5.1, llvm@4 4.0.1 -> 4.0.1_1, ghq 0.8.0 -> 0.12.6, source-highlight 3.1.8_9 -> 3.1.8_11, nginx 1.15.5 -> 1.17.0, gawk 4.2.1 -> 5.0.0, hadolint 1.15.0 -> 1.16.3, haskell-stack 1.7.1 -> 1.9.3, mercurial 4.8 -> 5.0.1, shellcheck 0.5.0 -> 0.6.0_1, clisp 2.49_1 -> 2.49_2, oniguruma 6.9.0 -> 6.9.2, httpie 0.9.9_4 -> 1.0.2, groovy 2.5.3 -> 2.5.7, node 11.0.0 -> 12.4.0, zsh 5.6.2_1 -> 5.7.1, neovim 0.3.1 -> 0.3.7, ripgrep 0.10.0 -> 11.0.1, coq 8.8.2 -> 8.9.1, fish 2.7.1 -> 3.0.2, hub 2.6.0 -> 2.11.2, bison 3.2 -> 3.4.1, protobuf 3.6.1 -> 3.7.1, rbenv 1.1.1 -> 1.1.2, git 2.19.1 -> 2.22.0, ctop 0.7.1 -> 0.7.2, ethereum/ethereum/ethereum 1.8.17 -> 1.8.27, gnutls 3.5.19 -> 3.6.8, diff-so-fancy 1.2.0 -> 1.2.5, libvterm 681 -> 726, packer 1.3.2 -> 1.4.1, flyway 5.2.1 -> 5.2.4, pony-stable 0.1.6 -> 0.2.0

一行で出るとちょっと読みづらい感じとか前後のバージョンを取り出すのとか難しそうな感じが、マシンリーダブルではないけどちょっと頑張るとマシンリーダブルになりそうないい塩梅のログになっていそうです。

普段なら以下のように改行を突っ込んでヒューマンリーダブルにするところで終わりにしちゃうんですが、今回はパースしてマシンリーダブルにしたいと思います。

$ cat brewout | gsed 's/, /\n/g'
cat brewout | gsed 's/, /\n/g' | head
llvm@6 6.0.1 -> 6.0.1_1
ocaml 4.07.0_1 -> 4.07.1
git-lfs 2.5.2 -> 2.7.2
pyenv 1.2.8 -> 1.2.11
tig 2.4.1 -> 2.4.1_1
plantuml 1.2018.12 -> 1.2019.6
tree 1.7.0 -> 1.8.0
terraform 0.11.10 -> 0.12.1
php@7.1 7.1.23 -> 7.1.30
coreutils 8.30 -> 8.31

といっても簡単なもんで以下のようにするだけです。

package brew

-- test package accepts "llvm@6", "ocaml", "php@7.1"
package = [[:alnum:] [@.] ]+

local alias brewver = { [:digit:]+ "." [:digit:]+ "." [:digit:]+ [^ [:space:] [,] ]* }

-- test before accepts "6.0.1", "23.0.1_1", "4.07.0_1", "7.1.30"
-- test after accepts "6.0.1", "23.0.1_1", "4.07.0_1", "7.1.30"
before = brewver
after = brewver

-- test brew accepts "llvm@6 6.0.1 -> 6.0.1_1", "ocaml 4.07.0_1 -> 4.07.1,", "php@7.1 7.1.23 -> 7.1.30,"
-- test brew rejects "12.01 -> 2.3.4",
brew = package before "->" after ","?

brewに登録されるパッケージのバージョンは厳密なsemverとは異なり07や0_1のような文字列が利用されるので標準のver.semverは使わず空気を読んでパースしています。(brew公式docとか見てないので今回のでパースできない例はいっぱいありそう)

llvm@6php@7.1, 23.0.1_1 など 怪しい部分は都度テストに突っ込むとデグレチェックにもなるので便利です。(実際php@6.1をパースしようとして [@.] とすべきところを [@ .] としてしまいスペースもpackageに飲み込まれるというデグレが起きるのを検知しました。)*2

テストは以下のように行います。

$ rosie test brew.rpl
brew.rpl
    All 15 tests passed

肝心のパースは以下のようにできます

 $ cat brewout | rosie  -f brew.rpl match -o jsonpp 'findall:brew.brew'
{"type": "*",
 "subs":
   [{"type": "brew",
     "subs":
       [{"type": "package",
         "s": 1,
         "data": "llvm@6",
         "e": 7},
        {"type": "before",
         "s": 8,
         "data": "6.0.1",
         "e": 13},
        {"type": "after",
         "s": 17,
         "data": "6.0.1_1",
         "e": 24}],
     "s": 1,
     "data": "llvm@6 6.0.1 -> 6.0.1_1,",
     "e": 25},
    {"type": "brew",
     "subs":
       [{"type": "package",
         "s": 26,
         "data": "ocaml",
         "e": 31},
        {"type": "before",
         "s": 32,
         "data": "4.07.0_1",
         "e": 40},
        {"type": "after",
         "s": 44,
         "data": "4.07.1",
         "e": 50}],
     "s": 26,
     "data": "ocaml 4.07.0_1 -> 4.07.1,",
     "e": 51},
...(snip)
}

ASTがあるのでpackageだけ取り出すやらなんやらは自由にできます。

$ cat brewout | rosie  -f brew.rpl match -o json 'findall:brew.brew' | jq -r '.subs | map(.subs) | flatten | map(select(.type == "package") | .data)[]'
llvm@6
ocaml
lfs
pyenv
tig
...

という感じで、日常にあるちょっとだけ取扱に困るログを取り扱ってみる回でした。

*1:なんかllvmがたくさん入ってるとか、こんなに一気に上げて大丈夫なのかとかはスルーで

*2:local のテストはかけるけどlocal aliasのテストがかけないような気がするけどもしかしたら書けるかも。

Rosie Pattern Languageに入門(2)

Rosie Pattern Languageに入門(1) - だいたいよくわからないブログ

前回の続きです。

前回までだと、semverやらurlなどを取り出せて便利なツールといった印象でしたが、 その真の価値は簡単にパターンを記述できる点にあります。

パターン定義

Rosieでは正規表現ではなくPEGベースの記法でパターンを記述します。 表現力は正規表現を上回りますが、一方で基本は正規表現と同じなので、(完璧に使いこなそうとしなければ)そこまで覚えることは多くないはずです。

パターン定義の方法は公式ドキュメントにあります。 https://gitlab.com/rosie-pattern-language/rosie/blob/d861ffd5805f9988d9ad430e7f124216f11df44e/doc/rpl.md

PEGについては PEG基礎文法最速マスター - kmizuの日記 を読めば最速でPEGれるようになれそうです。

まずは major.minor で構成される劣化semverを作ってみましょう。 https://gitlab.com/rosie-pattern-language/rosie/blob/d861ffd5805f9988d9ad430e7f124216f11df44e/rpl/ver.rpl を参照して少し変えてみましょう。

package myver

-- version like semver but less feature

local alias numeric = [0] / { [1-9] [:digit:]* }

major = numeric
minor = numeric

myver = { major "." minor }

numeric = [0] / { [1-9] [:digit:]* }正規表現でいうと /0|[1-9]\d*/ になります。 0に関する分岐を入れることで 0 にはマッチするけど 012 にはマッチしないといった条件を表現しています。

{} でくくると区切り文字なしでのマッチになります。( [1-9] [:digit:]*"1 2" にマッチ。 { [1-9] [:digit:]* } は"12"にマッチ)((Rosieでは内部で入力をトークナイズしています。そのため "1 2" などにもマッチします。))

preleaseを追加して 1.2-alpha2 などに対応してみると以下のようになります。

prerelease = [[:alnum:]]+

myver = { major "." minor {"-" prerelease}? }

実行

実行時は以下のようにパターンを指定して行います。

$ cat vers
12.01
1
2.3.4
3.52
1.2-alpha2
1.3-b
1.4-

$ cat vers | rosie  -f myver.rpl match -o subs  '{myver.myver $}'
3.52
1.2-alpha2
1.3-b

12.011 , 1.4- などバージョンとしてふさわしくないものを除外できています。

パターンがファイルで管理でき、かつ個別のパターンの組み合わせで新しいパターンが構築できています。 また、aliasキーワードを付けずに宣言したパターンについては -o json の出力で前回のようにマッチした文字列のどこがmajorやminorなのかを把握することができるため、 その後の処理にも使えます。

テスト

先程のように毎回versのようなファイルを作ってtest.shのようなスクリプトを書くのはそこそこ大変です。 Rosieにはdoctestの仕組みがあるのでこれも書いてみましょう。

公式ドキュメントはここにあります。 https://gitlab.com/rosie-pattern-language/rosie/blob/d861ffd5805f9988d9ad430e7f124216f11df44e/doc/unittest.md

書き方は簡単で test pattern accepts/rejects 入力 を書いてやればOKです。

package myver

-- version like semver but less feature

local alias numeric = [0] / { [1-9] [:digit:]* }

-- test major accepts "0", "10", "99"
-- test major rejects "01", "1.2"
major = numeric
minor = numeric

-- test prerelease accepts "alpha1", "1"
-- test prerelease rejects "beta1.2", "1.2"
prerelease = [[:alnum:]]+

-- test myver accepts "3.52", "1.2-alpha2", "1.3-b"
-- test myver rejects "12.01", "1" "2.3.4", "1.4-"
-- test myver includes prerelease "1.2-alpha2"
-- test myver excludes prerelease "1.2"
myver = { major "." minor {"-" prerelease}? }

includes, excludesはmyverでマッチしたときにprereleaseが含まれているかどうか?をチェックします。

あとは以下のようにテストを実行してやればOKです。

$ rosie test myver.rpl
myver.rpl
    All 16 tests passed

パターン自体もわかりやすく、テストには具体例が列挙してある。 これで半年前の自分が書いた意味不明な正規表現から卒業できそうです。*1

今回使ったsampleはここにおいています。 https://github.com/matsu-chara/rosie-example

豆知識

find

.* のような指定があると入力をすべてconsumeしてしまうので、 { .* "something" } のように書きたくなった場合は {find:something} と書きましょう。((findはmacroで、{ !"something" . }* "something" に展開されます。 https://gitlab.com/rosie-pattern-language/rosie/blob/d861ffd5805f9988d9ad430e7f124216f11df44e/doc/i-know-regex.md))

この辺の正規表現との違いは以下のドキュメントに書いてあります。 https://gitlab.com/rosie-pattern-language/rosie/blob/d861ffd5805f9988d9ad430e7f124216f11df44e/doc/i-know-regex.md

look ahead, look behind

look ahead, look behindは以下のように書けます

## look ahead. 先頭がhttpのみを出力
curl -s https://matsu-chara.hatenablog.com | rosie grep -o subs '{ >"http:" net.url }'

## look behind. 末尾が/aboutのみを出力
curl -s https://matsu-chara.hatenablog.com | rosie grep -o subs '{ net.url <"/about" }'

look aheadではパースした位置で、"https://matsu" があるかを確かめ、成功したら(解析する文字の位置は変えずに)そのままnet.urlでのマッチを試みる。 look behindはnet.urlでのマッチに成功したら、(解析する文字の位置は変えずに)戻って "/about" にマッチするかをチェックし、成功したら全体をマッチさせる。 という意味で、正規表現よりわかりやすいんじゃないかな?と思ったりしています。(もちろんこの辺は主観ですが)

ということでRosieの紹介でした。

次回 matsu-chara.hatenablog.com

*1:とはいえ、ワンライナーで使おうとすると少し難しい時があるのでgrepの利点が失われるわけではありません。

Rosie Pattern Languageに入門(1)

Rosie Pattern Languageを触ってみたのでメモを残します。

なおversion 1.1.0の情報なので詳細・更新後の情報は Rosie Pattern Language / Rosie · GitLab を御覧ください。

Rosie Pattern Languageとは

RosieはPEG(Parsing Expression Grammars)を使ってパターンを記述することで、正規表現よりも強力に検索や置換・パースなどを行うための言語・ツール群です。

作ったパターンのテストも可能であり、ad-hocな正規表現拡張を駆使するよりも、より保守しやすく・より複雑なデータを処理することを可能にすることを目的としているようです。

使ってみる

インストール

まずはインストール

$ brew tap rosie-community/rosie https://gitlab.com/rosie-community/packages/homebrew-rosie.git
$ brew install rosie

簡単な使い方

そして、以下のコマンドを実行します。

$ curl -s https://github.com | rosie grep -o color net.url

※ わかりやすくするためにoutputオプションとしてcolorをつけたりheadで行数を絞っています

f:id:matsu_chara:20190525151423p:plain

URLだけ欲しいという場合はoutputオプションとしてsubsを指定します。*1

$ curl -s https://github.com | rosie grep -o subs net.url

f:id:matsu_chara:20190525152938p:plain

subsは grep -o に似ていますが、httpだったりhttpsだったり、urlの終端判定はどうするのか?などを考えるとなかなか面倒です。 以下のようにすると一見うまくいきそうですが・・・

$ curl -s https://github.com | grep -o 'https\?://[^"]*'
...
https://github.githubassets.com/favicon.ico
https://github.com/
https://github.com/&quot;,&quot;referrer&quot;:null,&quot;user_id&quot;:null}}
...

と地味に目的どおりに行かない場合があります。

net.url以外にも例えば

  • "6.02e23", "3.00E08", "0.123"なども拾えるfloatを含めたnumber
  • "2015-10-14T22:11:20+00:00" などrfc3339や、その他いろいろにマッチできるtimestamp
  • "1.2.3-alpha.7.8.9" など1.2.3以外をパースしようとすると意外とサクッとできないsemver

など様々なパターンが標準で組み込まれています。

使い方は例えば以下です。

$ cat vers
1.2.3-alpha2.4
12.0.5
1
2.3.4

$ cat vers | rosie grep ver.semver
1.2.3-alpha2.4
12.0.5
2.3.4

このような便利な標準パターンが組み込まれていて便利ですね!

RPL

便利ですね!・・・で終わっても十分に便利なんですが、rosieはプログラミング言語のようにパターンを記述したファイル(.rpl)を用意して、 自分の欲しいパターンを柔軟に記述できる点を特徴としています。 正規表現みたいですがrosieはPEGベースで記述できるのでより強力です。*2

semverパターンを定義しているrplファイルの中では semverは以下のように定義されています。

local alias numeric = [0] / { [1-9] [:digit:]* }

major = numeric
minor = numeric
patch = numeric 

-- prerelease,buildmetadataの定義は省略...

semver = { major "." minor "." patch {"-" prerelease}? {"+" buildmetadata}? }

このようにaliasや各パターンを個別に定義して、連結させていくことでパターンを定義しています。

キャプチャ

パターンの書き方に入る前に、rosieは入力に対してどのようにパターンをマッチさせているのか?を見るために、 semverの中でminorバージョンだけ取り出したい!といったケースを考えてみます。

正規表現ではcaptureを使いますが、rosieではマッチした情報を以下のように出力することが可能です。

$ echo "1.2.3-alpha" | rosie match -o jsonpp ver.semver
{"s": 1,
 "e": 12,
 "type": "ver.semver",
 "subs":
   [{"s": 1,
     "e": 2,
     "type": "ver.major",
     "data": "1"},
    {"s": 3,
     "e": 4,
     "type": "ver.minor",
     "data": "2"},
    {"s": 5,
     "e": 6,
     "type": "ver.patch",
     "data": "3"},
    {"s": 7,
     "e": 12,
     "type": "ver.prerelease",
     "data": "alpha"}],
 "data": "1.2.3-alpha"}

余談: ところで rosie greprosie match は行内でもマッチするか行頭からマッチするかの違いがありそうです。 例えば"xxx1.2.3-alphabbb"rosie grep ではマッチしますが、 rosie match ではマッチしません。 実装的には rosie grep patrosie match findall:pat になりそうです。(多分) https://gitlab.com/rosie-pattern-language/rosie/blob/d861ffd5805f9988d9ad430e7f124216f11df44e/src/lua/builtins.lua#L98

キャプチャの話に戻りますが、json内にあるtypeに一致したpatternの情報が入っているので、あとは適当に取り出してやれそうです。

$ cat vers
1.2.3-alpha2.4
12.0.5
1
2.3.4
xx1.2.3mmm

$ cat vers | rosie grep -o json 'ver.semver'
{"type":"*","s":1,"subs":[{"type":"ver.semver","s":1,"subs":[{"type":"ver.major","s":1,"e":2,"data":"1"},{"type":"ver.minor","s":3,"e":4,"data":"2"},{"type":"ver.patch","s":5,"e":6,"data":"3"},{"type":"ver.prerelease","s":7,"e":15,"data":"alpha2.4"}],"e":15,"data":"1.2.3-alpha2.4"}],"e":15,"data":"1.2.3-alpha2.4"}
{"type":"*","s":1,"subs":[{"type":"ver.semver","s":1,"subs":[{"type":"ver.major","s":1,"e":3,"data":"12"},{"type":"ver.minor","s":4,"e":5,"data":"0"},{"type":"ver.patch","s":6,"e":7,"data":"5"}],"e":7,"data":"12.0.5"}],"e":7,"data":"12.0.5"}
{"type":"*","s":1,"subs":[{"type":"ver.semver","s":1,"subs":[{"type":"ver.major","s":1,"e":2,"data":"2"},{"type":"ver.minor","s":3,"e":4,"data":"3"},{"type":"ver.patch","s":5,"e":6,"data":"4"}],"e":6,"data":"2.3.4"}],"e":6,"data":"2.3.4"}
{"type":"*","s":1,"subs":[{"type":"ver.semver","s":3,"subs":[{"type":"ver.major","s":3,"e":4,"data":"1"},{"type":"ver.minor","s":5,"e":6,"data":"2"},{"type":"ver.patch","s":7,"e":8,"data":"3"}],"e":8,"data":"1.2.3"}],"e":8,"data":"xx1.2.3"}

cat vers | rosie grep -o json 'ver.semver' | jq -r '.subs | map(.subs | map(select(.type=="ver.minor") | .data))[][]'
2
0
3
2

※ 先程の例では-o jsonppでしたがpretty printする必要もないので-o jsonとしています。

少しjqが複雑ですが、ここまで情報があればpatch以降を切り落とした上で、patchバージョンをincrementしたsemerを作ることも可能です。

$ cat vers | rosie grep -o json 'ver.semver' | jq -r '.subs | map(.subs | map(select(.type=="ver.major")))[][] as $major | map(.subs | map(select(.type=="ver.minor")))[][] as $minor |map(.subs | map(select(.type=="ver.patch")))[][] as $patch | $major.data + "." + $minor.data + "." + ($patch.data | tonumber | .+1 | tostring)'
1.2.4
12.0.6
2.3.5
1.2.4

language support

と、ここまでいくとjqが複雑すぎますが、なんとrosieはCやgo, haskellpythonバインディングがあるので解析結果を各種プログラミング言語で扱うことができます。

goだとこんな感じになります。 https://gitlab.com/rosie-community/clients/go/blob/18b09df4802ba6018bf816cf05a48ed777396c40/src/rtest/rtest.go#L113-121

本格的にやる場合は使えそうですね。

さらに余談: gronというツール(JSONをgrepしやすくするコマンドラインツールgronの紹介 - Qiita)を使うという手もあります。*3

node -i -e "$(cat vers | rosie grep -o json 'ver.semver'  | gron)"
> let major = json.subs[0].subs.find(s => s.type == "ver.major").data;
> let minor = json.subs[0].subs.find(s => s.type == "ver.minor").data;
> let patch = json.subs[0].subs.find(s => s.type == "ver.patch").data;
> `${major}.${minor}.${Number(patch) + 1}`;
"1.2.4"

このあと、パターン定義や実行、テストについても書きたかったんですがボリューミーになってしまったので一旦ここで区切ります。

次回は Rosie Pattern Languageに入門(2) - だいたいよくわからないブログ

*1:オプションにはsubs, data, jsonpp, byte, bool, color, json, lineが指定できます。

*2:なのでJSONのような再帰的な構造にもマッチできるわけですね https://gitlab.com/rosie-pattern-language/rosie/blob/master/rpl/json.rpl

*3:というかgoでやるかーと思ったけどgronすればいいなと思ってgoでやるのをやめたレベル

amazoncorreto, openjdkの各バージョンをdocker container上で走らせた場合のCPU数, メモリーを確認した

Java 8でも安心。Dockerに対するCPU・メモリ対応。(2018年11月現在) - Mitsuyuki.Shiiba で紹介されているように8u191以降であれば、 コンテナでJVMを動かしたときのメモリ・CPU数に関する問題は解消されているはず。

openjdkにパッチが入っていれば基本的に確認する必要はなさそうだがamazon corretoのパッチリストには載ってなかったので一応確認してみた。 (とかいいつつdockerhubにあがってる最古のjdk8のバージョンが8u192になっているので、わざわざ確認する必要はなかった気がする。)

https://github.com/matsu-chara/jvm_docker_check/blob/master/result.txt

途中から確認自体がいらなさそうな予感を激しく感じつつも今後もこういう動かし方したいことがあるかもしれないと思って適当なイメージで回せるスクリプトを用意した。(今後に期待)

1on1と信頼残高

最近思ったことを書いておく備忘録的なものです。 特に1on1経験が豊富なわけではなく、むしろ経験が浅いので、何を学びながら進めばいいのやらという段階でのものです。

1on1と困りごと

1on1に何を求めるのか?というのはベースになる考え方はありつつも、個人・チーム・組織の状況によって異なると思います。 将来のキャリアパスについての相談やポジティブなことの共有などもあると思いますが、それと同じくらい困ったことを聞くことが多いと思います。

「困ったことを聞いて、(個人が解決できるように or マネージャーが or ...)解決に導く。」これが、チームの生産性向上に寄与するというのはある程度自明に思えますし、 困ったことを話して、解決してくれるマネージャー*1は頼りがいがあり、まさに信頼できる優秀なマネージャー像にピッタリではないでしょうか。

理想と現実

一方で組織の全権を持っているわけでもないマネージャーは、全ての問題を瞬時に解決できるわけではありません。*2 合理的に見えるような解決方法を提案してもらってもなお、すぐに実行に移せないことは多いと思います。 情報の非対称性があったり、単純にリソースの問題でもあり、日々の忙しさにかまけているという場合もあります。(最後のはできるだけなくしたいけど、人間だし早く帰って熱々緑茶を飲みたいというのが人情ってものよ・・・)

開発プロセスを変えたい。差し込みやボトルネックをなんとかしたい。こういった課題には常に挑戦し続けるべきではありますが、一方で次回の1on1までに何か進んでいるのかと言われるとそうではないことのほうが多いのではないかと思います。

そもそもの話

ここまで読んだ方は以下のような疑問をお持ちかと思います。

  • 組織的な問題はともかく個人の問題をマネージャーが”解決する”ってのは違和感があるよね
  • そもそも1on1で出た課題はマネージャーが解決するんじゃなくてコーチングしたりするのが重要でしょ
  • 問題みたいなものが1on1で初めてでるのは普段のコミュニケーションや、ふりかえり(やKPT)などに問題があるのでは?

こういった話もあると思います。そしておそらくこちらの方がより本質的な目的に沿うと思いますが、今回は「僕個人の気づきを記す」ことを目的にしているためこれらの話には触れません。 それらの問題がどうでも良いと思っているわけではなく、単にそういう気付きがあったのでそこをメモしているというスタンスです。(また、結論の部分については個人の掘り下げという意味での1on1の目的にもある程度沿うよねという思いもあります。)

今回は自分の気付きを残すために、もしマネージャーがアクションをとるべき課題が1on1で初めて言われたら?という想定でお読みください。 繰り返しになりますが、これは以下のような状況を意味しません。

  • 心理的安全性が無くて、ふりかえりでは問題が全然出ない
  • 1on1は問題を教えてもらう場で、それ以外のことは全く話されないし目的ではない

想定としては以下です。

  • ふりかえりの場では思いつかなかったけど1on1で話が弾むうちにポロッとでた
  • ふりかえりでも議論してたけど、そこからまた考えが変わったりしていて、実はねといった感じでより深く教えてくれた

ということで”もし、こういうことがあったら”、”あなたはどうする?私はこうする!”形式でお読みください!

マネージャーはスーパーマンではないという話

つい最近までは、「こういった現実の中で、じゃあ理想にどれだけ近づけるのか?」が腕の見せ所だと考えていました。 (マネージャーが解決すべき課題を)10個中3個解決できたからあなたのマネージャー能力は3です。10個中7個解決できたからあなたのマネージャー能力は7です。のように。*3

一方でこうした考え方は、減点法による評価を招きがちではないかと思います。 あれはまだ解決しないのか・これはまだ解決してないのか、いつ解決するのか・・・。 「課題があって、解決できないと信頼残高が少しずつ減って、0になるとチームが崩壊。」これは避けるべきことだと思います。

では、10個課題があって10個課題を解決すれば信頼残高も減らずに済むのでしょうか? たしかにそうだと思いますが、そんな事できる人少ないですよね。 それでもなお、ミスが許されないので心理的安全性もあまりなさそうに見えます。

ちょうど、こういった議論はem.fmやその他の記事でも言及されています。 ep2. Engineering Managerをスーパーマンだと思わないで by EM . FM #EMFM • A podcast on Anchor

理想を変えよう

そもそも「Xという課題があります」に対して、自分は「そうだね、課題だと思う。解決したいな。どうすればいいだろう」と自分の中で勝手に課題認定を行い、解決策を考えはじめてしまっていました。1on1中にactionが決まればハッピーだし、安心感もありますしね。共感力!

一方で、この人は何故Xが課題だと考えたのかについては全然聞かずに勝手に補完していました。 MTGが長いという課題なら、自分の時間が取られるのが嫌なのか、空中戦になって論理的じゃない議論が嫌いなのか、チーム全体の生産性低下を危惧しているのか、興味のない事柄に巻き込まれたくないのか・・とかいろいろ考えられる要因があって、まあ全体的に正論だしそんなに大きくハズレてはいないのだろうけど、一方でその人の価値観や考え方は置き去りです。

そこで、「Xが課題だと思う」という会話に対して、何故Xが課題だと思うのか?という考えを聞くことが大切だと考えるようになりました。

そうすれば、「何を大事にしているのか・何に価値を感じるのか」に踏み込んでいくきっかけになる。そして、価値観や考え方が(部分的にでも)つかめてくれば次の課題が出たときに、「言われて初めて気づいて行動する」のではなく、よりproactiveに行動を起こすことができるようになるはずです。

これは1on1を 課題報告機課題解決機 の同期会場にしないための工夫でもあるし、課題解決の成功・失敗を含めた少しのイベントで容易に増えたり減ったりする信頼(残高)ではなく、「自分のことがある程度分かっていて、それに基づいて行動してるはずだ」という長期的に安定した信頼関係につながるのではないかと思っています。 (もちろん、何か質問を増やした程度である人の価値観が全部わかるなんて到底あり得ないので程度の問題ではあります。)

ということで、そもそもたくさん課題を解決するのがマネージャー力だ!とか、困ったことがあったらできる限り多く解決するのが1on1の理想形だ!といった考え方をそもそも変えるのは大事だなという話でした。(後半の方はなんかいい感じに気持ち良いこと書いてわかったような感じですが、そこまで達観した何かではなく、「課題ですね」に対して「そうですね」と思考停止してスタートするのでは限界があるなという考えを少しだけ掘り下げてみただけです。)

とはいえ、困ったことを聞くということ自体は1on1の役割のうちの1つでしかないですし、困ったことを聞いたとしてもマネージャーが解決するべきものもあれば、コーチング・ティーチングを通して各自に解決を促す・問題解決に向けた話相手だけになれば十分なものなどがあるため、この記事ではそのうちのほんの一部にしか焦点を当てていません。総合的にいろいろ学んでいく必要があるなと感じています。

*1:上司・リーダー・テックリードなど、1on1する人の役職が違う場合もありますね

*2:組織の全権を持ってたとしても難しいですよね

*3:解決と書いていますが、解決以外にも、今は優先度が低いと言った意思決定についての説明をきっちり行うみたいなのも含まれると思います。