Clean code via dependency injection + guice

  • 1,261 views
Uploaded on

From Google DevFest 2012, Barcelona

From Google DevFest 2012, Barcelona

More in: Technology
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
No Downloads

Views

Total Views
1,261
On Slideshare
0
From Embeds
0
Number of Embeds
2

Actions

Shares
Downloads
52
Comments
0
Likes
11

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. Clean code via Dependency Injection + Guice @jordi9bit.ly/devfest12cleancode 9 noviembre 2012
  • 2. ... shameless self-promotion bro!
  • 3. About youGang of Four?Legacy Code?Dependency Injection?Monkey patching?Monkey testing?Unit testing?TDD / BDD?Agile? XP? Scrum what?
  • 4. S.O.L.I.D.Single responsibility principleOpen/closed principleLiskov substitution principleInterface segregation principleDependency inversion principle
  • 5. Legacy code is code without test
  • 6. F.I.R.S.T.Fast Run hundreds or thousands per secondIsolated Failure reasons become obviousReliable Run repeatedly in any order, any timeSelf-validating No manual evaluation requiredTimely 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 does real workclass Showtimes { MovieRepository repository; Showtimes() { repository = MovieRepositoryFactory.getInstance(); } Set<Movie> findMovies(City city) { return repository.findByCity(city); }}
  • 10. Smell: Constructor does real workclass Showtimes { MovieRepository repository; Showtimes() { repository = MovieRepositoryFactory.getInstance(); } Set<Movie> findMovies(City city) { return repository.findByCity(city); }}
  • 11. Smell: Constructor does real workclass Showtimes { MovieRepository repository; Showtimes() { repository = MovieRepositoryFactory.getInstance(); } Set<Movie> findMovies(City city) { return repository.findByCity(city); }}
  • 12. Factory Pattern, boilerplatepublic 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; }}
  • 13. Factory Pattern, boilerplatepublic 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; }}
  • 14. Factory Pattern, boilerplatepublic 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; }}
  • 15. Factory Pattern, boilerplatepublic 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; }}
  • 16. Factory Pattern, boilerplatepublic 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; }}
  • 17. Factory Pattern, boilerplatepublic 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; }}
  • 18. Hard to test@Testvoid findMovies_cantTestYouDude() { Showtimes showtimes = new Showtimes(); // FRAK! // No database setup... Im stuck with my collaborators // Cant really change it... Will it run in my // partners computer?}
  • 19. Fix: Constructor does real work
  • 20. Fix: Constructor does real workclass Showtimes { MovieRepository repository; Showtimes(MovieRepository repository) { this.repository = repository; } Set<Movie> findMovies(City city) { return repository.findByCity(city); }}
  • 21. Fix: Constructor does real workclass Showtimes { // Hollywood principle: "Dont call us, well call you" MovieRepository repository; Showtimes(MovieRepository repository) { this.repository = repository; } Set<Movie> findMovies(City city) { return repository.findByCity(city); }}
  • 22. Fix: Constructor does real workclass Showtimes { // Hollywood principle: "Dont call us, well call you" MovieRepository repository; @Inject Showtimes(MovieRepository repository) { this.repository = repository; } Set<Movie> findMovies(City city) { return repository.findByCity(city); }}
  • 23. Fix: MovieRepository creation?class App { public static void main(String[] args) { // Manual Dependency Injection... but much more // boilerplate! // Maybe some <init> method... but thats why // constructors exist right? // Some container injection... mmm... Spring, Pico? // Or better, Guice time! }}
  • 24. Fix: MovieRepository creationclass 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); }}
  • 25. Fix: MovieRepository creationclass 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); }}
  • 26. Fix: MovieRepository creationclass 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); }}
  • 27. Fix: Testable and flexible designShowtimes 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 and flexible designShowtimes 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 and flexible designShowtimes 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 and flexible designShowtimes 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 and flexible designShowtimes 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 real work,more flavours
  • 33. Flavour: Partial objects, Law of Demeterclass Ratings { User user; Ratings(AccountManager manager) { user = manager.getLoggedUser(); user.setQueue(new ItTakesForeverToBuildOSSQueue()); }}
  • 34. Flavour: Partial objects, Law of Demeterclass Ratings { User user; Ratings(AccountManager manager) { user = manager.getLoggedUser(); user.setQueue(new ItTakesForeverToBuildOSSQueue()); } // Test? Slow, really slow // We dont use AccountManager anywhere else}
  • 35. Fix: Partial objects, Law of Demeterclass Ratings { User user; @Inject Ratings(User user) { this.user = user; }}
  • 36. Fix: Partial objects, Law of Demeterclass Ratings { User user; // How do we inject this User like we need it? // Wheres the Queue? @Inject Ratings(User user) { this.user = user; }}
  • 37. Fix: Partial objects, Law of Demeter@ProvidesUser providesUser(AccountManager manager, Queue queue) { User user = manager.getLoggedUser(); user.setQueue(queue); return user;}@Provides @SingletonQueue providesLongRunningQueue() { return new ItTakesForeverToBuildOSSQueue();}
  • 38. Fix: Partial objects, Law of Demeter@ProvidesUser providesUser(AccountManager manager, Queue queue) { User user = manager.getLoggedUser(); user.setQueue(queue); return user;}@Provides @SingletonQueue providesLongRunningQueue() { return new ItTakesForeverToBuildOSSQueue();}
  • 39. Fix: Partial objects, Law of Demeter@ProvidesUser 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@ProvidesUser 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 DemeterRatings 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 dont care // about collaborators.}
  • 42. Warning signs: Constructor does real workNew keyword in constructor or fieldsStatic method callsAnything more than field assignmentsObject not fully initialized after constructorControl flow
  • 43. Smell: Digging into Collaborators
  • 44. Flavour: Digging aroundclass 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); }}
  • 45. Flavour: Digging aroundclass 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); }}
  • 46. Hard to test? Digging aroundTicketPriceCalculator 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 aroundTicketPriceCalculator 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 aroundclass 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); }}
  • 49. Flavour: Digging aroundclass 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); }}
  • 50. Fix: Digging aroundclass 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); }}
  • 51. Fix: Digging aroundTicketPriceCalculator 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"));}
  • 52. Digging into Collaborators,more flavours
  • 53. Flavour: "Context", "Container"...class MovieTheaterService { void processOrder(UserContext context) { User user = context.getUser(); Order order = context.getOrders().byUser(user).now(); order.process(); }}
  • 54. Flavour: "Context", "Container"...class MovieTheaterService { void processOrder(UserContext context) { User user = context.getUser(); Order order = context.getOrders().byUser(user).now(); order.process(); }}
  • 55. 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}
  • 56. 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}
  • 57. Fix: "Context", "Container"...class MovieTheaterService { void processOrder(User user, Order order) { order.process(); } // Test? Sure! You got everything you need in place.}
  • 58. Warning signs: Digging into CollaboratorsObjects to use other objects, not themself.Having to create mocks that return other mocksLaw of Demeter everywhereSeeing more than one period (.) in a methodchaining. Going deep in the object graph: mgs.getSolid().findOcelot().now()Exception: DSLsSuspicious names Context, Environment, Container...
  • 59. Smell: Global state and Singletons
  • 60. 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));}
  • 61. 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}
  • 62. 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));}
  • 63. 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}
  • 64. 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));}
  • 65. 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}
  • 66. 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));}
  • 67. 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 its documented somewhere... right?}
  • 68. How do you provide global variables in languageswithout global variables?"Dont. Your programs willthank you for taking thetime to think about designinstead." -- Kent Beck, JUnit and TDD creator
  • 69. Fix: They are liars!
  • 70. Fix: They are liars!Dependency Injection
  • 71. Fix: They are liars!Dependency InjectionDependency Injection
  • 72. Fix: They are liars!Dependency InjectionDependency InjectionDependency Injection
  • 73. Fix: They are liars!Dependency InjectionDependency InjectionDependency InjectionDependency Injection
  • 74. 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);}
  • 75. 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);}
  • 76. Fix: They are liars!@Test void chargeCreditCard() { CreditCardProcessor ccProc = mock(CreditCardProcessor.class); CreditCard cc = new CreditCard(ccProc, "9999 0000 7777"); cc.charge(30.0);}
  • 77. About SingletonsSingletons and JVM SingletonsGang of four, static instance field.Best known as an Anti-pattern.But theres nothing wrong about having oneinstance of an ObjectApplication SingletonManaged by someone, eg: Guice@Singleton scope
  • 78. About Global StateGlobal state is EvilDirty design, enables Action at a distance (anti-pattern),extremely-hard-to-test, etc.Caveat: When its 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: Global State and SingletonsUsing SingletonsStatic fields or static methods Dont confuse with helper or factory methodsStatic initialization block class App { static { MutableObject object = Registry.find(); } }Service Locators
  • 80. Smell: Class does too much
  • 81. Smell: Class does too much, akaKitchen Sink
  • 82. Smell: Class does too much, akaKitchen SinkDumping Ground
  • 83. Smell: Class does too much, akaKitchen SinkDumping GroundThis Class does this AND that AND ...
  • 84. Smell: Class does too much, akaKitchen SinkDumping GroundThis Class does this AND that AND ...God Class
  • 85. Smell: Class does too much, akaKitchen SinkDumping GroundThis Class does this AND that AND ...God ClassQuijote
  • 86. Smell: Class does too much, akaKitchen SinkDumping GroundThis Class does this AND that AND ...God ClassQuijote"You can look at everything except this Class"
  • 87. Smell: Class does too muchinterface 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 does too muchinterface 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 does too muchinterface 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: Class does too muchDescriptive names for this smellTiny scroll barMany fields, many methods Use Sonar and LCOM4 (Lack of Cohesion) to check thisMany collaboratorsJust dont getting the Class
  • 91. Dependency Injection enablesyou to write testable code
  • 92. Deep synergies betweentestable code and good design -- Michael Feathers
  • 93. Maintainable code
  • 94. Much more Guice!JSR330javax.inject -- Google + Spring effortRoboguiceAndroid, @InjectView, @InjectResourceDaggerFrom Square, original Guice committers,built on JSR330, pretty exciting!
  • 95. ReferencesWriting testable code Miško HeveryWorking effectively with legacy code Michael FeathersThe testability Explorer blog Blog, Miško HeveryDependency Injection Book, Dhanji R. PrasannaTest 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 MeszarosPainless Testing Slides, in Java or Python. Johannes Seitz
  • 96. Thanks!slideshare.net/giro9 jordi@donky.org @jordi9
  • 97. Q&A jordi@donky.org @jordi9