Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Refactor your way forward

198 views

Published on

The first question after people have learned about how to implement anadvanced architecture in their applications from scratch is: "How do I transform my codebase from its current state to a well architected app?" Forget about BigBang rewrites. Come and learn some strategies, tricks, and gotchas about how to do the migration to an advanced arch mobile app. Move to from Objective-C to Swift in the process and keep it functional during the whole process.

Published in: Software
  • Be the first to comment

Refactor your way forward

  1. 1. Refactor Your Way Forward Jorge D. Ortiz-Fuentes @jdortiz
  2. 2. A Canonical
 Examples
 Production
  3. 3. #AdvArchMobile Agenda ★ The challenge ★ Strategy ★ Tactics ★ Recap
  4. 4. The Challenge
  5. 5. #AdvArchMobile Sounds familiar? ★ Legacy application • No tests • Outdated ★ Written in Objective-C ★ Not (m)any tests ★ Multiple styles and ways to do things ★ Not much info from the previous developer
  6. 6. #AdvArchMobile Need a Better Architecture ★ Difficult to add new features without breaking existing ones ★ Difficult to find and solve bugs ★ Expensive to maintain ★ Difficult to add tests
  7. 7. #AdvArchMobile My Example ★ App: OpenIt ★ Credit: Patrick Balestra ★ Thanks! ★ Great code for an example ★ All criticism IS constructive
  8. 8. Strategy
  9. 9. Ideas to Enhance
  10. 10. Persistance FW View Network LocationFW Presenter Entity Gateway Clean Architecture Interactor Entity
  11. 11. Clean Architecture: iOS App Delegate View (VC) Presenter Interactor Entity Gateway Connector
  12. 12. #AdvArchMobile Goals ★ New feature: Apple rating API ★ Don’t break anything ★ Enhance when possible
  13. 13. No Big Bang Rewrite Still love the TV series
  14. 14. Information Gathering
  15. 15. – Sun Tzu “Know your enemy and know yourself and you can fight a hundred battles without disaster.”
  16. 16. Pragmatic Information Gathering Make it Work
  17. 17. #AdvArchMobile Make it Work ★ Install dependencies • Pods/Carthage, if any • API keys ★ Build ★ Fix big problems until it works
  18. 18. #AdvArchMobile Make it Work ★ DON’T update the project settings yet ★ DON’T add any functionality yet ★ DON’T fix any other bugs yet
  19. 19. Commit!
  20. 20. #AdvArchMobile Explore the Battlefield ★ Take a glimpse a the code ★ Tests? Runnable? Pass? ★ Documentation? ★ Oral transmission? ★ Business goals
  21. 21. #AdvArchMobile Design your strategy ★ Planed features ★ Pain points
  22. 22. #AdvArchMobile Main Strategic Approaches ★ From the model upwards • Less intuitive • Still requires injections from top to bottom ★ From the views inwards • More work initially
  23. 23. #AdvArchMobile Shape the Strategy ★ App delegate: Size? Relevant tasks? Easy to replace (And remove main.m)? ★ Storyboards? ★ Tests coverage? Only for the model? ★ Frameworks used (Persistence & others)? ★ DI? Abstractions for DIP? ★ VCs screaming for changes?
  24. 24. But remember Only small non breaking changes allowed
  25. 25. Tactics
  26. 26. Add Tests
  27. 27. #AdvArchMobile Add Tests ★ Set up the testing target ★ Language is Swift ★ Start with main target ★ Don't add others (frameworks) until required ★ Cmd+U To test that it works. ★ Delete the default tests
  28. 28. Commit!
  29. 29. Zero Visibility Here Be Dragons
  30. 30. Use the Tools
  31. 31. #AdvArchMobile Proper Git ★ Branch often ★ Better, git flow: Feature branch for each part of migration ★ Avoid long lived branches ★ Use branch by abstraction instead
  32. 32. #AdvArchMobile Branch by Abstraction 2 1 1 1 2
  33. 33. #AdvArchMobile Use Xcode Groups ★ Put all legacy code in a group ★ Support files and assets in another one ★ Create new Groups (or folders) to organize new code
  34. 34. Use Xcode Refactor Feature
  35. 35. 😂
  36. 36. Replace App Delegate (& main.m)
  37. 37. #AdvArchMobile Very Simple (when it is) @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { }
  38. 38. Commit!
  39. 39. Enable Dependency Injection
  40. 40. #AdvArchMobile Introduce DI from Root VC ★ Change behavior in Info.plist ★ App delegate creates initial view controller ★ Pass into a dumb (yet) connector ★ Add Bridging header
  41. 41. #AdvArchMobile Info.plist
  42. 42. Commit!
  43. 43. View Controller Talks to Dumb Presenter
  44. 44. Mark Connection Points
  45. 45. #AdvArchMobile Introduce Presenter ★ @objc ★ Pass events ★ Test VC ★ Generate a skeleton for the presenter
  46. 46. #AdvArchMobile Cheat to keep it working - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { // DONE: Invoke presenter numberOfActions NSInteger rows = self.presenter.numberOfActions; // TODO: Remove when using real presenter if (rows < 0) { rows = self.actions.count; } return rows; }
  47. 47. #AdvArchMobile Cheat to provide dependencies - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { // DONE: Invoke presenter configure(cell:forRow:) UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath]; // TODO: In the new view replace with the actual cell [self.presenter configureWithCell:[[ActionTableViewCell alloc] init] atRow:indexPath.row]; cell.textLabel.text = self.actions[indexPath.row][0][@"Title"]; cell.detailTextLabel.text = self.actions[indexPath.row][1] [@"Type"]; cell.imageView.image = [UIImage imageNamed:self.actions[indexPath.row][1][@"Type"]]; return cell; }
  48. 48. Commit!
  49. 49. Refactor Persistence
  50. 50. #AdvArchMobile - (void)viewDidLoad { [super viewDidLoad]; [self.presenter viewReady]; // … self.actions = [self fetchActions]; // … } - (NSMutableArray *) fetchActions { return [[NSMutableArray alloc] initWithArray:[[NSUserDefaults standardUserDefaults] objectForKey:@"actions"]]; } Extract Methods with Persistence - (void)viewDidLoad { [super viewDidLoad]; [self.presenter viewReady]; // … self.actions = [[NSMutableArray alloc] initWithArray: [[NSUserDefaults standardUserDefaults] objectForKey:@"actions"]]; // … }
  51. 51. #AdvArchMobile And Test It func testFetchActionsObtainsDataFromUserDefaults() { let userDefaultsMock = UserDefaultsMock() sut.setValue(userDefaultsMock, forKey: "userDefaults") _ = sut.fetchActions() XCTAssertTrue(userDefaultsMock.objectForKeyInvoked) }
  52. 52. Commit!
  53. 53. Get the new VC in
  54. 54. #AdvArchMobile New Swift VC ★ Replaces the old one ★ Refactor Storyboard ★ New Swift class with the desired name ★ Reuse the tests to create an instance of this one
  55. 55. Refactor the Storyboard
  56. 56. Deal with Limitations
  57. 57. #AdvArchMobile Rough Edges ★ Structs & Enums ★ Tuples ★ Generics ★ Curried & global functions ★ Typealiases
  58. 58. #AdvArchMobile @objcMembers class ActionWrapper: NSObject { private var action: Action var title: String { get { return action.title } set(newTitle) { action.title = newTitle } } //… init(action: Action) { self.action = action } init(title: String, type: String, url: String) { action = Action(title: title, type: type, url: url) } } Use Wrappers struct Action { var title: String var type: String var url: String }
  59. 59. Commit!
  60. 60. #AdvArchMobile But… ★ Entity Gateway should implement both ★ Value semantics are lost ★ Use scarcely ★ Remove when possible
  61. 61. And Finally… New Use Case
  62. 62. #AdvArchMobile Use Case typealias AskForRatingCompletion = (Bool) -> () class AskForRatingUseCase: UseCase { let entityGateway: EntityGateway let preferencesGateway: PreferencesGateway let completion: AskForRatingCompletion init(entityGateway: EntityGateway, preferencesGateway: PreferencesGateway, completion: @escaping AskForRatingCompletion) { self.entityGateway = entityGateway self.preferencesGateway = preferencesGateway self.completion = completion } func execute() { let ask = entityGateway.numberOfActions > 10 && preferencesGateway.daysSinceLastRating > 180 completion(ask) } }
  63. 63. #AdvArchMobile // View (extension) func askForRating() { SKStoreReviewController.requestRe view() } Presenter & View // Presenter func viewReady() { actions = fetchActions() mayAskUserForRating() } private func mayAskUserForRating() { let useCase = useCaseFactory.askForRatingUseCas e(completion: { (shouldAsk: Bool) in view.askForRating() }) useCase.execute() }
  64. 64. Commit!
  65. 65. Recap
  66. 66. #AdvArchMobile Recap ★ Incremental refactoring is feasible ★ Design your strategy ★ Use the tactics ★ Small non breaking changes are best ★ Tests are key ★ Don’t follow sequential order
  67. 67. Tusen Takk!
  68. 68. Thank You!
  69. 69. @jdortiz #AdvArchMobile

×