Making Swift even safer

34
Making Swift even safer

Transcript of Making Swift even safer

Page 1: Making Swift even safer

Making Swift even safer

Page 2: Making Swift even safer

18 months of development

120k+ lines of Swift

≈40 releases

6 months in production

Juno Rider iOS

Page 3: Making Swift even safer

Stricter interface

Page 4: Making Swift even safer

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) { … }}

!

Page 5: Making Swift even safer

NonEmptyString

NonEmptyArray

NonNegativeDouble

Stronger types

Page 6: Making Swift even safer

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

Page 7: Making Swift even safer

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

Page 8: Making Swift even safer

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

Page 9: Making Swift even safer

extension TaggedValueType where ValueType: JSONEncoding {

func encodeJSON() -> AnyObject { return self.taggedValue.value.encodeJSON() }}

Phantom types

Page 10: Making Swift even safer

enum StringKey { case AccessibilityHomeConfirmButton case DialogInsufficientFundsTitle

...}

func localized(value: Strings) -> String {switch value {case .AccessibilityHomeConfirmButton:

return String.localized(“Accessibility.Home.Confirm.Button”)

...

}}

Static resources

Page 11: Making Swift even safer

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

Page 12: Making Swift even safer

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

Page 13: Making Swift even safer

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

Page 14: Making Swift even safer

Changing code

Page 15: Making Swift even safer

Components

Page 16: Making Swift even safer

Context

App

Page 17: Making Swift even safer

ContextMay contain dirty things dealing with global state

Page 18: Making Swift even safer

ContextMay contain dirty things dealing with global state

Unit tests

Page 19: Making Swift even safer

Contexttypealias AppContext = protocol< StringsServiceContainer, StaticImageServicesContainer, BundleImagesServiceContainer, ReachabilityServiceContainer, AnalyticsServiceContainer, SchedulerContainer, RemoteNotificationsContainer, RemoteNotificationsPermissionContainer, RemoteNotificationClearActionContainer, LocationServiceContainer, ApplicationServiceContainer, DeviceServiceContainer,

…>

class ElDependor: AppContext { … }

class MockContext: AppContext { … }

Page 20: Making Swift even safer

App

Pure state machine

Page 21: Making Swift even safer

App

Pure state machine

- Unit tests- Integrated acceptance tests

Page 22: Making Swift even safer

Acceptance tests via TestAppclass 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() }

Page 23: Making Swift even safer
Page 24: Making Swift even safer

View: screenshot testing

Page 25: Making Swift even safer

View: screenshot testing

Page 26: Making Swift even safer

View: screenshot testing

Page 27: Making Swift even safer

Detecting & investigating bugs in the field

- smart assertions- diligent logging- daily duty

Page 28: Making Swift even safer

junoAssert

in Debug - crash 🙀in Release - log.error(), trackNonFatal() and recover 🙏

Page 29: Making Swift even safer

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 ] )

Page 30: Making Swift even safer

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

Page 31: Making Swift even safer

Logging

Page 32: Making Swift even safer

Production quality comes at a price

- takes up to x2 dev effort- challenging for new team members- performance considerations

Page 33: Making Swift even safer

But it brings satisfaction

Page 34: Making Swift even safer

Thank you