Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

筋肉によるGoコードジェネレーション

2,533 views

Published on

github.com/lestrrat/go-slackを主軸として、筋肉(fmt.Fprintf)を使ったコードジェネレーションを紹介

Published in: Internet
  • Be the first to comment

筋肉によるGoコードジェネレーション

  1. 1.     による Go コードジェネレーション kamakura.go #3 2018/1/20 株式会社 HDE 牧大輔 (@lestrrat) Brute-force Code Generation In Go 筋 肉
  2. 2. • @lestrrat • Perl/Go hacker, author, father • Author of github.com/peco/peco • Organizer for builderscon
  3. 3. agenda 1. go-slack 2. 私流コードジェネレーション 3. コードジェネレーションtips 4. 反省とまとめ
  4. 4. <宣伝> http://blog.builderscon.io/entry/call-for-sponsors-2018 スポンサー募集中!!!
  5. 5. go-slack
  6. 6. クライアント • APIが完全自動生成 (endpoints.jsonを編集) • APIが完全にGoogle風味 (google.golang.org/api) 自動生成前提 →大量のAPIが存在する時にはとても重要
  7. 7. 自動生成:なぜ? • 以前github.com/nlopes/slackにPR送ったりしていたら • ひとつの仕組みを直すのに20ファイルを手動で変更する 必要があった
  8. 8. Google(-ish) API client.Chat(). PostMessage(channelID). Text(“Hello, World!”). Do(ctx)
  9. 9. Google(-ish) API client.Chat(). PostMessage(channelID). Text(“Hello, World!”). Do(ctx) まず「サービス」を取得じゃ! サービスは論理的なグルーピングの ことじゃな
  10. 10. Google(-ish) API client.Chat(). PostMessage(channelID). Text(“Hello, World!”). Do(ctx) メソッドを呼ぶための 「Callオブジェクト」を作成するぞ! 必須引数もここで指定じゃ。
  11. 11. Google(-ish) API client.Chat(). PostMessage(channelID). Text(“Hello, World!”). Do(ctx) Callオブジェクトに任意引数を 付与していくぞい。
  12. 12. Google(-ish) API client.Chat(). PostMessage(channelID). Text(“Hello, World!”). Do(ctx) 最後にDoでようやく リクエスト送信じゃ!
  13. 13. サーバー • モックサーバー・プロキシサーバーを完全自動生成! • Slackへの通信をインターセプト! • ボットが無駄にポストしなくなる!
  14. 14. Previously… development instance chat.postMessage api.slack.com Multiple Messages production instance chat.postMessage
  15. 15. slaproxy development instance auth.test api.slack.com Only prod production instance chat.postMessage chat.postMessage slaproxy
  16. 16. slaproxy development instance auth.test api.slack.com Only prod production instance chat.postMessage chat.postMessage slaproxy 副作用のないメソッド だけ本番にパススルー するんじゃ!
  17. 17. Kubernetesと相性良い! • 本番Service (type=ExternalName, api.slack.com) • 開発Service (type=ClusterIP, slaproxy Pod) • どちらもクライアント側からは slack.$namespace.svc.cluster.local に見える
  18. 18. コードジェネレーション
  19. 19. Goでコードジェネレーションしてる・したことある人?
  20. 20. 手法は? • ASTを作成して printer.Fprintf? (このドMがッ!) • Perl/Python/Ruby/PHPから生成? • text/templateで生成?
  21. 21. 俺のおすすめ?
  22. 22. 筋 肉
  23. 23. fmt.Fprintf
  24. 24. • schemalex/schemalex • go-jsval • go-libxml2 internal/cmd/gen* • go-slack • go-msgpack • go-ical • go-jsschema 拙作のライブラリではinternal以下にコマンドを 仕込んでおき、go generateから呼んでる
  25. 25. • schemalex/schemalex • go-jsval • go-libxml2 internal/cmd/gen* • go-slack • go-msgpack • go-ical • go-jsschema 拙作のライブラリではinternal以下にコマンドを 仕込んでおき、go generateから呼んでる internal 以下なら godocには載らないんじゃ!
  26. 26. endpoints.json 定義は適当なJSONファイルに放り込んである
  27. 27. go generate あとは go generateから呼べるようにしておくだけ
  28. 28. go generate あとは go generateから呼べるようにしておくだけ サーバもクライアントも 同じ定義から作れるので とっても便利じゃ!
  29. 29. .oO(こいつ、なんでテンプ レート使わないんだろ…?
  30. 30. 利点:ただのGoコード • 他のGo資源を使える • 分岐・再利用ブロックの制御が簡単
  31. 31. text/templateの限界に 挑戦する必要がない • 「は!今のコンテキストからはグローバルな変数に直接アクセスできない…」 • 「あれ、この文字列を処理する関数をちょっと入れたいだけなのに… なに? FuncMapだぁ?」 • 「補助関数・変数一杯いれたけど、一体今どれが有効なのかわからん!」 利点:
  32. 32. text/templateの限界に 挑戦する必要がない • 「は!今のコンテキストからはグローバルな変数に直接アクセスできない…」 • 「あれ、この文字列を処理する関数をちょっと入れたいだけなのに… なに? FuncMapだぁ?」 • 「補助関数・変数一杯いれたけど、一体今どれが有効なのかわからん!」 利点: 個人的には、簡単な テンプレート以外で使う text/templateはクソじゃと 思っておる!
  33. 33. 利点:Goなら整形も簡単 • 改行だけ気をつけていれば細かいところは format.Sourceが良きように計らってくれる
  34. 34. コードジェネレーション Tips 筋 肉
  35. 35. Tip: Optionパターンが便利 • 任意の0~N個の引数を好きな順番で与える事ができる • メソッドチェーンと同じく「メソッドの正しい引数の順 番」を考えるより楽 • 必須でない引数に便利 slack.New( token, // 必須なので、Optionではない slack.WithDebug(true), // デバッグ有効化 slack.WithClient(&http.Client{}), // http.Clientをカスタマイズしたい )
  36. 36. Tip: メソッドチェーンは便利 • メソッドを呼ぶと、呼び出したオブジェクトを戻り値と して返す • メソッドの呼び出し順を(ほとんど)考えずに済む •「メソッドの正しい引数の順番」を考えるより楽 slackClient.Chat(). // go-slackの場合… PostMessage(…). // ここまでは必須。 Attachment(…). // ここから任意引数を… EscapeText(false). // メソッドチェーン。 Do(ctx) // 最後にDo()を呼ぶと発火
  37. 37. Tip: メソッドは”.n”から if (addMethod1) { fmt.Fprintf(“.nMethod1()”) } if (addMethod2) { fmt.Fprintf(“.nMethod2()”) }
  38. 38. • 以下はパース失敗する Foo() .Method1() .Method2() • 以下はパース成功する Foo(). Method1(). Method2() Tip: メソッドは”.n”から “n.” “.n”
  39. 39. Tip: 定義対象は必ずソートする • 元々リストから生成するならやらなくてもOK • マップから生成するクラス名、メソッド名、etc… • 順番が安定しないと、diffがおかしなことになる
  40. 40. Tip: 無駄に思えても、1行ずつ • 一気にPrint(複数行)とかしたくなるけど、我慢 • あとで何かを差し込みたくなったり、条件分岐する時に そのほうが楽
  41. 41. Tip: 改行は最初に • ブロックの開始を「予測」するより、ブロック開始時に2 個改行を入れるのが簡単
  42. 42. Tip: 生成されたファイル名を揃える • 後悔するので、必ずファイル名から生成されたことがわ かるようにしましょう • “xxxxx_gen.go” 等で揃えるとよい
  43. 43. Tip: CIでgit diffをチェック • 生成されたファイルがチャントコミットできてるか確認 • 結構漏れるので注意 • トラックされていないファイルは “git ls-files -- others —exclude-standard" https://github.com/lestrrat/go-slack/blob/master/scripts/check-diff.sh
  44. 44. 反省
  45. 45. 決してわかりやすくは無い… • 正直に言えば、コードジェネレータを「誰でも」読み解 けるかというと疑問が残る • 必要な時だけ行うべき
  46. 46. それでもやるべき瞬間(とき) • 今回のような機械的なコードの羅列がある場合 • そして、とにかくその数が多い • 「自分がイチイチ変更していくと絶対エンバグするぞ」 という時
  47. 47. まとめ
  48. 48. fmt.Fprintfも結構使える • テンプレートの書き方で悩むくらいなら、悩む時間 がもったい無いので筋肉で書けば良いのではないか (言うほど難しくない) • 慣れれば生成されたコードが心の眼で浮き上がって くるようになります
  49. 49. おまけ
  50. 50. ジェネリックス? • 欲しい時もあるけど、こういう時ではない • 長年やってきて、凝縮されてエレガントなコードよ り、機械的に量産できるシンプル・愚直なコードの ほうが良い気がしてる • というわけで今のGoでも充分

×