Clean code via
 Dependency Injection + Guice


                                @jordi9
bit.ly/devfest12cleancode   9 noviembre 2012
... shameless self-promotion bro!
About you
Gang of Four?
Legacy Code?
Dependency Injection?
Monkey patching?
Monkey testing?
Unit testing?
TDD / BDD?
Agile? XP? Scrum what?
S.O.L.I.D.
Single responsibility principle
Open/closed principle
Liskov substitution principle
Interface segregation principle
Dependency inversion principle
Legacy code is code without test
F.I.R.S.T.
Fast Run hundreds or thousands per second
Isolated Failure reasons become obvious
Reliable Run repeatedly in any order, any time
Self-validating No manual evaluation required
Timely Written before the code
Write some code, kill technical debt
                             (or not)
Showtime!
class Showtimes {
  MovieRepository repository;

    Showtimes() {
      repository = MovieRepositoryFactory.getInstance();
    }

    Set<Movie> findMovies(City city) {
      return repository.findByCity(city);
    }
}
Smell: Constructor does real work
class Showtimes {
  MovieRepository repository;

    Showtimes() {
      repository = MovieRepositoryFactory.getInstance();
    }

    Set<Movie> findMovies(City city) {
      return repository.findByCity(city);
    }
}
Smell: Constructor does real work
class Showtimes {
  MovieRepository repository;

    Showtimes() {
      repository = MovieRepositoryFactory.getInstance();
    }

    Set<Movie> findMovies(City city) {
      return repository.findByCity(city);
    }
}
Smell: Constructor does real work
class Showtimes {
  MovieRepository repository;

    Showtimes() {
      repository = MovieRepositoryFactory.getInstance();
    }

    Set<Movie> findMovies(City city) {
      return repository.findByCity(city);
    }
}
Factory Pattern, boilerplate
public class MovieRepositoryFactory {
  static class MovieRepositoryHolder {
    static MovieRepository instance = new MovieRepositoryImpl();
  }

    public static MovieRepository getInstance() {
      return MovieRepositoryHolder.instance;
    }

    static void setInstance(MovieRepository mock) {
      MovieRepositoryHolder.instance = mock;
    }
}
Factory Pattern, boilerplate
public class MovieRepositoryFactory {
  static class MovieRepositoryHolder {
    static MovieRepository instance = new MovieRepositoryImpl();
  }

    public static MovieRepository getInstance() {
      return MovieRepositoryHolder.instance;
    }

    static void setInstance(MovieRepository mock) {
      MovieRepositoryHolder.instance = mock;
    }
}
Factory Pattern, boilerplate
public class MovieRepositoryFactory {
  static class MovieRepositoryHolder {
    static MovieRepository instance = new MovieRepositoryImpl();
  }

    public static MovieRepository getInstance() {
      return MovieRepositoryHolder.instance;
    }

    static void setInstance(MovieRepository mock) {
      MovieRepositoryHolder.instance = mock;
    }
}
Factory Pattern, boilerplate
public class MovieRepositoryFactory {
  static class MovieRepositoryHolder {
    static MovieRepository instance = new MovieRepositoryImpl();
  }

    public static MovieRepository getInstance() {
      return MovieRepositoryHolder.instance;
    }

    static void setInstance(MovieRepository mock) {
      MovieRepositoryHolder.instance = mock;
    }
}
Factory Pattern, boilerplate
public class MovieRepositoryFactory {
  static class MovieRepositoryHolder {
    static MovieRepository instance = new MovieRepositoryImpl();
  }

    public static MovieRepository getInstance() {
      return MovieRepositoryHolder.instance;
    }

    static void setInstance(MovieRepository mock) {
      MovieRepositoryHolder.instance = mock;
    }
}
Factory Pattern, boilerplate
public class MovieRepositoryFactory {
  static class MovieRepositoryHolder {
    static MovieRepository instance = new MovieRepositoryImpl();
  }

    public static MovieRepository getInstance() {
      return MovieRepositoryHolder.instance;
    }

    static void setInstance(MovieRepository mock) {
      MovieRepositoryHolder.instance = mock;
    }
}
Hard to test
@Test
void findMovies_cantTestYouDude() {
  Showtimes showtimes = new Showtimes();
  // FRAK!
  // No database setup... I'm stuck with my collaborators
  // Can't really change it... Will it run in my
  // partners computer?
}
Fix: Constructor does real work
Fix: Constructor does real work
class Showtimes {
  MovieRepository repository;

    Showtimes(MovieRepository repository) {
      this.repository = repository;
    }

    Set<Movie> findMovies(City city) {
      return repository.findByCity(city);
    }
}
Fix: Constructor does real work
class Showtimes {
  // Hollywood principle: "Don't call us, we'll call you"
  MovieRepository repository;

    Showtimes(MovieRepository repository) {
      this.repository = repository;
    }

    Set<Movie> findMovies(City city) {
      return repository.findByCity(city);
    }
}
Fix: Constructor does real work
class Showtimes {
  // Hollywood principle: "Don't call us, we'll call you"
  MovieRepository repository;

    @Inject
    Showtimes(MovieRepository repository) {
      this.repository = repository;
    }

