2014年3月29日土曜日

Golang Cafe #22を開催しました。

Golang Cafe #22を開催しました。

ソースコードはgithubにありますので、そちらをごらんください。

今回は、私がインフルエンザにかかってしまって、少し時間が取れたので、じっくり準備をすることができました。(今回のテーマにする予定はしていなかったのですが…)

今回のテーマは、「OAuthのライブラリをリファクタリングする」というのをテーマにしてみました。私は出題者なので、出題の背景を書き残しておきます。

前回までに、Twelve Go Best Practicesを読み、ある程度、コードを書くためのコツを掴んだので、それを実践する事ができないかというところから考えました。
(Golang Cafe自体、私がコードを書く時間をネタで募集をしたところから始まっているので)
資料と標準パッケージの使い方はある程度やってきたので、次は、「人のソースコード」を読んでみるという方向性で考えました。公式サイトには、候補として、vitessと、dockerのソースコードを読むというのを挙げていましたが、以前、OAuth2をやったので、https://github.com/mrjones/oauthを読むのも良いかと思い、先行して読み進めていました。ただ、動きをみないとわからない点も多かったので、力づくで、最低限動くコードを作ってみた所、いい感じにダメコードができたので、ちょうど良いのかな?と思ったので設定しました。

ただ、ソースコードを修正するということをしようと思うと、まず、読んでもらわないといけないのもあって、今回は、最初のコードを読み、出題者(私)に質問するという内容になりました。

個人的には、以下の点について議論ができそうな気がしているので、良いテーマなのかな?と思っています。

  1. ライブラリ開発ということで、APIの設計(?)
  2. Twelve Go Best Practicesの適用
  3. ランダム文字列が生成される部分についてのテスト
  4. 通信が発生するので通信に関するテスト
  5. 必要ならMockを使ったテスト
それから、次回は、その次の京都(祝半年)のための準備もしておきたい気がしていますが、そういう時間が取れれば良いですが…。


2014年3月19日水曜日

Golang Cafe #21を開催しました。

Golang Cafe #21を開催しました。今回は、毎週日曜日に開催しているのですが、香川で「春のGoogle勉強会」があるということで、今回の参加者が全員参加する、しかも、開始時間が16:00からということで、その前にやってしまって、日曜日をゆっくりしようということになりました。

メモと動作確認したソースコードはgithubにありますのでごらんください。

では、続きです。

その14

 APIで非同期にするのは避けよう!


  • 順番にAPIを利用したい時はどうすればいいのか?
    • 同期APIを用意すると、並列実行が簡単に呼び出せる。
    • (※runtime.GOMAXPROCS(2)とかすると、順番がおかしくなるが、呼び出し順をどうするか?という議論ではない!)

ここでの議論は、API側で非同期の処理を実装するのではなく、同期APIにしておいて、非同期で実行させるかどうかは呼び出し側の都合で変えられるようにしておくのがいいということでしょう。

その15

  goroutineと通信するための構造体のメンバのChannelか、単体のChannelを使え!

Channelの動作について(何度も復習するところですが、私の認識は以下です。)
chanのバッファが0の時は、他のGoroutinesがChannelを受け取らないとGoroutineがブロックされる。chanのバッファが1の時は、1個はバッファに置けるので、Goroutineがブロックされず、突き抜ける。

goroutineを使った時のgoroutine間の通信をどうするか?という話題で、channelを使って、データのやりとりを行えばいいということです。

