Refactoring Legacy Code with Kotlin and Coroutines

Refactoring Legacy Code with Kotlin and Coroutines

by Ash Davies
presented on August 30, 2019 @Kotlin/Everywhere Hamburg

C67d4b2a0b74214c56b23969177e743c?s=128

Kotlin User Group Hamburg

August 30, 2019
Tweet

Transcript

  1. Refactoring Legacy Code with Kotlin & Coroutines Kotlin Everywhere: Hamburg

    @askashdavies
  2. @askashdavies

  3. Java @askashdavies

  4. { } @askashdavies

  5. Streams ↝ @askashdavies

  6. @askashdavies

  7. Kotlin @askashdavies

  8. Null @askashdavies

  9. if (foo == null) { bar(); } @askashdavies

  10. O"ensive Code < > @askashdavies

  11. Urgency @askashdavies

  12. Kotlin? @askashdavies

  13. Kotlin @askashdavies

  14. @askashdavies

  15. Google IO 2018 @askashdavies

  16. ! " @askashdavies

  17. Google IO 2019 @askashdavies

  18. Kotlin @askashdavies

  19. Libraries @askashdavies

  20. @askashdavies

  21. @askashdavies

  22. @askashdavies

  23. @askashdavies

  24. @askashdavies

  25. @askashdavies

  26. Kotlin @askashdavies

  27. Idiomacy @askashdavies

  28. @askashdavies

  29. Idiomatic Code @askashdavies

  30. Idiomatic Code • Consistent, easier to read • Less cognitive

    load • Less ambiguity • Function > Style @askashdavies
  31. Code Style • kotlinlang.org/docs/reference/coding-conventions.html • android.github.io/kotlin-guides/style.html @askashdavies

  32. Refactoring Legacy Code @askashdavies

  33. ⌃⌥⇧K @askashdavies

  34. @askashdavies

  35. @askashdavies

  36. public class BadJavaActivity extends Activity { @Inject Dependency dependency; }

    @askashdavies
  37. General Assumptions @askashdavies

  38. class BadKotlinActivity : Activity() { @Inject var dependency: Dependency? =

    null } @askashdavies
  39. class SlightlyLessBadKotlinActivity : Activity() { @Inject internal lateinit var dependency:

    Dependency } @askashdavies
  40. UninitializedPrope.yAccessException! @askashdavies

  41. lateinit var file: File if (::file.isInitialized) { /* ... */

    } @askashdavies
  42. Data Classes @askashdavies

  43. public class User { private String firstName; private String lastName;

    public User(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } User user = (User) o; return Objects.equals(firstName, user.firstName) && Objects.equals(lastName, user.lastName); } @Override public int hashCode() { return Objects.hash(firstName, lastName); } } @askashdavies
  44. class User(var firstName: String?, var lastName: String?) { override fun

    equals(o: Any?): Boolean { if (this === o) { return true } if (o == null || javaClass != o.javaClass) { return false } val user = o as User? return firstName == user!!.firstName && lastName == user.lastName } override fun hashCode(): Int { return Objects.hash(firstName, lastName) } } @askashdavies
  45. data class User(val firstName: String, val lastName: String) @askashdavies

  46. data class User(val firstName: String, val lastName: String? = null)

    @askashdavies
  47. @NotNull public final User copy(@Nullable String firstName, @Nullable String lastName)

    { return new User(firstName, lastName); } @askashdavies
  48. Kotlin • Singleton objects • String interpolation • Elvis operator

    ! • Destructuring • Extension functions • Scoping functions @askashdavies
  49. Maintaining History @askashdavies

  50. Maintaining History • Change extension .java -> .kt • First

    commit • Apply Kotlin conversion • Second commit @askashdavies
  51. Maintaining History Refactoring @askashdavies

  52. Refactoring @askashdavies

  53. Refactoring SOLID • Single-responsibility • Open-closed • Liskov substitution •

    Interface segregation • Dependency inversion @askashdavies
  54. Refactoring Single Responsibility @askashdavies

  55. Perspective @askashdavies

  56. Asynchronicity @askashdavies

  57. Concurrency is Hard @askashdavies

  58. Thread new Thread(() -> { foo(); }).start(); @askashdavies

  59. CompletableFuture CompletableFuture.supplyAsync(this::findSomeData) .thenApply(this:: intReturningMethod) .thenAccept(this::notify); @askashdavies

  60. RxJava Observable .fromIterable(resourceDraft.getResources()) .flatMap(resourceServiceApiClient::createUploadContainer) .zipWith(Observable.fromIterable(resourceDraft.getResources()), Pair::create) .flatMap(uploadResources()) .toList() .toObservable() .flatMapMaybe(resourceCache.getResourceCachedItem())

    .defaultIfEmpty(Resource.getDefaultItem()) .flatMap(postResource(resourceId, resourceDraft.getText(), currentUser, resourceDraft.getIntent())) .observeOn(AndroidSchedulers.mainThread()) .subscribeOn(Schedulers.io()) .subscribe( resource -> repository.setResource(resourceId, resource, provisionalResourceId), resourceUploadError(resourceId, resourceDraft, provisionalResourceId) ); @askashdavies
  61. @askashdavies

  62. What If? @askashdavies

  63. Coroutines @askashdavies

  64. fun main() { GlobalScope.launch { delay(1000L) println("World!") } println("Hello,") Thread.sleep(2000L)

    } // Hello, // World! @askashdavies
  65. fun main() { GlobalScope.launch { delay(1000L) println("World!") } println("Hello,") Thread.sleep(2000L)

    } // Hello, // World! @askashdavies
  66. fun main() { GlobalScope.launch { delay(1000L) println("World!") } println("Hello,") Thread.sleep(2000L)

    } // Hello, // World! @askashdavies
  67. ⚖ Stability @askashdavies

  68. @askashdavies

  69. @Annotations ( ! Here be dragons) @askashdavies

  70. Annotations @ExperimentalCoroutinesApi // ⚠ @askashdavies

  71. Annotations @ExperimentalCoroutinesApi // ⚠ @FlowPreview // ⚠ @askashdavies

  72. Annotations @ExperimentalCoroutinesApi // ⚠ @FlowPreview // ⚠ @ObsoleteCoroutinesApi // ⚠

    @askashdavies
  73. Annotations @ExperimentalCoroutinesApi // ⚠ @FlowPreview // ⚠ @ObsoleteCoroutinesApi // ⚠

    @InternalCoroutinesApi // ☠ @askashdavies
  74. Annotations @Experimental @askashdavies

  75. ! Coroutines @askashdavies

  76. ! Native (rst-pa-y library @askashdavies

  77. E"cient @askashdavies

  78. ! Easy-to-use @askashdavies

  79. ! suspend fun @askashdavies

  80. ! Suspend fun main() { GlobalScope.launch { delay(1000L) println("World!") }

    println("Hello,") Thread.sleep(2000L) } // Hello, // World! @askashdavies
  81. ! Suspend fun main() { GlobalScope.launch { doWorld() println("Hello,") Thread.sleep(2000L)

    } } suspend fun doWorld() { delay(1000L) println("World!") } // Hello, // World! @askashdavies
  82. ! Suspend fun main() { GlobalScope.launch { doWorld() println("Hello,") Thread.sleep(2000L)

    } } suspend fun doWorld() { withContext(Dispatchers.IO) { delay(1000L) println("World!") } } // Hello, // World! @askashdavies
  83. Dispatchers @askashdavies

  84. Dispatchers • Default • IO • Main • Android (Main

    Thread Dispatcher) • JavaFx (Application Thread Dispatcher) • Swing (Event Dispatcher Thread) • Unconfined @askashdavies
  85. Testing @Test fun testFoo() = runBlockingTest { val actual =

    foo() // ... } suspend fun foo() { delay(1_000) // ... } @askashdavies
  86. Fu#her Reading • Google Codelab: Refactoring to Kotlin https://codelabs.developers.google.com/codelabs/java-to-kotlin/ •

    KotlinX Coroutine Test https://github.com/Kotlin/kotlinx.coroutines/tree/master/kotlinx-coroutines-test • Sean McQuillan: Coroutines + Testing = https://www.droidcon.com/media-detail?video=352671106 • Ash Davies: RxJava & Coroutines: A Practical Analysis v3 https://speakerdeck.com/ashdavies/rxjava-and-coroutines-a-practical-analysis- v3 @askashdavies
  87. Thanks! @askashdavies