Upgrade to Pro — share decks privately, control downloads, hide ads and more …

令和に始めるcode generation

0e8279dc67437cb9eb0562620d9be061?s=47 もん
February 13, 2020

令和に始めるcode generation

2020/02/13 cookpad.apk #4 の発表資料です。

0e8279dc67437cb9eb0562620d9be061?s=128

もん

February 13, 2020
Tweet

Transcript

  1. cookpad.apk #4 令和に始めるcode generation 買物事業部 門田福男

  2. 自己紹介 2016年 新卒入社 技術部モバイル基盤グループ→買物事業部 Android エンジニア Twitter: @_litmon_ GitHub :

    @litmon
  3. コード自動生成 使ってますか?

  4. None
  5. (おもてたんとちがう・・・)

  6. None
  7. コード自動生成 作ってますか?

  8. ‍♂ (おもてたんとちがう・・・)

  9. 今日のお話 (タイトルで風呂敷を広げてしまったが) 今日はannotationProcessor(kapt)についてしゃべります • マートアプリで作ったannotationProcessor

  10. さっそくですがこちらをご覧ください GET /v1/product_tags/100?fields=name,products[id,name]

  11. さっそくですがこちらをご覧ください GET /v1/product_tags/100?fields=name,products[id,name]

  12. https://github.com/cookpad/garage

  13. マートのAPIはgarageで出来ている • query parameter の fields で指定した値が返ってくる ◦ 必要な分だけ取得するというのが出来る ◦

    (ちょっとGraphQLっぽい) • JSONをクラスにマッピングするときにはmoshi-kotlinを利用している
  14. None
  15. マートのAPIはgarageで出来ている • query parameter の fields で指定した値が返ってくる ◦ 必要な分だけ取得するというのが出来る ◦

    (ちょっとGraphQLっぽい) • JSONをクラスにマッピングするときにはmoshi-kotlinを利用している • fields 文字列は、マッピング先のclassのプロパティと合わせる必要がある
  16. fields=name,products[id,name]

  17. fields=....

  18. マートのAPIはgarageで出来ている • query parameter の fields で指定した値が返ってくる ◦ 必要な分だけ取得するというのが出来る ◦

    (ちょっとGraphQLっぽい) • JSONをクラスにマッピングするときにはmoshi-kotlinを利用している • fields は、マッピング先のclassのプロパティと合わせる必要がある • fields をメンテナンスしていくのは非常にめんどくさい ◦ fields とPOJOクラスのダブルメンテ……
  19. 脱線: fields=__default__ garageには fields に特別な文字列( __default__ )を指定できる (各Resourceの必須プロパティを返すようになる) メリット •

    全てのプロパティに対して fields を指定する必要がなくなる
  20. 脱線: fields=__default__ garageには fields に特別な文字列( __default__ )を指定できる (各Resourceの必須プロパティを返すようになる) デメリット •

    必須プロパティが何なのか分からなくなる ◦ fields のメンテナンスが困難になる(追加する必要があるのかないのかが分からない) • 本来は不要なデータもあわせて返ってくるのでgarageのメリットが薄れる
  21. fields 文字列にはルールがある 非常にシンプルなルール • 各プロパティ名を , 区切りで列挙していく fields=id,name,... • ネストした構造のときは

    [] で囲む fields=id,name,product[id,name,...],...
  22. ルールがあるということは 機械で作れる

  23. 生成後のコードをイメージする • まずは、どんなコードが出来てほしいかイメージする ◦ 利用シーンから考えてみる • 生成後のコードを自分で書いてみるのが一番しっくり来る

  24. None
  25. • 各プロパティ名を , 区切りで列挙していく

  26. • ネストした構造のときは [] で囲む

  27. どう作るかを理解する • annotationProcessor (kapt) ◦ javax.annotation.processing.Processor ◦ com.google.auto.common.BasicAnnotationProcessor (今回はこれを使います) ▪

    ラウンドなどいろいろな処理を簡素に提供してくれるラッパーProcessor ▪ https://github.com/google/auto/blob/master/common/src/main/java/com/google/auto/common/BasicAnn otationProcessor.java • Kotlinコードの生成 kotlinpoet ◦ https://github.com/square/kotlinpoet
  28. • モジュール(generator)を作って、build.gradleを記述

  29. • 便利ライブラリ(AutoService)を追加

  30. • 今回はmoshi-kotlinのアノテーションを利用するので依存を追加 (アノテーションも自作する場合はモジュールを分けて定義して おくと良い)

  31. • Processorクラスを定義

  32. • AutoService でプロセッサーを自動登録

  33. • 各ステップを定義

  34. • ステップの定義

  35. • 処理を実行するアノテーションを設定

  36. • この部分にコード生成ロジックを記述していく

  37. None
  38. コード生成の流れをイメージする • JsonClassアノテーションが付与されたクラスに対して ◦ コンストラクタに含まれるプロパティの @Json アノテーションを取得する ▪ @Json(name =

    “value”) <- name部分を取得する ◦ @Json アノテーションが付与されたプロパティの型を見て ▪ primitive タイプであれば => nameをそのまま使う ▪ Array<T>, List<T> など => T の部分のプロパティを列挙して [] で囲う ▪ その他 => (おそらくネストされたクラスとみなして)ネストしたプロパティを列挙して [] で囲う ◦ <クラス名>_fields() メソッドを生成して、上で判定した文字列を返す • 全ての <クラス名>_fields() メソッドをまとめる fields() メソッドを作る ◦ 型引数を与えて、型引数によってメソッドの呼び出しを行う
  39. • こんな感じのコードを生成する部分の実装

  40. • @JsonClass アノテーションが付与されたクラスに対して

  41. • コンストラクタを取得

  42. • @Json アノテーションを取り出して、 name を取得

  43. • プロパティの型情報を取得

  44. • 型に応じて返す値を変える( [] を付けるか付けないか)

  45. • @JsonClass クラスが付いた子クラスがいると仮定して、生成さ れたメソッドを呼び出し

  46. • こんな感じのメソッドを生成する部分の実装

  47. • inline functionを定義

  48. • reified T はこんな感じでうまくいった

  49. • when式をガッと作っていく

  50. • 出来上がったメソッドたちをまとめてファイルに書き出す

  51. • 使うときは kapt で作ったモジュールを指定するだけ (ライブラリとして配布するとより一般的な形になる)

  52. • app/build/kaptKotlin/debug/ 辺りに生成されるようになる

  53. いかがでしたか? • 自分で作ってみて、思ったよりも簡単だった • ルールさえハッキリしていればコード生成(annotationProcessor)は こわくない • アプリモジュールと同列で作ると導入も簡単で良い ◦ ※ときどきビルドツールでバグってコンパイルが通らなくなる

    • この機会にレッツcode generation!
  54. おまけ

  55. こんなこともあった • Android Gradle Plugin 3.5.0で Jetifier でエラーが出るようになった • 3.5.3

    で出なくなった模様なので、Jetifier のバグだった? • ↓エラー内容 ◦ 17:36:54 Execution failed for task ':app:kaptGenerateStubsDebugKotlin'. 17:36:54 > Could not resolve all files for configuration ':app:kapt'. 17:36:54 > Failed to transform artifact 'generator.jar (project :generator)' to match attributes {artifactType=processed-jar, org.gradle.category=library, org.gradle.dependency.bundling=external, org.gradle.jvm.version=8, org.gradle.usage=java-runtime-jars, org.jetbrains.kotlin.localToProject=public, org.jetbrains.kotlin.platform.type=jvm}. 17:36:54 > Execution failed for JetifyTransform: /mnt/vol/jenkins/workspace/android-mart-pull-request/generator/build/libs/generator.jar. 17:36:54 > Failed to transform '/mnt/vol/jenkins/workspace/android-mart-pull-request/generator/build/libs/generator.jar' using Jetifier. Reason: Cannot open a library at 'FileMapping(from=/mnt/vol/jenkins/workspace/android-mart-pull-request/generator/build/libs/generator.jar, to=/mnt/vol/jenkins/workspace/android-mart-pull-request/generator/build/.transforms/7bd08867addf7e0e0c415bce6c5839ac/jetified-generator.jar)'. (Run with --stacktrace for more details.)