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.

Making Swift even safer

418 views

Published on

How close to bug-free can you get?

Published in: Technology
  • Be the first to comment

Making Swift even safer

  1. 1. Making Swift even safer
  2. 2. 18 months of development 120k+ lines of Swift ≈40 releases 6 months in production Juno Rider iOS
  3. 3. Stricter interface
  4. 4. extension Array { public subscript (safe index: Int) -> Element? { … } } extension Int { public init?(safe value: Float) { … } public init?(safe value: Double) { … } public init?(safe value: UInt) { … } } !
  5. 5. NonEmptyString NonEmptyArray NonNegativeDouble Stronger types
  6. 6. struct LocationCoordinate<A> { let value: Double init(_ value: Double) { self.value = value } } enum Lat {} enum Lon {} struct Location { let latitude: LocationCoordinate<Lat> let longitude: LocationCoordinate<Lon> } Phantom types
  7. 7. struct TaggedValue<ValueType, Tag> { let value: ValueType init(_ value: ValueType) { self.value = value } } enum PickupTimeTag {} typealias PickupTime = TaggedValue<NSDate, PickupTimeTag> enum DropoffTimeTag {} typealias DropoffTime = TaggedValue<NSDate, DropoffTimeTag> Phantom types
  8. 8. enum PhoneTag {} typealias Phone = TaggedValue<String, PhoneTag> extension TaggedValueType where Tag == PhoneTag, ValueType == String { func trimCountryCode() -> Phone { return Phone(trimCountryCode(self.taggedValue.value)) } } private func trimCountryCode(phone: String) -> String { … } Phantom types
  9. 9. extension TaggedValueType where ValueType: JSONEncoding { func encodeJSON() -> AnyObject { return self.taggedValue.value.encodeJSON() } } Phantom types
  10. 10. enum StringKey { case AccessibilityHomeConfirmButton case DialogInsufficientFundsTitle ... } func localized(value: Strings) -> String { switch value { case .AccessibilityHomeConfirmButton: return String.localized(“Accessibility.Home.Confirm.Button”) ... } } Static resources
  11. 11. extension HTTP { enum Error: ErrorType { case InvalidResponse(request: NSURLRequest, response: NSURLResponse?) case TransportError(request: NSURLRequest, error: NSError) case HTTPError(request: NSURLRequest, response: NSHTTPURLResponse, responseData: NSData?) case CannotCreateURL(components: NSURLComponents) case InvalidURL(urlString: String) case AuthServiceFailure case CannotBindStreamPair(request: NSURLRequest) case StreamWriting(request: NSURLRequest, error: NSError?) case StreamGzipEncoding(request: NSURLRequest, operation: HTTP.Error.GzipOperation) } } Strong ErrorType
  12. 12. extension JSON.Error { struct Encode: ErrorType { public let error: NSError public let source: Any } enum Decode: ErrorType { case Unexpected case Serialization(error: NSError, data: NSData) case SchemeMismatch(error: JSON.Error.SchemeMismatch, body: AnyObject?) } struct SchemeMismatch: ErrorType { public let pathComponents: [String] public let reason: String } } Strong ErrorType
  13. 13. public enum JSONTaskError: ErrorType { case Task(error: HTTP.Error) case Request(error: JSON.Error.Encode) case Response(response: HTTP.Response, error: JSON.Error.Decode) } Strong ErrorType
  14. 14. Changing code
  15. 15. Components
  16. 16. Context App
  17. 17. Context May contain dirty things dealing with global state
  18. 18. Context May contain dirty things dealing with global state Unit tests
  19. 19. Context typealias AppContext = protocol< StringsServiceContainer, StaticImageServicesContainer, BundleImagesServiceContainer, ReachabilityServiceContainer, AnalyticsServiceContainer, SchedulerContainer, RemoteNotificationsContainer, RemoteNotificationsPermissionContainer, RemoteNotificationClearActionContainer, LocationServiceContainer, ApplicationServiceContainer, DeviceServiceContainer, … > class ElDependor: AppContext { … } class MockContext: AppContext { … }
  20. 20. App Pure state machine
  21. 21. App Pure state machine - Unit tests - Integrated acceptance tests
  22. 22. Acceptance tests via TestApp class Allow_Rider_to_Have_Max_X_Cards_per_Account_Spec: QuickSpec { override func spec() { var app: TestApp! beforeEach { app = TestApp() } given("rider is entering CC details on Add CC screen") { beforeEach { app.login() app.goToHomeScreen() app.receiveSomePaymentMethods() app.openPayments() app.payments.paymentMethods.tapAddPayment() app.payments.addPayment.enterSomeCC() app.payments.addPayment.tapNext() app.payments.addPayment.enterSomeZipCode() } when("rider taps Add CC button") { beforeEach { app.payments.addPayment.tapDone() } and("BE returns the message about max number of active cards") { beforeEach { app._context.addCreditCard.receive(.Failed(.TooManyPaymentMethods)) } then("present the Max Cards Added alert") { app.payments.addPayment.expectToPresentAlert() } when("rider taps Ok button") { beforeEach { app.payments.addPayment.alert.tapOK() } then("dismiss the alert") { app.payments.addPayment.expectToNotPresentAlert() }
  23. 23. View: screenshot testing
  24. 24. View: screenshot testing
  25. 25. View: screenshot testing
  26. 26. Detecting & investigating bugs in the field - smart assertions - diligent logging - daily duty
  27. 27. junoAssert in Debug - crash 🙀 in Release - log.error(), trackNonFatal() and recover 🙏
  28. 28. Logging switch error { case let .InvalidResponse(value): log.warn( category: logCategory, message: "Unexpected response", payload: [ .RequestIdentifier: error.requestIdentifier, .ResponseIdentifier: error.responseIdentifier, .Description: "(value.response)", .Path: value.request.path, .URL: value.request.absoluteURL ] ) …
  29. 29. Logging Analytics junoAssert ↓ ↓ log.verbose log.info log.warn log.error ↓ ↓ ↓ ↓ {'key0':'value0','key1':‘value1'} ↓ append to txt file ↓ roll & upload to AWS ↓ Elasticsearch + Kibana
  30. 30. Logging
  31. 31. Production quality comes at a price - takes up to x2 dev effort - challenging for new team members - performance considerations
  32. 32. But it brings satisfaction
  33. 33. Thank you

×