Who am I → iOS Developer @ XING → I curate Swift Weekly Brief → I do some open source things → I like continuous integration systems, too 8 — @basthomas
Have you ever tried... → using UIKit without documentation? (Xcode "helps" sometimes) → using someone's (undocumented) API without looking at the implementation? → using an undocumented API you wrote in the past, without looking at the implementation? 9 — @basthomas
This pull request adds a possibility to concat arrays without removing the duplicates in the parts that are not overlap completely (in the meaning that we care only if the last x values of 1st array overlaps with the same x values of the 2nd, but we don't care if some duplicates are randomly exist in the body of array) 16 — @basthomas
/// - Parameter array1: an array that contain all the string collected till /// this moment /// - Parameter array2: an array that contain new elements that should be /// added in array1 init(array1: [String], array2: [String]) { self.array1 = array1 self.array2 = array2 } /// Combines two arrays with partial removal of duplicates /// /// - Returns: An array of complate `array1` and partially (removing the first /// occurances in both arrays) added `array2` func combineOutputs() -> [String] { return concatArrays() } 18 — @basthomas
class Lists { enum ListError: Error { case noOverlap } /// Adds two arrays with removing the overlap between them /// /// - Parameter existing: the exsting elemets. /// - Parameter newEntries: the elements that should be added to the front /// - Returns: the sum of both array with `newEntries` at the front static func concatenateWithoutOverlap( existing: [String], newEntries: [String] ) throws -> [String] { let occourences = newEntries.enumerated().filter { index, entry in newEntries[index] == existing.first } let _overlapStart = occourences.first(where: { index in existing.starts(with: newEntries[index..<-1]) }) guard let overlapStart = _overlapStart else { throw ListError.noOverlap } return [Array(arr3.prefix(through: 1)), arr2].flatMap { $0 } } } 20 — @basthomas
class Lists { enum ListError: Error { case noOverlap } /// Adds two arrays with removing the overlap between them /// /// - Parameter existing: the exsting elemets. /// - Parameter newEntries: the elements that should be added to the front /// - Returns: the sum of both array with `newEntries` at the front static func concatenateWithoutOverlap( existing: [String], newEntries: [String] ) throws -> [String] { // implementation } } 22 — @basthomas
/// Prepends an array to front of an existing one, /// without adding the overlap twice. /// /// Example: /// /// ```swift /// let existing = [4, 3, 2, 1] /// let newEntries = [6, 5, 4, 3] /// Lists.concatenateWithoutOverlap( /// existing: existing, /// newEntries: newEntries /// ) // Returns [6, 5, 4, 3, 2, 1] /// ``` /// /// - Parameter existing: the existing array. /// - Parameter newEntries: the array that should be added to the front /// - Returns: returns the new array 23 — @basthomas
class Lists { enum ListError: Error { case noOverlap } /// Prepends an array to front of an existing one, /// without adding the overlap twice. /// /// Example: /// /// ```swift /// let existing = [4, 3, 2, 1] /// let newEntries = [6, 5, 4, 3] /// Lists.concatenateWithoutOverlap( /// existing: existing, /// newEntries: newEntries /// ) // Returns [6, 5, 4, 3, 2, 1] /// ``` /// /// - Parameter existing: the existing array. /// - Parameter newEntries: the array that should be added to the front /// - Returns: returns the new array static func stitch( existing: [String], with newEntries: [String] ) throws -> [String] { // Get the index of all occurrences of the first element from the // `newEntries` array in `existing` let occurrences = newEntries.enumerated().filter { index, entry in newEntries[index] == existing.first } // Check if one of the slices of `existing` is prefix of // the `newEntries` array let _overlapStart = occurrences.first(where: { index in existing.starts(with: newEntries[index..<-1]) }) guard let overlapStart = _overlapStart else { throw ListError.noOverlap } // Add the `newEntries` without the overlap at the end of the existing array return [Array(arr3.prefix(through: 1)), arr2].flatMap { $0 } } } 24 — @basthomas
What more can we do? → Give examples → Write simple, non near-code documentation → "No raw loops" → Self-enforced best practice → Strongly typed APIs → Scope → Compassion → ..? 41 — @basthomas
/// Creates a new `TextConfiguration` with the specified values. /// /// - Parameter color: the text color to use. Available colors are a subset /// of `XINGColor` and are safe to use for text. /// - Parameter weight: the text weight to use. This will be used for the text's font. /// - Parameter size: the text size to use. This will be used for the text's font. /// - Parameter numberOfLines: The number of lines of text to render. /// To remove any maximum limit, and use as many lines as needed, /// set the value of this property to 0. @objc public init( color: TextColor, weight: TextWeight, size: TextSize, numberOfLines: Int ) { self._color = color self.weight = weight self.size = size self.numberOfLines = numberOfLines } 44 — @basthomas
/// - Paramter label: the label to generate a text configuration from. /// /// - Returns: a `TextConfiguration` based on the label, if it uses valid /// text configuration values. If not, it will assert and revert to default options. @objc public static func from(label: UILabel) -> TextConfiguration { func textColor(from color: UIColor) -> TextColor { guard let _textColor = TextColor.all.first(where: { $0.uiColor == label.textColor }) else { assertionFailure("Could not convert label's color to a valid TextColor. This will default to `.grey800` in production, which may be unexpected.") return .grey800 } return _textColor } func textWeight(from font: UIFont) -> TextWeight { guard let _textStyle = TextWeight.from(font: font) else { assertionFailure("Could not convert font's weight to a valid TextWeight. This will default to `.regular` in production, which may be unexpected.") return .regular } return _textStyle } func textSize(from font: UIFont) -> TextSize { guard let _textSize = TextSize.from(font: font) else { assertionFailure("Could not convert font's size to a valid TextSize. This will default to `.default12` in production, which may be unexpected.") return .default12 } return _textSize } return .init( color: textColor(from: label.textColor), weight: textWeight(from: label.font), size: textSize(from: label.font), numberOfLines: label.numberOfLines ) } 46 — @basthomas
/// Applies the current `TextConfiguration` to the supplied label. /// /// - Parameter label: the `UILabel` instance to apply the /// text configuration to. /// /// - Returns: a reference to the label that has the configuration /// aplied to it. @discardableResult @objc public func apply(to label: UILabel) -> UILabel { label.textColor = color label.font = font label.numberOfLines = numberOfLines return label } 49 — @basthomas
Outcome, part 2 → The compiler is your friend → Don't be afraid to use types → The compiler is your enemy → We should explain the architectural design → Prevent "rest of the damn owl" syndrom → Use assertions! 52 — @basthomas
Things to keep in mind → I don't want to write the correct code. There is no correct code. There is no silver bullet. → I want to help the most correct code to be written. → I want to prevent incorrect code to be written. → I want to fail early and often. → (but for that, I need it to be testable) → Iterate, iterate, iterate 53 — @basthomas
Takeaways → Optimize code for reading, not writing → Work together and bounce off ideas → Documentation is helpful for yourself when writing code, too → Documentation helps you with naming, scope, testability and readability → Having testable code feels awesome → Snapshot tests are the root of all evil 54 — @basthomas
Further reading "Combinators" by Daniel Steinberg https://vimeo.com/290272240 "Denotational Design" by Conal Elliot https://github.com/conal/talk-2014-lambdajam-denotational-design "... But That Should Work?" by me https://basthomas.github.io/but-that-should-work Point-Free by Brandon Williams & Stephen Celis https://www.pointfree.co 56 — @basthomas