    Set<Movie> findMovies(City city) {
      return repository.findByCity(city);
    }
}
Fix: MovieRepository creation?
class App {
  public static void main(String[] args) {
    // Manual Dependency Injection... but much more
    // boilerplate!
    // Maybe some <init> method... but that's why
    // constructors exist right?
    // Some container injection... mmm... Spring, Pico?
    // Or better, Guice time!
  }
}
Fix: MovieRepository creation
class App {
  public static void main(String[] args) {
    Injector injector = Guice.createInjector(
      new AbstractModule() {
          @Override void configure() {
           bind(MovieRepository.class)
             .to(JpaMovieRepository.class);
        }
    });

        injector.getInstance(Showtimes.class).findMovies(bcn);
    }
}
Fix: MovieRepository creation
class App {
  public static void main(String[] args) {
    Injector injector = Guice.createInjector(
      new AbstractModule() {
          @Override void configure() {
           bind(MovieRepository.class)
             .to(JpaMovieRepository.class);
        }
    });

        injector.getInstance(Showtimes.class).findMovies(bcn);
    }
}
Fix: MovieRepository creation
class App {
  public static void main(String[] args) {
     Injector injector = Guice.createInjector(
       new AbstractModule() {
           @Override void configure() {
            bind(MovieRepository.class)
              .to(JpaMovieRepository.class);
         }
     });
    // Injection is VIRAL! Avoid at all cost using the
    // Injector in more than one place (aka Service Locator)
     injector.getInstance(Showtimes.class).findMovies(bcn);
  }
}
Fix: Testable and flexible design
Showtimes showtimes;
@Mock MovieRepository repository;

@Test void findMovies_easyNow() {
  showtimes = new Showtimes(repository);

    given(repository.findByCity(bcn))
      .willReturn(ImmutableSet.of(looper));

    Set<Movie> movies = showtimes.findMovies(bcn);

    assertThat(movies, hasItem(looper));
}
Fix: Testable and flexible design
Showtimes showtimes; //SUT
@Mock MovieRepository repository;

@Test void findMovies_easyNow() {
  showtimes = new Showtimes(repository);

    given(repository.findByCity(bcn))
      .willReturn(ImmutableSet.of(looper));

    Set<Movie> movies = showtimes.findMovies(bcn);

    assertThat(movies, hasItem(looper));
}
Fix: Testable and flexible design
Showtimes showtimes; //SUT
@Mock MovieRepository repository; // <3 Mockito, BDD

@Test void findMovies_easyNow() {
  showtimes = new Showtimes(repository);
  //given
  given(repository.findByCity(bcn))
    .willReturn(ImmutableSet.of(looper));
  //when
  Set<Movie> movies = showtimes.findMovies(bcn);
  //then
  assertThat(movies, hasItem(looper));
}
Fix: Testable and flexible design
Showtimes showtimes; //SUT
@Mock MovieRepository repository; // <3 Mockito, BDD

@Test void findMovies_easyNow() {
  showtimes = new Showtimes(repository);
  //given
  given(repository.findByCity(bcn))
    .willReturn(ImmutableSet.of(looper));
  //when
  Set<Movie> movies = showtimes.findMovies(bcn);
  //then
  assertThat(movies, hasItem(looper)); // <3 Hamcrest
}
Fix: Testable and flexible design
Showtimes showtimes; //SUT
@Mock MovieRepository repository; // <3 Mockito, BDD

@Test void findMovies_easyNow() {
  showtimes = new Showtimes(repository);
  //given
  given(repository.findByCity(bcn))
    .willReturn(ImmutableSet.of(looper)); // <3 Guava
  //when
  Set<Movie> movies = showtimes.findMovies(bcn);
  //then
  assertThat(movies, hasItem(looper)); // <3 Hamcrest
}
Constructor does real work,
more flavours
Flavour: Partial objects, Law of Demeter
class Ratings {
  User user;

    Ratings(AccountManager manager) {
      user = manager.getLoggedUser();
      user.setQueue(new ItTakesForeverToBuildOSSQueue());
    }
}
Flavour: Partial objects, Law of Demeter
class Ratings {
  User user;

    Ratings(AccountManager manager) {
      user = manager.getLoggedUser();
     user.setQueue(new ItTakesForeverToBuildOSSQueue());
    }
    // Test? Slow, really slow
    // We don't use AccountManager anywhere else
}
Fix: Partial objects, Law of Demeter
class Ratings {
  User user;

    @Inject Ratings(User user) {
      this.user = user;
    }
}
Fix: Partial objects, Law of Demeter
class Ratings {
  User user;

    // How do we inject this User like we need it?
    // Where's the Queue?
    @Inject Ratings(User user) {
      this.user = user;
    }

}
Fix: Partial objects, Law of Demeter
@Provides
User providesUser(AccountManager manager, Queue queue) {
  User user = manager.getLoggedUser();
  user.setQueue(queue);
  return user;
}

@Provides @Singleton
Queue providesLongRunningQueue() {
  return new ItTakesForeverToBuildOSSQueue();
}
Fix: Partial objects, Law of Demeter
@Provides
User providesUser(AccountManager manager, Queue queue) {
  User user = manager.getLoggedUser();
  user.setQueue(queue);
  return user;
}

@Provides @Singleton
Queue providesLongRunningQueue() {
  return new ItTakesForeverToBuildOSSQueue();
}
Fix: Partial objects, Law of Demeter
@Provides
User providesUser(AccountManager manager, Queue queue) {
  User user = manager.getLoggedUser();
  user.setQueue(queue);
  return user;
}

@Provides @Singleton // no JVM Singleton, yay!
Queue providesLongRunningQueue() {
  return new ItTakesForeverToBuildOSSQueue();
}
Fix: Partial objects, Law of Demeter
@Provides
User providesUser(AccountManager manager, Queue queue) {
  User user = manager.getLoggedUser();
  user.setQueue(queue);
  return user;
}

@Provides @Singleton // no JVM Singleton, yay!
Queue providesLongRunningQueue() {
  return new ItTakesForeverToBuildOSSQueue();
}

// Provider methods should be inside a Module
Test: Partial objects, Law of Demeter
Ratings ratings; //SUT

@Test void ratings_findAverage() {
  ratings = new Ratings(new DummyUser());
  // Easy to test now, we can have a mock or test-double
  // and check everything about Ratings. We don't care
  // about collaborators.
}
Warning signs: Constructor does real work

New keyword in constructor or fields
Static method calls
Anything more than field assignments
Object not fully initialized after constructor
Control flow
Smell: Digging into Collaborators
Flavour: Digging around
class TicketPriceCalculator {
  MovieTheaterService service;

    TicketPriceCalculator(MovieTheaterService service) {
      this.service = service;
    }

