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

Creating A Better Developer Experience By Avoiding Legacy Code

Creating A Better Developer Experience By Avoiding Legacy Code

Presented at the virtual Android Summit 2020.

Adam McNeilly

October 08, 2020
Tweet

More Decks by Adam McNeilly

Other Decks in Programming

Transcript

  1. Creating A Better Developer
    Experience By Avoiding Legacy
    Code
    Adam McNeilly - @AdamMc331
    @AdamMc331
    #AndroidSummit 1

    View Slide

  2. What Is Legacy Code?
    @AdamMc331
    #AndroidSummit 2

    View Slide

  3. What Is Legacy Code?
    @AdamMc331
    #AndroidSummit 3

    View Slide

  4. What Is Legacy Code?
    1. Code that was written before we knew better.
    @AdamMc331
    #AndroidSummit 3

    View Slide

  5. What Is Legacy Code?
    1. Code that was written before we knew better.
    2. Code that was written by someone else, who is no longer a
    maintainer.
    @AdamMc331
    #AndroidSummit 3

    View Slide

  6. What Is Legacy Code?
    1. Code that was written before we knew better.
    2. Code that was written by someone else, who is no longer a
    maintainer.
    3. Code that doesn't have tests.
    @AdamMc331
    #AndroidSummit 3

    View Slide

  7. What Is Legacy Code?
    1. Code that was written before we knew better.
    2. Code that was written by someone else, who is no longer a
    maintainer.
    3. Code that doesn't have tests.
    4. Code that we're unable to/afraid to change because everything might
    break.
    @AdamMc331
    #AndroidSummit 3

    View Slide

  8. What Is Legacy Code?
    1. Code that was written before we knew better.
    2. Code that was written by someone else, who is no longer a
    maintainer.
    3. Code that doesn't have tests.
    4. Code that we're unable to/afraid to change because everything might
    break.
    5. Code without any type of comments or documentation.
    @AdamMc331
    #AndroidSummit 3

    View Slide

  9. What Is Legacy Code?
    1. Code that was written before we knew better.
    2. Code that was written by someone else, who is no longer a
    maintainer.
    3. Code that doesn't have tests.
    4. Code that we're unable to/afraid to change because everything might
    break.
    5. Code without any type of comments or documentation.
    6. All of the above!
    @AdamMc331
    #AndroidSummit 3

    View Slide

  10. Why Do We Care?
    @AdamMc331
    #AndroidSummit 4

    View Slide

  11. Legacy Code Impacts Our Work
    @AdamMc331
    #AndroidSummit 5

    View Slide

  12. Legacy Code Impacts Our Work
    1. Legacy code can become a blocker for new development.
    @AdamMc331
    #AndroidSummit 5

    View Slide

  13. Legacy Code Impacts Our Work
    1. Legacy code can become a blocker for new development.
    2. Legacy code can prevent us from shipping with confidence.
    @AdamMc331
    #AndroidSummit 5

    View Slide

  14. Legacy Code Impacts Our Work
    1. Legacy code can become a blocker for new development.
    2. Legacy code can prevent us from shipping with confidence.
    3. Legacy code can be confusing and difficult to build on top of.
    @AdamMc331
    #AndroidSummit 5

    View Slide

  15. Legacy Code Impacts Our Work
    1. Legacy code can become a blocker for new development.
    2. Legacy code can prevent us from shipping with confidence.
    3. Legacy code can be confusing and difficult to build on top of.
    4. Legacy code is not fun to work with for these, and many other
    reasons.
    @AdamMc331
    #AndroidSummit 5

    View Slide

  16. Is Legacy Code
    Inevitable?
    @AdamMc331
    #AndroidSummit 6

    View Slide

  17. Is Legacy Code Inevitable?
    Code will age with time, and new practices will always arise.
    However, we can examine our own experiences with legacy code
    to help ensure our future selves and teammates have a better
    developer experience.
    @AdamMc331
    #AndroidSummit 7

    View Slide

  18. Myth: Legacy Code
    Was Written By
    Someone Else
    @AdamMc331
    #AndroidSummit 8

    View Slide

  19. Legacy Code Was Written By
    Someone Else
    @AdamMc331
    #AndroidSummit 9

    View Slide

  20. Legacy Code Was Written By
    Someone Else
    » We can't avoid people moving on to new opportunities.
    @AdamMc331
    #AndroidSummit 9

    View Slide

  21. Legacy Code Was Written By
    Someone Else
    » We can't avoid people moving on to new opportunities.
    » One day, YOU will be that someone.
    @AdamMc331
    #AndroidSummit 9

    View Slide

  22. Legacy Code Was Written By
    Someone Else
    » We can't avoid people moving on to new opportunities.
    » One day, YOU will be that someone.
    » Your past self from one year ago also wrote code like a
    different person.
    @AdamMc331
    #AndroidSummit 9

    View Slide

  23. @AdamMc331
    #AndroidSummit 10

    View Slide

  24. Exploring Causes Of
    Legacy Code
    @AdamMc331
    #AndroidSummit 11

    View Slide

  25. Legacy Code Doesn't
    Have Tests
    @AdamMc331
    #AndroidSummit 12

    View Slide

  26. Legacy Code Doesn't Have Tests
    @AdamMc331
    #AndroidSummit 13

    View Slide

  27. Legacy Code Doesn't Have Tests
    There are two reasons code doesn't have tests:
    @AdamMc331
    #AndroidSummit 14

    View Slide

  28. Legacy Code Doesn't Have Tests
    There are two reasons code doesn't have tests:
    1. The author chose not to write any.
    @AdamMc331
    #AndroidSummit 14

    View Slide

  29. Legacy Code Doesn't Have Tests
    There are two reasons code doesn't have tests:
    1. The author chose not to write any.
    2. The code wasn't testable in the first place.
    @AdamMc331
    #AndroidSummit 14

    View Slide

  30. Untestable Code
    @AdamMc331
    #AndroidSummit 15

    View Slide

  31. Untestable Code
    1. Code with a lot of static references.
    @AdamMc331
    #AndroidSummit 15

    View Slide

  32. Untestable Code
    1. Code with a lot of static references.
    2. Code that doesn't leverage proper dependency injection.
    @AdamMc331
    #AndroidSummit 15

    View Slide

  33. Untestable Code
    1. Code with a lot of static references.
    2. Code that doesn't leverage proper dependency injection.
    These unclear static dependencies can sometimes indicate fragile
    code that people are afraid to change.
    @AdamMc331
    #AndroidSummit 16

    View Slide

  34. Untestable Code
    class ProfileViewModel : ViewModel() {
    private fun loadProfile() {
    try {
    // ...
    } catch (e: Throwable) {
    MyErrorTool.getInstance().logException(e)
    }
    }
    }
    @AdamMc331
    #AndroidSummit 17

    View Slide

  35. Untestable Code
    } catch (e: Throwable) {
    MyErrorTool.getInstance().logException(e)
    }
    @AdamMc331
    #AndroidSummit 18

    View Slide

  36. Untestable Code
    } catch (e: Throwable) {
    MyErrorTool.getInstance().logException(e)
    }
    » MyErrorTool is difficult to mock for unit tests.
    @AdamMc331
    #AndroidSummit 18

    View Slide

  37. Untestable Code
    } catch (e: Throwable) {
    MyErrorTool.getInstance().logException(e)
    }
    » MyErrorTool is difficult to mock for unit tests.
    » We don't want our negative tests to actually log errors to our
    production tool.
    @AdamMc331
    #AndroidSummit 18

    View Slide

  38. Untestable Code
    } catch (e: Throwable) {
    MyErrorTool.getInstance().logException(e)
    }
    » MyErrorTool is difficult to mock for unit tests.
    » We don't want our negative tests to actually log errors to our
    production tool.
    » This tool could be running setup that crashes our tests.
    @AdamMc331
    #AndroidSummit 18

    View Slide

  39. Untestable Code - Quick Solution
    // Inject error tool with default value.
    class ProfileViewModel(
    private val errorTool: MyErrorTool = MyErrorTool.getInstance()
    ) : ViewModel() {
    private fun loadProfile() {
    try {
    // ...
    } catch (e: Throwable) {
    errorTool.logException(e)
    }
    }
    }
    @AdamMc331
    #AndroidSummit 19

    View Slide

  40. Tightly Coupled
    Dependencies Become
    Legacy Code
    @AdamMc331
    #AndroidSummit 20

    View Slide

  41. Tightly Coupled Dependencies
    Become Legacy Code
    @AdamMc331
    #AndroidSummit 21

    View Slide

  42. Tightly Coupled Dependencies
    Become Legacy Code
    » Companies change third party vendors (analytics trackers,
    error reporters).
    @AdamMc331
    #AndroidSummit 21

    View Slide

  43. Tightly Coupled Dependencies
    Become Legacy Code
    » Companies change third party vendors (analytics trackers,
    error reporters).
    » Companies may change tech stacks (REST to GraphQL).
    @AdamMc331
    #AndroidSummit 21

    View Slide

  44. Tightly Coupled Dependencies
    Become Legacy Code
    » Companies change third party vendors (analytics trackers,
    error reporters).
    » Companies may change tech stacks (REST to GraphQL).
    » New image loading libraries are made every few years.
    @AdamMc331
    #AndroidSummit 21

    View Slide

  45. Tightly Coupled Dependencies
    Become Legacy Code
    » Companies change third party vendors (analytics trackers,
    error reporters).
    » Companies may change tech stacks (REST to GraphQL).
    » New image loading libraries are made every few years.
    » Code that doesn't allow these things to be changed is likely to
    become legacy code.
    @AdamMc331
    #AndroidSummit 21

    View Slide

  46. Strict Dependencies
    // Codebase strongly relies on `MyErrorTool`
    class ProfileViewModel(
    private val errorTool: MyErrorTool = MyErrorTool.getInstance()
    ) : ViewModel()
    @AdamMc331
    #AndroidSummit 22

    View Slide

  47. Strict Dependencies
    // Codebase strongly relies on `MyErrorTool`
    class ProfileViewModel(
    private val errorTool: MyErrorTool = MyErrorTool.getInstance()
    ) : ViewModel()
    » This class is strictly tied to MyErrorTool method contracts.
    @AdamMc331
    #AndroidSummit 22

    View Slide

  48. Strict Dependencies
    // Codebase strongly relies on `MyErrorTool`
    class ProfileViewModel(
    private val errorTool: MyErrorTool = MyErrorTool.getInstance()
    ) : ViewModel()
    » This class is strictly tied to MyErrorTool method contracts.
    » Despite dependency injection, this will make swapping our
    error tool difficult.
    @AdamMc331
    #AndroidSummit 22

    View Slide

  49. Wrapping Dependencies
    interface ErrorReporter {
    fun reportError(error: Throwable)
    }
    class FirebaseErrorReporter : ErrorReporter {
    override fun reportError(error: Throwable) {
    Firebase.getInstance().logException(error)
    }
    }
    @AdamMc331
    #AndroidSummit 23

    View Slide

  50. Wrapping Dependencies
    // Firebase is default, but we have the ability
    // to pass anything we want here.
    class ProfileViewModel(
    private val errorReporter: ErrorReporter = FirebaseErrorReporter()
    ) : ViewModel()
    @AdamMc331
    #AndroidSummit 24

    View Slide

  51. Wrapping Dependencies
    @AdamMc331
    #AndroidSummit 25

    View Slide

  52. Wrapping Dependencies
    » Easier to provide fake/mock implementations in tests.
    @AdamMc331
    #AndroidSummit 25

    View Slide

  53. Wrapping Dependencies
    » Easier to provide fake/mock implementations in tests.
    » Easier to entirely swap third party tools.
    @AdamMc331
    #AndroidSummit 25

    View Slide

  54. Wrapping Dependencies
    » Easier to provide fake/mock implementations in tests.
    » Easier to entirely swap third party tools.
    » Easier to update tools.
    @AdamMc331
    #AndroidSummit 25

    View Slide

  55. Wrapping Dependencies
    val coreModule = module {
    /**
    * Change the reporter supplied by your DI manager,
    * the entire app just updates!
    */
    single {
    FirebaseErrorReporter()
    // SentryErrorReporter()
    // EmbraceErrorReporter()
    }
    }
    @AdamMc331
    #AndroidSummit 26

    View Slide

  56. Wrapping Dependencies
    class FirebaseErrorReporter : ErrorReporter {
    /**
    * Wrapping this method makes it easy to update our project
    * if the library changes its method signature.
    */
    override fun reportError(error: Throwable) {
    // Firebase.getInstance().logException(error)
    Firebase.getInstance().logError(error)
    }
    }
    @AdamMc331
    #AndroidSummit 27

    View Slide

  57. These Steps Avoid A Lot Of
    Legacy Code Pain Points
    @AdamMc331
    #AndroidSummit 28

    View Slide

  58. These Steps Avoid A Lot Of
    Legacy Code Pain Points
    » We have testable code that we can ship with confidence.
    @AdamMc331
    #AndroidSummit 28

    View Slide

  59. These Steps Avoid A Lot Of
    Legacy Code Pain Points
    » We have testable code that we can ship with confidence.
    » We have decoupled third party tools, giving us freedom to
    swap vendors with ease.
    @AdamMc331
    #AndroidSummit 28

    View Slide

  60. These Steps Avoid A Lot Of
    Legacy Code Pain Points
    » We have testable code that we can ship with confidence.
    » We have decoupled third party tools, giving us freedom to
    swap vendors with ease.
    » We can confidently upgrade our third party tools and limit the
    code changes required.
    @AdamMc331
    #AndroidSummit 28

    View Slide

  61. Legacy Code Is Code
    No One On The Team
    Understands
    @AdamMc331
    #AndroidSummit 29

    View Slide

  62. Legacy Code Is Code
    No One On The Team
    Understands Doesn't
    Have Documentation
    @AdamMc331
    #AndroidSummit 30

    View Slide

  63. How Undocumented Code
    Becomes Legacy Code
    @AdamMc331
    #AndroidSummit 31

    View Slide

  64. How Undocumented Code
    Becomes Legacy Code
    1. The author of the code understood what they were writing.
    @AdamMc331
    #AndroidSummit 31

    View Slide

  65. How Undocumented Code
    Becomes Legacy Code
    1. The author of the code understood what they were writing.
    2. The author felt that the code was self documenting.
    @AdamMc331
    #AndroidSummit 31

    View Slide

  66. How Undocumented Code
    Becomes Legacy Code
    1. The author of the code understood what they were writing.
    2. The author felt that the code was self documenting.
    3. The author didn't provide additional context.
    @AdamMc331
    #AndroidSummit 31

    View Slide

  67. How Undocumented Code
    Becomes Legacy Code
    1. The author of the code understood what they were writing.
    2. The author felt that the code was self documenting.
    3. The author didn't provide additional context.
    4. The author leaves the team, only for someone else to find this class
    much later.
    @AdamMc331
    #AndroidSummit 31

    View Slide

  68. How Undocumented Code
    Becomes Legacy Code
    1. The author of the code understood what they were writing.
    2. The author felt that the code was self documenting.
    3. The author didn't provide additional context.
    4. The author leaves the team, only for someone else to find this class
    much later.
    5. The new team has no idea how this code works, or if it can be changed.
    @AdamMc331
    #AndroidSummit 31

    View Slide

  69. Looking After Future
    Developers With
    Documentation
    @AdamMc331
    #AndroidSummit 32

    View Slide

  70. Document When You Rely
    External Code Samples
    /**
    * Feature XYZ requires a ViewPager like experience, but
    * without the user being able to control each step. We've
    * implemented a ViewPager that rejects any user interraction.
    *
    * Source: https://stackoverflow.com/a/9650884/3131147
    */
    class NonSwipeableViewPager(
    context: Context,
    attrs: AttributeSet? = null
    ) : ViewPager(context, attrs) {
    }
    @AdamMc331
    #AndroidSummit 33

    View Slide

  71. Document When You Rely
    External Code Samples
    /**
    * Feature XYZ requires a ViewPager like experience, but
    * without the user being able to control each step. We've
    * implemented a ViewPager that rejects any user interraction.
    *
    * Source: https://stackoverflow.com/a/9650884/3131147
    */
    class NonSwipeableViewPager(
    context: Context,
    attrs: AttributeSet? = null
    ) : ViewPager(context, attrs) {
    }
    » This allows future devs to understand why this class was added, and explore if there are new first-party solutions.
    @AdamMc331
    #AndroidSummit 33

    View Slide

  72. Document When You Work
    Around Library Bugs
    /**
    * Due to github.com/company/library/issues/1234 we needed
    * to implement this work around to prevent a crash on
    * Android devices running API 21.
    */
    private fun doLibraryWorkAround() {
    // ...
    }
    @AdamMc331
    #AndroidSummit 34

    View Slide

  73. Document When You Work
    Around Library Bugs
    /**
    * Due to github.com/company/library/issues/1234 we needed
    * to implement this work around to prevent a crash on
    * Android devices running API 21.
    */
    private fun doLibraryWorkAround() {
    // ...
    }
    » This allows future devs to understand what this block of code is doing, and explore if the bug has been fixed
    in a subsequent library update.
    @AdamMc331
    #AndroidSummit 34

    View Slide

  74. Document When You Use
    Libraries For Specific Use Cases
    implementation("androidx.security:security-crypto:$rootProject.ext.versions.security") {
    because("This dependency allows us to use EncryptedSharedPreferences to store sensitive information.")
    }
    @AdamMc331
    #AndroidSummit 35

    View Slide

  75. Document When You Use
    Libraries For Specific Use Cases
    implementation("androidx.security:security-crypto:$rootProject.ext.versions.security") {
    because("This dependency allows us to use EncryptedSharedPreferences to store sensitive information.")
    }
    » This allows future devs to update version numbers with
    confidence, as well as understanding what to test, and why
    this library was chosen.
    @AdamMc331
    #AndroidSummit 35

    View Slide

  76. Why This
    Documentation
    Matters
    @AdamMc331
    #AndroidSummit 36

    View Slide

  77. Why This Documentation Matters
    We're not trying to prevent code from becoming obsolete and
    outdated. We're trying to help the future maintainers respond
    accordingly.
    @AdamMc331
    #AndroidSummit 37

    View Slide

  78. Recap
    @AdamMc331
    #AndroidSummit 38

    View Slide

  79. Recap
    » Writing testable code with decent coverage helps future
    developers refactor with confidence.
    @AdamMc331
    #AndroidSummit 38

    View Slide

  80. Recap
    » Writing testable code with decent coverage helps future
    developers refactor with confidence.
    » Wrapping dependencies allows future developers to update
    and replace tooling easily.
    @AdamMc331
    #AndroidSummit 38

    View Slide

  81. Recap
    » Writing testable code with decent coverage helps future
    developers refactor with confidence.
    » Wrapping dependencies allows future developers to update
    and replace tooling easily.
    » Documenting certain decisions allows future developers to
    confidently understand, refactor, and replace code written by
    someone else.
    @AdamMc331
    #AndroidSummit 38

    View Slide

  82. Remember
    The goal isn't to completely eradicate legacy code. It's to look out
    for our future teammates by not leaving behind rigid, confusing,
    and unmaintainable code.
    @AdamMc331
    #AndroidSummit 39

    View Slide

  83. Thanks!
    @AdamMc331
    #AndroidSummit 40

    View Slide