あぼぼーぼ・ぼーぼぼ

のんびり生きたい

『論点思考』を読んだ

www.amazon.co.jp

論点思考とは、解くべき問題を定義するプロセスのこと。

解くべき問題を見極める、みたいな考え方は『イシューからはじめよ』『ライト、ついてますか』あたりの本でも出てくる大事な考え方。

「あれもこれも」では結局、なにもできない p80

「戦略とは捨てることなり」という言葉がある。 p83

この重要さは最近めちゃくちゃ実感していて、やらないことを決められる人は優秀だと思うようになりました。

あとは、論点のレベルの違いを意識する、とか、全体像を把握しながら目の前の仕事を行うみたいなところ。とくに、二つ上のポジションに就いているつもりで仕事をする、は具体的な思考法として機能しそうでした。

『知識ゼロから学ぶソフトウェアテスト』を読んだ

www.amazon.co.jp

文体がこんな感じだと思ってなかったのでちょっと面食らった。それからいわゆるQAと開発者が分かれているような組織で働いたことがない*1ので、それ前提で書かれている部分は想像が難しかったです。

カバレッジの説明とか、テスト書くにあたって境界値分析やディシジョンテーブルみたいな基礎が平易な文体で且つ図も交えて説明されているので、ちょっと他の人に説明するときにカジュアルに引用したい、みたいな場面でも役に立ちそう。

バグはプログラム中に平均的に散らばっているのではなく、特定の部分に偏在しています。 p9

それから複雑度の話も出てきて、先日行われたGo言語のカンファレンスであるGo Conference 2022 Springにて、Cognitive Complexity (認知的複雑度) とCyclomatic Complexity (循環的複雑度)についてのセッションあったなーって思い出したりした。

*1:厳密には少しだけあるけどそんなに記憶がない

Belong Study - BelonGo (4/26) 参加レポ

belong.connpass.com

connpassで参加する際のアンケートに質問書いたところ、丁寧に用意して答えてくれました。ありがとうございます!

Idomatic Go知らなかった〜〜〜。

dmitri.shuralyov.com

Go による API クライアント実装の勘所

docs.google.com

ディレクトリ構成は結構迷いますねー。認知負荷を考えるとたくさんファイルあるとなーと思いつつ話していた利用者側のメリットは結構納得感ありました。

ClientファクトリーのFunctional Optionsパターンは、「つい最近slack-go/slackで見たやつだ!」ってなりましたね。

aboy-perry.hatenablog.com

Belongで使用している3rd party module

docs.google.com

gRPCは以前学習がてら少し触った限りだと、コード書く分にはそこまで難しくなさそうだなーという印象。

aboy-perry.hatenablog.com

go-cmpはこの間知って便利だ〜って思ってたやつだ〜

github.com

実践!カスタムエラーとそのハンドリングについて

docs.google.com

エラーのラップ、エラーの種類を変えずメッセージだけ追加することができるんだ。

カスタムエラーのつくり、参考になるなぁ。サーバー向けエラーとクライアント向けエラー(メッセージ)分けるのナルホド。

以上です!

『イシューからはじめよ』を読んだ

https://www.amazon.co.jp/dp/B00MTL340Gwww.amazon.co.jp

4月に読んだ本のなかでは一番面白かったかも。Kindleでマーカー引いた箇所が多いです。これからの考え方の根本を変えうることが書いてあるし、一方で分析方法だったりプレゼン(アウトプット)まで後半は踏み込むのでプラクティス的なアイデアももらえます。

その中でも「問題を解く」より「問題を見極める」、「解の質を上げる」より「イシューの質を上げる」みたいな考え方は、『ライト、ついてますか』でもあったし、僕も感覚的にも納得できる部分が多かったなー。あとは実践していかなきゃです。

そもそも、具体的にスタンスをとって仮説に落とし込まないと、答えを出し切るレベルのイシューにすることができない。p52

この「スタンスをとる」って大事なんだな、って最近読む本からひしひしと感じています。

