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

[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.

Google Developers Group Lviv

October 12, 2018
Tweet

More Decks by Google Developers Group Lviv

Other Decks in Technology

Transcript

  1. 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; // ...
  2. 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); }
  3. Four Reasons to Change Software The Mechanics of Change •

    Adding a feature • Fixing a bug • Improving the design • Optimizing resource usage
  4. 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.
  5. Seam A place where you can alter behavior in your

    program without editing in that place.
  6. 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"); } // ... }
  7. 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)); }
  8. 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)); }
  9. Tools The Mechanics of Change • Automated Refactoring Tools (Android

    Studio) • Unit Tests • Instrumentation Tests • Mock Objects
  10. 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