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.

Bc87ea9c7a0f85b8761b716a677c6694?s=128

Adam McNeilly

October 08, 2020
Tweet

Transcript

  1. Creating A Better Developer Experience By Avoiding Legacy Code Adam

    McNeilly - @AdamMc331 @AdamMc331 #AndroidSummit 1
  2. What Is Legacy Code? @AdamMc331 #AndroidSummit 2

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

  4. What Is Legacy Code? 1. Code that was written before

    we knew better. @AdamMc331 #AndroidSummit 3
  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
  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
  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
  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
  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
  10. Why Do We Care? @AdamMc331 #AndroidSummit 4

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

  12. Legacy Code Impacts Our Work 1. Legacy code can become

    a blocker for new development. @AdamMc331 #AndroidSummit 5
  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
  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
  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
  16. Is Legacy Code Inevitable? @AdamMc331 #AndroidSummit 6

  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
  18. Myth: Legacy Code Was Written By Someone Else @AdamMc331 #AndroidSummit

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

  20. Legacy Code Was Written By Someone Else » We can't

    avoid people moving on to new opportunities. @AdamMc331 #AndroidSummit 9
  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
  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
  23. @AdamMc331 #AndroidSummit 10

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

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

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

  27. Legacy Code Doesn't Have Tests There are two reasons code

    doesn't have tests: @AdamMc331 #AndroidSummit 14
  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
  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
  30. Untestable Code @AdamMc331 #AndroidSummit 15

  31. Untestable Code 1. Code with a lot of static references.

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

    2. Code that doesn't leverage proper dependency injection. @AdamMc331 #AndroidSummit 15
  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
  34. Untestable Code class ProfileViewModel : ViewModel() { private fun loadProfile()

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

    #AndroidSummit 18
  36. Untestable Code } catch (e: Throwable) { MyErrorTool.getInstance().logException(e) } »

    MyErrorTool is difficult to mock for unit tests. @AdamMc331 #AndroidSummit 18
  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
  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
  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
  40. Tightly Coupled Dependencies Become Legacy Code @AdamMc331 #AndroidSummit 20

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

  42. Tightly Coupled Dependencies Become Legacy Code » Companies change third

    party vendors (analytics trackers, error reporters). @AdamMc331 #AndroidSummit 21
  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
  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
  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
  46. Strict Dependencies // Codebase strongly relies on `MyErrorTool` class ProfileViewModel(

    private val errorTool: MyErrorTool = MyErrorTool.getInstance() ) : ViewModel() @AdamMc331 #AndroidSummit 22
  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
  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
  49. Wrapping Dependencies interface ErrorReporter { fun reportError(error: Throwable) } class

    FirebaseErrorReporter : ErrorReporter { override fun reportError(error: Throwable) { Firebase.getInstance().logException(error) } } @AdamMc331 #AndroidSummit 23
  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
  51. Wrapping Dependencies @AdamMc331 #AndroidSummit 25

  52. Wrapping Dependencies » Easier to provide fake/mock implementations in tests.

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

    » Easier to entirely swap third party tools. @AdamMc331 #AndroidSummit 25
  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
  55. Wrapping Dependencies val coreModule = module { /** * Change

    the reporter supplied by your DI manager, * the entire app just updates! */ single<ErrorReporter> { FirebaseErrorReporter() // SentryErrorReporter() // EmbraceErrorReporter() } } @AdamMc331 #AndroidSummit 26
  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
  57. These Steps Avoid A Lot Of Legacy Code Pain Points

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

    » We have testable code that we can ship with confidence. @AdamMc331 #AndroidSummit 28
  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
  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
  61. Legacy Code Is Code No One On The Team Understands

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

    Doesn't Have Documentation @AdamMc331 #AndroidSummit 30
  63. How Undocumented Code Becomes Legacy Code @AdamMc331 #AndroidSummit 31

  64. How Undocumented Code Becomes Legacy Code 1. The author of

    the code understood what they were writing. @AdamMc331 #AndroidSummit 31
  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
  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
  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
  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
  69. Looking After Future Developers With Documentation @AdamMc331 #AndroidSummit 32

  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
  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
  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
  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
  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
  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
  76. Why This Documentation Matters @AdamMc331 #AndroidSummit 36

  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
  78. Recap @AdamMc331 #AndroidSummit 38

  79. Recap » Writing testable code with decent coverage helps future

    developers refactor with confidence. @AdamMc331 #AndroidSummit 38
  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
  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
  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
  83. Thanks! @AdamMc331 #AndroidSummit 40