Slide 1

Slide 1 text

Рефакторинг ! Проектів Refactoring ! Projects @akosma – @uaMobiTech – July 16th 2016 © akosma 2016 1

Slide 2

Slide 2 text

Рефакторинг !⌚#$ Проектів Refactoring !⌚#$ Projects @akosma – @uaMobiTech – July 16th 2016 © akosma 2016 2

Slide 3

Slide 3 text

© akosma 2016 3

Slide 4

Slide 4 text

© akosma 2016 4

Slide 5

Slide 5 text

Thank you ! More information at developer.apple.com © akosma 2016 5

Slide 6

Slide 6 text

© akosma 2016 6

Slide 7

Slide 7 text

Sorry! ! © akosma 2016 7

Slide 8

Slide 8 text

© akosma 2016 8

Slide 9

Slide 9 text

© akosma 2016 9

Slide 10

Slide 10 text

Thank you ! More information at jetbrains.com/objc © akosma 2016 10

Slide 11

Slide 11 text

© akosma 2016 11

Slide 12

Slide 12 text

Seriously? ! © akosma 2016 12

Slide 13

Slide 13 text

Live and Let IDE ! © akosma 2016 13

Slide 14

Slide 14 text

Big Topic ! © akosma 2016 14

Slide 15

Slide 15 text

© akosma 2016 15

Slide 16

Slide 16 text

More ! © akosma 2016 16

Slide 17

Slide 17 text

© akosma 2016 17

Slide 18

Slide 18 text

Yeah baby !" © akosma 2016 18

Slide 19

Slide 19 text

© akosma 2016 19

Slide 20

Slide 20 text

What is it? ! © akosma 2016 20

Slide 21

Slide 21 text

Refactoring (noun): a change made to the internal structure of software to make it easier to understand and cheaper to modify without changing its observable behavior. © akosma 2016 21

Slide 22

Slide 22 text

Why to? ! © akosma 2016 22

Slide 23

Slide 23 text

—To improve the design of software —To make it easier to understand —To make it easier to find and fix bugs —To program faster —To annoy your manager © akosma 2016 23

Slide 24

Slide 24 text

When to? ⏰ © akosma 2016 24

Slide 25

Slide 25 text

—When you add features —When fixing bugs —During code reviews © akosma 2016 25

Slide 26

Slide 26 text

When not to? ⛔ © akosma 2016 26

Slide 27

Slide 27 text

© akosma 2016 27

Slide 28

Slide 28 text

1. Add Tests 2. Refactor © akosma 2016 28

Slide 29

Slide 29 text

© akosma 2016 29

Slide 30

Slide 30 text

© akosma 2016 30

Slide 31

Slide 31 text

How to? ! © akosma 2016 31

Slide 32

Slide 32 text

Bad Smells !68 —Duplicated code —Long methods —Large class —Long parameter list —Divergent change —Shotgun surgery —Feature envy —Data clumps —Primitive obsession —Switch statements —Parallel inheritance hierarchies —Lazy class —Speculative generality —Temporary field —Message chains —Middle Man —Inappropriate intimacy —Alternative classes with different interfaces —Incomplete library class —Data class —Refused bequest —Comments 68 Source: “Refactoring” by Martin Fowler (1999) © akosma 2016 32

Slide 33

Slide 33 text

!⌚#$ Specific Smells ! Swift and Cocoa Smells ! Class Design Smells ! Project Management Smells © akosma 2016 33

Slide 34

Slide 34 text

! Swift and Cocoa Smells © akosma 2016 34

Slide 35

Slide 35 text

!" SwiftyLeaks © akosma 2016 35

Slide 36

Slide 36 text

© akosma 2016 36

Slide 37

Slide 37 text

Memory Leaks in Swift Reference Cycles23: —Instances —Closures 23 Source: Apple Developer Library © akosma 2016 37

Slide 38

Slide 38 text

