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

"Lost in maintenance: criteria of maintainable ...

"Lost in maintenance: criteria of maintainable in Swift" by Paul Taykalo

We rarely start projects from scratch and often blame our predecessors for their code. Problems begin yet upon estimation of live project’s maintainability. In his talk Taykalo Paul, iOS Team Leader at Stanfy, will reveal some particular criteria to consider on existing codebases’ maintainability.
He’ll also share his experience communicating current project’s state and related risks to stakeholders. Paul will show some practical decisions that will help to leave the project in better state than it was previously. Refactoring techniques, unit tests’ coverage, CoreData migrations are here to help.

This talk was made for CocoaHeads Kyiv #12 which took place July 22 2017.

CocoaHeads Ukraine

July 22, 2017
Tweet

More Decks by CocoaHeads Ukraine

Other Decks in Programming

Transcript

  1. Lost in maintenance: criteria of maintainable in Swift by Paul

    Taykalo, Stanfy Lost in maintenance, by Paul Taykalo, #cocoaheadskyiv 1
  2. Plan » Derermining project maintainability » Maintaining unmaintainable » Rewriting?

    » Speaking about maintainability Lost in maintenance, by Paul Taykalo, #cocoaheadskyiv 2
  3. We've got a project to check Be objective Lost in

    maintenance, by Paul Taykalo, #cocoaheadskyiv 9
  4. We decided not to take the project Lost in maintenance,

    by Paul Taykalo, #cocoaheadskyiv 11
  5. So, you've got a project First steps Lost in maintenance,

    by Paul Taykalo, #cocoaheadskyiv 15
  6. Are there any tests? You know the answer :( Lost

    in maintenance, by Paul Taykalo, #cocoaheadskyiv 18
  7. Are there any tests? You know the answer :( Lost

    in maintenance, by Paul Taykalo, #cocoaheadskyiv 19
  8. One (two) thing at the time Lost in maintenance, by

    Paul Taykalo, #cocoaheadskyiv 26
  9. Semantic Meaning if item.row == 2 { ... } Lost

    in maintenance, by Paul Taykalo, #cocoaheadskyiv 33
  10. Semantic Meaning if item.row == 2 { ... } Lost

    in maintenance, by Paul Taykalo, #cocoaheadskyiv 34
  11. Semantic Meaning if api.version > 2 { ... } Lost

    in maintenance, by Paul Taykalo, #cocoaheadskyiv 35
  12. Semantic Meaning if api.version > 2 { ... } let

    supportsThrottling = api.version > 2 if supportsThrottling { ... } Lost in maintenance, by Paul Taykalo, #cocoaheadskyiv 36
  13. Semantic Meaning if api.version > 2 { ... } extension

    Api { var supportsThrottling: Bool { return version > 2} } Lost in maintenance, by Paul Taykalo, #cocoaheadskyiv 37
  14. Semantic Meaning if let object = notification.object as? UIViewController ,

    object == self { ... } Lost in maintenance, by Paul Taykalo, #cocoaheadskyiv 38
  15. Semantic Meaning if let object = notification.object as? UIViewController ,

    object == self { ... } let notificationWasSentByUs = notification.object == self if notificationWasSentByUs { ... } Lost in maintenance, by Paul Taykalo, #cocoaheadskyiv 39
  16. Semantic Meaning if let currentUser = User.currentUser(inContext: mainContext) , currentUser.username

    != nil && currentUser.apiToken != nil ... else if let currentUser = User.currentUser(inContext: mainContext) , currentUser.username == nil && currentUser.apiToken != nil ... else Lost in maintenance, by Paul Taykalo, #cocoaheadskyiv 40
  17. Semantic meaning let user = User.currentUser(inContext: self.appData.mainContext) let userHasValidUserName =

    user?.username != nil let userHasValidToken = user?.apiToken != nil Lost in maintenance, by Paul Taykalo, #cocoaheadskyiv 41
  18. Semantic meaning if userHasValidToken && userHasValidUserName ... else if userHasValidToken

    && userHasNoName ... else Lost in maintenance, by Paul Taykalo, #cocoaheadskyiv 42
  19. Always be careful with any changes func overloaded(value: Double) ->

    String { return "Double" } func overloaded(value: Any) -> String { return "Any" } let size: Double? = dictionary["size"] let value = size ?? 0.0 let complex = overloaded(size ?? 0.0) // Any let simple = overloaded(value) // Double Lost in maintenance, by Paul Taykalo, #cocoaheadskyiv 46
  20. What are these functions about? UIImage -> UIImage String ->

    String -> String URL -> UIImage Lost in maintenance, by Paul Taykalo, #cocoaheadskyiv 48
  21. Phantom types PhotoAlbumImage -> TumbnailImage UserID -> AddressID -> Address

    FileURL -> ThumbnailImage Lost in maintenance, by Paul Taykalo, #cocoaheadskyiv 50
  22. Phantom types struct FilteredImage { // this will be filtered

    only let image: UIImage } struct BigImage { // in memory image private(set) var inMemoryImage: UIImage? // stored private var storedURL: URL? var image: UIImage { // return from the memory // or load from the fs } } Lost in maintenance, by Paul Taykalo, #cocoaheadskyiv 51
  23. Phantom types struct FileSystemURL { let url: NSURL // Failable

    initializer init?(url: NSURL) } Lost in maintenance, by Paul Taykalo, #cocoaheadskyiv 52
  24. Phantom types » Concrete » Unique behaviour » Control Lost

    in maintenance, by Paul Taykalo, #cocoaheadskyiv 54
  25. Hell? vs Hell! struct User { let name: String? let

    token: String? } Lost in maintenance, by Paul Taykalo, #cocoaheadskyiv 60
  26. Hell? vs Hell! struct UnverifiedUser {} struct User { let

    name: String } struct LoggedUser { let user: User let token: String // TokenString } Lost in maintenance, by Paul Taykalo, #cocoaheadskyiv 61
  27. Hell? vs Hell! // First var itemsPerKey: [Key: Item] =

    [:] // Then var itemsPerKey: [Key: Item] = [:] var isDeleted: [Key: Bool] = [:] Lost in maintenance, by Paul Taykalo, #cocoaheadskyiv 62
  28. Hell? vs Hell! enum ItemState { case assigned(let item) case

    deleted var item: Item? { if .assigned(let item) = self { return item } return nil } } var itemsPerKey: [Key: ItemState] = [:] Lost in maintenance, by Paul Taykalo, #cocoaheadskyiv 63
  29. Hell? vs Hell! enum UploadingState { case notStarted case uploading(Foundation.Operation)

    case uploaded case failed(Error) func operation() -> Foundation.Operation? { switch self { case .uploading(let op): return op default: return nil } } func isUploading() -> Bool { if case .uploading = self { return true } return false } } Lost in maintenance, by Paul Taykalo, #cocoaheadskyiv 64
  30. Hell? vs Hell! public enum Result<Value, Error: Swift.Error> { case

    success(Value) case error(Error) } Lost in maintenance, by Paul Taykalo, #cocoaheadskyiv 65
  31. How to make changes in the project Lost in maintenance,

    by Paul Taykalo, #cocoaheadskyiv 68
  32. How to make changes in the project Lost in maintenance,

    by Paul Taykalo, #cocoaheadskyiv 69
  33. How to make changes in the project » What is

    going on? » Freeze current behaviour » Write down desired behaviour » Check » Make sure that all unrelated sidefects still there Lost in maintenance, by Paul Taykalo, #cocoaheadskyiv 70
  34. How to make changes in the project » Make tests

    on current behavor » Make change » Check Lost in maintenance, by Paul Taykalo, #cocoaheadskyiv 71
  35. But I cannot test it because... » I dont understand

    how class works » I cannot instantiate class in test env » I cannot test method - it's too bit » I cannot... Lost in maintenance, by Paul Taykalo, #cocoaheadskyiv 73
  36. . . It's too big for testing Lost in maintenance,

    by Paul Taykalo, #cocoaheadskyiv 74
  37. 'But I cannot' solutions » Test method in class »

    Make an helper and use it class Huge { func perform { if object.price as? String == } } Lost in maintenance, by Paul Taykalo, #cocoaheadskyiv 75
  38. 'But I cannot' solutions class Huge { func perform {

    if let price = object.price as? String } } Lost in maintenance, by Paul Taykalo, #cocoaheadskyiv 76
  39. 'But I cannot' solutions struct Price { init?(object: [String: Any])

    {} var priceToShow: String {} var discont: String {} } Lost in maintenance, by Paul Taykalo, #cocoaheadskyiv 77
  40. 'But I cannot' solutions protocol Dispatcher { func background(_ block:

    @escaping (() -> Void)) func main(_ block: @escaping (() -> Void)) func main_from_background(_ block: @escaping(() -> Void)) func background_delay(_ delay: Int64, block: @escaping (() -> Void)) func main_delay(_ delay: Int64, block: @escaping (() -> Void)) } // for everything else class DefaultDispatcher: Dispatcher {} // For tests class SyncDispatcher: Dispatcher {} Lost in maintenance, by Paul Taykalo, #cocoaheadskyiv 78
  41. 'But I cannot' solutions » Core Data » Views »

    Snaphots » Hardware? Lost in maintenance, by Paul Taykalo, #cocoaheadskyiv 79
  42. L - is for Logs Lost in maintenance, by Paul

    Taykalo, #cocoaheadskyiv 82
  43. N - is for "No hope without Logs and Measurements"

    Lost in maintenance, by Paul Taykalo, #cocoaheadskyiv 84
  44. Small history about scrolling » App is not using cache

    Lost in maintenance, by Paul Taykalo, #cocoaheadskyiv 87
  45. Small history about scrolling » ✅ Not using cache »

    ❌ Freezing Lost in maintenance, by Paul Taykalo, #cocoaheadskyiv 88
  46. Small history about scrolling » ✅ Not using cache »

    ✅ Freezing (disk cacke) » ❌ Freezing ( after MW) Lost in maintenance, by Paul Taykalo, #cocoaheadskyiv 89
  47. Small history about scrolling » ✅ Not using cache »

    ✅ Freezing (disk cacke) » ✅ Freezing ( after MW) » ❌ Lags (Jpeg Decoding) Lost in maintenance, by Paul Taykalo, #cocoaheadskyiv 90
  48. Small history about scrolling » ✅ Not using cache »

    ✅ Freezing (disk cacke) » ✅ Freezing ( after MW) » ✅ Lags (Jpeg Decoding) » ❌ Out of memory crashes Lost in maintenance, by Paul Taykalo, #cocoaheadskyiv 91
  49. Small history about scrolling » ✅ Not using cache »

    ✅ Freezing (disk cacke) » ✅ Freezing ( after MW) » ✅ Lags (Jpeg Decoding) » ❌ Out of memory crashes » ??????? Lost in maintenance, by Paul Taykalo, #cocoaheadskyiv 92
  50. Rewriting » Modularity » Testing! » Enclosing » Unification Lost

    in maintenance, by Paul Taykalo, #cocoaheadskyiv 97
  51. Modularity What if I tell you that there can be

    more than one storyboard? Lost in maintenance, by Paul Taykalo, #cocoaheadskyiv 98
  52. Testing Start writing tests :( - There's no time for

    it - I cannot sell it to the customer - I am supporting a legacy application without unit tests. - QA and User Acceptance Testing is far more effective in finding bugs - I don’t know how to unit test, or I don’t know how to write good unit tests Lost in maintenance, by Paul Taykalo, #cocoaheadskyiv 99
  53. Testing There'll be no failed tests if there's no tests

    Lost in maintenance, by Paul Taykalo, #cocoaheadskyiv 100
  54. Lost in maintenance by Paul Taykalo, Stanfy @TT_Kilew Lost in

    maintenance, by Paul Taykalo, #cocoaheadskyiv 108