『リーダーの作法』を読んだ
https://www.amazon.co.jp/dp/4873119898:site
表紙の蜂 🐝 が毛並みも見えて結構リアルでした。
ぼくにとっては参考になったり魅力的に感じる章は限られていました。今の役割や興味が、この本で登場するマネージャー・ディレクター・エグゼクティブのどれにも当てはまらないですし。ただ「リーダーシップはそういった役割に限らず全員が発揮するものである」という考えは他のいくつもの本と同様にこの本でも共有されていました。
プロフェッショナルなリーダーとして私個人が果たすべき責任は、一刻一刻をできる限りの熱意と好奇心を持って前向きに取り組むことなのです。一見価値がないように見える状況に置かれたときでも、私は何らかの価値を見出すようにしています。価値は常にそこにあるからです。 p6 1章 誰からでも学ぶことがあると考える
何からでも学べると思うこと。
管理職に就くことは昇進ではない p46 9章 新任マネージャーのデス・スパイラル
別のページでキャリアラダー(はしご:上下に昇り降りするもの)ではなくキャリアパス(旅)である、と書かれていて、そうだよなぁと。
ほめ言葉とは、無欲で、うまく表現された、タイムリーな成果の承認のことです。 p82 14章 素敵なほめ言葉
改めて大事だと思いましたし、誰でも明日から意識して実践できることですね。
『ユニコーン企業のひみつ』を読んだ
ユニコーン企業(というかSpotify)の働き方を知ることができる本。ページ数がそんなに多くないので読みやすいです。
あとがきにもあるように、執筆当時のスナップショット。特にこういった企業は学習のスピードが速く変化も速いので現状と差分もあるでしょう。
プラクティスそのものではなく、「ミッションで仕事を定義する」「権限を与えて信頼する」「チームを大事にする」といったプラクティスを支える原則や文化に焦点を当てることで考え方を変えつつ。自分たちの仕事をどこから変えるか、一歩目を踏み出す勇気が出る本だと思います。
思考は戦略的に、行動は局所的に p155
『その仕事、全部やめてみよう』を読んだ
谷を埋めるのではなく山をつくる「ラストマン戦略」は最近はわりと同意できる部分があって、ふむふむと読めました。
パフォーマンスを高めるために力を抜く、の部分もなるほど〜と。400メートル走の例は分かりやすくて、スピードに乗ると全力を出さなくてもペース維持ができたり、自分に合ったペース配分を見つけたり、みたいなことは活かせるなと思いました。400メートル全力で走れる人間はいないですからね。
ただ「ひよコード」のくだりあたりから本書の主題がよく分からなくなりました。山が高く尖ってる(欠点もある)例も、言いたいことはなんとなく分かりつつも、その欠点なら無くした(谷を埋めた)ほうがいいのでは?と思うような例もいくつか。「長所に目を向ける」「個性を活かす」あたりの考え方は同意なんですが。
Goの静的解析の事始めとしてエラーラップフォーマットのLinterをつくった
こちらのもくもく会も活用して、簡単なLinterをつくりました。
つくったもの
Goの静的解析の事始めとして、エラーのラップフォーマットの間違いを検知するLinter errwrapfmtをつくりました。
errwrapfmtは文字列リテラルに %w があったらエラーをラップしようとしていると判断して : %w
(コロン半角スペースが前方にあるか)というフォーマットになっているかどうかチェックするLinterです。文字列リテラル(正確にはBasicLit)に対してシンプルに正規表現をかけてます。
このLinterをつくろうと思ったきっかけは、xerrorsによるエラーのラップでこのフォーマットを間違うと、スタックトレースが一部冗長になるという問題を知ったからです。
Goではエラーを扱う標準パッケージのerrorsはGo 1.18現在スタックトレースに対応していません。スタックトレースを出力したければpkg/errorsやxerrorsを使う必要があります。
xerrors.Errorf()はdeprecatedになりfmt.Errorf()を使うようにgodocに書かれていますが、fmt.Errorf()を使えばそこでスタックトレースは途切れます。スタックトレースを出力したい場合依然としてxerrors.Errorf()を利用することになると思っています。xerrors.Errorf()は : %w
でないとラップされたエラーがスタックトレース上で冗長に表示されます。そしてこれはコンパイラでも検知されないため、事前に検知するには静的解析が有効です。
※スタックトレース周りの話は Goでスタックトレースを出力するには - あぼぼーぼ・ぼーぼぼ にもうちょい詳しく書きました。
参考にしたもの
今回はgo/analysisを活用したのでだいぶ考えることが減りました。つくりたいLinterのロジックに集中できましたし、(独自の記法ではあるけど)analysistestでかなり簡単にテスト駆動開発ができるのが良かったです。
たくさんの参考情報をもとに理解を進めつつ、今回つくりたいものに必要最低限な知識は獲得できたかなと思います。具体的にはプログラミング言語Go完全入門 14.静的解析とコード生成がめちゃめちゃ役立ちました。
あとはAST周りだとGoの言語仕様を途中まで読んでいたので、EBNFを理解してNotationが読めるようになっていたのも大きかったです。やっぱり言語仕様に戻ってこれるのは大事です。それからGoAst ViewerでASTの可視化をしてみたりも。
書籍だと『エキスパートたちのGo言語』の第1章に循環複雑度を計測するCLIツールをつくるところがあり参考になりました。今回あえてgostaticanalysis/skeletonを使わずにつくってみましたが、結果的に似たような構成になりました。というのも書籍のなかで実装方法の参考にされていたloopclosure.go(などgo/analysis/passesにある実装)がそういう実装になっているからです。また業務でもお世話になっているstaticcheckなどのhttps://github.com/dominikh/go-toolのコードも参考にしました。
静的解析楽しいぞい
errwrapfmt自体はあとgolangci-lint対応くらいしかやりたいことがないのですが、このくらい簡単なものを作ってみて「静的解析は結構楽しいぞ」と思えたので、またなにか作りながら学習したいですね。
Goでスタックトレースを出力するには
スタックトレース周りについて調べたので放流。
概要
- Goでエラーを扱う標準パッケージ errors はGo1.18現在スタックトレースに対応していません。Go2では標準パッケージerrorsにスタックトレースがサポートされる予定です(proposal)。それまでの間、スタックトレースを出力するためには別のパッケージを使う必要があります。
- その場合の有力な選択肢として pkg/errors と xerrors があります。基本このどちらかを使えば良いですが、どちらを使えば良いかは色々な情報があり迷いどころです。これらの特徴を整理して、スタックトレースを扱いたい人が読めばなんとなく何をどう選べばいいかがわかります。
結論
- フォーマットミスを意識したくない場合はpkg/errors、標準のerrorsパッケージに似たAPIで使いたい場合はxerrorsがおすすめ。
- errors.New()による生成や、fmt.Errorf()によるラップは、それまでのスタックトレース情報が無くなるためスタックトレースを出力したいなら基本使わない。
スタックトレースを使いたい場合の主な選択肢
準標準パッケージであるxerrorsか、pkg/errorsの2つが一般的な模様。いくつか出典を紹介します。
実用Go言語
Go 1.16では標準ライブラリであるerrors.New()やfmt.Errorf()を使ってエラーを生成した場合、スタックトレースを出力する方法はありません。スタックトレースを出力させたい場合pkg/errorsやgolang.org/x/xerrorsといったライブラリを使う必要があります。
書籍『実用Go言語』p106
プログラミング言語Go完全入門
スタックトレースを付加する - pkg/errors.WithStackすると付加される - pkg/errors.Wrapでも可 - xerrorsでも付加されるがerrorsでは付加されない(Go 1.13)
Gophers Slack
Steve Coffman: So I know that both pkg/errors and x/xerrors have stacktraces, but in the post-Go 1.13 world that didn't bring that along, is there a package that only provides errors with stacktraces? Tim Heckman: I'm not sure I understand the question, because my initial answer are the two packages you identified. Gophers Slackの#newbiesにて
どれを使えばいい?
プロジェクト内で統一されていればどちらでも良いと思います。ただいくつか論点があります。
更新頻度
pkg/errorsは、かつてよりメンテナンスモードに移行することがアナウンスされており、2022年5月現在リポジトリはアーカイブされています。
xerrorsは、エラーのラップに関する機能がGo 1.13で標準パッケージのerrorsやfmt.Errorfに組み込まれたことにより一部機能がdeprecatedになっています。ですが標準パッケージのerrors自体はスタックトレース非対応のままなので、deprecatedな機能も引き続き使うことになります。
pkg/errorsがアーカイブされているならxerrorsを使った方がいいか?というと、そうではないと思います。エラーハンドリング周りは現在特に機能追加の必要性もなくAPIが安定しているため、両方とも安心して使うことができると思っています。
標準パッケージerrorsへの移行コスト
Go 2では標準パッケージであるerrorsにスタックトレースがサポートされる予定です。標準パッケージでやりたいことができそうなら、「繋ぎ」として使うものはGo 2におけるerrorsパッケージへ移行しやすい形だと嬉しいです。
これについては、pkg/errorsがWrap()、WithMessage()、WithStack()、Cause()と割と独自のAPIを提供しているのに対して、xerrorsはerrors同様フォーマットを用いたAPIが基本なのでどちらかといえばxerrorsの方が移行コストが低そうです。ただpkg/errorsもシンプルなAPIなのでそこまで大きな差はないと思います。
使いやすさ・運用しやすさ
Goはエラーハンドリングをたくさん書くので、使いやすさは大事なポイントです。
両者の特徴に関しては Golang Error Handling — Best Practice in 2020 による整理が分かりやすいです。この記事ではpkg/errorsと比較するとxerrorsには次の2つの問題があると言っています。
- 基本的な使い方であるフォーマットによるエラーのラップで、フォーマットを間違うとエラーメッセージが変になるし、その間違いがコンパイルエラーにならないので気づきにくい。
- エラーのnilチェックが必要
1について、エラーをラップするフォーマットは : %w
であり、コロンを忘れたり foo %w
、スペースを忘れて foo:%w
と書くと、エラーメッセージに重複が発生します。また、このフォーマットの違いはコンパイルエラーにならないため気づくことが難しく、Linterを自作するなどしないとレビュアーの負担にもなります。
例えばxerrorsを使った以下のコードをGo Playgroundで動かすと、正常にスタックトレースが出力されます。
package main import ( "database/sql" "fmt" "golang.org/x/xerrors" ) func foo() error { return xerrors.Errorf("foo failed: %w", bar()) //ココ } func bar() error { return xerrors.Errorf("bar failed: %w", sql.ErrNoRows) } func main() { fmt.Printf("%+v\n", foo()) } foo failed: main.foo /tmp/sandbox3601724689/prog.go:11 - bar failed: main.bar /tmp/sandbox3601724689/prog.go:15 - sql: no rows in result set
このコードで ココ とコメントした箇所のフォーマットを "foo failed: %w"
から "foo failed:%w"
に変更して動かしてみると、以下のようにスタックトレース中のメッセージが変になってしまいます。
foo failed:bar failed: sql: no rows in result set: main.foo /tmp/sandbox2690898140/prog.go:11 - bar failed: main.bar /tmp/sandbox2690898140/prog.go:15 - sql: no rows in result set
2のエラーのnilチェックが必要について、xerrorsではnilをラップするとエラーを生成しますが、pkg/errorsではnilをラップしようとしてもエラーを生成せずnilを返します。なのでpkg/errorsのほうが場合によってはnilチェックを省略することができます。
// xerrorsを使ったfoo()はラップしようとしたerrorがnilでも新しいerrorを返す func foo() error { return xerrors.Errorf("foo failed: %w", nil) } foo failed: %!w(<nil>): main.foo /tmp/sandbox1009969294/prog.go:10 // pkg/errorsを使ったfoo()はラップしようとしたerrorがnilならnilを返す func foo() error { return errors.WithMessage(nil, "foo failed") } <nil>
これらの違いから、僕は冒頭の結論に至りました。
スタックトレースを使いたいなら気を付けること
errors.New()でエラーを生成したり、fmt.Errorf()でエラーをラップすると、それまでのスタックトレース情報が無くなります。
前述したGo Playgroundのコードに出てくるfoo()の中でfmt.Errorf()を使ってラップすると、以下のようにbar()の位置情報が記録されません(fmt.Errorf()を使ったサンプルコード)。
foo failed:bar failed: sql: no rows in result set: main.foo /tmp/sandbox526836328/prog.go:11 - bar failed: sql: no rows in result set
また、同様に今度はbar()の中でerrors.New()を使ってエラーを生成した場合のスタックトレースも以下のようにbar()の位置情報が記録されません(errors.New()を使ったサンプルコード)。
foo failed:bar failed: main.foo /tmp/sandbox3786666462/prog.go:11 - bar failed
『実践システム・シンキング』を読んだ
『なぜあの人の解決策はいつもうまくいくのか?』を読んだ - あぼぼーぼ・ぼーぼぼ で知ったシステム思考について何冊か読んでみたかったので購入。
Kindleのメモ機能とか対応してなかったので残念。内容は1,2,3章がシステム思考の基礎やプロセスの説明が詳細に書かれていて、ベースは『なぜあの人の解決策はいつもうまくいくのか?』による説明と同じですんなり入ってきました。
逆に違うところは、システム思考のプロセスがより詳細で実践的な説明になっています。時間軸分析から始まるのは同じですが、その後「レファレンスモード」「ステークホルダー分析」「変数抽出」と順を追って因果ループ図をつくるための準備が分かるようになってました。因果分析のところも、文章を変数に変換するところなど実践的なプラクティスが多かったです。
4章が架空のケースを追体験する章になっていて、ここが本書で説明されたシステム思考プロセスの復習にもなり一番面白かったです。
5章で定量化に触れられて、ツールの話も入ってきます。僕はループ図書くときに最近は Mindmeister を使っています。個人的にKumuより使いやすくてお気に入り。
『問いかけの作法』を読んだ
一人では出せない成果を出すために集合知(集団脳)を活用するうえで「問い」というのが大事そうだ、というのはいくつか本を読んだりしてわかったのですが、本書はその問いを現場で実践する際のプラクティスがたくさん書いてありました。問いの「組み立て」だけではなく、その前後の「見立て」「投げかけ」も含めた「問いかけ」という行為に関する実践的な本です。
実践的な本なので、実際にチームのミーティングで色々試してみています。例えば「パラフレイズ」でチームの中で定義が曖昧になっていそうな言葉にユサブリをかけたり、質問の組み立てで主語を「私たち一人一人」にして方向性を調整したり。いずれにしてもチームのポテンシャルを引き出すためにはどうしたらいいか?を考えチャレンジするのは楽しいもんです。
「問い」だけ大事にしようと思うと、アフターフォローとしてのこの辺りがおざなりになってしまうだろうと、読んでいてハッとしました。
しかし忘れてはならないことは、質問に答えてくれた相手の反応に、ポジティブなフィードバックを返すことです。
(中略)
また、ミーティングの流れのなかでは、深い価値観に迫るような質問など、相手にとって「答えにくい質問」を投げかけざるを得ない場面もあります。そのようなときには、以下の例のように、質問に向き合ってくれたことに対するポジティブフィードバックがとても重要です。
(中略)
質問に対して「良い答え」が得られたときにだけ、質問の内容に対してポジティブなフィードバックをしていると、無自覚なうちにチームはだんだんと「正解」を探すようになり、ファクトリー型のチームに後戻りしてしまいます。 p354
問いかけるのは、正解を引き出すためでもなく、相手を試すためでもなく、相手の個性を引き出してチーム全体の集合知を高めるためだと改めて意識したいですね。