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 full-size slide

  2. "Code generating your
    way to happiness."

    View full-size 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 full-size 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 full-size 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 full-size 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 full-size 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 full-size 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 full-size slide

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

    View full-size 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 full-size 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 full-size slide

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

    View full-size slide

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

    View full-size 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 full-size 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 full-size slide

  16. Benefits
    • Boilerplate avoidance

    View full-size slide

  17. Benefits
    • Boilerplate avoidance
    • Correctness

    View full-size slide

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

    View full-size slide

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

    View full-size 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 full-size 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 full-size 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 full-size 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 full-size 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 full-size 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 full-size 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 full-size 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 full-size 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 full-size 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 full-size 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 full-size slide

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

    View full-size 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 full-size slide

  33. Benefits
    • Boilerplate
    • Correctness

    View full-size slide

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

    View full-size slide

  35. Tools
    •JavaPoet/KotlinPoet

    View full-size slide

  36. Tools
    •JavaPoet/KotlinPoet
    •Templating

    View full-size slide

  37. Tools
    •JavaPoet/KotlinPoet
    •Templating
    •SPI

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  41. Butter Knife

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  44. 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 full-size slide

  45. 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 full-size slide

  46. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  57. 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 full-size slide

  58. @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 full-size slide

  59. Butter Knife

    View full-size slide

  60. RxBinding
    KotlinGenTask
    Generates
    all
    these!

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  75. @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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  80. @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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  84. service UberService {
    Rider getRider()
    }

    View full-size slide

  85. astruct Rider

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  88. 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 full-size slide

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

    View full-size slide

  90. //aAnd 6000 more

    View full-size slide

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

    View full-size slide

  92. Rider
    GiftCard
    Pricing
    Driver
    EATS
    Payments

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  95. Rider
    GiftCard
    Pricing
    Driver
    EATS
    Payments

    View full-size slide

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

    View full-size slide

  97. xclass RiderModelFactory

    View full-size slide

  98. xclass RiderModelFactory
    // -> json

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  104. Rider app
    a b
    10% Dagger

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  112. Rider app
    a b c

    View full-size slide

  113. Rider app
    a b c $$

    View full-size slide

  114. Fin
    Zac Sweers - Uber
    @pandanomic

    View full-size slide