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

Kotlin: MAX PAYNE (Voxxed Days Minsk, May 2018)

Kotlin: MAX PAYNE (Voxxed Days Minsk, May 2018)

Kotlin gives us a lot of neat features and just general developer satisfaction, but little is known about its hidden costs and pain it causes. This talk is a almost a 3 year combination of small (and no so small) issues we encountered when developing a full-Kotlin application started from the scratch. Bugs, features, performance, testing — it’s all there with our answers, conclusions and advises.

Presented at Voxxed Days Minsk conference on May 26, 2018.

Artur Dryomov

May 26, 2018
Tweet

More Decks by Artur Dryomov

Other Decks in Programming

Transcript

  1. Kotlin: MAX PAYNE
    Artur Dryomov, Juno Lab
    @arturdryomov

    View Slide

  2. View Slide

  3. Juno Kotlin
    Kotlin Juno

    View Slide

  4. Juno Rider
    3 years
    100 % Kotlin
    Kotlin EAP in production
    1.1-EAP
    — 6 months
    1.2-EAP
    — 3 months
    99.9 % crash‑free
    2 week sprints
    4 developers, 1 QA

    View Slide

  5. Juno Rider
    cloc main/kotlin
    — 56 K
    cloc test/kotlin
    — 49 K
    cloc androidTest/kotlin
    — 17 K

    View Slide

  6. Style

    View Slide

  7. Java Tools
    Nope

    View Slide

  8. Specifications
    Java AST (Abstract Syntax Tree)
    PSI (Program Structure Interface)
    UAST (Universal AST)

    View Slide

  9. Checkstyle
    Checkstyle works only with AST as we depend on ANTLR and Java
    grammar.
    https://github.com/checkstyle/checkstyle/issues/4369

    View Slide

  10. Kotlin Tools
    IntelliJ IDEA
    detekt
    + ktlint
    android/lint
    kotlinc --force
    tasks.withType {
    kotlinOptions {
    allWarningsAsErrors = true
    }
    }

    View Slide

  11. Infinity War
    val gameBuilder = StringBuilder()
    gameBuilder.append("tic")
    gameBuilder.append("tac")
    gameBuilder.append("toe")
    val game = gameBuilder.toString()

    View Slide

  12. Infinity War
    val game = StringBuilder().also {
    it.append("tic")
    it.append("tac")
    it.append("toe")
    }
    .toString()

    View Slide

  13. Infinity War
    val game = StringBuilder().let {
    it.append("tic")
    it.append("tac")
    it.append("toe")
    it.toString()
    }

    View Slide

  14. Infinity War
    val game = StringBuilder().apply {
    append("tic")
    append("tac")
    append("toe")
    }
    .toString()

    View Slide

  15. Infinity War
    val game = StringBuilder().run {
    append("tic")
    append("tac")
    append("toe")
    toString()
    }

    View Slide

  16. Infinity War
    val game = with(StringBuilder()) {
    append("tic")
    append("tac")
    append("toe")
    toString()
    }

    View Slide

  17. Infinity War
    ?
    CODE_STYLE.md

    View Slide

  18. Weird Parts

    View Slide

  19. Destructuring
    val (first, second) = Pair(1, 2)
    val (first, second, third) = Triple(1, 2, 3)
    listOf(1 to "one", 2 to "two").map { (number, text) ->
    println("number is $number, text is $text")
    }

    View Slide

  20. Destructuring
    data class Person(val firstName: String, val lastName: String)
    val (firstName, lastName) = Person("f", "l")
    data class Person(val lastName: String, val firstName: String)
    val (firstName, lastName) = Person("f", "l") // It works!

    View Slide

  21. Destructuring
    data class Person(val firstName: String, val lastName: String)
    {
    operator fun component1() = firstName
    operator fun component2() = lastName
    }

    View Slide

  22. Destructuring
    Pair
    , Triple
    data class

    View Slide

  23. null
    safety?
    Observable.just(null)
    public static Observable just(T item) {
    ObjectHelper.requireNonNull(item, "The item is null");
    ...

    View Slide

  24. null
    safety!!
    @Nullable
    + @NonNull

    View Slide

  25. null
    safety!!
    Docs.
    Annotations.
    Abstractions.

    View Slide

  26. RxJava 2
    val o: Observable = Observable.just(null)
    // Runtime: NullPointerException
    public static Observable just(T item) {
    ObjectHelper.requireNonNull(item, "The item is null");
    ...

    View Slide

  27. Optional
    Swift: ?
    , nil enum Optional
    Kotlin: ?
    , null null

    View Slide

  28. Koptional
    https://github.com/gojuno/koptional
    val some = Some(value)
    val none = None
    val optional = nullableValue.toOptional()
    val nullable = o.toNullable()

    View Slide

  29. Mockito
    interface Api {
    fun call(id: String)
    }
    class Service(private val api: Api) {
    fun call() = api.call(generateId())
    }
    @Test fun test() {
    val api = Mockito.mock()
    Service(api).call()
    verify(api).call(Mockito.any())
    }
    // Runtime: NullPointerException

    View Slide

  30. Mockito
    class Mockito {
    public static T any() {
    return null;
    }
    }

    View Slide

  31. Mockito
    fun anything(): T = Mockito.any()
    @Test fun test() {
    val api = Mockito.mock()
    Service(api).call()
    verify(api).call(anything())
    }

    View Slide

  32. Mockito
    fun eq(value: T): T = Mockito.eq(obj)
    fun anything(): T = Mockito.any()
    fun whenever(call: T) = Mockito.`when`(call)

    View Slide

  33. Gson
    data class Model {
    @SerializedName("model_field")
    val field: String
    }
    val model = Gson().fromJson("{}", Model::javaClass)
    // Runtime: OK
    model.field.toString()
    // Runtime: NullPointerException

    View Slide

  34. Gson
    Java Reflection

    View Slide

  35. Gson
    data class Model {
    @SerializedName("model_field")
    val field: String
    } : Validatable {
    override fun validate() {
    if (field == null) throw ParseException()
    }
    }

    View Slide

  36. Gson
    Moshi vs. Jackson
    Kotlin Reflection (2.5 MiB)

    View Slide

  37. null
    safety!!
    Kotlin: The Problem with null
    https://arturdryomov.online/posts/kotlin‑the‑problem‑with‑null/

    View Slide

  38. Optimizations

    View Slide

  39. inline
    private fun createId(): Int = 42
    vs.
    @Suppress("nothing_to_inline")
    private inline fun createId(): Int = 42

    View Slide

  40. inline
    https://youtrack.jetbrains.com/issue/KT‑16769
    Revisit compiler and IDE warning about inline functions

    View Slide

  41. const
    companion object {
    private val NUMBER = 42
    }
    public static final class Companion {
    private final int NUMBER = 42;
    private final int getNUMBER() {
    return NUMBER;
    }
    }

    View Slide

  42. const
    companion object {
    private const val NUMBER = 42
    }
    public static final class Companion {
    private static final int NUMBER = 42;
    }

    View Slide

  43. const
    Primitives + String
    kotlinc
    ?

    View Slide

  44. Tools Kotlin Show Kotlin Bytecode

    View Slide

  45. Tooling

    View Slide

  46. Bugs
    fun multiply(double: Double) = double * double
    1.1.3
    org.jetbrains.kotlin.kapt3.diagnostic.KaptError
    Error while annotation processing

    View Slide

  47. Bugs
    https://youtrack.jetbrains.com/issue/KT‑18377
    Syntax error while generating kapt stubs

    View Slide

  48. Bugs
    Are a thing!

    View Slide

  49. Assemble
    javac
    kotlinc
    kapt

    View Slide

  50. kapt
    Kotlin Annotation Processing Tool
    Breaks incremental compilation.
    Throws errors.
    *_MemberInjector.java: error: package does not exist

    View Slide

  51. kapt
    A Dagger to Remember
    https://arturdryomov.online/posts/a‑dagger‑to‑remember/

    View Slide

  52. Assemble
    — i5-5257U @ 2.70GHz
    — i7-6700K @ 4.00GHz

    View Slide

  53. Mainframer
    https://github.com/gojuno/mainframer
    $ rsync laptop mainframe
    $ make
    $ rsync mainframe laptop

    View Slide

  54. Mainframer
    $ ./gradlew assembleDebug
    4 minutes 10 seconds
    $ mainframer ./gradlew assembleDebug
    1 minute 10 seconds

    View Slide

  55. Assemble
    Remove everything possible.
    Gradle build cache.
    Mainframer.

    View Slide

  56. Gradle Kotlin DSL

    View Slide

  57. Kotlin versions
    Gradle 4.2 — 1.1.4‑3
    Project — 1.2.0‑beta‑31
    Runtime files in the classpath should have the same version.
    These files were found in the classpath:
    .../dists/.../kotlin-stdlib-1.1.4-3.jar (version 1.1)
    .../caches/.../kotlin-stdlib-1.2.0-beta-31.jar (version 1.2)

    View Slide

  58. Kotlin versions
    https://github.com/gradle/kotlin‑dsl/issues/519

    View Slide

  59. Groovy
    unitTests.all {
    maxParallelForks = 2
    }
    unitTests.all(KotlinClosure1({
    apply {
    maxParallelForks = 2
    }
    }, this))

    View Slide

  60. Groovy
    import groovy.lang.Closure;
    public void unitTests(Closure closure)
    https://issuetracker.google.com/issues/78547461

    View Slide

  61. Groovy
    2012: groovy.lang.Closure
    2017: org.gradle.api.Action

    View Slide

  62. Groovy
    Promote DSL‑agnostic API

    View Slide

  63. Gradle Kotlin DSL
    IDE
    Dynamic Groovy
    Workflow
    Alpha!

    View Slide

  64. Spek

    View Slide

  65. JUnit
    class SystemTest {
    lateinit var system: System
    @BeforeEach fun create() {
    system = System()
    }
    @BeforeEach fun start() {
    system.boot()
    }
    @Test fun `has boot status`() {
    assertThat(system.status()).isEqualTo(BOOTED)
    }
    @Test fun `has positive uptime`() {
    assertThat(system.uptime()).isNotZero()
    }
    }

    View Slide

  66. Spek
    class SystemSpec : Spek({
    val system by memoized { System() }
    context("boot") {
    beforeEachTest {
    system.boot()
    }
    it("has boot status") {
    assertThat(system.status()).isEqualTo(BOOTED)
    }
    it("has positive uptime") {
    assertThat(system.uptime()).isNotZero()
    }
    }
    })

    View Slide

  67. Versions
    1.0.89
    : DescribeBody Dsl
    1.1.0-beta3
    : Dsl SpecBody
    1.0
    , 1.1.19
    : do not exist.
    JUnit 4 5

    View Slide

  68. memoized
    before and after
    class JustSpec : Spek({
    val system by memoized { System() }
    beforeEachTest {
    println("before $system") // system_before
    }
    afterEachTest {
    println("after $system") // system_after
    }
    })
    // system_before != system_after

    View Slide

  69. memoized
    before and after
    https://github.com/JetBrains/spek/issues/213
    Memoized value is not the same before and after test

    View Slide

  70. JUnit
    JUnit 5!
    Android Studio
    JUnit 4?
    @RunWith(JUnitPlatform::class)
    fon
    , fit
    Spek IJ plugin.
    JUnit 4
    Fun

    View Slide

  71. Spek
    jetbrains/spek spekframework/spek
    Compatibility
    Docs
    Bugs
    100 K tests
    2.0

    View Slide

  72. Spek
    Spectrum, KotlinTest

    View Slide

  73. Conclusion

    View Slide

  74. https://youtrack.jetbrains.com/issues/KT

    View Slide

  75. ...
    Kotlin is a thin layer on top of Java, and so it perpetuates through a lot of
    the Javaisms in its model.
    ...
    Chris Lattner (Swift, LLVM, Clang)

    View Slide

  76. Java — Executive Committee
    Oracle, Red Hat, Intel, IBM, Eclipse, Twitter, JetBrains...
    24 members
    Kotlin — Language Committee
    JetBrains, Google
    3 members

    View Slide

  77. View Slide

  78. Conclusion
    JVM
    Tooling
    Language

    View Slide

  79. View Slide