Clean code via dependency injection + guice
Upcoming SlideShare
Loading in...5
×

Like this? Share it with your network

Share

Clean code via dependency injection + guice

  • 1,624 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,624
On Slideshare
1,600
From Embeds
24
Number of Embeds
3

Actions

Shares
Downloads
51
Comments
0
Likes
11

Embeds 24

https://twitter.com 15
http://www.linkedin.com 8
https://si0.twimg.com 1

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