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

Using Swift to write better code

Alex Curran
October 10, 2016

Using Swift to write better code

Writing better code is what makes great developers, a great codebase, and great products. So how can you use Swift's power to make your life easier and your code even better?

Alex Curran

October 10, 2016
Tweet

More Decks by Alex Curran

Other Decks in Technology

Transcript

  1. @amlcurran
    Using Swift to write better code

    View full-size slide

  2. @amlcurran
    No fancy APIs

    View full-size slide

  3. @amlcurran
    No cool features

    View full-size slide

  4. @amlcurran
    No mind-blowing frameworks

    View full-size slide

  5. @amlcurran
    No life-changing architectures

    View full-size slide

  6. @amlcurran
    But learning to use all these better

    View full-size slide

  7. @amlcurran
    Software craft

    View full-size slide

  8. @amlcurran
    Some common craft keywords
    Unit test
    SOLID
    Architecture
    Mentorship
    Pragmatism
    Dependency inversion
    Functional
    Imperative
    Single responsibility
    Self-improvement
    Continuous integration
    Continuous deployment
    TDD Outside-in
    Clean code

    View full-size slide

  9. @amlcurran
    Some common craft keywords
    Unit test
    SOLID
    Architecture
    Mentorship
    Clean code
    Pragmatism
    Dependency inversion
    Functional
    Imperative
    Single responsibility
    Self-improvement
    Continuous integration
    Continuous deployment
    TDD Outside-in

    View full-size slide

  10. @amlcurran
    Why write good code
    • Makes working with your team (and yourself) easier
    • New features are quicker to code
    • Easier to onboard new developers

    View full-size slide

  11. @amlcurran
    Swift as a platform
    • Swift is a new(ish) software language
    • Lots of flexibility
    • Huge potential for great code
    • Rapidly evolving guidelines

    View full-size slide

  12. @amlcurran
    –Me
    “A good software developer uses something because they can,
    A great software developer uses something because they must”

    View full-size slide

  13. @amlcurran
    Write it like you read it
    • You write code once
    • You and other people read it multiple times
    • Write code to be easy to read, not easy to write

    View full-size slide

  14. @amlcurran
    Extensions

    View full-size slide

  15. @amlcurran
    Wrap APIs which are generic
    let string = "Hello world! Wie geht's?"
    let attributedString = NSMutableAttributedString(string: string)
    let range = (attributedString.string as NSString).range(of: "Hello")
    attributedString.addAttributes([NSForegroundColorAttributeName:
    UIColor.red], range: range)
    attributedString.setForegroundColor(.red, for: "Hello")

    View full-size slide

  16. @amlcurran
    Wrap APIs which are generic
    let string = "Hello world! Wie geht's?"
    let attributedString = NSMutableAttributedString(string: string)
    let range = (attributedString.string as NSString).range(of: "Hello")
    attributedString.addAttributes([NSForegroundColorAttributeName:
    UIColor.red], range: range)
    attributedString.setForegroundColor(.red, forText: "Hello")

    View full-size slide

  17. @amlcurran
    Private extensions for local code
    • Private extensions provide even more specific wrappers
    • Write code in your domain, not Apple’s
    fileprivate extension EKEventEditViewController {
    convenience init(calendarItem: SCCalendarItem,
    delegate: EKEventEditViewDelegate,
    eventStore: EKEventStore = EKEventStore.instance) {
    self.init()
    self.eventStore = eventStore
    self.event = EKEvent(representing: calendarItem)
    self.editViewDelegate = delegate
    }
    }

    View full-size slide

  18. @amlcurran
    Using small extensions
    let translation = gestureRecognizer.translationInView(gestureRecognizer.view!.superview!).y
    let rawProgress = (translation - initialPosition) / fullGestureLength
    let progress = CGFloat(fminf(fmaxf(Float(rawProgress), 0.0), 1.0))

    View full-size slide

  19. @amlcurran
    Using small extensions
    let progress = gestureRecognizer.yTranslation(from: initialPosition)
    .asProportion(of: fullGestureLength)
    .clamp(between: 0, 1)
    let translation = gestureRecognizer.translationInView(gestureRecognizer.view!.superview!).y
    let rawProgress = (translation - initialPosition) / fullGestureLength
    let progress = CGFloat(fminf(fmaxf(Float(rawProgress), 0.0), 1.0))

    View full-size slide

  20. @amlcurran
    My favourite extension
    let maybeInt = Int("4")
    let addedUsingOperator = (maybeInt ?? 0) + 3
    let addedUsingMethod = maybeInt.or(0) + 3

    View full-size slide

  21. @amlcurran
    Opinionated slides alert

    View full-size slide

  22. @amlcurran
    A common scenario…
    class ViewController: UIViewController {
    // implementation of the view controller
    }
    extension ViewController: UITableViewDataSource {
    // implementation of data source
    }
    extension ViewController: UITableViewDelegate {
    // implementation of delegate
    }

    View full-size slide

  23. @amlcurran
    Avoid implementation extensions
    • Common Swift pattern - but avoid!
    • Makes your code look organised… when really it isn’t
    • One class implementing protocol interfaces = doing multiple things

    View full-size slide

  24. @amlcurran
    A common scenario…
    class ViewController: UIViewController {
    let strings = ["foo", "bar", "baz"]
    }
    extension ViewController: UITableViewDataSource {
    func numberOfSections(in tableView: UITableView) -> Int {
    return strings.count
    }
    }
    extension ViewController: UITableViewDelegate {
    // implementation of delegate
    }

    View full-size slide

  25. @amlcurran
    A common solution
    class ViewController: UIViewController {
    let strings = ["foo", "bar", "baz"]
    override func viewDidLoad() {
    tableView.dataSource = StringDataSource(strings)
    tableView.delegate = OpenDetailsTableViewDelegate()
    }
    }
    Replacing inheritance with delegation

    View full-size slide

  26. @amlcurran
    Functions

    View full-size slide

  27. @amlcurran
    Avoid long methods
    • Long methods are difficult to read, require a lot of time
    • Multiple if or switch statements make it difficult to see what happens
    • Break methods down into smaller ones

    View full-size slide

  28. @amlcurran
    Avoid long methods
    func foo(string: String) -> String {
    // do something
    if (someCondition()) {
    // something else
    } else {
    // backup
    }
    // do more stuff
    return value
    }

    View full-size slide

  29. @amlcurran
    Function pointers tell a story
    • Where you can use a closure, you can use a function pointer
    • These are named - use the name to show what a closure does
    • Levels of abstraction

    View full-size slide

  30. @amlcurran
    Without function pointers
    let shoppingList = items
    .filter({ $0.priceCents < 99})
    .map({ "\($0.name): €\(Double($0.priceCents) / 100.0)" })
    .joined(separator: ", ")

    View full-size slide

  31. @amlcurran
    With function pointers
    let shoppingList = items
    .filter({ $0.priceCents < 99})
    .map({ "\($0.name): €\(Double($0.priceCents) / 100.0)" })
    .joined(separator: ", ")
    let shoppingList = items
    .filter(itemsUnder99Cents)
    .map(nameAndDescription)
    .joined(separator: ", ")

    View full-size slide

  32. @amlcurran
    Combining it all
    let inputItems = extensionContext?.inputItems ?? []
    _ = inputItems
    .flatMap({ return $0 as? NSExtensionItem })
    .flatMap({ (extensionItem: NSExtensionItem) -> [NSItemProvider] in
    let attachments = extensionItem.attachments ?? []
    return attachments.flatMap({ (attachment) -> NSItemProvider? in
    return attachment as? NSItemProvider
    })
    })
    .filter({ $0.hasItemConformingToTypeIdentifier("public.url") })
    .forEach({ (urlItem) -> Void in
    urlItem.loadItem(forTypeIdentifier: "public.url", options: nil, completionHandler: { (result, error) in
    if let url = result as? NSURL {
    // Save the URL
    }
    self.extensionContext!.completeRequest(returningItems: []) { (expired) in
    print("did expire: \(expired)")
    }
    })
    })

    View full-size slide

  33. @amlcurran
    Extensions + functions =
    inputItems.orEmptyArray()
    .attemptTo(castAsExtensionItems)
    .then(extractAllAttachments)
    .filter(onlyUrlItems)
    .forEach(loadUrl(using: extensionContext))
    let inputItems = extensionContext?.inputItems ?? []
    _ = inputItems
    .flatMap({ return $0 as? NSExtensionItem })
    .flatMap({ (extensionItem: NSExtensionItem) -> [NSItemProvider] in
    let attachments = extensionItem.attachments ?? []
    return attachments.flatMap({ (attachment) -> NSItemProvider? in
    return attachment as? NSItemProvider
    })
    })
    .filter({ $0.hasItemConformingToTypeIdentifier("public.url") })
    .forEach({ (urlItem) -> Void in
    urlItem.loadItem(forTypeIdentifier: "public.url", options: nil, completionHandler: { (result, error) in
    if let url = result as? NSURL {
    // Save the URL
    }
    self.extensionContext!.completeRequest(returningItems: []) { (expired) in
    print("did expire: \(expired)")
    }
    })
    })

    View full-size slide

  34. @amlcurran
    Some tips
    • Think about “what does this code mean to me”?
    • “How can I make this clearer to others?”
    • Don’t be afraid to make private extensions which just wrap a function
    with a better name

    View full-size slide

  35. @amlcurran
    Resources
    • https://sourcemaking.com/
    • http://martinfowler.com/
    • “Clean Code” by Bob Martin
    • www.amlcurran.co.uk/tutorials/better-swift.zip

    View full-size slide

  36. @amlcurran
    Go write great code!
    or come write great code with me - [email protected]

    View full-size slide