Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Showcase Driven Development

Sponsored · SiteGround - Reliable hosting with speed, security, and support you can count on.

Showcase Driven Development

Showcase Driven Development is a really simple idea that can help teams to continuously integrate their work into a mainline branch thanks to the ability to showcase every stage of their progress.

Avatar for Jérôme Alves

Jérôme Alves

October 07, 2019

More Decks by Jérôme Alves

Other Decks in Programming

Transcript

  1. Sounds Familiar? Working on a feature branch for 1+ month

    Having a Pull Request with 100+ commits Having a Pull Request with 5000+ changed lines
  2. –Soroush Khanlou “Build servers are just a tool used in

    the service of continuous integration. They don’t represent the philosophy behind it.” http://khanlou.com/2019/07/continuous-integration/
  3. –Soroush Khanlou “The name carries the meaning quite well —

    continuously (as frequently as possible) integrate your work into some shared place, usually the mainline branch.” http://khanlou.com/2019/07/continuous-integration/
  4. Implications • You don’t have to deal with horrible merges

    or horrible code reviews. • If you have to drop your work for months, [...] the work you’ve done isn’t in limbo, doesn’t go out of date, and can be built on by the rest of your team. • Not only do your teammates know what you’re working on at any given time, they can begin using it before you’re finished. http://khanlou.com/2019/07/continuous-integration/
  5. Time for a change • Adopt Trunk Based Development workflow

    • Replace big feature branches by several small branches • Split by sub-feature:
 List Screen → Detail Screen → Add / Edit flows → etc... • Split by layer:
 Model → Views → View Models → Coordinators → etc...
  6. Implications • You don’t have to deal with horrible merges

    or horrible code reviews. • If you have to drop your work for months, [...] the work you’ve done isn’t in limbo, doesn’t go out of date, and can be built on by the rest of your team. • Not only do your teammates know what you’re working on at any given time, they can begin using it before you’re finished. http://khanlou.com/2019/07/continuous-integration/
  7. Implications • You don’t have to deal with horrible merges

    or horrible code reviews. • If you have to drop your work for months, [...] the work you’ve done isn’t in limbo, doesn’t go out of date, and can be built on by the rest of your team. • Not only do your teammates know what you’re working on at any given time, they can begin using it before you’re finished. http://khanlou.com/2019/07/continuous-integration/
  8. • Playgrounds are not stable • Playgrounds are not compiled

    alongside the main project • Playgrounds become obsolete quickly after merge • Playgrounds are not shareable with QA team or PMs
  9. final class ConferenceDetailSubviewsShowcase: Showcase { func makeViewController() -> UIViewController {

    let headerView = ConferenceDetailHeaderView() headerView.conferenceName = "FrenchKit" headerView.formattedStartDate = "October 7th, 2019" headerView.formattedEndDate = "October 8th, 2019" headerView.tips = "Don't miss the cheese and wine " headerView.venueAddress = """ Le Beffroi de Montrouge Paris Avenue de la République 92120 Montrouge """ } }
  10. let headerView = ConferenceDetailHeaderView() headerView.conferenceName = "FrenchKit" headerView.formattedStartDate = "October

    7th, 2019" headerView.formattedEndDate = "October 8th, 2019" headerView.tips = "Don't miss the cheese and wine " headerView.venueAddress = """ Le Beffroi de Montrouge Paris Avenue de la République 92120 Montrouge """ let viewController = UIViewController() viewController.view.addSubview(headerView) /* Some autolayout code that doesn't fit this slide */ return viewController } }
  11. final class ConferenceDetailSubviewsShowcase: Showcase { func makeViewController() -> UIViewController {

    let headerView = ConferenceDetailHeaderView() headerView.conferenceName = "FrenchKit" headerView.formattedStartDate = "October 7th, 2019" headerView.formattedEndDate = "October 8th, 2019" headerView.tips = "Don't miss the cheese and wine " headerView.venueAddress = """ Le Beffroi de Montrouge Paris Avenue de la République 92120 Montrouge """ return ViewController(headerView) } } final class ConferenceDetailSubviewsShowcase: Showcase { func makeViewController() -> UIViewController { let headerView = ConferenceDetailHeaderView() headerView.conferenceName = "FrenchKit" headerView.formattedStartDate = "October 7th, 2019" headerView.formattedEndDate = "October 8th, 2019" headerView.tips = "Don't miss the cheese and wine " headerView.venueAddress = """ Le Beffroi de Montrouge Paris Avenue de la République 92120 Montrouge """ return ViewController(headerView) } }
  12. func makeViewController() -> UIViewController { let headerView = ConferenceDetailHeaderView() headerView.conferenceName

    = "FrenchKit" headerView.formattedStartDate = "October 7th, 2019" headerView.formattedEndDate = "October 8th, 2019" headerView.tips = "Don't miss the cheese and wine " headerView.venueAddress = """ Le Beffroi de Montrouge Paris Avenue de la République 92120 Montrouge, France """ let cell = TalkCellContentView() cell.formattedDate = "October 7th, 12:25 - 13:00" cell.speakerName = "Jérôme Alves" cell.talkTitle = "Showcase Driven Development" return ViewController(headerView) } }
  13. headerView.formattedEndDate = "October 8th, 2019" headerView.tips = "Don't miss the

    cheese and wine " headerView.venueAddress = """ Le Beffroi de Montrouge Paris Avenue de la République 92120 Montrouge, France """ let cell = TalkCellContentView() cell.formattedDate = "October 7th, 12:25 - 13:00" cell.speakerName = "Jérôme Alves" cell.talkTitle = "Showcase Driven Development" let stack = UIStackView( arrangedSubviews: [headerView, cell] ) stack.axis = .vertical return ViewController(stack) } }
  14. func makeViewController() -> UIViewController { let headerView = ConferenceDetailHeaderView() headerView.conferenceName

    = "FrenchKit" headerView.formattedStartDate = "October 7th, 2019" headerView.formattedEndDate = "October 8th, 2019" headerView.tips = "Don't miss the cheese and wine " headerView.venueAddress = """ Le Beffroi de Montrouge Paris Avenue de la République 92120 Montrouge, France """ let cell = TalkCellContentView() cell.formattedDate = "October 7th, 12:25 - 13:00" cell.speakerName = "Jérôme Alves" cell.talkTitle = "Showcase Driven Development" return StackViewController(headerView, cell) } } func makeViewController() -> UIViewController { let headerView = ConferenceDetailHeaderView() headerView.conferenceName = "FrenchKit" headerView.formattedStartDate = "October 7th, 2019" headerView.formattedEndDate = "October 8th, 2019" headerView.tips = "Don't miss the cheese and wine " headerView.venueAddress = """ Le Beffroi de Montrouge Paris Avenue de la République 92120 Montrouge, France """ let cell = TalkCellContentView() cell.formattedDate = "October 7th, 12:25 - 13:00" cell.speakerName = "Jérôme Alves" cell.talkTitle = "Showcase Driven Development" return StackViewController(headerView, cell) } }
  15. final class ConferenceDetailViewControllerShowcase: Showcase { func makeViewController() -> UIViewController {

    let viewModel = ConferenceDetailViewModel() return ConferenceDetailViewController(viewModel: viewModel) } }
  16. final class ConferenceDetailViewControllerShowcase: Showcase { func makeViewController() -> UIViewController {

    let viewModel = ConferenceDetailViewModel( api: ??? ) return ConferenceDetailViewController(viewModel: viewModel) } }
  17. protocol APIService { func getConferences( completion: @escaping ([Conference]?, Error?) ->

    Void ) func getTalks( for conference: Conference, completion: @escaping ([Talk]?, Error?) ) // etc... }
  18. final class PrivateBackendAPI: APIService { static let shared = PrivateBackendAPI()

    private let urlSession = URLSession.shared func getConferences( completion: @escaping ([Conference]?, Error?) -> Void ) { urlSession.dataTask(... } func getTalks( for conference: Conference, completion: @escaping ([Talk]?, Error?) ) { urlSession.dataTask(... } // etc... }
  19. final class RealConferenceDetailViewControllerShowcase: Showcase { func makeViewController() -> UIViewController {

    let viewModel = ConferenceDetailViewModel( api: PrivateBackendAPI.shared ) return ConferenceDetailViewController(viewModel: viewModel) } }
  20. final class FakeConferenceDetailViewControllerShowcase: Showcase { func makeViewController() -> UIViewController {

    let viewModel = ConferenceDetailViewModel( api: ConferenceDetailShowcaseStubAPI() ) return ConferenceDetailViewController(viewModel: viewModel) } }
  21. final class NoTalksConferenceDetailViewControllerShowcase: Showcase { func makeViewController() -> UIViewController {

    let viewModel = ConferenceDetailViewModel( api: NoTalksShowcaseStubAPI() ) return ConferenceDetailViewController(viewModel: viewModel) } }
  22. final class ErrorConferenceDetailViewControllerShowcase: Showcase { func makeViewController() -> UIViewController {

    let viewModel = ConferenceDetailViewModel( api: ErrorConferenceDetailShowcaseStubAPI() ) return ConferenceDetailViewController(viewModel: viewModel) } }
  23. final class ConferenceDetailViewControllerShowcase: Showcase { func makeViewController() -> UIViewController {

    let viewModel = ConferenceDetailViewModel() return ConferenceDetailViewController(viewModel: viewModel) } }
  24. func makeViewController() -> UIViewController { let frenchKit = Conference( name:

    "FrenchKit", startDate: Date(), endDate: Date(timeIntervalSinceNow: 24 * 3600), tips: "Don't miss the cheese and wine ", address: Address( place: "Le Beffroi de Montrouge Paris", street: "Avenue de la République", postalCode: "92120", city: "Montrouge", country: "France" ) ) let viewModel = ConferenceDetailViewModel() return ConferenceDetailViewController(viewModel: viewModel) }
  25. startDate: Date(), endDate: Date(timeIntervalSinceNow: 24 * 3600), tips: "Don't miss

    the cheese and wine ", address: Address( place: "Le Beffroi de Montrouge Paris", street: "Avenue de la République", postalCode: "92120", city: "Montrouge", country: "France" ) ) let talk1 = Talk(...) let talk2 = Talk(...) let talk3 = Talk(...) let viewModel = ConferenceDetailViewModel() return ConferenceDetailViewController(viewModel: viewModel) }
  26. let talk1 = Talk(...) let talk2 = Talk(...) let talk3

    = Talk(...) let viewModel = ConferenceDetailViewModel( conferences: { Observable .just([frenchKit]) .delay(.seconds(2), scheduler: ...) }, talks: { conference in Observable .just([talk1, talk2, talk3]) .delay(.seconds(2), scheduler: ...) } ) return ConferenceDetailViewController(viewModel: viewModel) }
  27. let talk1 = Talk(...) let talk2 = Talk(...) let talk3

    = Talk(...) let viewModel = ConferenceDetailViewModel( conferences: { Observable .just([frenchKit]) .delay(.seconds(2), scheduler: ...) }, talks: { conference in Observable .just([talk1, talk2, talk3]) .delay(.seconds(2), scheduler: ...) } ) return ConferenceDetailViewController(viewModel: viewModel) }
  28. let talk1 = Talk(...) let talk2 = Talk(...) let talk3

    = Talk(...) let viewModel = ConferenceDetailViewModel( conferences: { Observable .just([frenchKit]) .delay(.seconds(2), scheduler: ...) }, talks: { conference in Observable .just([talk1, talk2, talk3]) .delay(.seconds(2), scheduler: ...) } ) return ConferenceDetailViewController(viewModel: viewModel) }
  29. let talk1 = Talk(...) let talk2 = Talk(...) let talk3

    = Talk(...) let viewModel = ConferenceDetailViewModel( conferences: { Observable .just([frenchKit]) .delay(.seconds(2), scheduler: ...) }, talks: { conference in Observable .just([]) .delay(.seconds(2), scheduler: ...) } ) return ConferenceDetailViewController(viewModel: viewModel) }
  30. let talk1 = Talk(...) let talk2 = Talk(...) let talk3

    = Talk(...) let viewModel = ConferenceDetailViewModel( conferences: { Observable .just([frenchKit]) .delay(.seconds(2), scheduler: ...) }, talks: { conference in Observable .error(NetworkError.internalServerError) .delay(.seconds(2), scheduler: ...) } ) return ConferenceDetailViewController(viewModel: viewModel) }
  31. • Continuously Integrated during 1 month • Activated in the

    App a month later • Biggest PR diff was ~700 lines of code • Cummulated diff would have been ~7'000 lines of code • Only 5 comments per PR in average • 19 separated Pull Requests
  32. DIY • Basic implementation in 1 day of work •

    Adapted to your team and your codebase • Add features little by little according to your needs • Objective-C runtime is so fun to play with
  33. Open-Sourced! • https://github.com/heetch/ShowcaseKit • CocoaPods & Swift Package Manager •

    protocol Showcase • Built-in Showcases Browser • Example App