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. Legacy adj. Denoting or relating to software or hardware that

    has been superseded but is difficult to replace because of its wide use.
  2. • Lambda expressions • Method references • Default interfaces •

    Try-with-resources • Streams ↝ Java 8 Released March 18, 2014
  3. • Consistently familiar code • Idiomatically behaviour • Looks terse

    and concise • Aggressive development Kotlin Freakin’ awesome
  4. “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
  5. • Consistent, easier to read • Less cognitive load •

    Less ambiguity • Function over style Idiomatic Code Idiomatic use of language features
  6. class CoffeeMaker { @Inject internal lateinit var heater: Heater @Inject

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

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

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

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

    var pump: Pump? = null fun brew() { requireNotNull(heater) { "Heater was null because ..." }.brew() } ... }
  11. 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 ?:
  12. 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/
  13. 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() { ... } }
  14. class User(var firstName: String?, var lastName: String?) { override fun

    equals(o: Any?): Boolean { ... } override fun hashCode(): Int { ... } }
  15. inline fun SharedPreferences.edit( commit: Boolean = false, action: Editor.() ->

    Unit ) { val editor = edit() action(editor) if (commit) { editor.commit() } else { editor.apply() } }
  16. • Default values • Singleton objects • String interpolation •

    Destructuring • Scope functions Kotlin Idioms kotlinlang.org/docs/reference/idioms.html
  17. • Rename .java -> .kt • First commit/merge • Kotlin

    conversion • Second commit/merge Maintaining History Git
  18. • Single Responsibility Principle • Open/Closed Principle • Liskov Substitution

    Principle • Interface Segregation Principle • Dependency Inversion Principle Refactoring Legacy Code SOLID Design principles
  19. 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...
  20. • Native library • Efficient • Easy-to-use • suspend fun

    Refactoring Legacy Code Kotlin Coroutines
  21. fun main() { GlobalScope.launch { doWorld() } println("Hello,") Thread.sleep(2000L) }

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

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

    ... } suspend fun foo() { delay(1_000L) ... } Easy to test!