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.

Fc78fd09b8fee61efd4ef003fe104eb6?s=128

Ash Davies

April 30, 2020
Tweet

Transcript

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

    droidcon Online
  2. Legacy adj. Denoting or relating to software or hardware that

    has been superseded but is difficult to replace because of its wide use.
  3. Working Effectively with Legacy Code Michael Feathers • Untested code

    • Regression tests • Lack of confidence
  4. Java ☕

  5. JVM != ART

  6. None
  7. • Lambda expressions • Method references • Default interfaces •

    Try-with-resources • Streams ↝ Java 8 Released March 18, 2014
  8. Kotlin noun Freakin’ awesome

  9. • Consistently familiar code • Idiomatically behaviour • Looks terse

    and concise • Aggressive development Kotlin Freakin’ awesome
  10. Nullability. The billion dollar mistake

  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
  12. if (foo == null) { bar(); }

  13. Defensive Code <>

  14. Offensive Code <>

  15. Urgency

  16. Idiomacy

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

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

  19. • Consistent, easier to read • Less cognitive load •

    Less ambiguity • Function over style Idiomatic Code Idiomatic use of language features
  20. Kotlin style guide developer.android.com/kotlin/style-guide

  21. ktlint github.com/pinterest/ktlint

  22. Refactoring legacy code

  23. ⌥⇧⌘K

  24. Nullability

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

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

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

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

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

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

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

    internal lateinit var pump: Pump fun brew() { heater.heat() // Offensive<> } ... }
  32. class CoffeeMaker { private var heater: Heater? = null private

    var pump: Pump? = null fun brew() { requireNotNull(heater) { "Heater was null because ..." }.brew() } ... }
  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 ?:
  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/
  35. Beans

  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() { ... } }
  37. class User(var firstName: String?, var lastName: String?) { override fun

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

  39. Extensions

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

  41. inline fun SharedPreferences.edit( commit: Boolean = false, action: Editor.() ->

    Unit ) { val editor = edit() action(editor) if (commit) { editor.commit() } else { editor.apply() } }
  42. sharedPreferences.edit { putBoolean("key", value) }

  43. • Default values • Singleton objects • String interpolation •

    Destructuring • Scope functions Kotlin Idioms kotlinlang.org/docs/reference/idioms.html
  44. Maintaining History

  45. • Rename .java -> .kt • First commit/merge • Kotlin

    conversion • Second commit/merge Maintaining History Git
  46. Maintaining History Refactoring

  47. • Single Responsibility Principle • Open/Closed Principle • Liskov Substitution

    Principle • Interface Segregation Principle • Dependency Inversion Principle Refactoring Legacy Code SOLID Design principles
  48. None
  49. • Perspective • Better understanding • Static ‍♂ • Asynchronicity

    Refactoring Legacy Code SOLID Design principles
  50. Refactoring Legacy Code with Kotlin Concurrency

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

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

  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...
  54. Coroutines

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

    } Hello, World!
  56. Kotlin Coroutines Stability

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

  58. • Native library • Efficient • Easy-to-use • suspend fun

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

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

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

    suspend fun doWorld() { withContext(Dispatchers.IO) { delay(1000L) println("World!") } } Hello, World!
  62. @Test fun testFoo() = runBlockingTest { val actual = foo()

    ... } suspend fun foo() { delay(1_000L) ... } Easy to test!
  63. Coroutine Testing kotlinx-coroutines-test org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.3

  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
  65. Ash Davies GDE Berlin @askashdavies Thank You!