Instance Reference Cycles (1/2) class Person { let name: String var apartment: Apartment? } class Apartment { let unit: String var tenant: Person? // Ouch! } © akosma 2016 38

Slide 39

Slide 39 text

© akosma 2016 39

Slide 40

Slide 40 text

Instance Reference Cycles (2/2) class Person { let name: String var apartment: Apartment? } class Apartment { let unit: String weak var tenant: Person? // Yay! } © akosma 2016 40

Slide 41

Slide 41 text

© akosma 2016 41

Slide 42

Slide 42 text

weak vs. unowned —weak can be nil —unowned cannot be nil —unowned ➡ always nonoptional types © akosma 2016 42

Slide 43

Slide 43 text

Closure Reference Cycles (1/2) let obj = SomeObject() obj.blockMember = { return obj.property // Ouch! } © akosma 2016 43

Slide 44

Slide 44 text

Closure Reference Cycles (2/2) let obj = SomeObject() obj.blockMember = { [unowned obj] return obj.property // Yay! } © akosma 2016 44

Slide 45

Slide 45 text

Closures are reference types⾠ © akosma 2016 45

Slide 46

Slide 46 text

!" Hungarian Notation © akosma 2016 46

Slide 47

Slide 47 text

protocol IUnknown { func QueryInterface (_ riid: REFIID, _ ppvObject: LPVOID) -> HRESULT func AddRef() -> ULONG func Release() -> ULONG } protocol IDispatch : IUnknown { func GetTypeInfoCount(pctinfo: UInt) -> HRESULT func GetTypeInfo(_ iTInfo: UInt, _ lcid: LCID, _ ppTInfo: ITypeInfo) -> HRESULT func GetIDsOfNames(_ riid: REFIID, _ rgszNames: OLECHAR, _ cNames: UInt, _ lcid: LCID, _ rgDispId: DISPID) -> HRESULT func Invoke(_ dispIdMember: DISPID, _ riid: REFIID, _ lcid: LCID, _ wFlags: WORD, _ pDispParams: DISPPARAMS, _ pVarResult: VARIANT, _ pExcepInfo: EXCEPINFO, _ puArgErr: UInt32) -> HRESULT } © akosma 2016 47

Slide 48

Slide 48 text

let oPerson = Server.CreateObject("Person") var bBusy = true if (bBusy) { bBusy = false let riid = CFUUIDGetUUIDBytes(CFUUIDCreate(kCFAllocatorDefault)) let ppvObject = UnsafeMutablePointer(allocatingCapacity: 10) let result = oPerson.QueryInterface(riid, ppvObject) if result == 0x80090032 { bBusy = true oPerson.Invoke(0, riid, riid, 0, riid, nil, nil, 0) } } © akosma 2016 48

Slide 49

Slide 49 text

Programs must be written for people to read, and only incidentally for machines to execute. © akosma 2016 49

Slide 50

Slide 50 text

Swift API design guidelines55 // Make APIs read grammatically friends.remove(ted) mainView.addChild(button, at: origin) truck.removeBoxes(withLabel: "uaMobiTech") // Use verbs for side effects friends.reverse() viewController.present(animated: true) 55 Source: WWDC 2016 session 403 © akosma 2016 50

Slide 51

Slide 51 text

Swift API design guidelines55 // Use nouns for returned values button.backgroundTitle(for: .disabled) friends.suffix(3) // Mutating and non-mutating method pairs x.reverse() // mutating let y = x.reversed() // non-mutating dir.appendPathComponent(".txt") // mutating let newDir = dir.appendingPathComponent(".txt") // non-mutating 55 Source: WWDC 2016 session 403 © akosma 2016 51

Slide 52

Slide 52 text

Swift 㲗 Objective-C Two different signatures: one for Swift, another for Objective-C @objc() exports Swift API ➡ Objective-C NS_SWIFT_NAME() Objective-C APIs ➡ Swift © akosma 2016 52

Slide 53

Slide 53 text

!" Objective-C Nostalgia © akosma 2016 53

Slide 54

Slide 54 text

Migrate to Swift3 NS_ASSUME_NONNULL_BEGIN #define let __auto_type const #define var __auto_type @property(nonatomic, nonnull, copy) NSString *name; @property(nonatomic, nullable, weak) id delegate; - (nullable NSArray *)arrayWithWord:(nonnull NSString *)word { // NSArray * _Nonnull words = @[@"one", @"two", word]; __auto_type _Nonnull words = @[@"one", @"two", word]; return words; } NS_ASSUME_NONNULL_END 3 Source: Swift Blog © akosma 2016 54

Slide 55

Slide 55 text

Caveats —Objective-C cannot subclass a Swift class —…unless marked @objc —Tuples, Swift enums and structs are not accessible from Objective-C. © akosma 2016 55

Slide 56

Slide 56 text

!" Homemade Cache © akosma 2016 56

Slide 57

Slide 57 text

“There are only two hard things in Computer Science: cache invalidation and naming things.” Phil Karlton10 10 Source: Martin Fowler © akosma 2016 57

Slide 58

Slide 58 text

NSURLCache let config = URLSessionConfiguration.default config.requestCachePolicy = .returnCacheDataElseLoad let memoryCapacity = 10 * 1024 * 1024; let diskCapacity = 20 * 1024 * 1024; let cache = URLCache(memoryCapacity: memoryCapacity, diskCapacity: diskCapacity, diskPath: nil) URLCache.shared = cache © akosma 2016 58

Slide 59

Slide 59 text

!" Tagged Views © akosma 2016 59

Slide 60

Slide 60 text

Do not viewWithTag() override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) let object = objects[indexPath.row] as! NSDate cell.textLabel!.text = object.description let switchControl = cell.viewWithTag(1) as! UISwitch // Ouch! switchControl.setOn(false, animated: false) return cell } © akosma 2016 60

Slide 61

Slide 61 text

Custom View Class override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! CustomViewCell let object = objects[indexPath.row] as! NSDate cell.titleLabel.text = object.description cell.switchControl.setOn(true, animated: false) // Yay! return cell } © akosma 2016 61

Slide 62

Slide 62 text

!" Illicit Association © akosma 2016 62

Slide 63

Slide 63 text

Associated Objects 86 extension NSWhatever { private struct Keys { static var Value = "AssociatedValue" } var associatedString: String? { get { return objc_getAssociatedObject(self, &Keys.Value) as? String } set { objc_setAssociatedObject(self, &Keys.Value, newValue as NSString?, .OBJC_ASSOCIATION_COPY_NONATOMIC) } } } var obj = NSWhatever() obj.associatedString = "Some other string" 86 Source: NSHipster © akosma 2016 63

Slide 64

Slide 64 text

Associated Objects 14 Do not if you can use instead: —Subclassing —Target-Action —Gesture recognizers —Delegation —NSNotification 14 Source: NSHipster © akosma 2016 64

Slide 65

Slide 65 text

© akosma 2016 65

Slide 66

Slide 66 text

! Class Design Smells © akosma 2016 66

Slide 67

Slide 67 text

!" Class Struggle © akosma 2016 67

Slide 68

Slide 68 text

class or struct? ! (1/2) protocol Modifiable { // protocols FTW! var value : Int { get set } mutating func increase() } extension Modifiable { mutating func increase() { value += 100 } } © akosma 2016 68

Slide 69

Slide 69 text

class or struct? ! (2/2) struct TypeOne : Modifiable { var value = 0 } class TypeTwo : Modifiable { var value = 0 } var v1, v2, r1, r2 : Modifiable v1 = TypeOne(); v2 = v1 // Value type, copied r1 = TypeTwo(); r2 = r1 // Reference type, not copied v2.increase(); assert(v1.value != v2.value) r2.increase(); assert(r1.value == r2.value) © akosma 2016 69

Slide 70

Slide 70 text

Reference Types ! —Inheritance —Reference semantics —Use Swift types in Objective-C Value Types ! —Always implement Equatable on your own76 —More testable architectures —Immutable by default 76 Source: WWDC 2015 Session 414 © akosma 2016 70

Slide 71

Slide 71 text

!" Massive View Controller (MVC) and Massive App Delegate (MAD) © akosma 2016 71

Slide 72

Slide 72 text

© akosma 2016 72

Slide 73

Slide 73 text

Class Breakdown ! Maximum 400 lines ! © akosma 2016 73

Slide 74

Slide 74 text

Empty States72 1. First use39 2. User cleared 3. Errors 39 Source: Designing For The Empty States 72 Source: emptystat.es © akosma 2016 74

Slide 75

Slide 75 text

State Machines12 —AppState enumeration with associated lambdas —GKState and GKStateMachine Other Approaches —Model-View-ViewModel (MVVM) —Protocols & Extensions —Dependency Injection —ReSwift, PromiseKit, RxSwift, ReactKit, ReactiveCocoa… 12 Source: Github © akosma 2016 75

Slide 76

Slide 76 text

AppState (1/3) enum AppState { case None case Active(action: Block) // typealias Block = (Void) -> Void case Inactive(action: Block) func execute() { switch self { case .Active(let block): block() case .Inactive(let block): block() default: return } } } © akosma 2016 76

Slide 77

Slide 77 text

AppState (2/3) class SomeController { var state = AppState.None { didSet { state.execute() } } func viewDidLoad() { // ... } © akosma 2016 77

Slide 78

Slide 78 text

AppState (3/3) func viewDidLoad() { super.viewDidLoad() let inactive = AppState.Inactive { [unowned self] () in self.view.backgroundColor = UIColor.red() } let active = AppState.Active { [unowned self] () in self.view.backgroundColor = UIColor.green() } // ... self.state = inactive } © akosma 2016 78

Slide 79

Slide 79 text

GKState and GKStateMachine (1/2) import GameplayKit class ActiveState: GKState { override func isValidNextState(_ stateClass: AnyClass) -> Bool { if stateClass == InactiveState.self { return true } return false } override func didEnter(withPreviousState previousState: GKState?) { // change UI elements, etc, etc... } override func willExit(withNextState nextState: GKState) { // ... } } © akosma 2016 79

Slide 80

Slide 80 text

GKState and GKStateMachine (2/2) let active = ActiveState() let inactive = InactiveState() let stateMachine = GKStateMachine(states: [active, inactive]) stateMachine.enterState(InactiveState.self) © akosma 2016 80

Slide 81

Slide 81 text

© akosma 2016 81

Slide 82

Slide 82 text

Model – View – ViewModel (MVVM 1/2) class DetailViewModel: NSObject { let model : Model init(withModel model: Model) { self.model = model } var displayText : String { return self.model.description } } © akosma 2016 82

Slide 83

Slide 83 text

Model – View – ViewModel (MVVM 2/2) class DetailViewController: UIViewController { @IBOutlet weak var label: UILabel! var viewModel : DetailViewModel? override func viewDidLoad() { super.viewDidLoad() self.label.text = self.viewModel?.displayText } } © akosma 2016 83

Slide 84

Slide 84 text

Martin Fowler Says68 —Extract Class (149) —Extract Subclass (330) —Extract Interface (341) —Duplicate Observed Data (189) 68 Source: “Refactoring” by Martin Fowler (1999) © akosma 2016 84

Slide 85

Slide 85 text

!" Forgotten Memory Warnings © akosma 2016 85

Slide 86

Slide 86 text

Memory Warnings func applicationDidReceiveMemoryWarning(_ application: UIApplication) { // ... on the app delegate... } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // ... on every controller... } // ... and anywhere else let center = NotificationCenter.default() let notification = NSNotification.Name.UIApplicationDidReceiveMemoryWarning let selector = #selector(SomeClass.handler(_:)) center.addObserver(self, selector: selector, name: notification, object: nil) © akosma 2016 86

Slide 87

Slide 87 text

Lazy Loading Class class LazyLoader { var _backingField: String? var property: String? { get { if _backingField == nil { _backingField = "text" } return _backingField } set { _backingField = newValue } } } © akosma 2016 87

Slide 88

Slide 88 text

!" Long Switch Statement © akosma 2016 88

Slide 89

Slide 89 text

Pattern Matching44 switch (indexPath.section, indexPath.row) { case (0, let row): cell.backgroundColor = UIColor.lightGray() // ... case (let section, 0) where section % 2 == 0: cell.backgroundColor = UIColor.brown() // ... case let (3, row) where validate(row): cell.backgroundColor = UIColor.blue() // ... default: cell.backgroundColor = UIColor.white() } 44 Source: Ash Furrow © akosma 2016 89

Slide 90

Slide 90 text

Martin Fowler Says68 —Extract Method (110) —Move Method (142) —Replace Type Code With Subclasses (223) —Replace Type Code With State/Strategy (227) —Replace Conditional with Polymorphism (255) 68 Source: “Refactoring” by Martin Fowler (1999) © akosma 2016 90

Slide 91

Slide 91 text

!" Long Method Names © akosma 2016 91

Slide 92

Slide 92 text

“There are only two hard things in Computer Science: cache invalidation and naming things.” Phil Karlton10 10 Source: Martin Fowler © akosma 2016 92

Slide 93

Slide 93 text

Cocoa Awards™ ! © akosma 2016 93

Slide 94

Slide 94 text

Cocoa Awards™ ! Longest Method Name Category95 95 Source: Quora and Github © akosma 2016 94

Slide 95

Slide 95 text

…and the winner is… // QCPlugInContext class - (id) outputImageProviderFromBufferWithPixelFormat:(NSString*)format pixelsWide:(NSUInteger)width pixelsHigh:(NSUInteger)height baseAddress:(const void*)baseAddress bytesPerRow:(NSUInteger)rowBytes releaseCallback:(QCPlugInBufferReleaseCallback)callback releaseContext:(void*)context colorSpace:(CGColorSpaceRef)colorSpace shouldColorMatch:(BOOL)colorMatch; © akosma 2016 95

Slide 96

Slide 96 text

© akosma 2016 96

Slide 97

Slide 97 text

Martin Fowler Says68 —Replace Parameter with Method (292) —Preserve Whole Object (288) —Introduce Parameter Object (295) 68 Source: “Refactoring” by Martin Fowler (1999) © akosma 2016 97

Slide 98

Slide 98 text

Parameter Objects in Cocoa // Map snapshots let options = MKMapSnapshotOptions() options.region = mapView.region options.size = mapView.frame.size let snapshotter = MKMapSnapshotter(options: options) // URL Sessions let config = URLSessionConfiguration() config.httpShouldSetCookies = false config.timeoutIntervalForRequest = 2.0 let session = URLSession(configuration: config) © akosma 2016 98

Slide 99

Slide 99 text

!" Excessive Curiosity © akosma 2016 99

Slide 100

Slide 100 text

Excessive User Interface Idiom Checks // Only if you need compatibility with iPhone OS 3.2... really? if UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.pad { // ... iPad-only code } let idiom = UIDevice.current().userInterfaceIdiom if idiom == UIUserInterfaceIdiom.phone { // ... iPhone-only code } © akosma 2016 100

Slide 101

Slide 101 text

Excessive OS Version Checks46 let ver = OperatingSystemVersion(majorVersion: 10, minorVersion: 0, patchVersion: 0) if ProcessInfo().isOperatingSystemAtLeast(ver) { ... } let os = ProcessInfo().operatingSystemVersion switch (os.majorVersion, os.minorVersion, os.patchVersion) { case (10, _, _): print("iOS >= 10.0.0") default: print("Previous versions") } 46 Source: NSHipster © akosma 2016 101

Slide 102

Slide 102 text

Behave Appropriately 1. Encapsulate features into subclasses 2. Last resort: check feature existence if let feature = NSClassFromString("INIntent") { print("Intents.framework (SiriKit) available") } © akosma 2016 102

Slide 103

Slide 103 text

© akosma 2016 103

Slide 104

Slide 104 text

! Project Management Smells © akosma 2016 104

Slide 105

Slide 105 text

!" Invisible Documentation © akosma 2016 105

Slide 106

Slide 106 text

If you haven’t written documentation you haven’t done your job © akosma 2016 106

Slide 107

Slide 107 text

© akosma 2016 107

Slide 108

Slide 108 text

!" Folder Clusterfuck © akosma 2016 108

Slide 109

Slide 109 text

© akosma 2016 109

Slide 110

Slide 110 text

Extensions Explosion —19 iOS extensions —11 macOS extensions Check out Apple’s “Lister” application!99 99 Source: Apple Developer Library © akosma 2016 110

Slide 111

Slide 111 text

Conventions for Xcode28 —Create folders for each platform: iOS, macOS, watchOS, tvOS —Target folders inside platform folders —Shared folder for cross-platform files —Separate items into Assets and Source directories 28 Source: The.Swift.Dev © akosma 2016 111

Slide 112

Slide 112 text

!" Cocoapods Galore © akosma 2016 112

Slide 113

Slide 113 text

Cocoapods Best Practices —Check in Pods folder, Podfile and Podfile.lock. —pod outdated in a separate branch once a week —Use rbenv to control Ruby installations —Team uses the same version of Cocoapods © akosma 2016 113

Slide 114

Slide 114 text

Specify Pod Versions ✔ target 'MyApp' do pod 'AFNetworking', '~> 3.0' pod 'FBSDKCoreKit', '~> 4.9' end ➡ Choose your pods wisely! © akosma 2016 114

Slide 115

Slide 115 text

!" Rogue Compiler Warnings © akosma 2016 115

Slide 116

Slide 116 text

New in Xcode8 8 Source: WWDC 2016 Session 410 © akosma 2016 116

Slide 117

Slide 117 text

© akosma 2016 117

Slide 118

Slide 118 text

!" User Discrimination © akosma 2016 118

Slide 119

Slide 119 text

♿"#$%& '()*+㊙ -./012 © akosma 2016 119

Slide 120

Slide 120 text

!" Interface Builder Attack © akosma 2016 120

Slide 121

Slide 121 text

© akosma 2016 121

Slide 122

Slide 122 text

Problem —Controllers tied to Storyboard ➡ hard to reuse —Merge conflicts —Slow loading —Complex navigation © akosma 2016 122

Slide 123

Slide 123 text

Solution —Use storyboards only for navigation —Use XIB files only for UI design Steps: 1. Menu Editor -> Refactor to Storyboard… 2. ViewController.swift + ViewController.xib 3. Remove view outlet from Storyboard in Controller © akosma 2016 123

Slide 124

Slide 124 text

© akosma 2016 124

Slide 125

Slide 125 text

© akosma 2016 125

Slide 126

Slide 126 text

// Instantiating through the Storyboard if let controller = self.storyboard?.instantiateViewController(withIdentifier: "Ctrl") { // ... } // Instantiating manually // This constructor automatically loads XIB file let controller = ViewController() © akosma 2016 126

Slide 127

Slide 127 text

!" iOS Nostalgia © akosma 2016 127

Slide 128

Slide 128 text

© akosma 2016 128

Slide 129

Slide 129 text

Formula for Happiness©™ Minimum iOS Version to Support = (Current iOS Version Number) – 1 ! © akosma 2016 129

Slide 130

Slide 130 text

!" Backend API Anarchy © akosma 2016 130

Slide 131

Slide 131 text

Common Backend API Sins —SOAP, XML-RPC —Chatty design —Lack of Control —QOS, Security, Performance… ! " ↩ # © akosma 2016 131

Slide 132

Slide 132 text

Repent Of Your Sins —Build your own API Proxy —Chunky design —Socket.io & GraphQL ☺ " 㲗 ‘ ↩ # © akosma 2016 132

Slide 133

Slide 133 text

© akosma 2016 133

Slide 134

Slide 134 text

Summary © akosma 2016 134

Slide 135

Slide 135 text

! Swift and Cocoa Smells ! SwiftyLeaks " Hungarian Notation # Objective-C Nostalgia $ Homemade Cache % Tagged View & Illicit Association © akosma 2016 135

Slide 136

Slide 136 text

! Class Design Smells ! Class Struggle " Massive View Controller & App Delegate # Forgotten Memory Warnings $ Long Switch Statement % Long Method Names & Excessive Curiosity © akosma 2016 136

Slide 137

Slide 137 text

! Project Management Smells ! Invisible Documentation " Folder Clusterfuck # Cocoapods Galore $ Rogue Compiler Warnings % User Discrimination & Interface Builder Attack ' iOS Nostalgia ( Backend API Anarchy © akosma 2016 137

Slide 138

Slide 138 text

Got More Smells? Call to action! 1. Give smells a cool name and a description 2. Specify the refactoring 3. Share! © akosma 2016 138

Slide 139

Slide 139 text

Дякую! ! Thanks! ! More information at about.me/akosma © akosma 2016 139

Slide 140

Slide 140 text

Questions❔ © akosma 2016 140