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

Everyone is an API designer

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.

Everyone is an API designer

The slides from my talk "Everyone is an API designer", presented at NSSpain and Mobiconf 2017. The first 3/4 of the slides are used in both talks, while the last 25% were only in the extended director's cut HD remaster version presented at Mobiconf.

You can find a recording of the NSSpain version of this talk here: https://vimeo.com/234961067

Avatar for John Sundell

John Sundell

October 05, 2017
Tweet

Other Decks in Programming

Transcript

  1. class ProfileViewController: UIViewController { var headerView: ProfileHeaderView? var nameLabel: UILabel?

    var logOutButton: UIButton? } override func loadView() { super.loadView() let headerView = ProfileHeaderView() headerView.delegate = self self.headerView = headerView }
  2. override func loadView() { super.loadView() let headerView = ProfileHeaderView() headerView.delegate

    = self self.headerView = headerView } func userDidUnlockPremiumSubscription() { profileController.headerView = PremiumHeaderView() }
  3. class ProfileViewController: UIViewController { } var headerView: ProfileHeaderView? var nameLabel:

    UILabel? var logOutButton: UIButton? private private private extension ProfileViewController { enum Theme { case standard case premium } func style(with theme: Theme) { headerView?.apply(theme) } }
  4. class ListViewController: UIViewController { init(coreDataContext: NSManagedObjectContext) { ... } }

    class DetailViewController: UIViewController { init(coreDataContext: NSManagedObjectContext) { ... } } class ProfileViewController: UIViewController { init(coreDataContext: NSManagedObjectContext) { ... } }
  5. protocol Database { func loadObjects<T: Model>(matching query: Query) -> [T]

    func loadObject(withID id: String) -> T? func save<T: Model>(_ object: T) }
  6. class DatabaseMock: Database { ... } protocol Database { func

    loadObjects<T: Model>(matching query: Query) -> [T] func loadObject(withID id: String) -> T? func save<T: Model>(_ object: T) } extension NSManagedObjectContext: Database { ... } extension Realm: Database { ... }
  7. Great APIs are contained… …they don’t expose their implementation details…

    …which removes confusion and reduces the risk of “invalid use”. Protocols are a great way to define contained APIs… …that can have their underlying implementation easily changed.
  8. struct CATransform3D { init(m11: CGFloat, m12: CGFloat, m13: CGFloat, m14:

    CGFloat, m21: CGFloat, m22: CGFloat, m23: CGFloat, m24: CGFloat, m31: CGFloat, m32: CGFloat, m33: CGFloat, m34: CGFloat, m41: CGFloat, m42: CGFloat, m43: CGFloat, m44: CGFloat) }
  9. actionSheet.addButton(button: finishButton, dismiss: true) Button, button, button $ What is

    this dismissing? % class ActionSheetViewController: UIViewController { func addButton(button: UIButton, dismiss: Bool) { ... } }
  10. Great APIs are conversational… …they make their intent clear by

    using readable declarations… …which eliminates cruft and makes usages more obvious. By defining separate APIs for separate actions & functionality… …we can make them easier to read, and harder to misunderstand.
  11. extension User { func serialize() -> [String : Any] {

    return [ "name": name, "age": age, "hobbies": hobbies.map { $0.serialize() }, "address": address, "phoneNumber": phoneNumber ?? NSNull() ] } }
  12. func wrap<T>( _ object: T, keyConverter: KeyConverter, propertyOverrides: [String :

    Any], propertyNamesToIgnore: Set<String> ) throws -> Data
  13. func wrap( context: Any?, dateFormatter: DateFormatter? ) -> Any? }

    var wrapKeyStyle: WrapKeyStyle { get } func keyForWrapping( propertyNamed propertyName: String ) -> String? protocol WrapCustomizable {
  14. func wrap( context: Any?, dateFormatter: DateFormatter? ) -> Any? }

    var wrapKeyStyle: WrapKeyStyle { get } func keyForWrapping( propertyNamed propertyName: String ) -> String? protocol WrapCustomizable {
  15. public extension WrapCustomizable { } var wrapKeyStyle: WrapKeyStyle { return

    .matchPropertyName } func wrap( context: Any?, dateFormatter: DateFormatter? ) -> Any? { ... } func keyForWrapping( propertyNamed propertyName: String ) -> String? { ... }
  16. Great APIs are scalable… …they are easy to start using…

    …yet flexible enough for more advanced use cases. This can be achieved using extensions & default arguments… …and by moving overrides from the call site to type definitions.
  17. struct Animation { var duration: TimeInterval var curve: UIViewAnimationCurve var

    completionHandler: () -> Void } class ProfileViewController: UIViewController { init(profile: Profile, showFriends: Bool) } func present(_ profileViewController: ProfileViewController, with animation: Animation? = nil) { ... }
  18. func present(_ profileViewController: ProfileViewController, with animation: Animation? = nil) {

    ... } func presentProfile(animated: Bool, duration: TimeInterval, curve: UIViewAnimationCurve, showFriends: Bool, completionHandler: @escaping () -> Void) { @available(*, deprecated, renamed: “present(with:)") } ...
  19. duration: TimeInterval, curve: UIViewAnimationCurve, showFriends: Bool, completionHandler: @escaping () ->

    Void) { } let viewController = ProfileViewController(profile: profile, showFriends: showFriends) let animation: Animation? if animated { animation = Animation(duration: duration, curve: curve, completionHandler: completionHandler) } else { animation = nil } present(viewController, with: animation)
  20. Great APIs are constantly evolving… …rather than just growing in

    scope and complexity… …they are refactored and broken up into new ones. Backwards compatibility is a really beautiful thing… …and can be easily achieved using deprecations or additive changes.