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

Code review - basic stuff

Code review - basic stuff

Title: Code for humans
Description: In a version control system nobody knows you're a dog. How to keep them wondering.

CocoaHeads Kraków #38
https://www.meetup.com/CocoaHeads-Krakow/events/268456411/

danielgarbien

February 27, 2020
Tweet

Other Decks in Programming

Transcript

  1. ACCESS CONTROL DEFAULT TO PRIVATE PREVENT MUTATING FROM OUTSIDE private(set)

    var state: State CORRECT INTERFACE BUILDER @IBOutlet private var titleLabel: UILabel! @IBAction private func refresh() { } REMOVE DERIVED METHODS struct Uninstantiable { private init() {} }
  2. TYPES DON’T BE CRYPTIC func fetchMountains(completion: ((String, Double)?, Error?) ->

    Void) {} USE LIGHTWEIGHT TYPES struct Mountain { let name: String let height: Double } func fetchMountains(completion: ([Mountain]?, Error?) -> Void) {} TYPEALIASES typealias MountainsOrError = ([Mountain]?, Error?) func fetchMountains(completion: (MountainsOrError) -> Void) {}
  3. TYPES ENUM WITH ASSOCIATED VALUES? enum MountainsOrError { case mountains([Mountain])

    case error(Error) } RESULT TYPE! func fetchMountains(completion: (Result<[Mountain], Error>) -> Void) {} AND OTHER FUNDAMENTALS struct Mountain { let name: String let height: Measurement<UnitLength> }
  4. API FULFILL THE CONTRACT enum MyError: Error { case noData

    } func fetchMountains(completion: (Result<[Mountain], Error>) -> Void) { if let data = data { do { let mountains = try JSONDecoder().decode([Mountain].self, from: data) completion(.success(mountains)) } catch { completion(.failure(error)) } } else { completion(.failure(MyError.noData)) } }
  5. THREADING ASSUME MAIN THREAD ENCAPSULATE BACKGROUND OPERATIONS func fetchData(completion: (Result<[Mountain],

    Error>) -> Void) { func completeOnMain(_ result: Result<[Mountain], Error>) { DispatchQueue.main.async { completion(result) } } URLSession.shared.dataTask(with: url) { (data, _, _) in if let data = data { do { let mountains = try JSONDecoder().decode([Mountain].self, from: data) completeOnMain(.success(mountains)) } catch { completeOnMain(.failure(error)) } } else { completeOnMain(.failure(MyError.noData)) } }.resume() }
  6. METHODS OVERSIZED BODIES func updateLabels() { if isLoading { titleLabel.text

    = "Loading..." } else if let mountains = mountains { titleLabel.text = "Loaded \(mountains.count)" } else { titleLabel.text = nil } errorLabel.text = lastError?.localizedDescription }
  7. METHODS START REFACTORING func updateLabels() { updateTitleLabel() errorLabel.text = lastError?.localizedDescription

    } func updateTitleLabel() { if isLoading { titleLabel.text = "Loading..." } else if let mountains = mountains { titleLabel.text = "Loaded \(mountains.count)" } else { titleLabel.text = nil } }
  8. METHODS KEEP THE SAME LEVEL OF ABSTRACTION func updateLabels() {

    updateTitleLabel() updateErrorLabel() } func updateTitleLabel() { if isLoading { titleLabel.text = "Loading..." } else if let mountains = mountains { titleLabel.text = "Loaded \(mountains.count)" } else { titleLabel.text = nil } } func updateErrorLabel() { errorLabel.text = lastError?.localizedDescription }
  9. METHODS PREFER USE OF RETURN VALUES OVER „SIDE EFFECTS” func

    updateLabels() { titleLabel.text = titleText() errorLabel.text = errorText() } func titleText() -> String? { if isLoading { return "Loading..." } else if let mountains = mountains { return "Loaded \(mountains.count)" } else { return nil } } func errorText() -> String? { return lastError?.localizedDescription }
  10. STATE BASED ON A FEW PROPERTIES var lastError: LocalizedError? var

    isLoading: Bool { false } var mountains: [Mountain]? INTEGRATED enum State { case idle case loading case loaded([Mountain]) case failedLoading(LocalizedError) } var state = State.idle
  11. ENUMS EXPRESSIVE FLAGS extension PanInteractionState { enum Outcome { case

    complete case rollBack } func suggestedOutcome() -> Outcome { if currentRatio > 0.5 { return .complete } else { return .rollBack } } private var currentRatio: CGFloat }
  12. ENUMS EXPRESSIVE FLAGS - USAGE private func panDidEnd(state: PanInteractionState) {

    switch state.suggestedOutcome() { case .complete: complete(with: state) case .rollBack: rollBack(with: state) } }
  13. ENUMS SWITCHING CASES enum Outcome { case complete case rollBack

    } func translationLeft(for outcome: Outcome) -> Translation { if case .complete = outcome { willFinishTransition() } }
  14. ENUMS SWITCHING CASES enum Outcome { case complete case completeSlowly

    case rollBack } func translationLeft(for outcome: Outcome) -> Translation { if case .complete = outcome { // builds successfully willFinishTransition() } }
  15. ENUMS SWITCH ALL CASES. ALWAYS enum Outcome { case complete

    case completeSlowly case rollBack var isComplete: Bool { switch self { case .complete, .completeSlowly: return true case .rollBack: return false } } } func translationLeft(for outcome: Outcome) -> Translation { if outcome.isComplete { willFinishTransition() } }
  16. MIXING STRUCTS AND CLASSES STRUCT IN A CLASS CLASS IN

    A STRUCT struct ViewModel { let title: NSAttributedString } let attString = NSMutableAttributedString(string: "Hello") let viewModel = ViewModel(title: attString) // viewModel is passed by value attString.append(" stranger!”) // all viewModel „copies” are changed EXCEPTION: E.G. IMPLEMENTING COPY ON WRITE
  17. HAVE FUN CONCATENATE ATTRIBUTED STRINGS func concatenate(strings: [NSAttributedString]) -> NSAttributedString

    { let tmp = NSMutableAttributedString() for string in strings { tmp.append(string) } return tmp.copy() }
  18. HAVE FUN CONCATENATE ATTRIBUTED STRINGS func concatenate(strings: [NSAttributedString]) -> NSAttributedString

    { return strings.reduce(NSAttributedString(), +) } extension NSAttributedString { static func +(lhs: NSAttributedString, rhs: NSAttributedString) -> NSAttributedString { let result = NSMutableAttributedString(attributedString: lhs) result.append(rhs) return result.copy() as! NSAttributedString } }
  19. HAVE FUN CONCATENATE ATTRIBUTED STRINGS extension NSAttributedString { static func

    +(lhs: NSAttributedString, rhs: NSAttributedString) -> NSAttributedString { let result = NSMutableAttributedString(attributedString: lhs) result.append(rhs) return result.copy() as! NSAttributedString } } extension Array where Element: NSAttributedString { func joined() -> NSAttributedString { return reduce(NSAttributedString(), +) } } let strings: [NSAttributedString] let concatenated = strings.joined()