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

Code Generating Your Way to Happiness

85a1166e93654865b4dcafdafe2b2dfd?s=47 Zac Sweers
October 27, 2017

Code Generating Your Way to Happiness

This talk explores how code gen can be used to eliminate a class of issues and sources of developer boilerplate, as well as walking through some popular libraries and uber-specific use cases.

85a1166e93654865b4dcafdafe2b2dfd?s=128

Zac Sweers

October 27, 2017
Tweet

More Decks by Zac Sweers

Other Decks in Programming

Transcript

  1. Code generating your way to happiness Zac Sweers - Uber

    @pandanomic
  2. "Code generating your way to happiness."

  3. FileSpec.builder("", "Presentation") .addComment("Code generating your way to happiness.") .addAnnotation(AnnotationSpec.builder(Author::class) .addMember("name",

    "%S", "Zac Sweers") .useSiteTarget(FILE) .build()) .build()
  4. FileSpec.builder("", "Presentation") .addComment("Code generating your way to happiness.") .addAnnotation(AnnotationSpec.builder(Author::class) .addMember("name",

    "%S", "Zac Sweers") .useSiteTarget(FILE) .build()) .build()
  5. FileSpec.builder("", "Presentation") .addComment("Code generating your way to happiness.") .addAnnotation(AnnotationSpec.builder(Author::class) .addMember("name",

    "%S", "Zac Sweers") .useSiteTarget(FILE) .build()) .build()
  6. FileSpec.builder("", "Presentation") .addComment("Code generating your way to happiness.") .addAnnotation(AnnotationSpec.builder(Author::class) .addMember("name",

    "%S", "Zac Sweers") .useSiteTarget(FILE) .build()) .build()
  7. FileSpec.builder("", "Presentation") .addComment("Code generating your way to happiness.") .addAnnotation(AnnotationSpec.builder(Author::class) .addMember("name",

    "%S", "Zac Sweers") .useSiteTarget(FILE) .build()) .build()
  8. FileSpec.builder("", "Presentation") .addComment("Code generating your way to happiness.") .addAnnotation(AnnotationSpec.builder(Author::class) .addMember("name",

    "%S", "Zac Sweers") .useSiteTarget(FILE) .build()) .build()
  9. // Code generating your way to happiness. @file:Author(name = "Zac

    Sweers") Presentation.kt
  10. FileSpec.builder("", "Presentation")a .addComment("Code generating your way to happiness.") .addAnnotation(AnnotationSpec.builder(Author::class) .addMember("name",

    "%S", "Zac Sweers") .useSiteTarget(FILE) .build()) .build()b
  11. FileSpec.builder("", presentationName)a .addComment("Code generating your way to happiness.") .addAnnotation(AnnotationSpec.builder(Author::class) .addMember("name",

    "%S", "Zac Sweers") .useSiteTarget(FILE) .build()) .build()b
  12. FileSpec.builder("", presentationName)a .addComment(comment) .addAnnotation(AnnotationSpec.builder(Author::class) .addMember("name", "%S", "Zac Sweers") .useSiteTarget(FILE) .build())

    .build()b
  13. FileSpec.builder("", presentationName)a .addComment(comment) .addAnnotation(AnnotationSpec.builder(Author::class) .addMember("name", "%S", author) .useSiteTarget(FILE) .build()) .build()b

  14. presentations.onEach { (presentationName, comment, author) -> FileSpec.builder("", presentationName)a .addComment(comment) .addAnnotation(AnnotationSpec.builder(Author::class)

    .addMember("name", "%S", author) .useSiteTarget(FILE) .build()) .build()b }
  15. conferences .flatMap { it.presentations } .onEach { (presentationName, comment, author)

    -> FileSpec.builder("", presentationName)a .addComment(comment) .addAnnotation(AnnotationSpec.builder(Author::class) .addMember("name", "%S", author) .useSiteTarget(FILE) .build()) .build()b } Boilerplate
  16. Benefits • Boilerplate avoidance

  17. Benefits • Boilerplate avoidance • Correctness

  18. Correctness class Person(val firstName: String, val lastName: String)m

  19. class Person(val firstName: String, val lastName: String)m class PersonJsonAdapter :

    JsonAdapter<Person>() {a }l
  20. class Person(val firstName: String, val lastName: String)m class PersonJsonAdapter :

    JsonAdapter<Person>() {a override fun fromJson(reader: JsonReader): Person? {b }k }l
  21. class Person(val firstName: String, val lastName: String)m class PersonJsonAdapter :

    JsonAdapter<Person>() {a override fun fromJson(reader: JsonReader): Person? {b lateinit var firstName: Stringc lateinit var lastName: Stringd }k }l
  22. class Person(val firstName: String, val lastName: String)m class PersonJsonAdapter :

    JsonAdapter<Person>() {a override fun fromJson(reader: JsonReader): Person? {b lateinit var firstName: Stringc lateinit var lastName: Stringd return Person(firstName, lastName)j }k }l
  23. class Person(val firstName: String, val lastName: String)m class PersonJsonAdapter :

    JsonAdapter<Person>() {a override fun fromJson(reader: JsonReader): Person? {b lateinit var firstName: Stringc lateinit var lastName: Stringd while (reader.hasNext()) {e }n return Person(firstName, lastName)j }k }l
  24. class Person(val firstName: String, val lastName: String)m class PersonJsonAdapter :

    JsonAdapter<Person>() {a override fun fromJson(reader: JsonReader): Person? {b lateinit var firstName: Stringc lateinit var lastName: Stringd while (reader.hasNext()) {e when (reader.nextName()) {f }o }n return Person(firstName, lastName)j }k }l
  25. class Person(val firstName: String, val lastName: String)m class PersonJsonAdapter :

    JsonAdapter<Person>() {a override fun fromJson(reader: JsonReader): Person? {b lateinit var firstName: Stringc lateinit var lastName: Stringd while (reader.hasNext()) {e when (reader.nextName()) {f "firstName" -> firstName = reader.nextString()g }o }n return Person(firstName, lastName)j }k }l
  26. class Person(val firstName: String, val lastName: String)m class PersonJsonAdapter :

    JsonAdapter<Person>() {a override fun fromJson(reader: JsonReader): Person? {b lateinit var firstName: Stringc lateinit var lastName: Stringd while (reader.hasNext()) {e when (reader.nextName()) {f "firstName" -> firstName = reader.nextString()g "lastName" -> lastName = reader.nextString()h }o }n return Person(firstName, lastName)j }k }l
  27. class Person(val firstName: String, val lastName: String)m class PersonJsonAdapter :

    JsonAdapter<Person>() {a override fun fromJson(reader: JsonReader): Person? {b lateinit var firstName: Stringc lateinit var lastName: Stringd while (reader.hasNext()) {e when (reader.nextName()) {f "firstName" -> firstName = reader.nextString()g "lastName" -> lastName = reader.nextString()h else -> reader.skipValue()i }o }n return Person(firstName, lastName)j }k }l
  28. class Person(val firstName: String, val lastName: String)m class PersonJsonAdapter :

    JsonAdapter<Person>() {a override fun fromJson(reader: JsonReader): Person? {b lateinit var firstName: Stringc lateinit var lastName: Stringd while (reader.hasNext()) {e when (reader.nextName()) {f "firstname" -> firstName = reader.nextString()g "lastName" -> lastName = reader.nextString()h else -> reader.skipValue()i }o }n return Person(firstName, lastName)j }k }l
  29. class Person(val firstName: String, val lastName: String)m class PersonJsonAdapter :

    JsonAdapter<Person>() {a override fun fromJson(reader: JsonReader): Person? {b lateinit var firstName: Stringc lateinit var lastName: Stringd while (reader.hasNext()) {e when (reader.nextName()) {f "firstname" -> firstName = reader.nextString()g "lastName" -> lastName = reader.nextString()h else -> reader.skipValue()i }o }n return Person(lastName, firstName)j }k }l
  30. class Person(val firstName: String?, val lastName: String)m class PersonJsonAdapter :

    JsonAdapter<Person>() {a override fun fromJson(reader: JsonReader): Person? {b lateinit var firstName: Stringc lateinit var lastName: Stringd while (reader.hasNext()) {e when (reader.nextName()) {f "firstname" -> firstName = reader.nextString()g "lastName" -> lastName = reader.nextString()h else -> reader.skipValue()i }o }n return Person(lastName, firstName)j }k }l
  31. class Person(val firstName: String, val lastName: String)m

  32. class Person(val firstName: String, val lastName: String)m class City(val name:

    String, val country: String) class Vehicle(val licensePlate: String) class Restaurant(val type: String, val address: Address) class Payment(val cardNumber: String, val type: String) class TipAmount(val value: Double) class Rating(val numStars: Int) class Correctness(val confidence: Double)
  33. Benefits • Boilerplate • Correctness

  34. Recognizing • Boilerplate • Correctness Boilerplate Ceremony Value types! Especially

    if you have specs! Tests too
  35. Tools

  36. Tools •JavaPoet/KotlinPoet

  37. Tools •JavaPoet/KotlinPoet •Templating

  38. Tools •JavaPoet/KotlinPoet •Templating •SPI

  39. Tools •JavaPoet/KotlinPoet •Templating •SPI •Compile Testing

  40. Tools •JavaPoet/KotlinPoet •Templating •SPI •Compile Testing •Annotation Processing (APT)

  41. Tools •JavaPoet/KotlinPoet •Templating •SPI •Compile Testing •Annotation Processing (APT) •Gradle

  42. Examples

  43. Butter Knife

  44. Butter Knife TextView title;a void onCreate(Bundle savedInstanceState) {c title =

    findViewById(R.id.title);d }f
  45. Butter Knife TextView title;a ImageView icon;b void onCreate(Bundle savedInstanceState) {c

    title = findViewById(R.id.title);d }f
  46. Butter Knife TextView title;a ImageView icon;b void onCreate(Bundle savedInstanceState) {c

    title = findViewById(R.id.title);d icon = findViewById(R.id.icon);e }f
  47. Butter Knife @BindView(R.id.title) TextView title;a @BindView(R.id.icon) ImageView icon;b void onCreate(Bundle

    savedInstanceState) {c ButterKnife.bind(this); }f
  48. Butter Knife @BindView(R.id.title) TextView title;a @BindView(R.id.icon) ImageView icon;b @BindView(R.id.button) Button

    button; void onCreate(Bundle savedInstanceState) {c ButterKnife.bind(this);g }f
  49. Butter Knife @BindView(R.id.title) TextView title;a void onCreate(Bundle savedInstanceState) {c ButterKnife.bind(this);

    }f
  50. @BindView(R.id.title) TextView title;

  51. // FooActivity @BindView(R.id.title) TextView title;

  52. // FooActivity @BindView(R.id.title) TextView title;

  53. // FooActivity @BindView(R.id.title) TextView title;

  54. // FooActivity @BindView(2131361859) TextView title;

  55. // FooActivity @BindView(2131361859) TextView title;

  56. // FooActivity @BindView(2131361859) TextView title;

  57. // FooActivity @BindView(2131361859) TextView title;

  58. ViewBinding( target = "FooActivity", id = 2131361859, name = "title",

    type = "field", viewType = TextView.class )
  59. public final class FooActivity_ViewBinding implements Unbinder { private FooActivity target;

    @UiThread public FooActivity_ViewBinding(FooActivity target, View source) { this.target = target; target.title = Utils.findRequiredViewAsType(source, 2131361859, // R.id.title "field 'title'", TextView.class); }a }b
  60. @BindView(R.id.title) TextView title;a void onCreate(Bundle savedInstanceState) {c ButterKnife.bind(this); }f public

    final class FooActivity_ViewBinding implements Unbinder { private FooActivity target; @UiThread public FooActivity_ViewBinding(FooActivity target, View source) { this.target = target; target.title = Utils.findRequiredViewAsType(source, 2131361859, // R.id.title "field 'title'", TextView.class); }a }b APT Runtime
  61. Butter Knife

  62. RxBinding

  63. RxBinding KotlinGenTask Generates all these!

  64. public static Observable<Object> clicks(View view) { return new ViewClickObservable(view); }

  65. fun View.clicks(): Observable<Object> = RxView.clicks(this)a

  66. fun View.clicks(): Observable<Unit> = RxView.clicks(this)a

  67. fun View.clicks(): Observable<Unit> = RxView.clicks(this)a public static Observable<Object> clicks(View view)

    { return new ViewClickObservable(view); }b
  68. fun View.clicks(): Observable<Unit> = RxView.clicks(this)a public static Observable<Object> clicks(View view)

    { return new ViewClickObservable(view); }b
  69. fun View.clicks(): Observable<Unit> = RxView.clicks(this)a public static Observable<Object> clicks(View view)

    { return new ViewClickObservable(view); }b
  70. fun View.clicks(): Observable<Unit> = RxView.clicks(this)a public static Observable<Object> clicks(View view)

    { return new ViewClickObservable(view); }b
  71. fun View.clicks(): Observable<Unit> = RxView.clicks(this)a public static Observable<Object> clicks(View view)

    { return new ViewClickObservable(view); }b
  72. fun View.clicks(): Observable<Unit> = RxView.clicks(this)a public static Observable<Object> clicks(View view)

    {a return new ViewClickObservable(view); }b
  73. public static Observable<Object> clicks(View view) {a return new ViewClickObservable(view); }b

  74. // RxView public static Observable<Object> clicks(View view) {a return new

    ViewClickObservable(view); }b
  75. BindingMethod( target = "RxView", name = "clicks", type = View.class,

    returnType = "Observable<Object>" )b
  76. BindingMethod( target = "RxView", name = "clicks", type = View.class,

    returnType = "Observable<Unit>" )b
  77. fun View.clicks(): Observable<Unit> = RxView.clicks(this)a

  78. RxBinding

  79. Service Gen

  80. @AutoValuea abstract class Rider {b abstract String uuid();c abstract String

    firstName();d abstract String lastName();e abstract Address address();f }g Service Gen
  81. interface UberService {a @GET("/rider")b Rider getRider()c }d

  82. interface UberService {a @GET("/rider")b Single<Rider> getRider()c }d

  83. struct Rider { 1: required string uuid; 2: required string

    firstName; 3: required string lastName; 4: optional Address address; }
  84. @AutoValuea abstract class Rider {b abstract String uuid();c abstract String

    firstName();d abstract String lastName();e abstract Address address();f }g
  85. @AutoValuea abstract class Rider {b abstract String uuid();c abstract String

    firstName();d abstract String lastName();e abstract Address address();f static JsonAdapter<Rider> jsonAdapter(Moshi moshi) { return new AutoValue_Rider.JsonAdapter(moshi); } }g
  86. service UberService {a Rider getRider() }b

  87. service UberService {a Rider getRider() (path="/rider") }b

  88. interface UberService { @GET("/rider") Single<Rider> getRider() }

  89. service UberService { Rider getRider() }

  90. astruct Rider

  91. astruct Rider struct City struct Vehicle struct Restaurant struct Payment

    struct TipAmount struct Rating
  92. astruct Rider struct City struct Vehicle struct Restaurant struct Payment

    struct TipAmount struct Rating // And 6000 more
  93. class ModelsAdapterFactory implements JsonAdapter.Factory { @Override public JsonAdapter<?> create(Type type,

    Set<? extends Annotation> annotations, Moshi moshi) { Class<?> rawType = Types.getRawType(type); if (rawType.isAssignableFrom(Rider.class)) { return Rider.adapter(moshi); } else if (rawType.isAssignableFrom(City.class)) { return City.adapter(moshi); } else if (rawType.isAssignableFrom(Vehicle.class)) { return Vehicle.adapter(moshi); } // Etc etc return null; } }
  94. astruct Rider struct City struct Vehicle struct Restaurant struct Payment

    struct TipAmount struct Rating //aAnd 6000 more
  95. //aAnd 6000 more

  96. //aAnd 6000 more Rider EATS Payments Driver GiftCard Pricing

  97. Rider GiftCard Pricing Driver EATS Payments

  98. Rider - build.gradle GiftCard - build.gradle Pricing - build.gradle Driver

    - build.gradle EATS - build.gradle Payments - build.gradle
  99. Rider - build.gradle GiftCard - build.gradle Pricing - build.gradle Driver

    - build.gradle EATS - build.gradle Payments - build.gradle
  100. Rider GiftCard Pricing Driver EATS Payments

  101. xclass RiderModelFactory class GiftCardModelFactory class PricingModelFactory class DriverModelFactory class EATSModelFactory

    class PaymentsModelFactory
  102. xclass RiderModelFactory

  103. xclass RiderModelFactory // -> json

  104. xclass RiderModelFactory // -> json // -> ridermodelfactory-fractory.binx

  105. class RiderModelFactory // -> json // -> ridermodelfactory-fractory.bin class MyAppGlobalFactory

    // Delegates to all discovered fractories
  106. class RiderModelFactory // -> json // -> ridermodelfactory-fractory.bin class MyAppGlobalFactory

    // Delegates to all discovered fractories Fractory C om ing Soon™
  107. .thrift Thrifty "Jenga" .java APT AutoValueProcessor auto-value-moshi fractory Pre-build Compilation

    *SPI* Project Gen *Gradle* *Templating* *JavaPoet*
  108. Tools •JavaPoet •KotlinPoet •Templating •SPI •Compile Testing •Annotation Processing (APT)

    •Gradle
  109. Pitfalls

  110. BLOAT

  111. Rider app a

  112. Rider app a b 10% Dagger

  113. Rider app a b 10% Dagger c 25% Models

  114. struct Rider { 1: required string uuid; 2: required string

    firstName; 3: required string lastName; 4: optional Address address; }a
  115. struct Rider { 1: required string uuid; 2: required string

    firstName; 3: required string lastName; 4: optional Address address; }a
  116. struct Rider { 1: required string uuid; 2: required string

    firstName; 3: required string lastName; }a
  117. struct Rider { 1: required string uuid; 2: required string

    firstName; 3: required string lastName; }a service UberService {a Rider getRider() }b
  118. struct Rider { 1: required string uuid; 2: required string

    firstName; 3: required string lastName; }a service UberService {a Rider getRider() }b
  119. struct Rider { 1: required string uuid; 2: required string

    firstName; 3: required string lastName; }a service UberService {a Rider getRider() }b
  120. Rider app a b c

  121. Rider app a b c $$

  122. Fin Zac Sweers - Uber @pandanomic