Code Whispering

Code Whispering

Being a craftsman in development practises is only part of the problem! Structuring and refactoring your code can make it easier to add new functionality and reduce complexity, and make it easier for people to read your code. We'll take a brief look at some principles that help make great software, and the hints that your code can give you about what you can improve.

0faee5216d2841b23acdbfa28588e2de?s=128

Alex Curran

March 13, 2015
Tweet

Transcript

  1. Code Whispering Can your code tell you what’s wrong?

  2. What makes good code? • Small, single responsibility classes with

    well-defined contracts • Hide implementation details from outsiders • Predictable state • SOLID principles
  3. SOLID Principles Single responsibility Open/closed principle Liskov’s substitution principle Interface

    segration Dependency inversion
  4. What’s a code smell? "a code smell is a surface

    indication that usually corresponds to a deeper problem in the system" Martin Fowler • Long methods • God objects (cough, Context) • Subclassing
  5. Check your imports! • Lots of imports can imply lots

    of responsibilities • Perhaps some obvious delegation or leaking of implementation detail // Animations
 import android.animation.Animator;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.app.Activity;
 // Widget?
 import android.appwidget.AppWidgetManager;
 // Copy/pasting
 import android.content.ClipData;
 import android.content.ClipboardManager;
 import android.content.ComponentName;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
 // Data
 import android.database.Cursor;
 // UI
 import android.graphics.Typeface;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Build;
 import android.os.Bundle;
 // More data
 import android.support.v4.app.LoaderManager.LoaderCallbacks;
 import android.support.v4.content.CursorLoader;
 import android.support.v4.content.Loader;
 // More UI
 import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.EditText;
 import android.widget.ImageView;
 import android.widget.TextView;
 import android.widget.Toast;
 
 import com.espian.formulae.data.ProProvider;
 import com.espian.formulae.data.ProviderHelper;
 import com.espian.formulae.data.UserDatabaseHelper;
 import com.espian.formulae.lib.BaseContentFragment;
 import com.espian.formulae.lib.R;
 // Rendering
 import com.espian.formulae.utils.AssetImage;
 import com.espian.formulae.utils.EquationParser;
 import com.espian.formulae.utils.Helper;
 // More animations
 import com.espian.formulae.utils.InLeftHideHelper;
 import com.espian.formulae.utils.InRightHideHelper;
 // More rendering
 import com.espian.formulae.utils.SuperAdapter;
  6. Fields and parameters • Make as many fields final as

    possible • Temporary fields often mean an obvious delegate (e.g. lastX and lastY in an activity) • Lots of parameters/constructor args imply lot of responsibility • Many parameters passed around together - is this a thing?
  7. Branching • Avoid an “if” statement if you can •

    Imply two different behaviours • Makes it more difficult to test all the routes • Strategy pattern is a really good idea public void doSomething() {
 downloadLargeFile(preferences.wifiDownloadsOnly());
 }
 
 public void downloadLargeFile(boolean wifiOnly) {
 if (wifiOnly) {
 if (connectivity.isOnWifi()) {
 download();
 }
 } else {
 download();
 }
 } public void doSomething() {
 if (preferences.wifiDownloadsOnly()) {
 downloadOnWifiOnly();
 } else {
 download();
 }
 } public void doSomething() {
 DownloadStrategy ds = preferences.downloadStrategy(connectivity);
 ds.attemptDownload();
 }
 
 public class WifiOnlyStrategy implements DownloadStrategy {
 
 public void attemptDownload() {
 if (connectivity.isOnWifi()) {
 download();
 }
 }
 
 }
  8. Null checks • Try to avoid setters - required dependencies

    in the constructor • If an interface is optional, define a null implementation - no null checks! • Make use of the @Nonnull and @Nullable annotations public interface MessagesCache {
 
 Bitmap getContactPhoto(Contact contact);
 
 void storeContactPhoto(Contact contact, Bitmap bitmap);
 
 void invalidate();
 
 MessagesCache NO_CACHE = new MessagesCache() {
 
 @Override
 public Bitmap getContactPhoto(Contact contact) {
 return null;
 }
 
 @Override
 public void storeContactPhoto(Contact contact, Bitmap bitmap) {
 
 }
 
 @Override
 public void invalidate() {
 
 }
 };
 }
  9. AsyncTask • Defines how work is done, and the work

    that is done • Not exactly single responsibility • Base class inheritance • Contrast with Executor
  10. Flaky tests • Avoid network as much as you can

    • @SmallTest? All tests should be small! • Fewer integration tests, the better • Threading - mixing how to execute with what to execute • Emulators and UI
  11. Large tests • Large set-up implies complex, badly-defined responsibilities •

    But not always true! • JVM tests are much faster than Robolectric • Pushing Android out of your domain = faster feedback
  12. Metrics • Cyclomatic complexity • Ecomp (imports, conditionals, inheritance) •

    Code coverage before and after test, if it has gone down then new behaviour has been added and it isn’t a refactor!
  13. Structural patterns • Structure code according to responsibility • MVC

    - model-view-controller • MVP - model-view-presenter
  14. More resources • Ecomp project on Github • TDD: Where

    Did It All Go Wrong? • Growing Object-Oriented Software