diff --git a/api-guidelines/Sources/APIGuidelines.docc/Documentation.md b/api-guidelines/Sources/APIGuidelines.docc/Documentation.md index 0461ddc50..85d3f077b 100644 --- a/api-guidelines/Sources/APIGuidelines.docc/Documentation.md +++ b/api-guidelines/Sources/APIGuidelines.docc/Documentation.md @@ -1,5 +1,91 @@ # ``APIGuidelines`` +Design Swift APIs that prioritize clarity at the point of use through effective naming and consistent conventions. + @Metadata { @DisplayName("API Guidelines") -} \ No newline at end of file +} + +## Overview + +Delivering a clear, consistent developer experience when writing Swift code is largely defined by the names and idioms that appear in APIs. +These design guidelines explain how to make sure that your code feels like a part of the larger Swift ecosystem. + +* **Clarity at the point of use** is your most important goal. + Entities such as 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, so don't put it off. + +> Warning: +> If you are having trouble describing your API's +> functionality in simple terms, **you may have designed the wrong API.** + +## Topics + +### Fundamentals + +- + +### Naming — Promote Clear Usage + +- +- +- +- + +### Naming — Strive for Fluent Usage + +- +- +- +- +- +- +- +- + +### Naming — Use Terminology Well + +- +- +- +- + +### Conventions — General + +- +- +- +- + +### Conventions — Parameters + +- +- +- +- + +### Conventions — Argument Labels + +- +- +- +- +- + +### Special Instructions + +- +- diff --git a/api-guidelines/Sources/APIGuidelines.docc/avoid-abbreviations.md b/api-guidelines/Sources/APIGuidelines.docc/avoid-abbreviations.md new file mode 100644 index 000000000..c5b86acae --- /dev/null +++ b/api-guidelines/Sources/APIGuidelines.docc/avoid-abbreviations.md @@ -0,0 +1,10 @@ +# Avoid abbreviations + +Avoid abbreviations. + +## Overview + +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. diff --git a/api-guidelines/Sources/APIGuidelines.docc/avoid-ambiguity.md b/api-guidelines/Sources/APIGuidelines.docc/avoid-ambiguity.md new file mode 100644 index 000000000..fbb88d6ac --- /dev/null +++ b/api-guidelines/Sources/APIGuidelines.docc/avoid-ambiguity.md @@ -0,0 +1,24 @@ +# Include words to avoid ambiguity + +Include all the words needed to avoid ambiguity for a person reading code where the name is used. + +## Overview + +✅ For example, consider a method that removes the element at a +given position within a collection. + +```swift +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 `x` to indicate the +position of the element to remove. + +```swift +employees.remove(x) // unclear: are we removing x? +``` diff --git a/api-guidelines/Sources/APIGuidelines.docc/avoid-obscure-terms.md b/api-guidelines/Sources/APIGuidelines.docc/avoid-obscure-terms.md new file mode 100644 index 000000000..aebfe5f54 --- /dev/null +++ b/api-guidelines/Sources/APIGuidelines.docc/avoid-obscure-terms.md @@ -0,0 +1,13 @@ +# Avoid obscure terms + +Avoid obscure terms if a more common word conveys meaning just as well. + +## Overview + +**Term of Art** +: *noun* - a word or phrase that has a precise, specialized meaning + within a particular field or profession. + +Don't say "epidermis" if "skin" will serve your purpose. +Terms of art are an essential communication tool, but should only be +used to capture crucial meaning that would otherwise be lost. diff --git a/api-guidelines/Sources/APIGuidelines.docc/boolean-assertions.md b/api-guidelines/Sources/APIGuidelines.docc/boolean-assertions.md new file mode 100644 index 000000000..a4c2f02f8 --- /dev/null +++ b/api-guidelines/Sources/APIGuidelines.docc/boolean-assertions.md @@ -0,0 +1,7 @@ +# Boolean methods read as assertions + +Uses of Boolean methods and properties should read as assertions about the receiver when the use is nonmutating. + +## Overview + +For example: `x.isEmpty`, `line1.intersects(line2)`. diff --git a/api-guidelines/Sources/APIGuidelines.docc/case-conventions.md b/api-guidelines/Sources/APIGuidelines.docc/case-conventions.md new file mode 100644 index 000000000..70e52e28b --- /dev/null +++ b/api-guidelines/Sources/APIGuidelines.docc/case-conventions.md @@ -0,0 +1,24 @@ +# Follow case conventions + +Follow case conventions. + +## Overview + +Names of types and protocols are `UpperCamelCase`. Everything else is `lowerCamelCase`. + +[Acronyms and initialisms](https://en.wikipedia.org/wiki/Acronym) +that commonly appear as all upper case in American English should be +uniformly up- or down-cased according to case conventions: + +```swift +var **utf8**Bytes: [**UTF8**.CodeUnit] +var isRepresentableAs**ASCII** = true +var user**SMTP**Server: Secure**SMTP**Server +``` + +Other acronyms should be treated as ordinary words: + +```swift +var **radar**Detector: **Radar**Scanner +var enjoys**Scuba**Diving = true +``` diff --git a/api-guidelines/Sources/APIGuidelines.docc/computed-property-complexity.md b/api-guidelines/Sources/APIGuidelines.docc/computed-property-complexity.md new file mode 100644 index 000000000..3a24a4826 --- /dev/null +++ b/api-guidelines/Sources/APIGuidelines.docc/computed-property-complexity.md @@ -0,0 +1,10 @@ +# Document computed property complexity + +Document the complexity of any computed property that is not O(1). + +## Overview + +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. diff --git a/api-guidelines/Sources/APIGuidelines.docc/default-parameter-order.md b/api-guidelines/Sources/APIGuidelines.docc/default-parameter-order.md new file mode 100644 index 000000000..012ad517c --- /dev/null +++ b/api-guidelines/Sources/APIGuidelines.docc/default-parameter-order.md @@ -0,0 +1,7 @@ +# Locate parameters with defaults toward the end + +Prefer to locate parameters with defaults toward the end of the parameter list. + +## Overview + +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. diff --git a/api-guidelines/Sources/APIGuidelines.docc/defaulted-parameters.md b/api-guidelines/Sources/APIGuidelines.docc/defaulted-parameters.md new file mode 100644 index 000000000..660d1a787 --- /dev/null +++ b/api-guidelines/Sources/APIGuidelines.docc/defaulted-parameters.md @@ -0,0 +1,62 @@ +# Take advantage of defaulted parameters + +Take advantage of defaulted parameters when it simplifies common uses. + +## Overview + +Any parameter with a single commonly-used value is a candidate for a default. Default arguments improve readability by hiding irrelevant information. + +⛔ Passing every option explicitly buries the one argument that matters among values callers rarely change: + +```swift +let order = lastName.compare( + royalFamilyName**, options: [], range: nil, locale: nil**) +``` + +✅ can become the much simpler: + +```swift +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. + +✅ A single method with defaults replaces the whole family below, at a much lower cognitive cost: + +```swift +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: + +```swift +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. diff --git a/api-guidelines/Sources/APIGuidelines.docc/documentation-comments.md b/api-guidelines/Sources/APIGuidelines.docc/documentation-comments.md new file mode 100644 index 000000000..f5a8da067 --- /dev/null +++ b/api-guidelines/Sources/APIGuidelines.docc/documentation-comments.md @@ -0,0 +1,115 @@ +# Write a documentation comment for every declaration + +Insights gained by writing documentation can have a profound impact on your design, so don't put it off. + +## Overview + +* Use Swift's [dialect of Markdown](https://docs.swift.org/latest/documentation/docc/formatting-your-documentation-content). + +* Begin with a summary that describes the entity being declared. + Often, an API can be completely understood from its declaration and + its summary. + + ```swift + /// **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: + + ```swift + /// **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*: + + ```swift + /// **Accesses** the `index`th element. + subscript(index: Int) -> Element { get set } + ``` + + * Describe what an initializer *creates*: + + ```swift + /// **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*. + + ```swift + /// **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. + + The following example shows the summary, additional discussion, + parameters section, and symbol commands: + + ```swift + /// 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](https://docs.swift.org/latest/documentation/docc/writing-symbol-documentation-in-your-source-files) + elements to add information beyond the summary, whenever + appropriate. + + * Know and use recognized bullet items with + [symbol command syntax](https://docs.swift.org/latest/documentation/docc/writing-symbol-documentation-in-your-source-files#Describe-the-Parameters-of-a-Method). Popular development + tools such as Xcode give special treatment to bullet items that + start with the following keywords: + + | [Attention](https://docs.swift.org/latest/documentation/docc/other-formatting-options#Add-Notes-and-Other-Asides) | [Author](https://docs.swift.org/latest/documentation/docc/other-formatting-options#Add-Notes-and-Other-Asides) | [Authors](https://docs.swift.org/latest/documentation/docc/other-formatting-options#Add-Notes-and-Other-Asides) | [Bug](https://docs.swift.org/latest/documentation/docc/other-formatting-options#Add-Notes-and-Other-Asides) | + | [Complexity](https://docs.swift.org/latest/documentation/docc/other-formatting-options#Add-Notes-and-Other-Asides) | [Copyright](https://docs.swift.org/latest/documentation/docc/other-formatting-options#Add-Notes-and-Other-Asides) | [Date](https://docs.swift.org/latest/documentation/docc/other-formatting-options#Add-Notes-and-Other-Asides) | [Experiment](https://docs.swift.org/latest/documentation/docc/other-formatting-options#Add-Notes-and-Other-Asides) | + | [Important](https://docs.swift.org/latest/documentation/docc/other-formatting-options#Add-Notes-and-Other-Asides) | [Invariant](https://docs.swift.org/latest/documentation/docc/other-formatting-options#Add-Notes-and-Other-Asides) | [Note](https://docs.swift.org/latest/documentation/docc/other-formatting-options#Add-Notes-and-Other-Asides) | [Parameter](https://docs.swift.org/latest/documentation/docc/writing-symbol-documentation-in-your-source-files#Describe-the-Parameters-of-a-Method) | + | [Parameters](https://docs.swift.org/latest/documentation/docc/writing-symbol-documentation-in-your-source-files#Describe-the-Parameters-of-a-Method) | [Postcondition](https://docs.swift.org/latest/documentation/docc/other-formatting-options#Add-Notes-and-Other-Asides) | [Precondition](https://docs.swift.org/latest/documentation/docc/other-formatting-options#Add-Notes-and-Other-Asides) | [Remark](https://docs.swift.org/latest/documentation/docc/other-formatting-options#Add-Notes-and-Other-Asides) | + | [Requires](https://docs.swift.org/latest/documentation/docc/other-formatting-options#Add-Notes-and-Other-Asides) | [Returns](https://docs.swift.org/latest/documentation/docc/writing-symbol-documentation-in-your-source-files#Describe-the-Return-Value-of-a-Method) | [SeeAlso](https://docs.swift.org/latest/documentation/docc/other-formatting-options#Add-Notes-and-Other-Asides) | [Since](https://docs.swift.org/latest/documentation/docc/other-formatting-options#Add-Notes-and-Other-Asides) | + | [Throws](https://docs.swift.org/latest/documentation/docc/writing-symbol-documentation-in-your-source-files#Describe-the-Thrown-Errors-of-a-Method) | [ToDo](https://docs.swift.org/latest/documentation/docc/other-formatting-options#Add-Notes-and-Other-Asides) | [Version](https://docs.swift.org/latest/documentation/docc/other-formatting-options#Add-Notes-and-Other-Asides) | [Warning](https://docs.swift.org/latest/documentation/docc/other-formatting-options#Add-Notes-and-Other-Asides) | diff --git a/api-guidelines/Sources/APIGuidelines.docc/embrace-precedent.md b/api-guidelines/Sources/APIGuidelines.docc/embrace-precedent.md new file mode 100644 index 000000000..427e6b3b8 --- /dev/null +++ b/api-guidelines/Sources/APIGuidelines.docc/embrace-precedent.md @@ -0,0 +1,24 @@ +# Embrace precedent + +Embrace precedent. + +## Overview + +Don't optimize terms for the total beginner at the expense of conformance to existing culture. + +It is better to name a contiguous data structure `Array` than to +use a simplified term such as `List`, even though a beginner +might grasp the meaning of `List` more easily. Arrays are +fundamental in modern computing, so every programmer knows—or +will soon learn—what an array is. Use a term that most +programmers are familiar with, and their web searches and +questions will be rewarded. + +Within a particular programming *domain*, such as mathematics, a +widely precedented term such as `sin(x)` is preferable to an +explanatory phrase such as +`verticalPositionOnUnitCircleAtOriginOfEndOfRadiusWithAngle(x)`. +Note that in this case, precedent outweighs the guideline to +avoid abbreviations: although the complete word is `sine`, +"sin(*x*)" has been in common use among programmers for decades, +and among mathematicians for centuries. diff --git a/api-guidelines/Sources/APIGuidelines.docc/established-meaning.md b/api-guidelines/Sources/APIGuidelines.docc/established-meaning.md new file mode 100644 index 000000000..6850f2a0a --- /dev/null +++ b/api-guidelines/Sources/APIGuidelines.docc/established-meaning.md @@ -0,0 +1,17 @@ +# Stick to the established meaning + +Stick to the established meaning if you do use a term of art. + +## Overview + +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. diff --git a/api-guidelines/Sources/APIGuidelines.docc/factory-methods.md b/api-guidelines/Sources/APIGuidelines.docc/factory-methods.md new file mode 100644 index 000000000..723b1ddfa --- /dev/null +++ b/api-guidelines/Sources/APIGuidelines.docc/factory-methods.md @@ -0,0 +1,7 @@ +# Begin factory method names with "make" + +Begin names of factory methods with "`make`." + +## Overview + +For example: `x.makeIterator()`. diff --git a/api-guidelines/Sources/APIGuidelines.docc/grammatical-phrase-arguments.md b/api-guidelines/Sources/APIGuidelines.docc/grammatical-phrase-arguments.md new file mode 100644 index 000000000..058a735da --- /dev/null +++ b/api-guidelines/Sources/APIGuidelines.docc/grammatical-phrase-arguments.md @@ -0,0 +1,31 @@ +# Omit label when first argument forms a grammatical phrase + +Otherwise, if the first argument forms part of a grammatical phrase, omit its label, appending any preceding words to the base name. + +## Overview + +For example: `x.addSubview(y)`. + +This guideline implies that if the first argument *doesn't* form +part of a grammatical phrase, it should have a label. + +✅ Each first argument reads as part of a grammatical phrase with the base name, so it can safely omit its label: + +```swift +view.dismiss(**animated:** false) +let text = words.split(**maxSplits:** 12) +let studentsByName = students.sorted(**isOrderedBefore:** Student.namePrecedes) +``` + +⛔ Note that it's important that the phrase convey the correct meaning. +The following would be grammatical but would express the wrong +thing. + +```swift +view.dismiss(false) // Don't dismiss? Dismiss a Bool? +words.split(12) // Split the number 12? +``` + +Note also that arguments with default values can be omitted, and +in that case do not form part of a grammatical phrase, so they +should always have labels. diff --git a/api-guidelines/Sources/APIGuidelines.docc/grammatical-phrases.md b/api-guidelines/Sources/APIGuidelines.docc/grammatical-phrases.md new file mode 100644 index 000000000..87b32429e --- /dev/null +++ b/api-guidelines/Sources/APIGuidelines.docc/grammatical-phrases.md @@ -0,0 +1,30 @@ +# Prefer grammatical English phrases + +Prefer method and function names that make use sites form grammatical English phrases. + +## Overview + +✅ Each of these reads as a grammatical English phrase at the call site: + +```swift +x.insert(y, at: z) // "x, insert y at z" +x.subviews(havingColor: y) // "x's subviews having color y" +x.capitalizingNouns() // "x, capitalizing nouns" +``` + +⛔ Each of these drops the words that make the phrase grammatical, so the call no longer reads as English: + +```swift +x.insert(y, position: z) +x.subviews(color: y) +x.nounCapitalize() +``` + +It is acceptable for fluency to degrade after the first argument or +two when those arguments are not central to the call's meaning: + +```swift +AudioUnit.instantiate( + with: description, + **options: [.inProcess], completionHandler: stopProgressBar**) +``` diff --git a/api-guidelines/Sources/APIGuidelines.docc/indistinguishable-arguments.md b/api-guidelines/Sources/APIGuidelines.docc/indistinguishable-arguments.md new file mode 100644 index 000000000..cc1b760b3 --- /dev/null +++ b/api-guidelines/Sources/APIGuidelines.docc/indistinguishable-arguments.md @@ -0,0 +1,14 @@ +# Omit labels for indistinguishable arguments + +Omit all labels when arguments can't be usefully distinguished. + +## Overview + +For example: `min(number1, number2)`, `zip(sequence1, sequence2)`. + +Argument labels appear at a function or method's point of use: + +```swift +func move(**from** start: Point, **to** end: Point) +x.move(**from:** x, **to:** y) +``` diff --git a/api-guidelines/Sources/APIGuidelines.docc/initializer-first-arguments.md b/api-guidelines/Sources/APIGuidelines.docc/initializer-first-arguments.md new file mode 100644 index 000000000..cc64964d7 --- /dev/null +++ b/api-guidelines/Sources/APIGuidelines.docc/initializer-first-arguments.md @@ -0,0 +1,36 @@ +# Initializer and factory method first arguments + +The first argument to initializer and factory method calls should not form a phrase starting with the base name. + +## Overview + +For example: `x.makeWidget(cogCount: 47)`. + +For more on factory methods, see [Factory method pattern](https://en.wikipedia.org/wiki/Factory_method_pattern). + +✅ For example, the first arguments to these calls do not read as part of the same +phrase as the base name: + +```swift +let foreground = **Color**(red: 32, green: 64, blue: 128) +let newPart = **factory.makeWidget**(gears: 42, spindles: 14) +let ref = **Link**(target: destination) +``` + +⛔ In the following, the API author has tried to create grammatical +continuity with the first argument. + +```swift +let foreground = **Color(havingRGBValuesRed: 32, green: 64, andBlue: 128)** +let newPart = **factory.makeWidget(havingGearCount: 42, andSpindleCount: 14)** +let ref = **Link(to: destination)** +``` + +In practice, this guideline along with those for +argument labels (see ) means the first argument will +have a label unless the call is performing a +[value preserving type conversion](). + +```swift +let rgbForeground = RGBColor(cmykForeground) +``` diff --git a/api-guidelines/Sources/APIGuidelines.docc/label-other-arguments.md b/api-guidelines/Sources/APIGuidelines.docc/label-other-arguments.md new file mode 100644 index 000000000..5a868eb28 --- /dev/null +++ b/api-guidelines/Sources/APIGuidelines.docc/label-other-arguments.md @@ -0,0 +1,3 @@ +# Label all other arguments + +Label all other arguments. diff --git a/api-guidelines/Sources/APIGuidelines.docc/methods-over-free-functions.md b/api-guidelines/Sources/APIGuidelines.docc/methods-over-free-functions.md new file mode 100644 index 000000000..a51ddd4cc --- /dev/null +++ b/api-guidelines/Sources/APIGuidelines.docc/methods-over-free-functions.md @@ -0,0 +1,25 @@ +# Prefer methods and properties to free functions + +Prefer methods and properties to free functions. + +## Overview + +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) + ``` diff --git a/api-guidelines/Sources/APIGuidelines.docc/name-according-to-roles.md b/api-guidelines/Sources/APIGuidelines.docc/name-according-to-roles.md new file mode 100644 index 000000000..1585e48f2 --- /dev/null +++ b/api-guidelines/Sources/APIGuidelines.docc/name-according-to-roles.md @@ -0,0 +1,40 @@ +# Name according to roles + +Name variables, parameters, and associated types according to their roles, rather than their type constraints. + +## Overview + +⛔ Repurposing a type name fails to optimize clarity and expressivity: + +```swift +var **string** = "Hello" +protocol ViewController { + associatedtype **View**Type : View +} +class ProductionLine { + func restock(from **widgetFactory**: WidgetFactory) +} +``` + +✅ Instead, strive to choose a name that expresses the entity's *role*: + +```swift +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 *is* the role, avoid collision by appending +`Protocol` to the protocol name: + +```swift +protocol Sequence { + associatedtype Iterator : Iterator**Protocol** +} +protocol Iterator**Protocol** { ... } +``` diff --git a/api-guidelines/Sources/APIGuidelines.docc/noun-names.md b/api-guidelines/Sources/APIGuidelines.docc/noun-names.md new file mode 100644 index 000000000..21d3c4c29 --- /dev/null +++ b/api-guidelines/Sources/APIGuidelines.docc/noun-names.md @@ -0,0 +1,3 @@ +# Other names read as nouns + +The names of other types, properties, variables, and constants should read as nouns. diff --git a/api-guidelines/Sources/APIGuidelines.docc/omit-needless-words.md b/api-guidelines/Sources/APIGuidelines.docc/omit-needless-words.md new file mode 100644 index 000000000..f2e1f7987 --- /dev/null +++ b/api-guidelines/Sources/APIGuidelines.docc/omit-needless-words.md @@ -0,0 +1,25 @@ +# Omit needless words + +Every word in a name should convey salient information at the use site. + +## Overview + +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. + +⛔ In this case, the word `Element` adds nothing salient at the call site: + +```swift +public mutating func removeElement(_ member: Element) -> Element? + +allViews.removeElement(cancelButton) +``` + +✅ This API would be better: + +```swift +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 for details. diff --git a/api-guidelines/Sources/APIGuidelines.docc/parameter-names-for-documentation.md b/api-guidelines/Sources/APIGuidelines.docc/parameter-names-for-documentation.md new file mode 100644 index 000000000..f747db622 --- /dev/null +++ b/api-guidelines/Sources/APIGuidelines.docc/parameter-names-for-documentation.md @@ -0,0 +1,38 @@ +# Choose parameter names to serve documentation + +Choose parameter names to serve documentation. + +## Overview + +Even though parameter names do not appear at a function or method's point of use, they play an important explanatory role. + +Parameter names appear in a function or method's declaration: + +```swift +func move(from **start**: Point, to **end**: Point) +``` + +Choose these names to make documentation easy to read. + +✅ For example, these names make documentation read naturally: + +```swift +/// 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: + +```swift +/// 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]) +``` diff --git a/api-guidelines/Sources/APIGuidelines.docc/prefer-fileid.md b/api-guidelines/Sources/APIGuidelines.docc/prefer-fileid.md new file mode 100644 index 000000000..304435652 --- /dev/null +++ b/api-guidelines/Sources/APIGuidelines.docc/prefer-fileid.md @@ -0,0 +1,10 @@ +# Prefer #fileID in production + +If your API will run in production, prefer `#fileID` over alternatives. + +## Overview + +`#fileID` saves space and protects developers' privacy. Use `#filePath` in +APIs that are never run by end users (such as test helpers and scripts) if +the full path will simplify development workflows or be used for file I/O. +Use `#file` to preserve source compatibility with Swift 5.2 or earlier. diff --git a/api-guidelines/Sources/APIGuidelines.docc/prepositional-phrase-labels.md b/api-guidelines/Sources/APIGuidelines.docc/prepositional-phrase-labels.md new file mode 100644 index 000000000..d9c39fcb3 --- /dev/null +++ b/api-guidelines/Sources/APIGuidelines.docc/prepositional-phrase-labels.md @@ -0,0 +1,27 @@ +# Label prepositional phrase arguments + +When the first argument forms part of a prepositional phrase, give it an argument label. + +## Overview + +The argument label should normally begin at the preposition. For example: `x.removeBoxes(havingLength: 12)`. + +For more on prepositional phrases, see [prepositional phrase](https://en.wikipedia.org/wiki/Adpositional_phrase#Prepositional_phrases). For more on prepositions, see [preposition](https://en.wikipedia.org/wiki/Preposition). + +An exception arises when the first two arguments represent parts of +a single abstraction. + +⛔ Splitting the label across both arguments breaks the single abstraction into two disconnected phrases: + +```swift +a.move(**toX:** b, **y:** c) +a.fade(**fromRed:** b, **green:** c, **blue:** d) +``` + +✅ In such cases, begin the argument label *after* the preposition, to +keep the abstraction clear. + +```swift +a.moveTo(**x:** b, **y:** c) +a.fadeFrom(**red:** b, **green:** c, **blue:** d) +``` diff --git a/api-guidelines/Sources/APIGuidelines.docc/protocol-capability-suffixes.md b/api-guidelines/Sources/APIGuidelines.docc/protocol-capability-suffixes.md new file mode 100644 index 000000000..24868b55b --- /dev/null +++ b/api-guidelines/Sources/APIGuidelines.docc/protocol-capability-suffixes.md @@ -0,0 +1,7 @@ +# Protocols describing capability use suffixes + +Protocols that describe a *capability* should be named using the suffixes `able`, `ible`, or `ing`. + +## Overview + +For example: `Equatable`, `ProgressReporting`. diff --git a/api-guidelines/Sources/APIGuidelines.docc/protocol-nouns.md b/api-guidelines/Sources/APIGuidelines.docc/protocol-nouns.md new file mode 100644 index 000000000..5b0953931 --- /dev/null +++ b/api-guidelines/Sources/APIGuidelines.docc/protocol-nouns.md @@ -0,0 +1,7 @@ +# Protocols describing "what" read as nouns + +Protocols that describe *what something is* should read as nouns. + +## Overview + +For example: `Collection`. diff --git a/api-guidelines/Sources/APIGuidelines.docc/shared-base-names.md b/api-guidelines/Sources/APIGuidelines.docc/shared-base-names.md new file mode 100644 index 000000000..438304bb2 --- /dev/null +++ b/api-guidelines/Sources/APIGuidelines.docc/shared-base-names.md @@ -0,0 +1,68 @@ +# Methods can share a base name + +Methods can share a base name when they share the same basic meaning or when they operate in distinct domains. + +## Overview + + + +✅ For example, the following is encouraged, since the methods do essentially +the same things: + +```swift +extension Shape { + /// Returns `true` if `other` is within the area of `self`; + /// otherwise, `false`. + func **contains**(_ other: **Point**) -> Bool { ... } + + /// Returns `true` if `other` is entirely within the area of `self`; + /// otherwise, `false`. + func **contains**(_ other: **Shape**) -> Bool { ... } + + /// Returns `true` if `other` is within the area of `self`; + /// otherwise, `false`. + func **contains**(_ other: **LineSegment**) -> Bool { ... } +} +``` + +✅ And since geometric types and collections are separate domains, +this is also fine in the same program: + +```swift +extension Collection where Element : Equatable { + /// Returns `true` if `self` contains an element equal to + /// `sought`; otherwise, `false`. + func **contains**(_ sought: Element) -> Bool { ... } +} +``` + +⛔ However, these `index` methods have different semantics, and should +have been named differently: + +```swift +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. + +```swift +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? { ... } +} +``` diff --git a/api-guidelines/Sources/APIGuidelines.docc/side-effect-naming.md b/api-guidelines/Sources/APIGuidelines.docc/side-effect-naming.md new file mode 100644 index 000000000..67eec6f1d --- /dev/null +++ b/api-guidelines/Sources/APIGuidelines.docc/side-effect-naming.md @@ -0,0 +1,65 @@ +# Name according to side-effects + +Name functions and methods according to their side-effects. + +## Overview + +* Those without side-effects should read as noun phrases, + e.g. `x.distance(to: y)`, `i.successor()`. + +* Those with side-effects should read as imperative verb phrases, + e.g., `print(x)`, `x.sort()`, `x.append(y)`. + +* Name mutating/nonmutating method pairs consistently. + A mutating method will often have a nonmutating variant with + similar semantics, but that returns a new value rather than + updating an instance in-place. + + * When the operation is naturally described by a verb, use the + verb's imperative for the mutating method and apply the "ed" or + "ing" suffix to name its nonmutating counterpart. + + |Mutating|Nonmutating| + |-|-| + |`x.sort()`|`z = x.sorted()`| + |`x.append(y)`|`z = x.appending(y)`| + + * Prefer to name the nonmutating variant using the verb's past + [participle](https://en.wikipedia.org/wiki/Participle) (usually + appending "ed"): + + ```swift + /// Reverses `self` in-place. + mutating func reverse() + + /// Returns a reversed copy of `self`. + func revers**ed**() -> Self + ... + x.reverse() + let y = x.reversed() + ``` + + * When adding "ed" is not grammatical because the verb has a direct + object, name the nonmutating variant using the verb's present + [participle](https://en.wikipedia.org/wiki/Participle), by + appending "ing." + + ```swift + /// Strips all the newlines from `self` + mutating func stripNewlines() + + /// Returns a copy of `self` with all the newlines stripped. + func strip**ping**Newlines() -> String + ... + s.stripNewlines() + let oneLine = t.strippingNewlines() + ``` + + * When the operation is naturally described by a noun, use the + noun for the nonmutating method and apply the "form" prefix to + name its mutating counterpart. + + |Nonmutating|Mutating| + |-|-| + |`x = y.union(z)`|`y.formUnion(z)`| + |`j = c.successor(i)`|`c.formSuccessor(&i)`| diff --git a/api-guidelines/Sources/APIGuidelines.docc/tuple-and-closure-labels.md b/api-guidelines/Sources/APIGuidelines.docc/tuple-and-closure-labels.md new file mode 100644 index 000000000..11ab09f66 --- /dev/null +++ b/api-guidelines/Sources/APIGuidelines.docc/tuple-and-closure-labels.md @@ -0,0 +1,32 @@ +# Label tuple members and name closure parameters + +Label tuple members and name closure parameters where they appear in your API. + +## Overview + +These names have +explanatory power, can be referenced from documentation comments, +and provide expressive access to tuple members. + +```swift +/// Ensure that we hold uniquely-referenced storage for at least +/// `requestedCapacity` elements. +/// +/// If more storage is needed, `allocate` is called with +/// **`byteCount`** equal to the number of maximally-aligned +/// bytes to allocate. +/// +/// - Returns: +/// - **reallocated**: `true` if a new block of memory +/// was allocated; otherwise, `false`. +/// - **capacityChanged**: `true` if `capacity` was updated; +/// otherwise, `false`. +mutating func ensureUniqueStorage( + minimumCapacity requestedCapacity: Int, + allocate: (_ **byteCount**: Int) -> UnsafePointer +) -> (**reallocated:** Bool, **capacityChanged:** Bool) +``` + +Names used for closure parameters should be chosen like +parameter names (see ) for top-level functions. Labels for +closure arguments that appear at the call site are not supported. diff --git a/api-guidelines/Sources/APIGuidelines.docc/unconstrained-polymorphism.md b/api-guidelines/Sources/APIGuidelines.docc/unconstrained-polymorphism.md new file mode 100644 index 000000000..33a5aaa37 --- /dev/null +++ b/api-guidelines/Sources/APIGuidelines.docc/unconstrained-polymorphism.md @@ -0,0 +1,52 @@ +# Take extra care with unconstrained polymorphism + +Take extra care with unconstrained polymorphism to avoid ambiguities in overload sets. + +## Overview + +Unconstrained polymorphism includes types such as `Any`, `AnyObject`, and unconstrained generic parameters. + +⛔ For example, consider this overload set: + +```swift +struct Array { + /// Inserts `newElement` at `self.endIndex`. + public mutating func append(_ newElement: Element) + + /// Inserts the contents of `newElements`, in order, at + /// `self.endIndex`. + public mutating func append(_ newElements: S) + where S.Generator.Element == Element +} +``` + +These methods form a semantic family, and the argument types +appear at first to be sharply distinct. However, when `Element` +is `Any`, a single element can have the same type as a sequence of +elements. + +⛔ Because `Element` can be `Any`, this call is ambiguous between the two overloads: + +```swift +var values: [Any] = [1, "a"] +values.append([2, 3, 4]) // [1, "a", [2, 3, 4]] or [1, "a", 2, 3, 4]? +``` + +✅ To eliminate the ambiguity, name the second overload more +explicitly. + +```swift +struct Array { + /// Inserts `newElement` at `self.endIndex`. + public mutating func append(_ newElement: Element) + + /// Inserts the contents of `newElements`, in order, at + /// `self.endIndex`. + public mutating func append(**contentsOf** newElements: S) + where S.Generator.Element == Element +} +``` + +Notice how the new name better matches the documentation comment. +In this case, the act of writing the documentation comment +actually brought the issue to the API author's attention. diff --git a/api-guidelines/Sources/APIGuidelines.docc/value-preserving-conversions.md b/api-guidelines/Sources/APIGuidelines.docc/value-preserving-conversions.md new file mode 100644 index 000000000..57a784c97 --- /dev/null +++ b/api-guidelines/Sources/APIGuidelines.docc/value-preserving-conversions.md @@ -0,0 +1,50 @@ +# Omit first label for value-preserving type conversions + +In initializers that perform value preserving type conversions, omit the first argument label. + +## Overview + +For example: `Int64(someUInt32)`. + +The first argument should always be the source of the conversion. + +``` +extension String { + // Convert `x` into its textual representation in the given radix + init(**_** x: BigInt, radix: Int = 10) // ← Note the initial underscore +} + +text = "The value is: " +text += **String(veryLargeNumber)** +text += " and in hexadecimal, it's" +text += **String(veryLargeNumber, radix: 16)** +``` + +In "narrowing" type conversions, though, a label that describes +the narrowing is recommended. + +```swift +extension UInt32 { + /// Creates an instance having the specified `value`. + init(**_** value: Int16) // ← Widening, so no label + /// Creates an instance having the lowest 32 bits of `source`. + init(**truncating** source: UInt64) + /// Creates an instance having the nearest representable + /// approximation of `valueToApproximate`. + init(**saturating** valueToApproximate: UInt64) +} +``` + +> A value preserving type conversion is a +> [monomorphism](https://en.wikipedia.org/wiki/Monomorphism), i.e. +> every difference in the +> value of the source results in a difference in the value of the +> result. +> For example, conversion from `Int8` to `Int64` is value +> preserving because every distinct `Int8` value is converted to a +> distinct `Int64` value. Conversion in the other direction, however, +> cannot be value preserving: `Int64` has more possible values than +> can be represented in an `Int8`. +> +> Note: the ability to retrieve the original value has no bearing +> on whether a conversion is value preserving. diff --git a/api-guidelines/Sources/APIGuidelines.docc/weak-type-information.md b/api-guidelines/Sources/APIGuidelines.docc/weak-type-information.md new file mode 100644 index 000000000..0a47b05f2 --- /dev/null +++ b/api-guidelines/Sources/APIGuidelines.docc/weak-type-information.md @@ -0,0 +1,26 @@ +# Compensate for weak type information + +Compensate for weak type information to clarify a parameter's role. + +## Overview + +Especially when a parameter type is `NSObject`, `Any`, `AnyObject`, +or a fundamental type such as `Int` or `String`, type information and +context at the point of use may not fully convey intent. In this +example, the declaration may be clear, but the use site is vague. + +⛔ Neither parameter name compensates for the weak `NSObject` and `String` types, so the call site reads as vague: + +```swift +func add(_ observer: NSObject, for keyPath: String) + +grid.add(self, for: graphics) // vague +``` + +✅ To restore clarity, precede each weakly typed parameter with a +noun describing its role: + +```swift +func add**Observer**(_ observer: NSObject, for**KeyPath** path: String) +grid.addObserver(self, forKeyPath: graphics) // clear +```