で、ここで議論になったのは、以下のコードです。(抜粋ですので、全体は原著の資料か、githubの資料をごらんください。

func (s *Server) Stop() {
    fmt.Println("server stopping")
    s.quit <- true
    <-s.quit
    fmt.Println("server stopped")
}

s.quit<-trueをして、すぐに、<-s.quitでChannelから値を取り出しているように見えますが、Channelはバッファ0のChannelですので、s.quit<-trueした時点で、goroutineはブロックされます。(他のgoroutineでChannelから抜き取るまで。私はスレッドがブロックされるという表現ではなく、goroutineの切り替わりと言っています。)したがって、別のgoroutineが終了処理を行って、完全に終了した後に<-s.quitが実行されることになります。

その16

Buffered Channelを使って、goroutineのリークを避けよう!

  1. 何度も繰り返しになりますが、channelに書き込むと、goroutineはブロックされます。
  2. goroutineはchannelの参照を握っているので、channelがガベージコレクトされることはないことになります。
  3. goroutinesの切り替わりは「明示的なブロック」で発生するので、今回のようにバッファがないchannelだと、goroutineの切り替わりが発生するので、goroutineの処理が終わる前にchannelの受信待ちが終了してしまうことがある。資料の例だとlocalhost:8080がエラー応答になった時に、エラーを検知して終了する。しかし、google.comの処理がまだ残っているので、結果としてリークしたことになる。

その17

quit chanを使って、goroutinesのリークを避けよう!

close(quit)を行うと、channelに受信しない事が明示されます。
その16と同じように、先にエラー応答が発生した時にそのまま終了するのではなく、close(quit)することで、登録されているgoroutineはすべて、case: <-quit:が実行されます。したがって、全て、終了させる事ができます。
goroutineの切り替わりを意識したコードを書くことで、channelのリークを防ぐことができるのでしっかり意識しましょう。
chan(struct{})はクローズ(終了)用なので、誰もChannelに送信できないようにするために、空の構造体にしているものと思われます。

次回は明確にテーマを決めなかったのですが、何かやります。

2014年3月9日日曜日

Golang Cafe #20を開催しました。

Golang Cafe #20を開催しました。

今回は、前回に引き続き、Twelve Go Best Practicesの13ページから読み進めました。
プラクティスのメモはgithubにありますので、ごらんください。
(今回は、新しいコードを書くことはしていないので、メモが追記されただけです)

以下、今日のまとめです。資料を見ながら読み進めてみてください。

その7

重要なコードは先頭に書くこと!


  1. ライセンス情報、ビルドタグ、パッケージドキュメントを記載する。
  2. import文は、(gofmtコマンドを実行すればいい気がするが…。)標準パッケージを最初に記述し、1行開けて、go getで取得したパッケージを記述する。
    1. gofmtは自動的にアルファベット順に並べてくれるが、1行あけると、並べ替えの適用を受けなくなります。
    2. gofmt sample.goだと、標準出力に出力されてファイルが書き換わらないので、gofmt -w sample.goと-wオプションをつけよう!
  3. 重要な型から始まり、ヘルパー関数などで終わるようにする。
  4. ファイル名は型に合わせたものにした方が良い!
    1. という結論に至りました。goの標準ライブラリのソースコードを見る限りだと、最初にtypeの宣言があり、typeの名前とファイル名が一致していたので。
    2. 機能ごとにファイルを分けて、わかりやすくするべきなんだろう。

その8

パッケージ名の前に連想させるようなドキュメントを書くこと!

godocに出力されるものは、ドキュメントを書きましょう!

その9

短い事はいいこと。


  1. 意味がわかって、できるだけ短い名前をつけるようにしましょう!
    1. "MarshalWithIndentation"よりも"MarshalIndent"という名前が好ましい。
  2. 識別子にパッケージ名が含まれる場合は、つける必要はない!
    1. encoding/jsonにEncoderがあるが、JSONEncoderではない。
    2. 参照される時は、json.Encoderとなるから、必要ないでしょう?
      1. 確かに、Goでパッケージのコードを書く時は、json.JSONEncoderだと命名としてブサイクなのかな…?

その10

1つのパッケージを複数に分けるべきか?


  1. コードの長いファイルは避けよう。
    1. net/httpパッケージは15734行を47ファイルに分けている。
  2. コードとテストは分けよう。
    1. net/http/cookie.goとcookie_test.goみたいに。
    2. そもそも、go testが動かないはずなので、ほぼ強制的にそうなるではないかと思われる。(go testは**_test.goを実行するので)
  3. パッケージドキュメントをdoc.goに分けておくといい。
    1. 複数のファイルがある時に、どこにパッケージドキュメントを書くか迷うので、わかるようにdoc.goを作り、そこに書く。標準のパッケージもdoc.goはpackageの宣言と、ドキュメントの記述しかない。
    2. ファイルが1つの時はそのファイルの先頭に書けばいい。


その11

go getができるようなパッケージを作りましょう。

ネットワークプロトコルを定義するようなパッケージは使われるかもしれない。
mainから始まるコマンドは再利用されないかもしれない。


その12

必要なものだけを求める。

Gopher型を使います。
    
以下のメソッドを定義した。
func (g *Gopher) WriteToFile(f *os.File) (int64, error) {

しかし、テストがしにくい!そこで、インターフェイスに書きなおした。
func (g *Gopher) WriteToReadWriter(rw io.ReadWriter) (int64, error) {

必要最低限のインターフェイスを使ってメソッドを定義すべきだ。
このメソッドは、ファイルへの書き出し機能だけなので、Readerの機能は必要がない。したがって、必要なものを引数に渡すように求めること!
func (g *Gopher) WriteToWriter(f io.Writer) (int64, error) {

ここでは、余計なものを指定できると、よくわかっていない人が想定外の引数を指定してしまうのを防ぐためにもこのプラクティスは必要!という結論になりました。
(が、それぞれ流儀があると思うので…。と議論がおかしい方向になる前にやめました)

その13

独立を保つ!パッケージごとに独立させておく。


  1. parserは解析のみ。drawerは描画のみ。pngへのエンコードはエンコードのみにする。など。
  2. 依存するパッケージをimportするのではなくて、interfaceを使って、Parse機能を持った関数を受け取り、呼び出すようにしよう!
  3. 具体的な型を使うよりも、インターフェイスを使った方がテストが簡単に作れる。
  4. TestFunc型はEval()を実装しているので、interfaceを実装していることになる。

この時点で12個じゃない!と思いましたが、資料の最後に12個のリストがあるようです
…。が、12個じゃないよな…?という気がします。

引数で受け取った関数を呼び出す形にして、他パッケージの依存を防ぐというのは良いのだけど、引数で指定する関数の中身は呼び出し側が実装するわけなので、「誰でも新たな処理を追加できる」というのが良いのか悪いのか…。という意見が出ました。
これも奥が深い話になる(というか、プロジェクトのメンバー間の意識統一かな?)と思ったので、やめておくことにしました。

ということで、次は、並列処理などのパターンが記述された、25ページ以降を読み進める予定です。

2014年3月3日月曜日

Golang Cafe #19を開催しました。

Golang Cafe #19を開催しました。

今回は、Twelve Go Best Practicesの資料を読むということで、(恐らく)その6まで読み進めました。
ソースコードと、プラクティスのメモはgithubにありますので、ごらんください。
commitログと履歴を遡ればどういう変化があるかを見ることができるようになっています。

以下、今日のまとめです。資料と共に読み進めて下さい。

その1
深いネストは避けよう!

Goは例外機構がないので、全ての呼び出しについて、エラーチェックが必要になります。
(エラーチェックじゃなくても、戻り値を受け取った後に何らかのif文が必要になる)
そこで、全ての(仮にここではerrorをチェックするとして)エラーをif文で囲むとネストが深くなりやすくなります。
では、どうするかというと、”エラーがある場合は、即return”ということになります。
こうすることで、ネストが深くなるということが避けられます。

その2
構造体に必要なオブジェクトを格納することで、シンプルなコードに!

その1で”エラーがある時は即return”としたのですが、「毎回チェック」をする必要はないわけです。というと、errorを構造体のメンバにしておき、戻り値をメンバに退避しておきます。
そして、メソッド呼び出しの最初に、メンバのerrorがnilかどうかをチェックし、nilでなければ、即終了させる。という形にします。
これにより、呼び出し側でエラーを確認する必要がなくなるので、シンプルな呼び出しにすることができます。

その3
型で処理が決まる場合は、型で処理を分岐させましょう!

型によって、処理がわかれる場合に、switch v.(type) {}を使うと、型の判定を行うことができます。実は、標準パッケージだと、よく書かれている記述で、ユーティリティを作るのに非常に便利です。(Go以外の言語で型による分岐って無い気がするので、独自のパターンかもしれません)
引数は、interface{}で受け取ることがポイントです。

その4
switch文の:=を使え!

その3で型による分岐が出てきましたが、実際に処理を進めるに当たって、Type Assertionをする必要があるのですが、v.(type)については、Goの構文として短縮構文にすると、case文は型で判定し、値は短縮構文の時にType Assertionされているという状況を作ることができます。したがって、型によって、Type Assertionをする必要がなくなります。

その5
全て書くか、何も書かないかのどちらかにする。

データベースのトランザクションと同じような感じですが、例えば、通信プログラムなど、「途中まで成功」というのは都合がよくありません。そこで、一旦、bytes.Bufferに送信電文のデータを書き出しておき、Flush()メソッドなどで、一括で送信するというようにしておくと、良いですよという話。
しかも、このソースコードは、再帰呼び出しを使っていて、エラーのチェックも1か所に集約されています。が、これは日本(の中小企業(?))だと怒られるのではないか?という話になりました。慣れないとすぐには気がつかない…w

その6
リクエストを処理し、エラーを返す関数と、エラーを受け取り、エラーを処理する関数に分ける!

ここから、Webサーバの例になりますが、複数の処理に対するエラーの処理をどうするか。というのがテーマだと思います。(実は、私もGAE/Gで同じ失敗をしているのですが…)
毎回if文でエラーチェックして、エラーレスポンスの処理を書くとイケてないコードになります。(これは、私も疑問に思っていた)そこで、エラーを返すだけの主処理と、主処理を呼び出し、エラーチェック後、レスポンスを返す、エラー処理関数を用意します。
すると、エラー処理がきれいに集約されてきれいになります。
また、errorを変数で宣言しておくと、エラーの判定により、レスポンスを変える事ができるので非常に便利です。ただ、資料のfmt.Errorf()は、内部でerrors.New()をしているので、一見便利に見えますが、エラー判定ができなくなるので、使うと後で困ることになるので使わないようにしようということでした。

今回は、資料の12ページまで読み進めたので、次回は、13ページから読み進める予定です。