Kotlin Everywhere Coimbra: Refactoring Legacy Code with Kotlin

Fc78fd09b8fee61efd4ef003fe104eb6?s=47 Ash Davies
October 01, 2019

Kotlin Everywhere Coimbra: Refactoring Legacy Code with Kotlin

Legacy code can be quite the challenge to manage, often resulting from untested scenarios, quick fixes, or less than successful initiatives. With few developers wanting to deal with it, it can end up with little remaining knowledge of its inner workings.

We can take many learnings from Michael Feathers book on "Working Effectively with Legacy Code", but we can also use Kotlin migration as an effective tool to leverage the management, reduction, and removal of legacy code in our applications.

In this session, you'll learn how to get started with Kotlin in your projects, tips and tricks on how to preserve your version control history, some pitfalls when migrating from Java, and what new technologies you can make use of in your journey with Kotlin.

Fc78fd09b8fee61efd4ef003fe104eb6?s=128

Ash Davies

October 01, 2019
Tweet

Transcript

  1. Refactoring Legacy Code with Kotlin Kotlin Everywhere: Coimbra ! @askashdavies

    | sli.do/Z169
  2. Legacy adj. Denoting or relating to so-ware or hardware that

    has been superseded but is di4cult to replace because of its wide use. @askashdavies | sli.do/Z169
  3. Working Effectively with Legacy Code Michael Feathers » Untested code

    » Regression tests » Lack of con3dence @askashdavies | sli.do/Z169
  4. ! @askashdavies | sli.do/Z169

  5. Java @askashdavies | sli.do/Z169

  6. JVM != ART @askashdavies | sli.do/Z169

  7. twitter.com/QuinnyPig/status/1174434046853599232

  8. Java 8 » Lambda expressions » Method references » Default

    inte8aces » try-with-resources @askashdavies | sli.do/Z169
  9. Streams ↝ @askashdavies | sli.do/Z169

  10. @askashdavies | sli.do/Z169

  11. Kotlin @askashdavies | sli.do/Z169

  12. Null ! @askashdavies | sli.do/Z169

  13. if (foo == null) { bar(); } @askashdavies | sli.do/Z169

  14. Defensive Code < > @askashdavies | sli.do/Z169

  15. Offensive Code < > @askashdavies | sli.do/Z169

  16. Urgency @askashdavies | sli.do/Z169

  17. Kotlin? @askashdavies | sli.do/Z169

  18. Kotlin @askashdavies | sli.do/Z169

  19. @askashdavies | sli.do/Z169

  20. Google IO 2018 @askashdavies | sli.do/Z169

  21. ! " @askashdavies | sli.do/Z169

  22. Google IO 2019 @askashdavies | sli.do/Z169

  23. Kotlin @askashdavies | sli.do/Z169

  24. Libraries @askashdavies | sli.do/Z169

  25. @askashdavies | sli.do/Z169

  26. @askashdavies | sli.do/Z169

  27. @askashdavies | sli.do/Z169

  28. @askashdavies | sli.do/Z169

  29. @askashdavies | sli.do/Z169

  30. Rick and Morty, Adult Swim

  31. Kotlin @askashdavies | sli.do/Z169

  32. Idiomacy @askashdavies | sli.do/Z169

  33. @askashdavies | sli.do/Z169

  34. Idiomatic adj. Using, containing, or denoting expressions that are natural

    to a native speaker @askashdavies | sli.do/Z169
  35. Idiomatic use of Language Features kotlinlang.org/docs/reference/coding-conventions.html#idiomatic-use-of-language-features

  36. Idiomatic Code » Consistent, easier to read » Less cognitive

    load » Less ambiguity » Function > Style @askashdavies | sli.do/Z169
  37. Code Style » kotlinlang.org/docs/reference/coding-conventions.html » android.github.io/kotlin-guides/style.html @askashdavies | sli.do/Z169

  38. Refactoring Legacy Code @askashdavies | sli.do/Z169

  39. ⌃⌥⇧K @askashdavies | sli.do/Z169

  40. None
  41. None
  42. None
  43. public class BadJavaActivity extends Activity { @Inject Dependency dependency; }

    @askashdavies | sli.do/Z169
  44. General Assumptions How I Met Your Mother, CBS

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

    null } @askashdavies | sli.do/Z169
  46. class SlightlyLessBadKotlinActivity : Activity() { @Inject internal lateinit var dependency:

    Dependency } @askashdavies | sli.do/Z169
  47. UninitializedPropertyAccessException! @askashdavies | sli.do/Z169

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

    } @askashdavies | sli.do/Z169
  49. Nullability @askashdavies | sli.do/Z169

  50. !! @askashdavies | sli.do/Z169

  51. // Throws NullPointerException myNullVal.myMethod() @askashdavies | sli.do/Z169

  52. // Still throws NullPointerException myNullVal!!.myMethod() @askashdavies | sli.do/Z169

  53. // Does nothing ! myNullVal?.myMethod() @askashdavies | sli.do/Z169

  54. // Still does nothing ! myNullVal?.also { it.myMethod() } @askashdavies

    | sli.do/Z169
  55. // Defensive Code < ! > myNullVal?.also { it.myMethod() }

    @askashdavies | sli.do/Z169
  56. // Offensive Code < ! > myNullVal!!.myMethod() @askashdavies | sli.do/Z169

  57. // throws IllegalArgumentException("Required value was null.") requireNotNull(myNullVal).myMethod() @askashdavies | sli.do/Z169

  58. // throws IllegalArgumentException("myNullVaL was null because ...") requireNotNull(myNullVal) { "myNullVaL

    was null because ..." }.myMethod() @askashdavies | sli.do/Z169
  59. class ImpossibleException : IllegalArgumentException() // throws ImpossibleException() val myNotNullVal =

    myNullVall ?: throw ImpossibleException() myNotNullVal.myMethod() @askashdavies | sli.do/Z169
  60. class ImpossibleException : IllegalArgumentException(""" If you're seeing this, the code

    is in what I thought was an unreachable state. I could give you advice for what to do, but honestly, why should you trust me? I clearly screwed this up. I'm writing a message that should never appear, yet I know it will probably appear someday. On a deep level, I know I'm not up to this task. I'm so sorry. """) // throws ImpossibleException() val myNotNullVal = myNullVall ?: throw ImpossibleException() myNotNullVal.myMethod() xkcd.com/2200/
  61. Data Classes @askashdavies | sli.do/Z169

  62. 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 | sli.do/Z169
  63. 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 | sli.do/Z169
  64. data class User( val firstName: String, val lastName: String )

    @askashdavies | sli.do/Z169
  65. data class User( val firstName: String, val lastName: String? =

    null ) @askashdavies | sli.do/Z169
  66. @NotNull public final User copy(@Nullable String firstName, @Nullable String lastName)

    { return new User(firstName, lastName); } @askashdavies | sli.do/Z169
  67. Kotlin: Idioms » Singleton objects » String interpolation » Elvis

    operator ! » Destructuring » Extension functions » Scoping functions kotlinlang.org/docs/reference/idioms.html
  68. Maintaining History @askashdavies | sli.do/Z169

  69. Maintaining History » Change extension .java -> .kt » First

    commit » Apply Kotlin conversion » Second commit @askashdavies | sli.do/Z169
  70. Maintaining History Refactoring @askashdavies | sli.do/Z169

  71. Refactoring @askashdavies | sli.do/Z169

  72. Refactoring SOLID » Single-responsibility » Open-closed » Liskov substitution »

    Inte9ace segregation » Dependency inversion @askashdavies | sli.do/Z169
  73. @askashdavies | sli.do/Z169

  74. Refactoring Single Responsibility @askashdavies | sli.do/Z169

  75. Perspective @askashdavies | sli.do/Z169

  76. Static @askashdavies | sli.do/Z169

  77. Asynchronicity @askashdavies | sli.do/Z169

  78. Concurrency is Hard @askashdavies | sli.do/Z169

  79. Thread new Thread(() -> { foo(); }).start(); @askashdavies | sli.do/Z169

  80. CompletableFuture CompletableFuture.supplyAsync(this::findSomeData) .thenApply(this:: intReturningMethod) .thenAccept(this::notify); @askashdavies | sli.do/Z169

  81. 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 | sli.do/Z169
  82. @askashdavies | sli.do/Z169

  83. What If? @askashdavies | sli.do/Z169

  84. Coroutines @askashdavies | sli.do/Z169

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

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

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

    } // Hello, // World! @askashdavies | sli.do/Z169
  88. Stability @askashdavies | sli.do/Z169

  89. Annotations @Experimental @askashdavies | sli.do/Z169

  90. Building SDKs - The Kotlin Way Kotlin Everywhere: Hamburg -

    Jossi Wolf speakerdeck.com/jossiwolf/building-sdks-the-kotlin-way-ccb4a237-b45c-42e7-8391-640dd058f50c
  91. ! Coroutines @askashdavies | sli.do/Z169

  92. ! Native library @askashdavies | sli.do/Z169

  93. Efficient @askashdavies | sli.do/Z169

  94. ! Easy-to-use @askashdavies | sli.do/Z169

  95. ! suspend fun @askashdavies | sli.do/Z169

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

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

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

    } println("Hello,") } Thread.sleep(2000L) } suspend fun doWorld() { withContext(Dispatchers.IO) { delay(1000L) println("World!") } } // Hello, // World! @askashdavies | sli.do/Z169
  99. Testing @Test fun testFoo() = runBlockingTest { val actual =

    foo() // ... } suspend fun foo() { delay(1_000) // ... } @askashdavies | sli.do/Z169
  100. Further Reading ! » Google Codelab: Refactoring to Kotlin codelabs.developers.google.com/codelabs/java-to-kotlin/

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

  102. Questions sli.do/Z169