データ駆動 + Test::Unit

大量のログデータの処理の前段階として、fluentプラグインで処理させるときのこと。

fluentプラグインのテストはTest::Unitで書くのが主流なようで、Test::Unitを書いている。 ただ、プラグインは大量のデータを処理するので、テストケースの量も多くなってしまう。

何かいい方法はないかと思っていたら、データ駆動でのテストが書ける機構が備わっているのがとても便利だと感じた。

data(
  "case" => ["expected", "target test data"],
  ...
)
test "it's a data driven test" |data|
  expected, target = data
  assert_equal(expected, SUT(target))
end

test メソッドを使ったDSL syntaxでもブロック引数に渡せるのでこのように書ける。 テストするデータ量が多いと、 each を回すような書き方になって見通し悪くなるが、この書き方だととてもスマートに表現できる。

やるじゃん、Test::Unit。

Redis 3.0.0

https://raw.githubusercontent.com/antirez/redis/3.0/00-RELEASENOTES

正式にRedis Clusterをサポートするstable版がリリースされたのでコード読んだりぽちぽち触ってみている。

  • クラスタリングとは全然関係ないのだけどdict.cはHash関数つくりたくなるときにちょいちょい参考にしたりしていたので、綺麗に掃除されてて嬉しいナァ
  • パフォーマンス改善されている模様(BITCOUNT、集計処理とかでよく使うので嬉しい)
  • cluster.cは結構前からいたのだけど、まぁようやく陽の目を見たという感じで感慨深い
  • slot(最大16384)に分割してkeyを管理する、という戦略はRedisのデータ構造からしてみると仕方がないという感じではあるのだけど、HitしなかったらMOVED返すんじゃなくて勝手にクエリを投げ直して欲しい。client側でkeyからslotを調べてノードを判定するというのは一度書けばいいのだけど、まぁなんとなくクラスタリングしてない感じがしててしょっぱい

というわけで絶賛redis-rbを対応している途中です。

コードレビューするときの視点

普段、Ruby(RailsSinatraといったWAFにおける)のコードレビューするときに、 こういった事を念頭においてレビューする、というものが自分の中だけで閉じている気がしているので、 文章にしてリスト化しておくことでコードレビュー時に、自分自身も漏れが少なくなり、相手にもレビュー箇所を伝えやすいんじゃないかと思案してみた。 静的解析(Rubyであればrubocopやhoundなどを使う)して指摘できる部分は極力はぶいたものとなっています。

TODO: パッと思いついたことで網羅できている感じはないので、増やしていく。

アプリケーションが解決するドメインの視点

ドメインについてレビューすることが一番重要であると考えているので時間をかけるところだったりします。 つまり、問題を解決する戦略についてレビューするところ。

  • 解決しようとする問題が明確になっているか
    • けっこうあったりする。たとえば、p-rにいろいろ含まれていたりするのがその傾向にあって、レビュイーの人が問題を明確になってなかったり
      • やんわり、イシュー分割しましょうと促す
  • 解決する問題に対して粒度が適切かどうか
    • めっちゃ頑張ってオーバースペック過ぎたり、逆にもうちょっと汎用的に作っておいたほうが良かったり

プログラミングという視点

プログラミング言語特有のお作法だったりするものがあると思うのだけど、この3つは必ず見ます。

  • 条件文が明確かどうか
    • if文やcase文の条件文が正しいかどうか
  • データ構造が適切かどうか
  • 各名前付け(クラス名、モジュール名、メソッド名、変数名)が見通しがよいか、妥当かどうか

Ruby固有の視点

  • Enumerableなオブジェクトに対する操作が冗長になっていないかどうか
    • たとえば、Arrayの要素を足し合わせる時に、

      sum = 0 array.each { |v| sum += v }

      と書くより、

      array.inject(:+)

      と書いたほうが見通しがよくなる(と思っているのはinjectおじさんだからかも)

    • 冗長かな、と思ったらEnumerableのメソッドをひと通り確認する

Rails(やSinatra)、各gem固有の視点

モデル(ARを使う場合)

  • N+1問題が起きていないかどうか
    • includes を使うことでだいたいにおいて回避可能
  • LEFT OUTER JOINが起きていないかどうか
    • 基本的にLEFT OUTER JOINを発行するくらいならクエリを分けたほうがロジックの見通しがよくなることが多い(NULLを含めるのは避けたい)
      • その結果N+1問題が発生するのは本末転倒なので、その場合、in句でしっかり1クエリで取れるようにする
  • scopeの使い方は妥当かどうか
    • scope定義してみたはいいが1箇所しか使わない、という場合が多いことがよく見受けられる。意味のある名前にすることでロジックの見通しがよくなるなら構わないが、where句をその場で書いたほうが見通しは良くなることが往々にしてある。
  • >= のような演算の場合に、べたにSQLを書かないで、arel_table を使っているかどうか

コントローラ

  • たとえば、before_action 等で params に値を突っ込んでないか(個人的にジャグリング問題と名づけている)
  • トランザクション境界は明確になっているか

ビュー

  • ロジックを書きすぎていないか
    • helperに分離する

テスト(RSpec + FactoryGirl + RSpec mocksを使う場合)

  • letとlet!は使い分けているかどうか
  • mockとstubは使い分けているかどうか
  • FactoryGirlでテストデータを定義するときにPK(特に、サロゲートキーの場合)を固定の値にしていないかどうか

