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
  2. What Is Legacy Code? 1. Code that was written before

    we knew better. @AdamMc331 #AndroidSummit 3
  3. 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
  4. 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
  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. 3. Code that doesn't have tests. 4. Code that we're unable to/afraid to change because everything might break. @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. 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
  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. 5. Code without any type of comments or documentation. 6. All of the above! @AdamMc331 #AndroidSummit 3
  8. Legacy Code Impacts Our Work 1. Legacy code can become

    a blocker for new development. @AdamMc331 #AndroidSummit 5
  9. 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
  10. 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
  11. 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
  12. 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
  13. Legacy Code Was Written By Someone Else » We can't

    avoid people moving on to new opportunities. @AdamMc331 #AndroidSummit 9
  14. 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
  15. 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
  16. Legacy Code Doesn't Have Tests There are two reasons code

    doesn't have tests: @AdamMc331 #AndroidSummit 14
  17. 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
  18. 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
  19. Untestable Code 1. Code with a lot of static references.

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

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

    MyErrorTool is difficult to mock for unit tests. @AdamMc331 #AndroidSummit 18
  23. 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
  24. 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
  25. 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
  26. Tightly Coupled Dependencies Become Legacy Code » Companies change third

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

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

    FirebaseErrorReporter : ErrorReporter { override fun reportError(error: Throwable) { Firebase.getInstance().logException(error) } } @AdamMc331 #AndroidSummit 23
  34. 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
  35. Wrapping Dependencies » Easier to provide fake/mock implementations in tests.

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

    » Easier to entirely swap third party tools. » Easier to update tools. @AdamMc331 #AndroidSummit 25
  37. 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
  38. 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
  39. These Steps Avoid A Lot Of Legacy Code Pain Points

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

    Doesn't Have Documentation @AdamMc331 #AndroidSummit 30
  43. How Undocumented Code Becomes Legacy Code 1. The author of

    the code understood what they were writing. @AdamMc331 #AndroidSummit 31
  44. 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
  45. 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
  46. 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
  47. 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
  48. 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
  49. 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
  50. 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
  51. 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
  52. 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
  53. 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
  54. 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
  55. Recap » Writing testable code with decent coverage helps future

    developers refactor with confidence. @AdamMc331 #AndroidSummit 38
  56. 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
  57. 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
  58. 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