    BigDecimal calculatePrice(User user, Invoice invoice) {
      UserBalance balance = user.getBalance();
      BigDecimal amount = invoice.getSubtotal();
      return service.calculateTotal(balance, amount);
    }
}
Flavour: Digging around
class TicketPriceCalculator {
  MovieTheaterService service;

    TicketPriceCalculator(MovieTheaterService service) {
      this.service = service;
    }

    BigDecimal calculatePrice(User user, Invoice invoice) {
      UserBalance balance = user.getBalance();
      BigDecimal amount = invoice.getSubtotal();
      return service.calculateTotal(balance, amount);
    }
}
Hard to test? Digging around
TicketPriceCalculator calculator; //SUT
@Mock MovieTheaterService service;

@Test void calculatePrice_tooMuchWork() {
  calculator = new TicketPriceCalculator(service);
  given(user.getBalance()).willReturn(new DummyBalance());
  given(invoice.getSubtotal()).willReturn(new BigDecimal("9.3"));

    BigDecimal result = calculator.calculatePrice(balance, invoice);

    assertThat(result, is(new BigDecimal("8.3"));
}
Hard to test? Digging around
TicketPriceCalculator calculator; //SUT
@Mock MovieTheaterService service;

@Test void calculatePrice_tooMuchWork() {
  calculator = new TicketPriceCalculator(service);
  given(user.getBalance()).willReturn(new DummyBalance());
  given(invoice.getSubtotal()).willReturn(new BigDecimal("9.3"));

    BigDecimal result = calculator.calculatePrice(balance, invoice);

    assertThat(result, is(new BigDecimal("8.3"));
}
Flavour: Digging around
class TicketPriceCalculator {
  MovieTheaterService service;

    TicketPriceCalculator(MovieTheaterService service) {
      this.service = service;
    }

    // Deceitful API, Law of Demeter
    BigDecimal calculatePrice(User user, Invoice invoice) {
      UserBalance balance = user.getBalance();
      BigDecimal amount = invoice.getSubtotal();
      return service.calculateTotal(balance, amount);
    }
}
Flavour: Digging around
class TicketPriceCalculator {
  MovieTheaterService service;

    TicketPriceCalculator(MovieTheaterService service) {
      this.service = service;
    }

    // Problem? Imagine this in a big application, your code
    // will be much more complicated than you (and your team) need
    BigDecimal calculatePrice(User user, Invoice invoice) {
      UserBalance balance = user.getBalance();
      BigDecimal amount = invoice.getSubtotal();
      return service.calculateTotal(balance, amount);
    }
}
Fix: Digging around
class TicketPriceCalculator {
  MovieTheaterService service;

    TicketPriceCalculator(MovieTheaterService service) {
      this.service = service;
    }

    // Cleaner API, forget about "Middle men" class
    BigDecimal calculatePrice(UserBalance balance, BigDecimal am) {
      return service.calculateTotal(balance, am);
    }
}
Fix: Digging around
TicketPriceCalculator calculator; //SUT
@Mock MovieTheaterService service;

@Test void calculatePrice_tooMuchWork() {
  calculator = new TicketPriceCalculator(service);
  UserBalance balance = new DummyBalance("1.0");
  BigDecimal amount = new BigDecimal("9.3"));

    BigDecimal result = calculator.calculatePrice(balance, amount);

    assertThat(result, is(new BigDecimal("8.3"));
}
Digging into Collaborators,
more flavours
Flavour: "Context", "Container"...
class MovieTheaterService {

    void processOrder(UserContext context) {
      User user = context.getUser();
      Order order = context.getOrders().byUser(user).now();
      order.process();
    }
}
Flavour: "Context", "Container"...
class MovieTheaterService {

    void processOrder(UserContext context) {
      User user = context.getUser();
      Order order = context.getOrders().byUser(user).now();
      order.process();
    }
}
Flavour: "Context", "Container"...
class MovieTheaterService {

    void processOrder(UserContext context) {
      User user = context.getUser();
      Order order = context.getOrders().byUser(user).now();
      order.process();
    }

    // Test? Create a UserContext with ton of mocks and stuff
}
Flavour: "Context", "Container"...
class MovieTheaterService {

    void processOrder(UserContext context) {
      User user = context.getUser();
      Order order = context.getOrders().byUser(user).now();
      order.process();
    }

    // Test? Create a UserContext with ton of mocks and stuff
}
Fix: "Context", "Container"...
class MovieTheaterService {

    void processOrder(User user, Order order) {
      order.process();
    }

    // Test? Sure! You got everything you need in place.
}
Warning signs: Digging into Collaborators

Objects to use other objects, not themself.
Having to create mocks that return other mocks
Law of Demeter everywhere
Seeing more than one period (.) in a method
chaining. Going deep in the object graph:
  mgs.getSolid().findOcelot().now()
Exception: DSLs
Suspicious names
 Context, Environment, Container...
Smell: Global state and Singletons
Flavour: They are liars!
@Test void chargeCreditCard() { // Example: @mhevery
  CreditCard cc = new CreditCard("9999 0000 7777", 5, 2009);
  assertThat(cc.charge(30.0), is(true));
}
Flavour: They are liars!
@Test void chargeCreditCard() {
  CreditCard cc = new CreditCard("9999 0000 7777", 5, 2009);
  assertThat(cc.charge(30.0), is(true));

 // Fails at runtime:
 // java.lang.NullPointerExpection
}
Flavour: They are liars!
@Test void chargeCreditCard() {
  CreditCardProcessor.init();
  CreditCard cc = new CreditCard("9999 0000 7777", 5, 2009);
  assertThat(cc.charge(30.0), is(true));
}
Flavour: They are liars!
@Test void chargeCreditCard() {
  CreditCardProcessor.init();
  CreditCard cc = new CreditCard("9999 0000 7777", 5, 2009);
  assertThat(cc.charge(30.0), is(true));

 // Argh! This is getting awkward
 // java.lang.NullPointerExpection
}
Flavour: They are liars!
@Test void chargeCreditCard() {
  CreditCardProcessor.init();
  OfflineQueue.start();
  CreditCard cc = new CreditCard("9999 0000 7777", 5, 2009);
  assertThat(cc.charge(30.0), is(true));
}
Flavour: They are liars!
@Test void chargeCreditCard() {
  CreditCardProcessor.init();
  OfflineQueue.start();
  CreditCard cc = new CreditCard("9999 0000 7777", 5, 2009);
  assertThat(cc.charge(30.0), is(true));

 // Are you kidding?
 // java.lang.NullPointerExpection
}
Flavour: They are liars!
@Test void chargeCreditCard() {
  Database.connect("hsqldb");
  CreditCardProcessor.init();
  OfflineQueue.start();
  CreditCard cc = new CreditCard("9999 0000 7777", 5, 2009);
  assertThat(cc.charge(30.0), is(true));
}
Flavour: They are liars!
@Test void chargeCreditCard() {
  Database.connect("hsqldb"); // Global State
  CreditCardProcessor.init(); // Global State
  OfflineQueue.start(); // Global State
  CreditCard cc = new CreditCard("9999 0000 7777", 5, 2009);
  assertThat(cc.charge(30.0), is(true));

    // How do we know this? Sure it's documented somewhere... right?
}
How do you provide global variables in languages
without global variables?


"Don't. Your programs will
thank you for taking the
time to think about design
instead."
                          -- Kent Beck,
                             JUnit and TDD creator
Fix: They are liars!
Fix: They are liars!
Dependency Injection
Fix: They are liars!
Dependency Injection
Dependency Injection
Fix: They are liars!
Dependency Injection
Dependency Injection
Dependency Injection
Fix: They are liars!
Dependency Injection
Dependency Injection
Dependency Injection
Dependency Injection
Fix: They are liars!
@Test void chargeCreditCard() {
  Database db = mock(Database.class);
  Queue queue = new OfflineQueue(db);
  CreditCardProcessor ccProc = new CreditCardProcessor(queue);
  CreditCard cc = new CreditCard(ccProc, "9999 0000 7777");
  cc.charge(30.0);
}
Fix: They are liars!
@Test void chargeCreditCard() {
  Database db = mock(Database.class);
  Queue queue = new OfflineQueue(db);
  CreditCardProcessor ccProc = new CreditCardProcessor(queue);
  CreditCard cc = new CreditCard(ccProc, "9999 0000 7777");
  cc.charge(30.0);
}
Fix: They are liars!
@Test void chargeCreditCard() {
  CreditCardProcessor ccProc = mock(CreditCardProcessor.class);
  CreditCard cc = new CreditCard(ccProc, "9999 0000 7777");
  cc.charge(30.0);
}
About Singletons

Singletons and JVM Singletons
Gang of four, static instance field.
Best known as an Anti-pattern.


But there's nothing wrong about having one
instance of an Object
Application Singleton
Managed by someone, eg: Guice
@Singleton scope
About Global State

Global state is Evil
Dirty design, enables Action at a distance (anti-pattern),
extremely-hard-to-test, etc.


Caveat: When it's ok?
Primitive or immutable constant reference
  static final String DEVFEST = "Devfest rocks"
When information only travels one way
 org.slf4j.Logger // but hard to test
Warning signs: Global State and Singletons

Using Singletons
Static fields or static methods
 Don't confuse with helper or factory methods
Static initialization block
 class App {
   static {
     MutableObject object = Registry.find();
   }
 }
Service Locators
Smell: Class does too much
Smell: Class does too much, aka
Kitchen Sink
Smell: Class does too much, aka
Kitchen Sink
Dumping Ground
Smell: Class does too much, aka
Kitchen Sink
Dumping Ground
This Class does this AND that AND ...
Smell: Class does too much, aka
Kitchen Sink
Dumping Ground
This Class does this AND that AND ...
God Class
Smell: Class does too much, aka
Kitchen Sink
Dumping Ground
This Class does this AND that AND ...
God Class
Quijote
Smell: Class does too much, aka
Kitchen Sink
Dumping Ground
This Class does this AND that AND ...
God Class
Quijote
"You can look at everything except this
 Class"
Smell: Class does too much
interface MovieTheater {

    void open();

    void addShowtime(Showtime showtime);

    Set<Showtime> getShowtimes();

    void purchaseTicket(Buyer buyer, Movie movie);

    UserBalance findUserBalance(User balance);

    void processOrder(Order order);

    // ...
}
Smell: Class does too much
interface MovieTheater {

    void open();

    void addShowtime(Showtime showtime);

    Set<Showtime> getShowtimes();

    void purchaseTicket(Buyer buyer, Movie movie);

    UserBalance findUserBalance(User balance);

    void processOrder(Order order);

    // ...
}
Fix: Class does too much
interface MovieTheater {
  void open();

    void addShowtime(Showtime showtime);

    Set<Showtime> getShowtimes();
}

interface BillingService {
  void purchaseTicket(Buyer buyer, Movie movie);

    UserBalance findUserBalance(User balance);

    void processOrder(Order order);
}
Warning signs: Class does too much

Descriptive names for this smell
Tiny scroll bar
Many fields, many methods
 Use Sonar and LCOM4 (Lack of Cohesion) to check this
Many collaborators
Just don't getting the Class
Dependency Injection enables
you to write testable code
Deep synergies between
testable code and good design
                     -- Michael Feathers
Maintainable code
Much more Guice!
JSR330
javax.inject -- Google + Spring effort
Roboguice
Android, @InjectView, @InjectResource
Dagger
From Square, original Guice committers,
built on JSR330, pretty exciting!
References
Writing testable code Miško Hevery
Working effectively with legacy code Michael Feathers

The testability Explorer blog Blog, Miško Hevery
Dependency Injection Book, Dhanji R. Prasanna
Test Driven - Practical TDD for Java devs Book, Lasse Koskela (2007)
Effective Unit Testing - For Java devs Book, Lasse Koskela (2012)
xUnit Test Patterns Book, Gerard Meszaros
Painless Testing Slides, in Java or Python. Johannes Seitz
Thanks!

slideshare.net/giro9    jordi@donky.org
                                @jordi9
Q&A

      jordi@donky.org
              @jordi9

Clean code via dependency injection + guice

  • 1.
    Clean code via Dependency Injection + Guice @jordi9 bit.ly/devfest12cleancode 9 noviembre 2012
  • 2.
  • 3.
    About you Gang ofFour? Legacy Code? Dependency Injection? Monkey patching? Monkey testing? Unit testing? TDD / BDD? Agile? XP? Scrum what?
  • 4.
    S.O.L.I.D. Single responsibility principle Open/closedprinciple Liskov substitution principle Interface segregation principle Dependency inversion principle
  • 5.
    Legacy code iscode without test
  • 6.
    F.I.R.S.T. Fast Run hundredsor thousands per second Isolated Failure reasons become obvious Reliable Run repeatedly in any order, any time Self-validating No manual evaluation required Timely Written before the code
  • 7.
    Write some code,kill technical debt (or not)
  • 8.
    Showtime! class Showtimes { MovieRepository repository; Showtimes() { repository = MovieRepositoryFactory.getInstance(); } Set<Movie> findMovies(City city) { return repository.findByCity(city); } }
  • 9.
    Smell: Constructor doesreal work class Showtimes { MovieRepository repository; Showtimes() { repository = MovieRepositoryFactory.getInstance(); } Set<Movie> findMovies(City city) { return repository.findByCity(city); } }
  • 10.
    Smell: Constructor doesreal work class Showtimes { MovieRepository repository; Showtimes() { repository = MovieRepositoryFactory.getInstance(); } Set<Movie> findMovies(City city) { return repository.findByCity(city); } }
  • 11.
    Smell: Constructor doesreal work class Showtimes { MovieRepository repository; Showtimes() { repository = MovieRepositoryFactory.getInstance(); } Set<Movie> findMovies(City city) { return repository.findByCity(city); } }
  • 12.
    Factory Pattern, boilerplate publicclass MovieRepositoryFactory { static class MovieRepositoryHolder { static MovieRepository instance = new MovieRepositoryImpl(); } public static MovieRepository getInstance() { return MovieRepositoryHolder.instance; } static void setInstance(MovieRepository mock) { MovieRepositoryHolder.instance = mock; } }
  • 13.
    Factory Pattern, boilerplate publicclass MovieRepositoryFactory { static class MovieRepositoryHolder { static MovieRepository instance = new MovieRepositoryImpl(); } public static MovieRepository getInstance() { return MovieRepositoryHolder.instance; } static void setInstance(MovieRepository mock) { MovieRepositoryHolder.instance = mock; } }
  • 14.
    Factory Pattern, boilerplate publicclass MovieRepositoryFactory { static class MovieRepositoryHolder { static MovieRepository instance = new MovieRepositoryImpl(); } public static MovieRepository getInstance() { return MovieRepositoryHolder.instance; } static void setInstance(MovieRepository mock) { MovieRepositoryHolder.instance = mock; } }
  • 15.
    Factory Pattern, boilerplate publicclass MovieRepositoryFactory { static class MovieRepositoryHolder { static MovieRepository instance = new MovieRepositoryImpl(); } public static MovieRepository getInstance() { return MovieRepositoryHolder.instance; } static void setInstance(MovieRepository mock) { MovieRepositoryHolder.instance = mock; } }
  • 16.
    Factory Pattern, boilerplate publicclass MovieRepositoryFactory { static class MovieRepositoryHolder { static MovieRepository instance = new MovieRepositoryImpl(); } public static MovieRepository getInstance() { return MovieRepositoryHolder.instance; } static void setInstance(MovieRepository mock) { MovieRepositoryHolder.instance = mock; } }
  • 17.
    Factory Pattern, boilerplate publicclass MovieRepositoryFactory { static class MovieRepositoryHolder { static MovieRepository instance = new MovieRepositoryImpl(); } public static MovieRepository getInstance() { return MovieRepositoryHolder.instance; } static void setInstance(MovieRepository mock) { MovieRepositoryHolder.instance = mock; } }
  • 18.
    Hard to test @Test voidfindMovies_cantTestYouDude() { Showtimes showtimes = new Showtimes(); // FRAK! // No database setup... I'm stuck with my collaborators // Can't really change it... Will it run in my // partners computer? }
  • 19.
  • 20.
    Fix: Constructor doesreal work class Showtimes { MovieRepository repository; Showtimes(MovieRepository repository) { this.repository = repository; } Set<Movie> findMovies(City city) { return repository.findByCity(city); } }
  • 21.
    Fix: Constructor doesreal work class Showtimes { // Hollywood principle: "Don't call us, we'll call you" MovieRepository repository; Showtimes(MovieRepository repository) { this.repository = repository; } Set<Movie> findMovies(City city) { return repository.findByCity(city); } }
  • 22.
    Fix: Constructor doesreal work class Showtimes { // Hollywood principle: "Don't call us, we'll call you" MovieRepository repository; @Inject Showtimes(MovieRepository repository) { this.repository = repository; } Set<Movie> findMovies(City city) { return repository.findByCity(city); } }
  • 23.
    Fix: MovieRepository creation? classApp { public static void main(String[] args) { // Manual Dependency Injection... but much more // boilerplate! // Maybe some <init> method... but that's why // constructors exist right? // Some container injection... mmm... Spring, Pico? // Or better, Guice time! } }
  • 24.
    Fix: MovieRepository creation classApp { public static void main(String[] args) { Injector injector = Guice.createInjector( new AbstractModule() { @Override void configure() { bind(MovieRepository.class) .to(JpaMovieRepository.class); } }); injector.getInstance(Showtimes.class).findMovies(bcn); } }
  • 25.
    Fix: MovieRepository creation classApp { public static void main(String[] args) { Injector injector = Guice.createInjector( new AbstractModule() { @Override void configure() { bind(MovieRepository.class) .to(JpaMovieRepository.class); } }); injector.getInstance(Showtimes.class).findMovies(bcn); } }
  • 26.
    Fix: MovieRepository creation classApp { public static void main(String[] args) { Injector injector = Guice.createInjector( new AbstractModule() { @Override void configure() { bind(MovieRepository.class) .to(JpaMovieRepository.class); } }); // Injection is VIRAL! Avoid at all cost using the // Injector in more than one place (aka Service Locator) injector.getInstance(Showtimes.class).findMovies(bcn); } }
  • 27.
    Fix: Testable andflexible design Showtimes showtimes; @Mock MovieRepository repository; @Test void findMovies_easyNow() { showtimes = new Showtimes(repository); given(repository.findByCity(bcn)) .willReturn(ImmutableSet.of(looper)); Set<Movie> movies = showtimes.findMovies(bcn); assertThat(movies, hasItem(looper)); }
  • 28.
    Fix: Testable andflexible design Showtimes showtimes; //SUT @Mock MovieRepository repository; @Test void findMovies_easyNow() { showtimes = new Showtimes(repository); given(repository.findByCity(bcn)) .willReturn(ImmutableSet.of(looper)); Set<Movie> movies = showtimes.findMovies(bcn); assertThat(movies, hasItem(looper)); }
  • 29.
    Fix: Testable andflexible design Showtimes showtimes; //SUT @Mock MovieRepository repository; // <3 Mockito, BDD @Test void findMovies_easyNow() { showtimes = new Showtimes(repository); //given given(repository.findByCity(bcn)) .willReturn(ImmutableSet.of(looper)); //when Set<Movie> movies = showtimes.findMovies(bcn); //then assertThat(movies, hasItem(looper)); }
  • 30.
    Fix: Testable andflexible design Showtimes showtimes; //SUT @Mock MovieRepository repository; // <3 Mockito, BDD @Test void findMovies_easyNow() { showtimes = new Showtimes(repository); //given given(repository.findByCity(bcn)) .willReturn(ImmutableSet.of(looper)); //when Set<Movie> movies = showtimes.findMovies(bcn); //then assertThat(movies, hasItem(looper)); // <3 Hamcrest }
  • 31.
    Fix: Testable andflexible design Showtimes showtimes; //SUT @Mock MovieRepository repository; // <3 Mockito, BDD @Test void findMovies_easyNow() { showtimes = new Showtimes(repository); //given given(repository.findByCity(bcn)) .willReturn(ImmutableSet.of(looper)); // <3 Guava //when Set<Movie> movies = showtimes.findMovies(bcn); //then assertThat(movies, hasItem(looper)); // <3 Hamcrest }
  • 32.
    Constructor does realwork, more flavours
  • 33.
    Flavour: Partial objects,Law of Demeter class Ratings { User user; Ratings(AccountManager manager) { user = manager.getLoggedUser(); user.setQueue(new ItTakesForeverToBuildOSSQueue()); } }
  • 34.
    Flavour: Partial objects,Law of Demeter class Ratings { User user; Ratings(AccountManager manager) { user = manager.getLoggedUser(); user.setQueue(new ItTakesForeverToBuildOSSQueue()); } // Test? Slow, really slow // We don't use AccountManager anywhere else }
  • 35.
    Fix: Partial objects,Law of Demeter class Ratings { User user; @Inject Ratings(User user) { this.user = user; } }
  • 36.
    Fix: Partial objects,Law of Demeter class Ratings { User user; // How do we inject this User like we need it? // Where's the Queue? @Inject Ratings(User user) { this.user = user; } }
  • 37.
    Fix: Partial objects,Law of Demeter @Provides User providesUser(AccountManager manager, Queue queue) { User user = manager.getLoggedUser(); user.setQueue(queue); return user; } @Provides @Singleton Queue providesLongRunningQueue() { return new ItTakesForeverToBuildOSSQueue(); }
  • 38.
    Fix: Partial objects,Law of Demeter @Provides User providesUser(AccountManager manager, Queue queue) { User user = manager.getLoggedUser(); user.setQueue(queue); return user; } @Provides @Singleton Queue providesLongRunningQueue() { return new ItTakesForeverToBuildOSSQueue(); }
  • 39.
    Fix: Partial objects,Law of Demeter @Provides User providesUser(AccountManager manager, Queue queue) { User user = manager.getLoggedUser(); user.setQueue(queue); return user; } @Provides @Singleton // no JVM Singleton, yay! Queue providesLongRunningQueue() { return new ItTakesForeverToBuildOSSQueue(); }
  • 40.
    Fix: Partial objects,Law of Demeter @Provides User providesUser(AccountManager manager, Queue queue) { User user = manager.getLoggedUser(); user.setQueue(queue); return user; } @Provides @Singleton // no JVM Singleton, yay! Queue providesLongRunningQueue() { return new ItTakesForeverToBuildOSSQueue(); } // Provider methods should be inside a Module
  • 41.
    Test: Partial objects,Law of Demeter Ratings ratings; //SUT @Test void ratings_findAverage() { ratings = new Ratings(new DummyUser()); // Easy to test now, we can have a mock or test-double // and check everything about Ratings. We don't care // about collaborators. }
  • 42.
    Warning signs: Constructordoes real work New keyword in constructor or fields Static method calls Anything more than field assignments Object not fully initialized after constructor Control flow
  • 43.
    Smell: Digging intoCollaborators
  • 44.
    Flavour: Digging around classTicketPriceCalculator { MovieTheaterService service; TicketPriceCalculator(MovieTheaterService service) { this.service = service; } BigDecimal calculatePrice(User user, Invoice invoice) { UserBalance balance = user.getBalance(); BigDecimal amount = invoice.getSubtotal(); return service.calculateTotal(balance, amount); } }
  • 45.
    Flavour: Digging around classTicketPriceCalculator { MovieTheaterService service; TicketPriceCalculator(MovieTheaterService service) { this.service = service; } BigDecimal calculatePrice(User user, Invoice invoice) { UserBalance balance = user.getBalance(); BigDecimal amount = invoice.getSubtotal(); return service.calculateTotal(balance, amount); } }
  • 46.
    Hard to test?Digging around TicketPriceCalculator calculator; //SUT @Mock MovieTheaterService service; @Test void calculatePrice_tooMuchWork() { calculator = new TicketPriceCalculator(service); given(user.getBalance()).willReturn(new DummyBalance()); given(invoice.getSubtotal()).willReturn(new BigDecimal("9.3")); BigDecimal result = calculator.calculatePrice(balance, invoice); assertThat(result, is(new BigDecimal("8.3")); }
  • 47.
    Hard to test?Digging around TicketPriceCalculator calculator; //SUT @Mock MovieTheaterService service; @Test void calculatePrice_tooMuchWork() { calculator = new TicketPriceCalculator(service); given(user.getBalance()).willReturn(new DummyBalance()); given(invoice.getSubtotal()).willReturn(new BigDecimal("9.3")); BigDecimal result = calculator.calculatePrice(balance, invoice); assertThat(result, is(new BigDecimal("8.3")); }
  • 48.
    Flavour: Digging around classTicketPriceCalculator { MovieTheaterService service; TicketPriceCalculator(MovieTheaterService service) { this.service = service; } // Deceitful API, Law of Demeter BigDecimal calculatePrice(User user, Invoice invoice) { UserBalance balance = user.getBalance(); BigDecimal amount = invoice.getSubtotal(); return service.calculateTotal(balance, amount); } }
  • 49.
    Flavour: Digging around classTicketPriceCalculator { MovieTheaterService service; TicketPriceCalculator(MovieTheaterService service) { this.service = service; } // Problem? Imagine this in a big application, your code // will be much more complicated than you (and your team) need BigDecimal calculatePrice(User user, Invoice invoice) { UserBalance balance = user.getBalance(); BigDecimal amount = invoice.getSubtotal(); return service.calculateTotal(balance, amount); } }
  • 50.
    Fix: Digging around classTicketPriceCalculator { MovieTheaterService service; TicketPriceCalculator(MovieTheaterService service) { this.service = service; } // Cleaner API, forget about "Middle men" class BigDecimal calculatePrice(UserBalance balance, BigDecimal am) { return service.calculateTotal(balance, am); } }
  • 51.
    Fix: Digging around TicketPriceCalculatorcalculator; //SUT @Mock MovieTheaterService service; @Test void calculatePrice_tooMuchWork() { calculator = new TicketPriceCalculator(service); UserBalance balance = new DummyBalance("1.0"); BigDecimal amount = new BigDecimal("9.3")); BigDecimal result = calculator.calculatePrice(balance, amount); assertThat(result, is(new BigDecimal("8.3")); }
  • 52.
  • 53.
    Flavour: "Context", "Container"... classMovieTheaterService { void processOrder(UserContext context) { User user = context.getUser(); Order order = context.getOrders().byUser(user).now(); order.process(); } }
  • 54.
    Flavour: "Context", "Container"... classMovieTheaterService { void processOrder(UserContext context) { User user = context.getUser(); Order order = context.getOrders().byUser(user).now(); order.process(); } }
  • 55.
    Flavour: "Context", "Container"... classMovieTheaterService { void processOrder(UserContext context) { User user = context.getUser(); Order order = context.getOrders().byUser(user).now(); order.process(); } // Test? Create a UserContext with ton of mocks and stuff }
  • 56.
    Flavour: "Context", "Container"... classMovieTheaterService { void processOrder(UserContext context) { User user = context.getUser(); Order order = context.getOrders().byUser(user).now(); order.process(); } // Test? Create a UserContext with ton of mocks and stuff }
  • 57.
    Fix: "Context", "Container"... classMovieTheaterService { void processOrder(User user, Order order) { order.process(); } // Test? Sure! You got everything you need in place. }
  • 58.
    Warning signs: Digginginto Collaborators Objects to use other objects, not themself. Having to create mocks that return other mocks Law of Demeter everywhere Seeing more than one period (.) in a method chaining. Going deep in the object graph: mgs.getSolid().findOcelot().now() Exception: DSLs Suspicious names Context, Environment, Container...
  • 59.
    Smell: Global stateand Singletons
  • 60.
    Flavour: They areliars! @Test void chargeCreditCard() { // Example: @mhevery CreditCard cc = new CreditCard("9999 0000 7777", 5, 2009); assertThat(cc.charge(30.0), is(true)); }
  • 61.
    Flavour: They areliars! @Test void chargeCreditCard() { CreditCard cc = new CreditCard("9999 0000 7777", 5, 2009); assertThat(cc.charge(30.0), is(true)); // Fails at runtime: // java.lang.NullPointerExpection }
  • 62.
    Flavour: They areliars! @Test void chargeCreditCard() { CreditCardProcessor.init(); CreditCard cc = new CreditCard("9999 0000 7777", 5, 2009); assertThat(cc.charge(30.0), is(true)); }
  • 63.
    Flavour: They areliars! @Test void chargeCreditCard() { CreditCardProcessor.init(); CreditCard cc = new CreditCard("9999 0000 7777", 5, 2009); assertThat(cc.charge(30.0), is(true)); // Argh! This is getting awkward // java.lang.NullPointerExpection }
  • 64.
    Flavour: They areliars! @Test void chargeCreditCard() { CreditCardProcessor.init(); OfflineQueue.start(); CreditCard cc = new CreditCard("9999 0000 7777", 5, 2009); assertThat(cc.charge(30.0), is(true)); }
  • 65.
    Flavour: They areliars! @Test void chargeCreditCard() { CreditCardProcessor.init(); OfflineQueue.start(); CreditCard cc = new CreditCard("9999 0000 7777", 5, 2009); assertThat(cc.charge(30.0), is(true)); // Are you kidding? // java.lang.NullPointerExpection }
  • 66.
    Flavour: They areliars! @Test void chargeCreditCard() { Database.connect("hsqldb"); CreditCardProcessor.init(); OfflineQueue.start(); CreditCard cc = new CreditCard("9999 0000 7777", 5, 2009); assertThat(cc.charge(30.0), is(true)); }
  • 67.
    Flavour: They areliars! @Test void chargeCreditCard() { Database.connect("hsqldb"); // Global State CreditCardProcessor.init(); // Global State OfflineQueue.start(); // Global State CreditCard cc = new CreditCard("9999 0000 7777", 5, 2009); assertThat(cc.charge(30.0), is(true)); // How do we know this? Sure it's documented somewhere... right? }
  • 68.
    How do youprovide global variables in languages without global variables? "Don't. Your programs will thank you for taking the time to think about design instead." -- Kent Beck, JUnit and TDD creator
  • 69.
  • 70.
    Fix: They areliars! Dependency Injection
  • 71.
    Fix: They areliars! Dependency Injection Dependency Injection
  • 72.
    Fix: They areliars! Dependency Injection Dependency Injection Dependency Injection
  • 73.
    Fix: They areliars! Dependency Injection Dependency Injection Dependency Injection Dependency Injection
  • 74.
    Fix: They areliars! @Test void chargeCreditCard() { Database db = mock(Database.class); Queue queue = new OfflineQueue(db); CreditCardProcessor ccProc = new CreditCardProcessor(queue); CreditCard cc = new CreditCard(ccProc, "9999 0000 7777"); cc.charge(30.0); }
  • 75.
    Fix: They areliars! @Test void chargeCreditCard() { Database db = mock(Database.class); Queue queue = new OfflineQueue(db); CreditCardProcessor ccProc = new CreditCardProcessor(queue); CreditCard cc = new CreditCard(ccProc, "9999 0000 7777"); cc.charge(30.0); }
  • 76.
    Fix: They areliars! @Test void chargeCreditCard() { CreditCardProcessor ccProc = mock(CreditCardProcessor.class); CreditCard cc = new CreditCard(ccProc, "9999 0000 7777"); cc.charge(30.0); }
  • 77.
    About Singletons Singletons andJVM Singletons Gang of four, static instance field. Best known as an Anti-pattern. But there's nothing wrong about having one instance of an Object Application Singleton Managed by someone, eg: Guice @Singleton scope
  • 78.
    About Global State Globalstate is Evil Dirty design, enables Action at a distance (anti-pattern), extremely-hard-to-test, etc. Caveat: When it's ok? Primitive or immutable constant reference static final String DEVFEST = "Devfest rocks" When information only travels one way org.slf4j.Logger // but hard to test
  • 79.
    Warning signs: GlobalState and Singletons Using Singletons Static fields or static methods Don't confuse with helper or factory methods Static initialization block class App { static { MutableObject object = Registry.find(); } } Service Locators
  • 80.
  • 81.
    Smell: Class doestoo much, aka Kitchen Sink
  • 82.
    Smell: Class doestoo much, aka Kitchen Sink Dumping Ground
  • 83.
    Smell: Class doestoo much, aka Kitchen Sink Dumping Ground This Class does this AND that AND ...
  • 84.
    Smell: Class doestoo much, aka Kitchen Sink Dumping Ground This Class does this AND that AND ... God Class
  • 85.
    Smell: Class doestoo much, aka Kitchen Sink Dumping Ground This Class does this AND that AND ... God Class Quijote
  • 86.
    Smell: Class doestoo much, aka Kitchen Sink Dumping Ground This Class does this AND that AND ... God Class Quijote "You can look at everything except this Class"
  • 87.
    Smell: Class doestoo much interface MovieTheater { void open(); void addShowtime(Showtime showtime); Set<Showtime> getShowtimes(); void purchaseTicket(Buyer buyer, Movie movie); UserBalance findUserBalance(User balance); void processOrder(Order order); // ... }
  • 88.
    Smell: Class doestoo much interface MovieTheater { void open(); void addShowtime(Showtime showtime); Set<Showtime> getShowtimes(); void purchaseTicket(Buyer buyer, Movie movie); UserBalance findUserBalance(User balance); void processOrder(Order order); // ... }
  • 89.
    Fix: Class doestoo much interface MovieTheater { void open(); void addShowtime(Showtime showtime); Set<Showtime> getShowtimes(); } interface BillingService { void purchaseTicket(Buyer buyer, Movie movie); UserBalance findUserBalance(User balance); void processOrder(Order order); }
  • 90.
    Warning signs: Classdoes too much Descriptive names for this smell Tiny scroll bar Many fields, many methods Use Sonar and LCOM4 (Lack of Cohesion) to check this Many collaborators Just don't getting the Class
  • 91.
    Dependency Injection enables youto write testable code
  • 92.
    Deep synergies between testablecode and good design -- Michael Feathers
  • 93.
  • 94.
    Much more Guice! JSR330 javax.inject-- Google + Spring effort Roboguice Android, @InjectView, @InjectResource Dagger From Square, original Guice committers, built on JSR330, pretty exciting!
  • 95.
    References Writing testable codeMiško Hevery Working effectively with legacy code Michael Feathers The testability Explorer blog Blog, Miško Hevery Dependency Injection Book, Dhanji R. Prasanna Test Driven - Practical TDD for Java devs Book, Lasse Koskela (2007) Effective Unit Testing - For Java devs Book, Lasse Koskela (2012) xUnit Test Patterns Book, Gerard Meszaros Painless Testing Slides, in Java or Python. Johannes Seitz
  • 96.
    Thanks! slideshare.net/giro9 jordi@donky.org @jordi9
  • 97.
    Q&A jordi@donky.org @jordi9