refactor your way forward

69
Refactor Your Way Forward Jorge D. Ortiz-Fuentes @jdortiz

Upload: jorge-ortiz

Post on 23-Jan-2018

147 views

Category:

Software


0 download

TRANSCRIPT

Page 1: Refactor your way forward

Refactor Your Way Forward

Jorge D. Ortiz-Fuentes @jdortiz

Page 2: Refactor your way forward

A Canonical Examples Production

Page 3: Refactor your way forward

#AdvArchMobile

Agenda

★The challenge

★Strategy

★Tactics

★Recap

Page 4: Refactor your way forward

The Challenge

Page 5: Refactor your way forward

#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

Page 6: Refactor your way forward

#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

Page 7: Refactor your way forward

#AdvArchMobile

My Example

★App: OpenIt

★Credit: Patrick Balestra

★Thanks!

★Great code for an example

★All criticism IS constructive

Page 8: Refactor your way forward

Strategy

Page 9: Refactor your way forward

Ideas to Enhance

Page 10: Refactor your way forward

Persistance FW

View

Netw

ork

Location FW

Presenter

Entity Gateway

Clean Architecture

Interactor

Entity

Page 11: Refactor your way forward

Clean Architecture: iOS

App Delegate

View (VC) Presenter Interactor Entity Gateway

Connector

Page 12: Refactor your way forward

#AdvArchMobile

Goals

★New feature: Apple rating API

★Don’t break anything

★Enhance when possible

Page 13: Refactor your way forward

No Big Bang RewriteStill love the TV series

Page 14: Refactor your way forward

Information Gathering

Page 15: Refactor your way forward

– Sun Tzu

“Know your enemy and know yourself and you can fight a hundred battles without

disaster.”

Page 16: Refactor your way forward

Pragmatic Information Gathering

Make it Work

Page 17: Refactor your way forward

#AdvArchMobile

Make it Work

★ Install dependencies

• Pods/Carthage, if any

• API keys

★Build

★ Fix big problems until it works

Page 18: Refactor your way forward

#AdvArchMobile

Make it Work

★DON’T update the project settings yet

★DON’T add any functionality yet

★DON’T fix any other bugs yet

Page 19: Refactor your way forward

Commit!

Page 20: Refactor your way forward

#AdvArchMobile

Explore the Battlefield

★Take a glimpse a the code

★Tests? Runnable? Pass?

★Documentation?

★Oral transmission?

★Business goals

Page 21: Refactor your way forward

#AdvArchMobile

Design your strategy

★Planed features

★Pain points

Page 22: Refactor your way forward

#AdvArchMobile

Main Strategic Approaches

★ From the model upwards

• Less intuitive

• Still requires injections from top to bottom

★ From the views inwards

• More work initially

Page 23: Refactor your way forward

#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?

Page 24: Refactor your way forward

But rememberOnly small non breaking changes allowed

Page 25: Refactor your way forward

Tactics

Page 26: Refactor your way forward

Add Tests

Page 27: Refactor your way forward

#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

Page 28: Refactor your way forward

Commit!

Page 29: Refactor your way forward

Zero VisibilityHere Be Dragons

Page 30: Refactor your way forward

Use the Tools

Page 31: Refactor your way forward

#AdvArchMobile

Proper Git

★Branch often

★Better, git flow: Feature branch for each part of migration

★Avoid long lived branches

★Use branch by abstraction instead

Page 32: Refactor your way forward

#AdvArchMobile

Branch by Abstraction

2 1

1

1 2

Page 33: Refactor your way forward

#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

Page 34: Refactor your way forward

Use Xcode Refactor Feature

Page 35: Refactor your way forward

😂

Page 36: Refactor your way forward

Replace App Delegate (& main.m)

Page 37: Refactor your way forward

#AdvArchMobile

Very Simple (when it is)

@UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { }

Page 38: Refactor your way forward

Commit!

Page 39: Refactor your way forward

Enable Dependency Injection

Page 40: Refactor your way forward

#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

Page 41: Refactor your way forward

#AdvArchMobile

Info.plist

Page 42: Refactor your way forward

Commit!

Page 43: Refactor your way forward

View Controller Talks to Dumb Presenter

Page 44: Refactor your way forward

Mark Connection Points

Page 45: Refactor your way forward

#AdvArchMobile

Introduce Presenter

★@objc

★Pass events

★Test VC

★Generate a skeleton for the presenter

Page 46: Refactor your way forward

#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; }

Page 47: Refactor your way forward

#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; }

Page 48: Refactor your way forward

Commit!

Page 49: Refactor your way forward

Refactor Persistence

Page 50: Refactor your way forward

#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"]]; // … }

Page 51: Refactor your way forward

#AdvArchMobile

And Test It

func testFetchActionsObtainsDataFromUserDefaults() { let userDefaultsMock = UserDefaultsMock() sut.setValue(userDefaultsMock, forKey: "userDefaults")

_ = sut.fetchActions() XCTAssertTrue(userDefaultsMock.objectForKeyInvoked) }

Page 52: Refactor your way forward

Commit!

Page 53: Refactor your way forward

Get the new VC in

Page 54: Refactor your way forward

#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

Page 55: Refactor your way forward

Refactor the Storyboard

Page 56: Refactor your way forward

Deal with Limitations

Page 57: Refactor your way forward

#AdvArchMobile

Rough Edges

★Structs & Enums

★Tuples

★Generics

★Curried & global functions

★Typealiases

Page 58: Refactor your way forward

#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 }

Page 59: Refactor your way forward

Commit!

Page 60: Refactor your way forward

#AdvArchMobile

But…

★Entity Gateway should implement both

★Value semantics are lost

★Use scarcely

★Remove when possible

Page 61: Refactor your way forward

And Finally… New Use Case

Page 62: Refactor your way forward

#AdvArchMobile

Use Casetypealias 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) } }

Page 63: Refactor your way forward

#AdvArchMobile

// View (extension) func askForRating() { SKStoreReviewController.requestReview() }

Presenter & View// Presenter func viewReady() { actions = fetchActions() mayAskUserForRating() } private func mayAskUserForRating() { let useCase = useCaseFactory.askForRatingUseCase(completion: { (shouldAsk: Bool) in view.askForRating() }) useCase.execute() }

Page 64: Refactor your way forward

Commit!

Page 65: Refactor your way forward

Recap

Page 66: Refactor your way forward

#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

Page 67: Refactor your way forward

Tusen Takk!

Page 68: Refactor your way forward

Thank You!

Page 69: Refactor your way forward

@jdortiz #AdvArchMobile