あとお気に入りの例えがあって、イシューをどう分解するかという話で、ゆでたまごのスライスの例えがあって、これ一生使っていきたいと思いました。ゆでたまごをスライスするような分解方法だとダブリもモレもなくいわゆるMECEではあるが、似たようなスライスばかりで洞察がないと。意味のある分解とは、ゆでたまごの白身と黄身を分けるように、MECEでありながら検証したいことも明確になるようなこと、みたいな話。

🥚

slack-go/slackを使ったAPI通信をモックしてテストする

Go言語でSlack APIを使ったシステムを作る場合https://pkg.go.dev/github.com/slack-go/slackが便利なのですが、テストどう書くと良いかな〜と迷いました。

github.com

先に結論

結論から言うとhttps://pkg.go.dev/github.com/slack-go/slack/slacktestパッケージを利用するとこのように記述することができます。

// ①
//go:embed testdata/sample.json
var sampleJSON []byte

func Test(t *testing.T) {
    // ②
    ts := slacktest.NewTestServer(func(c slacktest.Customize) {
        c.Handle("/conversations.history", func(w http.ResponseWriter, r *http.Request) {
            w.Write(sampleJSON)
        })
    })
    ts.Start()

    // ③
    s := slack.New("testToken", slack.OptionAPIURL(ts.GetAPIURL()))
    resp, err := s.GetConversationHistory(nil)
// 省略...

以下3つのポイントを説明します。

① Slack APIのレスポンスを模したJSONファイルを読み込む

Slackのテストには直接関係ありませんが、Slack APIのレスポンスを模したJSONファイルを用意して、Go1.16から使えるgo:embedで読み込みます。

② slacktestパッケージのNewTestServer関数を使ってモックサーバーを立ち上げる

slacktestパッケージをうまく使うことでラクにテストが書けました。

NewTestServer関数の実装は以下のようになっています。ポイントはbinder型の可変長引数を、for rangeでループしながら実行しているところです。またその下には固定でいくつかのAPIをモックしているのが見えますね。今回はここに無い/conversations.historyAPIをモックします。

func NewTestServer(custom ...binder) *Server {
    serverChans := newMessageChannels()

    channels := &serverChannels{}
    groups := &serverGroups{}
    s := &Server{
        registered:           map[string]struct{}{},
        mux:                  http.NewServeMux(),
        seenInboundMessages:  &messageCollection{},
        seenOutboundMessages: &messageCollection{},
    }

    for _, c := range custom {
        c(s)
    }

    s.Handle("/conversations.info", s.conversationsInfoHandler)
    s.Handle("/ws", s.wsHandler)
    s.Handle("/rtm.start", rtmStartHandler)
    s.Handle("/rtm.connect", RTMConnectHandler)
    s.Handle("/chat.postMessage", s.postMessageHandler)
    s.Handle("/conversations.create", createConversationHandler)
// 省略

binder型は何なのか見にいくと、Customizeインタフェースを引数に持つ関数だということが分かります。

// Customize the server's responses.
type Customize interface {
    Handle(pattern string, handler http.HandlerFunc)
}

type binder func(Customize)

なので、モックしたいパスと、そのレスポンスをbinder型に合うようにNewTestServer関数に引数として渡してあげることで、モックサーバーが作れます。

ts := slacktest.NewTestServer(func(c slacktest.Customize) {
    c.Handle("/conversations.history", func(w http.ResponseWriter, r *http.Request) {
        w.Write(sampleJSON)
    })
})
ts.Start()

③ モックサーバーのURLを使ってslack.Clientを作成する

slackパッケージのNew関数でslack.Clientを作成する際に、引数にオプションを指定します。

slack.New関数は以下のような実装になっています。options引数はOptionの可変長引数で、for-rangeで実行していることから何かの関数だということがわかります。

// New builds a slack client from the provided token and options.
func New(token string, options ...Option) *Client {
    s := &Client{
        token:      token,
        endpoint:   APIURL,
        httpclient: &http.Client{},
        log:        log.New(os.Stderr, "slack-go/slack", log.LstdFlags|log.Lshortfile),
    }

    for _, opt := range options {
        opt(s)
    }

    return s
}

Optionは以下のように定義されており、Clientのポインタを引数に持つ関数です。その下のコードを見ていくと、Optionを返すユースケースごとの関数がいくつか用意されています。今回は指定したURLのレスポンスをモックしたいので、OptionAPIURL関数を利用します。

// Option defines an option for a Client
type Option func(*Client)

// OptionHTTPClient - provide a custom http client to the slack client.
func OptionHTTPClient(client httpClient) func(*Client) {
    return func(c *Client) {
        c.httpclient = client
    }
}

// OptionDebug enable debugging for the client
func OptionDebug(b bool) func(*Client) {
    return func(c *Client) {
        c.debug = b
    }
}

// OptionLog set logging for client.
func OptionLog(l logger) func(*Client) {
    return func(c *Client) {
        c.log = internalLog{logger: l}
    }
}

// OptionAPIURL set the url for the client. only useful for testing.
func OptionAPIURL(u string) func(*Client) {
    return func(c *Client) { c.endpoint = u }
}

モックサーバーのURLはts.GetAPIURL()で取得できるので、OptionAPIURL関数の引数に渡してあげます。

s := slack.New("testToken", slack.OptionAPIURL(ts.GetAPIURL()))

あとは、モックサーバーで指定したパスに該当するslack.Clientのメソッドを呼ぶことで、モックされたレスポンスを受け取ることができます。

s.GetConversationHistory(params) //paramsは別途定義が必要です

Go Code Review Commentsを読んだ

Go Code Review Comments · GitHub

Effective Go - The Go Programming Languageとどっち先に読もうかなと思って、分量が短いのでGo Code Review Commentsを読んだのですが、冒頭で「Effective Goの補足として使えるで」って書いてあったので結局両方読むしかなさそうです。

全部書いてもしょうがないので一部ピックアップすると

Error Stringは、「エラー文字列は他の文脈の中で表示されるので(基本)大文字で始めたり句読点付けたりしない。ただログは行指向で他のメッセージに結合されることがないのでOK。」と書いてあってなるほど〜てなりましたね。

Import Dotはそもそもこの仕様を知らなかったなー。

Initialismsは、言語によってこの辺ルール変わりますよね。GoはSwiftと同じ感じかな?

Line Lengthは、組織だったりプロジェクトで一定ルールは決めたほうが機能しそうだなって一瞬思ったけど、過去違う言語で同様の議論があったあと決まったルールで自分が結構見づらくなってしまった経験があるので、なかなか難しいな。

Receiver Typeは、まさに自分も考えることが多いので気になってたところでした。Go Code Review Commentsにはケースごとに値が適切かポインタが適切か書かれていてリファレンスとして使えそうです。

以上です!

『問いかける技術』を読んだ

https://www.amazon.co.jp/dp/B00PY6XVAQwww.amazon.co.jp

「謙虚に問いかける」ことの大事さが書かれていました。

問いかけるというのは、自分で話すのではなく、相手に話させるとでもいうような感じ。ついつい誰かに対して自分の意見を表明したくなったりするけど、信頼関係を構築するには相手の話を引き出す、聞くことが大事だと。

謙虚に、というのは「そのとき必要な謙虚さ」。その問題を今解決するために自分はその人を頼らなければならない、みたいなことを自分で認め、相手にそれを伝えるということ。

聞き方が上手い人っていますよね。ここでいう上手いは聞かれても詰められてる感じがしないとか、答えやすいとかそういう類です。でもそれが謙虚さから来てるのかといえば、どちらかというと表情だったり声のトーンだったり、もっと言うとその人との関係性からきてるんじゃないかなー。

僕は何をするにも上手くいったり楽しくなったりするかどうかはお互いの関係性で決まる、みたいな考えを持ってるので、関係構築の視点から見ると多少理解できる部分もあったかな。