[Chuck Greb] Working Effectively with (Android) Legacy Code

[Chuck Greb] Working Effectively with (Android) Legacy Code

Presentation from GDG DevFest Ukraine 2018 - the biggest community-driven Google tech conference in the CEE.

Learn more at: https://devfest.gdg.org.ua

__

- How are we going to add this new feature when the code is a mess?
- We can’t change this file-- it’s too risky!
- How do I test this class when it depends on X, Y, and Z?
- There is not enough time to make the changes you want!
- What does this code even do!?

I feel overwhelmed and it’s never going to get any better.

Android isn’t new anymore. Most applications are not greenfield projects. Many of us find ourselves in the position of working with code we did not author, with outdated architecture, and which we don’t fully understand.

In the spirit of Michael Feathers' classic book, Working Effectively with Legacy Code, this talk explores the ways we can navigate, maintain, and evolve a legacy codebase. We will cover topics like testing, refactoring, architecture, and dependency breaking techniques with examples based on the speaker’s own experience over the past 9+ years as an Android engineer.

3a6de6bc902de7f75c0e753b3202ed52?s=128

Google Developers Group Lviv

October 12, 2018
Tweet

Transcript

  1. Working Effectively with (Android) Legacy Code Chuck Greb DevFest Ukraine

    2018
  2. Intro

  3. None
  4. None
  5. May 18, 2012 Philly Tech Week

  6. Legacy

  7. None
  8. None
  9. None
  10. None
  11. public class ContactActivity extends ListActivity { public void onCreate(Bundle savedInstanceState)

    { super.onCreate(savedInstanceState); ContactApi.instance.initContext(getApplicationContext()); ContactApi.instance.initContentResolver(getContentResolver()); ContactApi[] params = new ContactApi[] { ContactApi.instance }; LoadContactsTask mLoadContactsTask = new LoadContactsTask(); mLoadContactsTask.execute(params); } private class LoadContactsTask extends AsyncTask<ContactApi, Void, ContactList> { private final ProgressDialog dialog = new ProgressDialog(ContactActivity.this); protected void onPreExecute() { dialog.setMessage("Loading contacts..."); dialog.show(); } protected ContactList doInBackground(ContactApi... params) { return new ContactList(params[0]); } protected void onPostExecute(final ContactList contacts) { if (dialog.isShowing()) { dialog.dismiss(); } setListAdapter(new ContactArrayAdapter(ContactActivity.this, R.layout.list_item, contacts)); } } private class ContactArrayAdapter extends ArrayAdapter<ContactList.Contact> { private List<ContactList.Contact> mContacts; ContactArrayAdapter(Context context, int id, List<ContactList.Contact> contacts) { super(context, id, contacts); mContacts = contacts; // ...
  12. public abstract class ContactApi { public static final ContactApi instance;

    static { int sdkVersion = Integer.parseInt(Build.VERSION.SDK); if (sdkVersion < Build.VERSION_CODES.ECLAIR) { instance = new ContactApiSdk3(); } else { instance = new ContactApiSdk5(); } } protected Context mContext; protected ContentResolver mResolver; public void initContext(Context context) { mContext = context; } public void initContentResolver(ContentResolver contentResolver) { mResolver = contentResolver; } public abstract String getColumnId(); public abstract String getColumnContactId(); public abstract String getColumnDisplayName(); public abstract String getColumnPhoneNumber(); public abstract String getColumnEmailAddress(); public abstract String getColumnGivenName(); public abstract String getColumnFamilyName(); public abstract Cursor queryContacts(); public abstract Cursor queryPhoneNumbers(); public abstract Cursor queryEmailAddresses(); public abstract Cursor queryStructuredNames(); public abstract Bitmap queryPhotoById(long id); }
  13. Legacy Code

  14. Legacy Code Code that someone else has written.

  15. Legacy Code Code that is difficult to change and that

    we don't understand.
  16. Legacy Code Code without tests.

  17. The Mechanics of Change

  18. Four Reasons to Change Software The Mechanics of Change •

    Adding a feature • Fixing a bug • Improving the design • Optimizing resource usage
  19. Refactoring

  20. Refactoring The act of improving design without changing it's behavior.

  21. Sensing and Separation The Mechanics of Change Sensing - break

    dependencies to sense when we can't access values computed by code. Separation - break dependencies to separate when we can't get code into a test harness.
  22. Seam

  23. Seam A place where you can alter behavior in your

    program without editing in that place.
  24. Seam Types The Mechanics of Change • Preprocessing Seams •

    Link Seams • Object Seams
  25. Exercise: Finding the Seam

  26. public class WebViewActivity extends Activity { // ... @Override protected

    void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_web_view); // ... NotificationFactory notificationFactory = new NotificationFactory(this); Intent intent = Intent intent = new Intent(this, WebViewActivity.class); notificationFactory.displayNotification(intent, "Internet", "Continue surfing web"); } // ... }
  27. public class WebViewActivity extends Activity { // ... @Override protected

    void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_web_view); // ... WebViewPresenter presenter = new WebViewPresenter(this); presenter.displayNotification(new NotificationFactory(this)); }
  28. public class WebViewActivity extends Activity { // ... @Override protected

    void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_web_view); // ... WebViewPresenter presenter = new WebViewPresenter(this); presenter.displayNotification(new NotificationFactory(this)); }
  29. Tools The Mechanics of Change • Automated Refactoring Tools (Android

    Studio) • Unit Tests • Instrumentation Tests • Mock Objects
  30. Strategies for Changing Software

  31. Sprout Method

  32. // Code Sample

  33. Sprout Method Strategies for Changing Software 1. Step 1 2.

    Step 2 3. Step 3
  34. Sprout Class

  35. // Code Sample

  36. Sprout Class Strategies for Changing Software 1. Step 1 2.

    Step 2 3. Step 3
  37. Wrap Method

  38. // Code Sample

  39. Wrap Method Strategies for Changing Software 1. Step 1 2.

    Step 2 3. Step 3
  40. Wrap Class

  41. // Code Sample

  42. Wrap Class Strategies for Changing Software 1. Step 1 2.

    Step 2 3. Step 3
  43. Breaking Dependencies

  44. Parameterize Constructor

  45. // Code Sample

  46. Parameterize Constructor Breaking Dependencies 1. Step 1 2. Step 2

    3. Step 3
  47. Parameterize Method

  48. // Code Sample

  49. Parameterize Method Breaking Dependencies 1. Step 1 2. Step 2

    3. Step 3
  50. Introduce Static Setter

  51. // Code Sample

  52. Introduce Static Setter Breaking Dependencies 1. Step 1 2. Step

    2 3. Step 3
  53. Subclass and Override Method

  54. // Code Sample

  55. Subclass and Override Method Breaking Dependencies 1. Step 1 2.

    Step 2 3. Step 3
  56. Case Study: WebViewActivity

  57. Final Thoughts

  58. How do I know I'm not breaking anything? Final Thoughts

    • Motivation • Greenfield vs. Legacy • Single-Goal Editing • Refactoring Tools • Pair Programming • Tests! “Remember, your code is your house, and you have to live in it.” -- Michael Feathers
  59. usebutton.com