Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

コード自動生成 使ってますか?

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

(おもてたんとちがう・・・)

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

コード自動生成 作ってますか?

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

https://github.com/cookpad/garage

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

マートのAPIはgarageで出来ている ● query parameter の fields で指定した値が返ってくる ○ 必要な分だけ取得するというのが出来る ○ (ちょっとGraphQLっぽい) ● JSONをクラスにマッピングするときにはmoshi-kotlinを利用している ● fields 文字列は、マッピング先のclassのプロパティと合わせる必要がある

Slide 16

Slide 16 text

fields=name,products[id,name]

Slide 17

Slide 17 text

fields=....

Slide 18

Slide 18 text

マートのAPIはgarageで出来ている ● query parameter の fields で指定した値が返ってくる ○ 必要な分だけ取得するというのが出来る ○ (ちょっとGraphQLっぽい) ● JSONをクラスにマッピングするときにはmoshi-kotlinを利用している ● fields は、マッピング先のclassのプロパティと合わせる必要がある ● fields をメンテナンスしていくのは非常にめんどくさい ○ fields とPOJOクラスのダブルメンテ……

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

脱線: fields=__default__ garageには fields に特別な文字列( __default__ )を指定できる (各Resourceの必須プロパティを返すようになる) デメリット ● 必須プロパティが何なのか分からなくなる ○ fields のメンテナンスが困難になる(追加する必要があるのかないのかが分からない) ● 本来は不要なデータもあわせて返ってくるのでgarageのメリットが薄れる

Slide 21

Slide 21 text

fields 文字列にはルールがある 非常にシンプルなルール ● 各プロパティ名を , 区切りで列挙していく fields=id,name,... ● ネストした構造のときは [] で囲む fields=id,name,product[id,name,...],...

Slide 22

Slide 22 text

ルールがあるということは 機械で作れる

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

No content

Slide 25

Slide 25 text

● 各プロパティ名を , 区切りで列挙していく

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

どう作るかを理解する ● 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

Slide 28

Slide 28 text

● モジュール(generator)を作って、build.gradleを記述

Slide 29

Slide 29 text

● 便利ライブラリ(AutoService)を追加

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

● Processorクラスを定義

Slide 32

Slide 32 text

● AutoService でプロセッサーを自動登録

Slide 33

Slide 33 text

● 各ステップを定義

Slide 34

Slide 34 text

● ステップの定義

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

No content

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

● こんな感じのコードを生成する部分の実装

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

● コンストラクタを取得

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

● プロパティの型情報を取得

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

● inline functionを定義

Slide 48

Slide 48 text

● reified T はこんな感じでうまくいった

Slide 49

Slide 49 text

● when式をガッと作っていく

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

おまけ

Slide 55

Slide 55 text

こんなこともあった ● 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.)