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

droidcon Online: Refactoring Legacy Code with Kotlin

droidcon Online: 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.

Ash Davies

April 30, 2020
Tweet

More Decks by Ash Davies

Other Decks in Programming

Transcript

  1. Ash Davies
    GDE Berlin
    @askashdavies
    Refactoring Legacy
    Code with Kotlin
    droidcon Online

    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. Java ☕

    View Slide

  5. JVM != ART

    View Slide

  6. View Slide

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

    View Slide

  8. Kotlin
    noun
    Freakin’ awesome

    View Slide

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

    View Slide

  10. Nullability.
    The billion dollar mistake

    View Slide

  11. “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

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

    View Slide

  13. Defensive Code <>

    View Slide

  14. Offensive Code <>

    View Slide

  15. Urgency

    View Slide

  16. Idiomacy

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  21. ktlint
    github.com/pinterest/ktlint

    View Slide

  22. Refactoring legacy code

    View Slide

  23. ⌥⇧⌘K

    View Slide

  24. Nullability

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  33. 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

  34. 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

  35. Beans

    View Slide

  36. 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

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

    View Slide

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

    View Slide

  39. Extensions

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  44. Maintaining History

    View Slide

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

    View Slide

  46. Maintaining History
    Refactoring

    View Slide

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

    View Slide

  48. View Slide

  49. ● Perspective
    ● Better understanding
    ● Static ‍♂
    ● Asynchronicity
    Refactoring Legacy Code
    SOLID Design principles

    View Slide

  50. Refactoring Legacy
    Code with Kotlin
    Concurrency

    View Slide

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

    View Slide

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

    View Slide

  53. 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

  54. Coroutines

    View Slide

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

    View Slide

  56. Kotlin Coroutines
    Stability

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  63. Coroutine Testing
    kotlinx-coroutines-test
    org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.3

    View Slide

  64. ● 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

  65. Ash Davies
    GDE Berlin
    @askashdavies
    Thank You!

    View Slide