Mobile Coding Conventions

Fundamentals

  • DO NOT USE STORYBOARDS & XIB Files 
    • it slows XCode & your computer
    • Makes merging practically impossible
    • Hides the underlying implementation which in it’s turn makes one unaware of what’s really going on behind the scene
    • Makes code unreadable, specifically initialization part of the objects that depends on storyboards & xib files
    • Storyboards fail at runtime, not at compile time.
    • With large projects you get to the point where you cannot find beginnig and the end as well as connections between objects in storyboard get extremely convoluted.
    • Storyboards make code reviews hard or nearly impossible
    • Storyboards are not searchable
    • Storyboards have a higher risk of breaking backwards compatibility
    • etc…..
  • Clarity at the point of use is your most important goal.
  • Make sure Methods and Properties are declared only once but used repeatedly.
  • Design APIs to make those uses clear and concise.
  • When evaluating a design, reading a declaration is seldom sufficient; always examine a use case to make sure it looks clear in context.
  • Clarity is more important than brevity. Although Swift code can be compact, it is a non-goal to enable the smallest possible code with the fewest characters. Brevity in Swift code, where it occurs, is a side-effect of the strong type system and features that naturally reduce boilerplate.

Write a documentation comment for every declaration. Insights gained by writing documentation can have a profound impact on your design.

