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

A Neatly Typed Message

A Neatly Typed Message

The talk is about the readability of Swift code. I'll treat it not as a well-defined goal to achieve, but as a spectrum that you need to decide where to land on. Looking at the variations of popular Cocoa patterns and Swift language constructs we'll identify their readability tradeoffs and chances for improvement. We'll also learn some fine techniques to widen readability spectrum using the power of the Swift type system.

Krzysztof Siejkowski

March 03, 2017
Tweet

More Decks by Krzysztof Siejkowski

Other Decks in Technology

Transcript

  1. A Neatly Typed
    Message
    Krzysztof Siejkowski

    View Slide

  2. View Slide

  3. What’s our biggest
    problem?
    “Maintaining Mental Models: A Study of Developer Work Habits”

    View Slide

  4. Understanding
    the rationale behind
    a piece of code

    View Slide

  5. Understanding
    the rationale behind
    a piece of code
    Switching tasks
    because of
    requests
    from my
    teammates

    View Slide

  6. “Story of my life”

    View Slide

  7. Readability
    is crucial for
    understanding

    View Slide

  8. programs must be
    written for people to read,
    and only incidentally for
    machines to execute
    — Structure and Interpretation of Computer Programs book

    View Slide

  9. the essential material (are)
    techniques used to control the
    intellectual complexity
    of software systems.
    — Structure and Interpretation of Computer Programs book

    View Slide

  10. Complexity
    • intrinsic
    • incidental

    View Slide

  11. Readability quest
    [Press start]

    View Slide

  12. World 1:

    API design

    View Slide

  13. func split(email: String) -> (String, String)? {
    let components = email.components(separatedBy: "@")
    guard components.count == 2,
    let username = components.first,
    let domain = components.last,
    !username.isEmpty && !domain.isEmpty
    else { return nil }
    return (username, domain)
    }
    let splitEmail = split(email: ”[email protected]")

    View Slide

  14. func split(email: String) -> (String, String)? {
    let components = email.components(separatedBy: "@")
    guard components.count == 2,
    let username = components.first,
    let domain = components.last,
    !username.isEmpty && !domain.isEmpty
    else { return nil }
    return (username, domain)
    }
    let splitEmail = split(email: ”[email protected]")

    View Slide

  15. func split(email: String) -> (String, String)? {
    let components = email.components(separatedBy: "@")
    guard components.count == 2,
    let username = components.first,
    let domain = components.last,
    !username.isEmpty && !domain.isEmpty
    else { return nil }
    return (username, domain)
    }
    let splitEmail = split(email: ”[email protected]")

    View Slide

  16. func split(email: String) -> (String, String)?

    View Slide

  17. Level 1-1: comments
    /// Splits the email into username and domain.
    /// - Parameter email: A string that possibly
    /// contains the email address value.
    /// - Returns: Either:
    /// - Tuple with username as first element
    /// and domain as second
    /// - Nil when passed argument cannot be split
    func split(email: String) -> (String, String)?

    View Slide

  18. Level 1-1: comments
    /// Splits the email into username and domain.
    /// - Parameter email: A string that possibly
    /// contains the email address value.
    /// - Returns: Either:
    /// - Tuple with username as first element
    /// and domain as second
    /// - Nil when passed argument cannot be split
    func split(email: String) -> (String, String)?

    View Slide

  19. Level 1-2: symbols
    typealias SplitEmail = (username: String,
    domain: String)
    struct SplitEmail {
    let username: String
    let domain: String
    }
    func split(email: String) -> SplitEmail?

    View Slide

  20. Costs of symbols
    • fixed discoverability
    • marginal recognition

    View Slide

  21. Either a lot

    or not at all

    View Slide

  22. Level 1-2: symbols
    func split(email: String) -> SplitEmail
    ?

    View Slide

  23. Level 1-3: wrappers
    enum SplitEmail {
    case valid(username: String, domain: String)
    case invalid
    init(from email: String) {
    let components = email.components(separatedBy: "@")
    if /* same checks as before */ {
    self = .valid(username: username, domain: domain)
    } else {
    self = .invalid
    }

    View Slide

  24. Level 1-3: wrappers
    func split(email: String) -> SplitEmail

    View Slide

  25. Level 1-4: concepts
    protocol Splitter {
    associatedtype Splitting
    associatedtype Splitted
    func split(value: Splitting) -> (Splitted, Splitted)?
    }
    struct EmailSplitter: Splitter {
    func split(value: String) -> (String, String)? {
    let components = value.components(separatedBy: "@")
    guard /* same checks as before */ else { return nil }
    return (first, second)
    }

    View Slide

  26. Level 1-4: concepts
    enum Split where S.Splitting == Value,
    S.Splitted == Value {
    case splitted(Value, Value)
    case invalid
    init(_ value: Value, using splitter: S) {
    if let (first, second) = splitter.split(value: value) {
    self = .splitted(first, second)
    } else { self = .invalid }
    See https://github.com/Ben-G/Validated/ by Benjamin Encz

    View Slide

  27. Level 1-4: concepts
    func split(email: String) -> Split
    // result: Split
    // value: String
    // transform: EmailSplitter

    View Slide

  28. Is complexity
    justified?

    View Slide

  29. End of
    World 1

    View Slide

  30. World 2:

    Delegation

    View Slide

  31. protocol Delegate: class {
    func foo()
    }
    struct Announcing {
    open weak var delegate: Delegate?
    }
    var announcing = Announcing()
    announcing.delegate = delegate

    View Slide

  32. Conventions are HP
    for readability

    View Slide

  33. Level 2-1: Initializer injection
    struct Announcing {
    private weak var delegate: Delegate?
    init(to delegate: Delegate) {
    self.delegate = delegate
    }
    }
    let announcing = Announcing(to: delegate)

    View Slide

  34. Level 2-2: Weak closure
    struct Announcing {
    private let delegate: () -> Delegate?
    init(to delegate: @escaping () -> Delegate?) { … }
    }
    let announcing = Announcing(to: {
    [weak delegate] in delegate
    })

    View Slide

  35. Level 2-3: Weak function
    func weak(_ object: T) -> () -> T? {
    return { [weak object] in object }
    }
    struct Announcing {
    private let delegate: () -> Delegate?
    init(to delegate: @escaping () -> Delegate?) { … }
    }
    let announcing = Announcing(to: weak(delegate))

    View Slide

  36. Level 2-4: Weak wrapper
    struct Weak {
    private let internalValue: () -> T?
    init(_ value: T) {
    internalValue = { [weak value] in value }
    }
    }
    struct Announcing {
    private let delegate: Weak
    init(to delegate: Weak) { ... }
    }

    View Slide

  37. Readability is
    contextual

    View Slide

  38. There is no castle

    with a princess
    inside

    View Slide

  39. Empathy
    is the god mode

    View Slide

  40. Who are we 

    writing for?

    View Slide

  41. No such thing as
    the readability

    View Slide

  42. How do we win?

    View Slide

  43. May 

    the feedback

    be with you

    View Slide

  44. Thank you!
    @_siejkowski siejkowski.net

    View Slide