• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
Clean code via dependency injection + guice
 

Clean code via dependency injection + guice

on

  • 1,399 views

From Google DevFest 2012, Barcelona

From Google DevFest 2012, Barcelona

Statistics

Views

Total Views
1,399
Views on SlideShare
1,375
Embed Views
24

Actions

Likes
11
Downloads
47
Comments
0

3 Embeds 24

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

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    Clean code via dependency injection + guice Clean code via dependency injection + guice Presentation Transcript

    • Clean code via Dependency Injection + Guice @jordi9bit.ly/devfest12cleancode 9 noviembre 2012
    • ... shameless self-promotion bro!
    • About youGang 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 principleOpen/closed principleLiskov substitution principleInterface segregation principleDependency inversion principle
    • Legacy code is code without test
    • 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
    • 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 workclass Showtimes { MovieRepository repository; Showtimes() { repository = MovieRepositoryFactory.getInstance(); } Set<Movie> findMovies(City city) { return repository.findByCity(city); }}
    • Smell: Constructor does real workclass Showtimes { MovieRepository repository; Showtimes() { repository = MovieRepositoryFactory.getInstance(); } Set<Movie> findMovies(City city) { return repository.findByCity(city); }}
    • Smell: Constructor does real workclass Showtimes { MovieRepository repository; Showtimes() { repository = MovieRepositoryFactory.getInstance(); } Set<Movie> findMovies(City city) { return repository.findByCity(city); }}
    • 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; }}
    • 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; }}
    • 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; }}
    • 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; }}
    • 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; }}
    • 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; }}
    • 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?}
    • Fix: Constructor does real work
    • Fix: Constructor does real workclass Showtimes { MovieRepository repository; Showtimes(MovieRepository repository) { this.repository = repository; } Set<Movie> findMovies(City city) { return repository.findByCity(city); }}
    • 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); }}
    • 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); }}
    • 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! }}
    • 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); }}
    • 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); }}
    • 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); }}
    • 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));}
    • 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));}
    • 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));}
    • 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}
    • 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}
    • Constructor does real work,more flavours
    • Flavour: Partial objects, Law of Demeterclass Ratings { User user; Ratings(AccountManager manager) { user = manager.getLoggedUser(); user.setQueue(new ItTakesForeverToBuildOSSQueue()); }}
    • 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}
    • Fix: Partial objects, Law of Demeterclass Ratings { User user; @Inject Ratings(User user) { this.user = user; }}
    • 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; }}
    • 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();}
    • 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();}
    • 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();}
    • 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
    • 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.}
    • Warning signs: Constructor does real workNew keyword in constructor or fieldsStatic method callsAnything more than field assignmentsObject not fully initialized after constructorControl flow
    • Smell: Digging into Collaborators
    • 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); }}
    • 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); }}
    • 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"));}
    • 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"));}
    • 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); }}
    • 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); }}
    • 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); }}
    • 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"));}
    • 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 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...
    • 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 its documented somewhere... right?}
    • 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
    • Fix: They are liars!
    • Fix: They are liars!Dependency Injection
    • Fix: They are liars!Dependency InjectionDependency Injection
    • Fix: They are liars!Dependency InjectionDependency InjectionDependency Injection
    • Fix: They are liars!Dependency InjectionDependency InjectionDependency InjectionDependency 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 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
    • 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
    • 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
    • Smell: Class does too much
    • Smell: Class does too much, akaKitchen Sink
    • Smell: Class does too much, akaKitchen SinkDumping Ground
    • Smell: Class does too much, akaKitchen SinkDumping GroundThis Class does this AND that AND ...
    • Smell: Class does too much, akaKitchen SinkDumping GroundThis Class does this AND that AND ...God Class
    • Smell: Class does too much, akaKitchen SinkDumping GroundThis Class does this AND that AND ...God ClassQuijote
    • Smell: Class does too much, akaKitchen SinkDumping GroundThis Class does this AND that AND ...God ClassQuijote"You can look at everything except this Class"
    • 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); // ...}
    • 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); // ...}
    • 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);}
    • 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
    • Dependency Injection enablesyou to write testable code
    • Deep synergies betweentestable code and good design -- Michael Feathers
    • Maintainable code
    • Much more Guice!JSR330javax.inject -- Google + Spring effortRoboguiceAndroid, @InjectView, @InjectResourceDaggerFrom Square, original Guice committers,built on JSR330, pretty exciting!
    • 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
    • Thanks!slideshare.net/giro9 jordi@donky.org @jordi9
    • Q&A jordi@donky.org @jordi9