If you are having trouble describing your API’s functionality in simple terms, you may have designed the wrong API.
  • Use Swift’s dialect of Markdown.
  • Begin with a summary that describes the entity being declared. Often, an API can be completely understood from its declaration and its summary.
    /// Returns a "view" of `self` containing the same elements in
    /// reverse order.
    func reversed() -> ReverseCollection
    
    • Focus on the summary; it’s the most important part. Many excellent documentation comments consist of nothing more than a great summary.
    • Use a single sentence fragment if possible, ending with a period. Do not use a complete sentence.
    • Describe what a function or method does and what it returns, omitting null effects and Void returns:
      /// Inserts `newHead` at the beginning of `self`.
      mutating func prepend(_ newHead: Int)
      
      /// Returns a `List` containing `head` followed by the elements
      /// of `self`.
      func prepending(_ head: Element) -> List
      
      /// Removes and returns the first element of `self` if non-empty;
      /// returns `nil` otherwise.
      mutating func popFirst() -> Element?
      

      Note: in rare cases like popFirst above, the summary is formed of multiple sentence fragments separated by semicolons.

    • Describe what a subscript accesses:
      /// Accesses the `index`th element.
      subscript(index: Int) -> Element { get set }
      
    • Describe what an initializer creates:
      /// Creates an instance containing `n` repetitions of `x`.
      init(count n: Int, repeatedElement x: Element)
      
    • For all other declarations, describe what the declared entity is.
      /// A collection that supports equally efficient insertion/removal
      /// at any position.
      struct List {
      
        /// The element at the beginning of `self`, or `nil` if self is
        /// empty.
        var first: Element?
        ...
      
  • Optionally, continue with one or more paragraphs and bullet items. Paragraphs are separated by blank lines and use complete sentences.
    /// Writes the textual representation of each     Summary
    /// element of `items` to the standard output.
    ///                                               Blank line
    /// The textual representation for each item `x`  Additional discussion
    /// is generated by the expression `String(x)`.
    ///
    /// - Parameter separator: text to be printed    
    ///   between items.                             
    /// - Parameter terminator: text to be printed    Parameters section
    ///   at the end.                                
    ///                                              
    /// - Note: To print without a trailing          
    ///   newline, pass `terminator: ""`             
    ///                                               Symbol commands
    /// - SeeAlso: `CustomDebugStringConvertible`,   
    ///   `CustomStringConvertible`, `debugPrint`.   
    public func print(
      _ items: Any..., separator: String = " ", terminator: String = "\n")
  • Use recognized symbol documentation markup elements to add information beyond the summary, whenever appropriate.
  • Know and use recognized bullet items with symbol command syntaxXcode give special treatment to bullet items that start with the following keywords:

     


     

  • Naming

    Promote Clear Usage

    • Include all the words needed to avoid ambiguity for a person reading code where the name is used.

      For example, consider a method that removes the element at a given position within a collection.

      extension List {
        public mutating func remove(at position: Index) -> Element
      }
      employees.remove(at: x)
      

      If we were to omit the word at from the method signature, it could imply to the reader that the method searches for and removes an element equal to x, rather than using xto indicate the position of the element to remove.

      employees.remove(x) // unclear: are we removing x?
      
       

Omit needless words. Every word in a name should convey salient information at the use site.

More words may be needed to clarify intent or disambiguate meaning, but those that are redundant with information the reader already possesses should be omitted. In particular, omit words that merely repeat type information.

public mutating func removeElement(_ member: Element) -> Element?

allViews.removeElement(cancelButton)

In this case, the word Element adds nothing salient at the call site. This API would be better:

public mutating func remove(_ member: Element) -> Element?

allViews.remove(cancelButton) // clearer

Occasionally, repeating type information is necessary to avoid ambiguity, but in general it is better to use a word that describes a parameter’s role rather than its type. See the next item for details.


Name variables, parameters, and associated types according to their roles, rather than their type constraints.
var string = "Hello"
protocol ViewController {
  associatedtype ViewType : View
}
class ProductionLine {
  func restock(from widgetFactory: WidgetFactory)
}

Repurposing a type name in this way fails to optimize clarity and expressivity. Instead, strive to choose a name that expresses the entity’s role.


 

var greeting = "Hello"
protocol ViewController {
  associatedtype ContentView : View
}
class ProductionLine {
  func restock(from supplier: WidgetFactory)
}

If an associated type is so tightly bound to its protocol constraint that the protocol name isthe role, avoid collision by appending Type to the associated type name:

protocol Sequence {
  associatedtype IteratorType : Iterator
}


Stick to the established meaning if you do use a term of art.

The only reason to use a technical term rather than a more common word is that it precisely expresses something that would otherwise be ambiguous or unclear. Therefore, an API should use the term strictly in accordance with its accepted meaning.

  • Don’t surprise an expert: anyone already familiar with the term will be surprised and probably angered if we appear to have invented a new meaning for it.
  • Don’t confuse a beginner: anyone trying to learn the term is likely to do a web search and find its traditional meaning.
      • Avoid abbreviations. Abbreviations, especially non-standard ones, are effectively terms-of-art, because understanding depends on correctly translating them into their non-abbreviated forms.

        The intended meaning for any abbreviation you use should be easily found by a web search.


Conventions

General Conventions

  • Document the complexity of any computed property that is not O(1). People often assume that property access involves no significant computation, because they have stored properties as a mental model. Be sure to alert them when that assumption may be violated.
  • Prefer methods and properties to free functions. Free functions are used only in special cases:
    1. When there’s no obvious self:
      min(x, y, z)
      
    2. When the function is an unconstrained generic:
      print(x)
      
    3. When function syntax is part of the established domain notation:
      sin(x)
      
  • Case conventions.
    • Protocols are UpperCamelCase.
    • Everything else is lowerCamelCase.

      Acronyms and initialisms that commonly appear as all upper case in American English should be uniformly up- or down-cased according to case conventions:

      var utf8Bytes: [UTF8.CodeUnit]
      var isRepresentableAsASCII = true
      var userSMTPServer: SecureSMTPServer
      

      Other acronyms should be treated as ordinary words:

      var radarDetector: RadarScanner
      var enjoysScubaDiving = true
      
  • Methods can share a base name when they share the same basic meaning or when they operate in distinct domains.
    For example, the following is encouraged, since the methods do essentially the same things:

    extension Shape {
      /// Returns `true` iff `other` is within the area of `self`.
      func contains(_ other: Point) -> Bool { ... }
    
      /// Returns `true` iff `other` is entirely within the area of `self`.
      func contains(_ other: Shape) -> Bool { ... }
    
      /// Returns `true` iff `other` is within the area of `self`.
      func contains(_ other: LineSegment) -> Bool { ... }
    }
    

    And since geometric types and collections are separate domains, this is also fine in the same program:

    extension Collection where Element : Equatable {
      /// Returns `true` iff `self` contains an element equal to
      /// `sought`.
      func contains(_ sought: Element) -> Bool { ... }
    }
    

    However, these index methods have different semantics, and should have been named differently:

    extension Database {
      /// Rebuilds the database's search index
      func index() { ... }
    
      /// Returns the `n`th row in the given table.
      func index(_ n: Int, inTable: TableID) -> TableRow { ... }
    }
    

    Lastly, avoid “overloading on return type” because it causes ambiguities in the presence of type inference.

    extension Box {
      /// Returns the `Int` stored in `self`, if any, and
      /// `nil` otherwise.
      func value() -> Int? { ... }
    
      /// Returns the `String` stored in `self`, if any, and
      /// `nil` otherwise.
      func value() -> String? { ... }
    }

     

    Parameters

    func move(from start: Point, to end: Point)
    
    • Choose parameter names to serve documentation. Even though parameter names do not appear at a function or method’s point of use, they play an important explanatory role.

      Choose these names to make documentation easy to read. For example, these names make documentation read naturally:

      /// Return an `Array` containing the elements of `self`
      /// that satisfy `predicate`.
      func filter(_ predicate: (Element) -> Bool) -> [Generator.Element]
      
      /// Replace the given `subRange` of elements with `newElements`.
      mutating func replaceRange(_ subRange: Range, with newElements: [E])
      

      These, however, make the documentation awkward and ungrammatical:

      /// Return an `Array` containing the elements of `self`
      /// that satisfy `includedInResult`.
      func filter(_ includedInResult: (Element) -> Bool) -> [Generator.Element]
      
      /// Replace the range of elements indicated by `r` with
      /// the contents of `with`.
      mutating func replaceRange(_ r: Range, with: [E])
      
    • Take advantage of defaulted parameters when it simplifies common uses. Any parameter with a single commonly-used value is a candidate for a default.

      Default arguments improve readability by hiding irrelevant information. For example:

      let order = lastName.compare(
        royalFamilyName, options: [], range: nil, locale: nil)
      

      can become the much simpler:

      let order = lastName.compare(royalFamilyName)
      

      Default arguments are generally preferable to the use of method families, because they impose a lower cognitive burden on anyone trying to understand the API.

      extension String {
        /// ...description...
        public func compare(
           _ other: String, options: CompareOptions = [],
           range: Range? = nil, locale: Locale? = nil
        ) -> Ordering
      }
      

      The above may not be simple, but it is much simpler than:

      extension String {
        /// ...description 1...
        public func compare(_ other: String) -> Ordering
        /// ...description 2...
        public func compare(_ other: String, options: CompareOptions) -> Ordering
        /// ...description 3...
        public func compare(
           _ other: String, options: CompareOptions, range: Range) -> Ordering
        /// ...description 4...
        public func compare(
           _ other: String, options: StringCompareOptions,
           range: Range, locale: Locale) -> Ordering
      }
      

      Every member of a method family needs to be separately documented and understood by users. To decide among them, a user needs to understand all of them, and occasional surprising relationships—for example, foo(bar: nil) and foo() aren’t always synonyms—make this a tedious process of ferreting out minor differences in mostly identical documentation. Using a single method with defaults provides a vastly superior programmer experience.

    • Prefer to locate parameters with defaults toward the end of the parameter list. Parameters without defaults are usually more essential to the semantics of a method, and provide a stable initial pattern of use where methods are invoked.

Argument Labels

  • Omit all labels when arguments can’t be usefully distinguished, e.g. min(number1, number2)zip(sequence1, sequence2).
  • In initializers that perform value preserving type conversions, omit the first argument label, e.g. Int64(someUInt32)

    The first argument should always be the source of the conversion.

Advertisements