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

Code Generating Your Way to Happiness

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.

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

    View Slide

  2. "Code generating your
    way to happiness."

    View Slide

  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()

    View Slide

  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()

    View Slide

  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()

    View Slide

  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()

    View Slide

  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()

    View Slide

  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()

    View Slide

  9. // Code generating your way to happiness.
    @file:Author(name = "Zac Sweers")
    Presentation.kt

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  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
    }

    View Slide

  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

    View Slide

  16. Benefits
    • Boilerplate avoidance

    View Slide

  17. Benefits
    • Boilerplate avoidance
    • Correctness

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  23. class Person(val firstName: String, val lastName: String)m
    class PersonJsonAdapter : JsonAdapter() {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

    View Slide

  24. class Person(val firstName: String, val lastName: String)m
    class PersonJsonAdapter : JsonAdapter() {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

    View Slide

  25. class Person(val firstName: String, val lastName: String)m
    class PersonJsonAdapter : JsonAdapter() {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

    View Slide

  26. class Person(val firstName: String, val lastName: String)m
    class PersonJsonAdapter : JsonAdapter() {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

    View Slide

  27. class Person(val firstName: String, val lastName: String)m
    class PersonJsonAdapter : JsonAdapter() {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

    View Slide

  28. class Person(val firstName: String, val lastName: String)m
    class PersonJsonAdapter : JsonAdapter() {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

    View Slide

  29. class Person(val firstName: String, val lastName: String)m
    class PersonJsonAdapter : JsonAdapter() {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

    View Slide

  30. class Person(val firstName: String?, val lastName: String)m
    class PersonJsonAdapter : JsonAdapter() {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

    View Slide

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

    View Slide

  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)

    View Slide

  33. Benefits
    • Boilerplate
    • Correctness

    View Slide

  34. Recognizing
    • Boilerplate
    • Correctness
    Boilerplate
    Ceremony
    Value types!
    Especially if you have specs!
    Tests too

    View Slide

  35. Tools

    View Slide

  36. Tools
    •JavaPoet/KotlinPoet

    View Slide

  37. Tools
    •JavaPoet/KotlinPoet
    •Templating

    View Slide

  38. Tools
    •JavaPoet/KotlinPoet
    •Templating
    •SPI

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  42. Examples

    View Slide

  43. Butter Knife

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  58. ViewBinding(
    target = "FooActivity",
    id = 2131361859,
    name = "title",
    type = "field",
    viewType = TextView.class
    )

    View Slide

  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

    View Slide

  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

    View Slide

  61. Butter Knife

    View Slide

  62. RxBinding

    View Slide

  63. RxBinding
    KotlinGenTask
    Generates
    all
    these!

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  67. fun View.clicks(): Observable = RxView.clicks(this)a
    public static Observable clicks(View view) {
    return new ViewClickObservable(view);
    }b

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  74. // RxView
    public static Observable clicks(View view) {a
    return new ViewClickObservable(view);
    }b

    View Slide

  75. BindingMethod(
    target = "RxView",
    name = "clicks",
    type = View.class,
    returnType = "Observable"
    )b

    View Slide

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

    View Slide

  77. fun View.clicks(): Observable = RxView.clicks(this)a

    View Slide

  78. RxBinding

    View Slide

  79. Service Gen

    View Slide

  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

    View Slide

  81. interface UberService {a
    @GET("/rider")b
    Rider getRider()c
    }d

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  85. @AutoValuea
    abstract class Rider {b
    abstract String uuid();c
    abstract String firstName();d
    abstract String lastName();e
    abstract Address address();f
    static JsonAdapter jsonAdapter(Moshi moshi) {
    return new AutoValue_Rider.JsonAdapter(moshi);
    }
    }g

    View Slide

  86. service UberService {a
    Rider getRider()
    }b

    View Slide

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

    View Slide

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

    View Slide

  89. service UberService {
    Rider getRider()
    }

    View Slide

  90. astruct Rider

    View Slide

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

    View Slide

  92. astruct Rider
    struct City
    struct Vehicle
    struct Restaurant
    struct Payment
    struct TipAmount
    struct Rating
    // And 6000 more

    View Slide

  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;
    }
    }

    View Slide

  94. astruct Rider
    struct City
    struct Vehicle
    struct Restaurant
    struct Payment
    struct TipAmount
    struct Rating
    //aAnd 6000 more

    View Slide

  95. //aAnd 6000 more

    View Slide

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

    View Slide

  97. Rider
    GiftCard
    Pricing
    Driver
    EATS
    Payments

    View Slide

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

    View Slide

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

    View Slide

  100. Rider
    GiftCard
    Pricing
    Driver
    EATS
    Payments

    View Slide

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

    View Slide

  102. xclass RiderModelFactory

    View Slide

  103. xclass RiderModelFactory
    // -> json

    View Slide

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

    View Slide

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

    View Slide

  106. class RiderModelFactory
    // -> json
    // -> ridermodelfactory-fractory.bin
    class MyAppGlobalFactory
    // Delegates to all discovered fractories
    Fractory
    C
    om
    ing
    Soon™

    View Slide

  107. .thrift Thrifty "Jenga" .java
    APT
    AutoValueProcessor
    auto-value-moshi
    fractory
    Pre-build
    Compilation
    *SPI*
    Project Gen *Gradle*
    *Templating*
    *JavaPoet*

    View Slide

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

    View Slide

  109. Pitfalls

    View Slide

  110. BLOAT

    View Slide

  111. Rider app
    a

    View Slide

  112. Rider app
    a b
    10% Dagger

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  120. Rider app
    a b c

    View Slide

  121. Rider app
    a b c $$

    View Slide

  122. Fin
    Zac Sweers - Uber
    @pandanomic

    View Slide