gem

  • メンテナンスされていないgemを採用してないかどうか
    • 採用を考えている時点で最後のコミットが1年前くらいだとプロダクトに使うものとしては警鐘を出す
      • どうしてもそのgemを使わないと、問題を解決するのに遠回りになったりする場合、そのgemのメンテナになる意気込みで採用する

Golangでエレガントだと思うこと

@kana さんとハッカソンしていて、Golangのどこが好きか? と聞かれた時にうまく説明できなかったのでまとめておきます。 よく、Golangはgoroutineとchannelが取り上げられることが多いと思いますが、 それよりも、僕がGolangGolangたらしめていると考えているものとして、TypeとInterfaceの機構です。 Golangの思想は、他の言語ではこうなんだけど? ということを踏まえてFAQで簡単に説明されています。

http://golang.org/doc/faq#types

例えばFAQのTypesの章にあるサンプルコードで示されているように、

package main

import "fmt"

type Fooer interface {
    Foo() string
    ImplementsFooer()
}

type Bar struct {}
func (b *Bar) Foo() string { return "bar" }
func (b *Bar) ImplementsFooer() { fmt.Println("implements bar") }

type Buzz struct {}
func (b *Buzz) Foo() string { return "buzz" }

func foo(o Fooer) {
    o.Foo()
}

func main() {
    bar := &Bar{}
    fmt.Println(bar.Foo())
    bar.ImplementsFooer()
    foo(bar)

    buzz := &Buzz{}
    fmt.Println(buzz.Foo())
    foo(buzz) // <- コンパイルエラー
}

Barは、関数Foo()ImplementsFooer()を実装しているのでBarFooerを充足している、という表現になります。 Interfaceはよく、「Interfaceを実装する」という表現で使われます。 そのため、言語の文法レベルだと予約語を使ったり何かしら型やクラスに対して宣言するものが多いと思います。 Golangの場合だとInterfaceで定義されている関数を実装しているだけで、そのInterfaceとして振る舞えるので、 「Interfaceを充足している」という表現が適切だと思っています(FAQではsatisfyという英単語が使われています)。

サンプルコードに戻ってみると、関数foo()は、引数に型Fooerを要求しています。 つまり、Fooerを充足しているものであればなんでも渡せるわけです。 逆に、Fooerを充足していなければそこでコンパイルエラーとなります。 型BuzzFoo()は実装していますが、ImplementsFooer()を実装していないことをすぐに検出できます。 コンパイル時に漏れがないことがわかるので安全です。

よくあるDuck Typingのサンプルコード(Wikipediaよりhttp://en.wikipedia.org/wiki/Duck_typing#Implementations)を書いてみると:

package main

import "fmt"

type Duckable interface {
    Quack()
    Feathers()
}

type Duck struct{}
func (d *Duck) Quack()    { fmt.Println("Quaaaaaack !") }
func (d *Duck) Feathers() { fmt.Println("The duck has white and gray feathers.") }

type Person struct{}
func (p *Person) Quack()    { fmt.Println("The person imitates a duck.") }
func (p *Person) Feathers() { fmt.Println("The person takes a feather from the ground and shows it.") }

func InTheForest(duck Duckable) {
    duck.Quack()
    duck.Feathers()
}

func main() {
    donald := &Duck{}
    john := &Person{}
    InTheForest(donald)
    InTheForest(john)
}

静的型付けにおけるDuck Typinpの一設計をエレガントな実装に落とし込めていると思ったので、僕のなかでGolang熱が高まっているわけですね。

octopusに待望のsharding + replication構成がサポートされそう

Railsで、master/slave構成を作る場合や垂直分割する場合などは、octopusに頼る場合が多いです。 ただ、sharding + replicationの構成はずっとpendingのままで、僕も何度かトライしたことがあるのですがうまい設計を練ることができずにそのまま指を加えたまま時が過ぎていました。 が、最近octopusにsharding + replication構成をサポートするPRがありました。

https://github.com/tchandy/octopus/pull/239

ちょっと追いかけてどうなっていくのか注目です。

追記

大きく改変もなくマージされました。これでoctopusでsharding + replication構成を簡単に構築できます。

JSONを扱うときにkeyとfieldを対応づける

Goの、encoding/jsonUnmarshal で、例えばJSONデータのkeyが小文字の場合、自動でcapitalizeしてくれる。

pakcage main

import(
  "encoding/json"
)

type A struct {
  A_Id int
  Field string
}

func main() {
  jsonBlob := []byte(`
    {"a_id": 100, "field": "field"}
  `)
  var a A
  json.Unmarshal(jsonBlob, &a)
  fmt.Printf("%+v\n", a)
}

ここで、JSONの要素にsnake_caseなa_idがkeyになっている。 Goが変数や、構造体のフィールド名はCamelCaseを推奨しているので、CamelCaseなフィールド名にしたい。

type A struct {
  AId int
  Field string
}

Goのドキュメントを読んでいると、

type A struct {
  AId int `json:"a_id"`
  Field string `json:"field"`
}

と書いておくと、UnmarshalしたときにKeyに紐づくフィールドを認識してくれることを知る。 文法として、Goのstructはフィールドにtag(型はstring)を付与でき、この情報はreflectで取得できる

func main() {
  a := A{}
  t := reflect.TypeOf(a)
  field := t.Field(0)
  fmt.Println(field.Tag.Get("json")) //=> a_id
}

便利。