Using Swift to write better code

0faee5216d2841b23acdbfa28588e2de?s=47 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?

0faee5216d2841b23acdbfa28588e2de?s=128

Alex Curran

October 10, 2016
Tweet

Transcript

  1. @amlcurran Using Swift to write better code

  2. @amlcurran

  3. @amlcurran No fancy APIs

  4. @amlcurran No cool features

  5. @amlcurran No mind-blowing frameworks

  6. @amlcurran No life-changing architectures

  7. @amlcurran But learning to use all these better

  8. @amlcurran Software craft

  9. @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
  10. @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
  11. @amlcurran Why write good code • Makes working with your

    team (and yourself) easier • New features are quicker to code • Easier to onboard new developers
  12. @amlcurran Swift as a platform • Swift is a new(ish)

    software language • Lots of flexibility • Huge potential for great code • Rapidly evolving guidelines
  13. @amlcurran –Me “A good software developer uses something because they

    can, A great software developer uses something because they must”
  14. @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
  15. @amlcurran Extensions

  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, for: "Hello")
  17. @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")
  18. @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 } }
  19. @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))
  20. @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))
  21. @amlcurran My favourite extension let maybeInt = Int("4") let addedUsingOperator

    = (maybeInt ?? 0) + 3 let addedUsingMethod = maybeInt.or(0) + 3
  22. @amlcurran Opinionated slides alert

  23. @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 }
  24. @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
  25. @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 }
  26. @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
  27. @amlcurran Functions

  28. @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
  29. @amlcurran Avoid long methods func foo(string: String) -> String {

    // do something if (someCondition()) { // something else } else { // backup } // do more stuff return value }
  30. @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
  31. @amlcurran Without function pointers let shoppingList = items .filter({ $0.priceCents

    < 99}) .map({ "\($0.name): €\(Double($0.priceCents) / 100.0)" }) .joined(separator: ", ")
  32. @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: ", ")
  33. @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)") } }) })
  34. @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)") } }) })
  35. @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
  36. @amlcurran Resources • https://sourcemaking.com/ • http://martinfowler.com/ • “Clean Code” by

    Bob Martin • www.amlcurran.co.uk/tutorials/better-swift.zip
  37. @amlcurran Go write great code! or come write great code

    with me - jobs@novoda.com