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

mDevCamp: Refactoring Legacy Code with Kotlin

mDevCamp: Refactoring Legacy Code with Kotlin

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.

Ash Davies

June 10, 2020
Tweet

More Decks by Ash Davies

Other Decks in Programming

Transcript

  1. Ash Davies
    GDE Berlin
    @askashdavies
    Refactoring Legacy
    Code with Kotlin
    mDevCamp - June

    View Slide

  2. Legacy
    adj.
    Denoting or relating to software or hardware that has been superseded
    but is difficult to replace because of its wide use.

    View Slide

  3. Working Effectively
    with Legacy Code
    Michael Feathers
    ● Untested code
    ● Regression tests
    ● Lack of confidence

    View Slide

  4. View Slide

  5. Java ☕

    View Slide

  6. JVM != ART

    View Slide

  7. View Slide

  8. ● Lambda expressions
    ● Method references
    ● Default interfaces
    ● Try-with-resources
    ● Streams ↝
    Java 8
    Released March 18, 2014

    View Slide

  9. Kotlin
    noun
    Freakin’ awesome

    View Slide

  10. ● Consistently familiar code
    ● Idiomatically behaviour
    ● Looks terse and concise
    ● Aggressive development
    Kotlin
    Freakin’ awesome

    View Slide

  11. Nullability.
    The billion dollar mistake

    View Slide

  12. “This has led to innumerable errors,
    vulnerabilities, and system crashes,
    which have probably caused a billion
    dollars of pain and damage in the last
    forty years.”
    - Sir Tony Hoare

    View Slide

  13. if (foo == null) {
    bar();
    }

    View Slide

  14. Defensive Code <>

    View Slide

  15. Offensive Code <>

    View Slide

  16. Urgency

    View Slide

  17. Idiomacy

    View Slide

  18. Idiomatic
    adj.
    Using, containing, or denoting expressions that are natural to a native
    speaker.

    View Slide

  19. Idiomatic use of
    language features
    kotlinlang.org/docs/reference/coding-conventions.html
    #idiomatic-use-of-language-feature

    View Slide

  20. ● Consistent, easier to read
    ● Less cognitive load
    ● Less ambiguity
    ● Function over style
    Idiomatic Code
    Idiomatic use of language features

    View Slide

  21. Kotlin style guide
    developer.android.com/kotlin/style-guide

    View Slide

  22. ktlint
    github.com/pinterest/ktlint

    View Slide

  23. Refactoring legacy code

    View Slide

  24. ⌥⇧⌘K

    View Slide

  25. ⌥⇧⌘K

    View Slide

  26. Nullability

    View Slide

  27. class CoffeeMaker {
    @Inject Heater heater;
    @Inject Pump pump;
    ...
    }

    View Slide

  28. class CoffeeMaker {
    @Inject var heater: Heater? = null
    @Inject var pump: Pump? = null
    ...
    }

    View Slide

  29. class CoffeeMaker {
    @Inject internal lateinit var heater: Heater
    @Inject internal lateinit var pump: Pump
    ...
    }

    View Slide

  30. class CoffeeMaker {
    @Inject internal lateinit var heater: Heater
    @Inject internal lateinit var pump: Pump
    fun brew() {
    heater.heat() // Throws UninitializedPropertyAccessException
    }
    ...
    }

    View Slide

  31. class CoffeeMaker {
    @Inject internal lateinit var heater: Heater
    @Inject internal lateinit var pump: Pump
    fun brew() {
    heater!!.heat() // Throws NullPointerException
    }
    ...
    }

    View Slide

  32. class CoffeeMaker {
    @Inject internal lateinit var heater: Heater
    @Inject internal lateinit var pump: Pump
    fun brew() {
    if (::file.isInitialized) {
    heater.heat() // Defensive<>
    }
    }
    ...
    }

    View Slide

  33. class CoffeeMaker {
    @Inject internal lateinit var heater: Heater
    @Inject internal lateinit var pump: Pump
    fun brew() {
    heater.heat() // Offensive<>
    }
    ...
    }

    View Slide

  34. class CoffeeMaker {
    private var heater: Heater? = null
    private var pump: Pump? = null
    fun brew() {
    requireNotNull(heater) {
    "Heater was null because ..."
    }.brew()
    ...
    }
    }

    View Slide

  35. class CoffeeMaker {
    private var heater: Heater? = null
    private var pump: Pump? = null
    fun brew() {
    val heater: Heater = heater ?: throw ImpossibleException
    heater.brew()
    }
    ...
    object ImpossibleException : IllegalArgumentException()
    }
    Elvis Operator

    View Slide

  36. object 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.
    """)
    xkcd.com/2200/

    View Slide

  37. Beans

    View Slide

  38. public class User {
    private String firstName;
    private String lastName;
    public User(String firstName, String lastName) { ... }
    public String getFirstName() { ... }
    public void setFirstName(String firstName) { ... }
    public String getLastName() { ... }
    public void setLastName(String lastName) { ... }
    @Override
    public boolean equals(Object o) { ... }
    @Override
    public int hashCode() { ... }
    }

    View Slide

  39. class User(var firstName: String?, var lastName: String?) {
    override fun equals(o: Any?): Boolean { ... }
    override fun hashCode(): Int { ... }
    }

    View Slide

  40. data class User(
    val firstName: String,
    val lastName: String
    )

    View Slide

  41. Extensions

    View Slide

  42. sharedPreferences
    .edit()
    .putBoolean("key", value)
    .apply()

    View Slide

  43. inline fun SharedPreferences.edit(
    commit: Boolean = false,
    action: Editor.() -> Unit
    ) {
    val editor = edit()
    action(editor)
    if (commit) {
    editor.commit()
    } else {
    editor.apply()
    }
    }

    View Slide

  44. sharedPreferences.edit {
    putBoolean("key", value)
    }

    View Slide

  45. ● Default values
    ● Singleton objects
    ● String interpolation
    ● Destructuring
    ● Scope functions
    Kotlin Idioms
    kotlinlang.org/docs/reference/idioms.html

    View Slide

  46. Maintaining History?

    View Slide

  47. ● Rename .java -> .kt
    ● First commit/merge
    ● Kotlin conversion
    ● Second commit/merge
    Maintaining History
    Git

    View Slide

  48. Maintaining History
    Refactoring

    View Slide

  49. ● Single Responsibility Principle
    ● Open/Closed Principle
    ● Liskov Substitution Principle
    ● Interface Segregation Principle
    ● Dependency Inversion Principle
    Refactoring Legacy Code
    SOLID Design principles

    View Slide

  50. View Slide

  51. ● Perspective
    ● Better understanding
    ● ‍♂ Static
    ● Concurrency
    Refactoring Legacy Code
    SOLID Design principles

    View Slide

  52. Refactoring Legacy
    Code with Kotlin
    Concurrency (is hard)

    View Slide

  53. new Thread(() -> {
    foo();
    }).start();

    View Slide

  54. CompletableFuture.supplyAsync(this::findSomeData)
    .thenApply(this:: intReturningMethod)
    .thenAccept(this::notify);

    View Slide

  55. 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, getIntent()))
    .observeOn(AndroidSchedulers.mainThread())
    .subscribeOn(Schedulers.io())
    .subscribe(
    resource -> repository.setResource(resourceId, resource, provisionalResourceId),
    resourceUploadError(resourceId, resourceDraft, provisionalResourceId)
    );
    Well that escalated quickly...

    View Slide

  56. Coroutines

    View Slide

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

    View Slide

  58. Kotlin Coroutines
    Stability

    View Slide

  59. Building SDKs - The
    Kotlin Way: Jossi Wolf
    bit.ly/sdks-the-kotlin-way

    View Slide

  60. ● Native library
    ● Efficient
    ● Easy-to-use ™
    ● suspend fun
    Refactoring Legacy Code
    Kotlin Coroutines

    View Slide

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

    View Slide

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

    View Slide

  63. fun main() {
    GlobalScope.launch {
    doWorld()
    }
    println("Hello,")
    Thread.sleep(2000L)
    }
    suspend fun doWorld() {
    withContext(Dispatchers.IO) {
    delay(1000L)
    println("World!")
    }
    }
    Hello,
    World!

    View Slide

  64. @Test
    fun testFoo() = runBlockingTest {
    val actual = foo()
    ...
    }
    suspend fun foo() {
    delay(1_000L)
    ...
    }
    Easy to test!

    View Slide

  65. Coroutine Testing
    kotlinx-coroutines-tes
    t
    org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.3

    View Slide

  66. ● codelabs.developers.google.com/codelabs/java-to-kotlin/
    ● github.com/Kotlin/kotlinx.coroutines/tree/master/kotlinx-coroutines-test
    ● droidcon.com/media-detail?video=352671106
    ● speakerdeck.com/ashdavies/mdevcamp-rxjava-and-coroutines-a-practical-analysis
    Refactoring Legacy Code
    Further Reading

    View Slide

  67. Ash Davies
    GDE Berlin
    @askashdavies
    Thank You!

    View Slide