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 full-size 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 full-size slide

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

    View full-size slide

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

    View full-size slide

  5. Kotlin
    noun
    Freakin’ awesome

    View full-size slide

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

    View full-size slide

  7. Nullability.
    The billion dollar mistake

    View full-size slide

  8. “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 full-size slide

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

    View full-size slide

  10. Defensive Code <>

    View full-size slide

  11. Offensive Code <>

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  16. ktlint
    github.com/pinterest/ktlint

    View full-size slide

  17. Refactoring legacy code

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  26. 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 full-size slide

  27. 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 full-size slide

  28. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  35. Maintaining History

    View full-size slide

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

    View full-size slide

  37. Maintaining History
    Refactoring

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  40. Refactoring Legacy
    Code with Kotlin
    Concurrency

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  43. 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 full-size slide

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

    View full-size slide

  45. Kotlin Coroutines
    Stability

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  53. ● 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 full-size slide

  54. Ash Davies
    GDE Berlin
    @askashdavies
    Thank You!

    View full-size slide