Spec-Zone.ru › Swift

Рекомендации по проектированию API

Для удобства использования в качестве быстрого справочника, подробности многих рекомендаций можно развернуть индивидуально. Подробности никогда не скрываются при печати этой страницы.

Оглавление

  • Введение
  • Основы
  • Именование
    • Повышение ясности использования
    • Стремитесь к плавному использованию
    • Правильное использование терминологии
  • Конвенции
    • Общие конвенции
    • Параметры
    • Метки аргументов
  • Специальные инструкции

Введение

Обеспечение ясного и согласованного опыта разработчика при написании кода Swift в значительной степени определяется именами и идиомами, которые появляются в API. Эти рекомендации по проектированию объясняют, как убедиться, что ваш код ощущается как часть более широкой экосистемы Swift.

Основы

  • Ясность в момент использования является вашей самой важной целью. Сущности, такие как методы и свойства, объявляются только один раз, но используются многократно. Проектируйте API таким образом, чтобы эти использования были ясными и лаконичными. При оценке дизайна редко достаточно просто прочитать объявление; всегда проверяйте пример использования, чтобы убедиться, что он выглядит понятно в контексте.

  • Ясность важнее краткости. Хотя код Swift может быть компактным, стремление к самому короткому коду с наименьшим количеством символов не является целью. Краткость в коде Swift, где она встречается, является побочным эффектом сильной системы типов и функций, которые естественным образом сокращают избыточность.

  • Напишите комментарий к документации для каждого объявления. Знания, полученные при написании документации, могут значительно повлиять на ваш дизайн, поэтому не откладывайте это.

    Если у вас возникают трудности с описанием функциональности вашего API простыми словами, возможно, вы спроектировали неправильный API.

    • Используйте диалект Markdown Swift.

    • Начните с краткого описания, которое описывает объявляемую сущность. Часто API можно полностью понять по его объявлению и краткому описанию.

      /// **Returns a "view" of `self` containing the same elements in**
      /// **reverse order.**
      func reversed() -> ReverseCollection<Self>
      
      • Сфокусируйтесь на кратком описании; это самая важная часть. Многие отличные комментарии к документации состоят лишь из хорошего краткого описания.

      • Используйте фрагмент предложения, если это возможно, заканчивающийся точкой. Не используйте полное предложение.

      • Опишите, что делает функция или метод и что она возвращает, опуская нулевые эффекты и Void возвращаемые значения:

        /// **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?
        

        Примечание: в редких случаях, подобных popFirst выше, краткое описание состоит из нескольких фрагментов предложений, разделенных точкой с запятой.

      • Опишите, к чему обращается индекс:

        /// **Accesses** the `index`th element.
        subscript(index: Int) -> Element { get set }
        
      • Опишите, что создаёт инициализатор:

        /// **Creates** an instance containing `n` repetitions of `x`.
        init(count n: Int, repeatedElement x: Element)
        
      • Для всех остальных объявлений опишите, что представляет собой объявляемая сущность.

        /// **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?
          ...
        
    • Дополнительно можно использовать один или несколько абзацев и пунктов списка. Абзацы разделены пустыми строками и используют полные предложения.

      /// Writes the textual representation of each    <span class="graphic">←</span><span class="commentary"> Summary</span>
      /// element of `items` to the standard output.
      ///                                              <span class="graphic">←</span><span class="commentary"> Blank line</span>
      /// The textual representation for each item `x` <span class="graphic">←</span><span class="commentary"> Additional discussion</span>
      /// is generated by the expression `String(x)`.
      ///
      /// - **Parameter separator**: text to be printed    <span class="graphic">⎫</span>
      ///   between items.                             <span class="graphic">⎟</span>
      /// - **Parameter terminator**: text to be printed   <span class="graphic">⎬</span><span class="commentary"> <a href="https://developer.apple.com/library/prerelease/mac/documentation/Xcode/Reference/xcode_markup_formatting_ref/SymbolDocumentation.html#//apple_ref/doc/uid/TP40016497-CH51-SW14">Parameters section</a></span>
      ///   at the end.                                <span class="graphic">⎟</span>
      ///                                              <span class="graphic">⎭</span>
      /// - **Note**: To print without a trailing          <span class="graphic">⎫</span>
      ///   newline, pass `terminator: ""`             <span class="graphic">⎟</span>
      ///                                              <span class="graphic">⎬</span><span class="commentary"> <a href="https://developer.apple.com/library/prerelease/mac/documentation/Xcode/Reference/xcode_markup_formatting_ref/SymbolDocumentation.html#//apple_ref/doc/uid/TP40016497-CH51-SW13">Symbol commands</a></span>
      /// - **SeeAlso**: `CustomDebugStringConvertible`,   <span class="graphic">⎟</span>
      ///   `CustomStringConvertible`, `debugPrint`.   <span class="graphic">⎭</span>
      public func print<Target: OutputStreamType>(
        _ items: Any..., separator: String = " ", terminator: String = "\n")
      
      • Используйте общепринятые элементы разметки разметки символов, чтобы добавить информацию, выходящую за рамки краткого описания, если это уместно.

      • Знайте и используйте общепринятые пункты списка с синтаксисом команд символов. Такие популярные инструменты разработки, как Xcode, предоставляют особый вид отображения для пунктов списка, начинающихся со следующих ключевых слов:

        Внимание Автор Авторы Ошибка
        Сложность Авторские права Дата Эксперимент
        Важно Инвариант Примечание Параметр
        Параметры Постословие Предварительное условие Примечание
        Требует Возвращает См. также С
        Выбрасывает ToDo Версия Предупреждение

Именование

Повышение ясности использования

  • Включайте все необходимые слова для избежания неоднозначности при чтении кода, где используется имя.

    Например, рассмотрите метод, удаляющий элемент по заданной позиции в коллекции.

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

    Если мы опустим слово at из сигнатуры метода, то читатель может предположить, что метод ищет и удаляет элемент, равный x, а не использует x для указания позиции удаляемого элемента.

    employees.remove(x) // unclear: are we removing x?
    
  • Опускайте ненужные слова. Каждое слово в имени должно содержать важную информацию в месте использования.

    Могут потребоваться дополнительные слова для уточнения намерения или устранения неоднозначности, но те, которые избыточны с точки зрения уже имеющейся у читателя информации, следует опустить. В частности, опускайте слова, которые просто повторяют информацию о типе.

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

    В этом случае слово Element не добавляет ничего существенного в месте вызова. Этот API был бы лучше:

    public mutating func remove(_ member: Element) -> Element?
    
    allViews.remove(cancelButton) // clearer
    

    Иногда повторение информации о типе необходимо для избежания неоднозначности, но в целом лучше использовать слово, описывающее роль параметра, а не его тип. Подробности см. в следующем пункте.

  • Называйте переменные, параметры и связанные типы в соответствии с их ролью, а не с ограничениями их типа.

    var **string** = "Hello"
    protocol ViewController {
      associatedtype **View**Type : View
    }
    class ProductionLine {
      func restock(from **widgetFactory**: WidgetFactory)
    }
    

    Переиспользование имени типа таким образом не оптимизирует ясность и выразительность. Вместо этого следует выбирать имя, отражающее роль сущности.

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

    Если связанный тип настолько тесно связан с ограничением протокола, что имя протокола является ролью, избегайте коллизий, добавляя Protocol к имени протокола:

    protocol Sequence {
      associatedtype Iterator : Iterator**Protocol**
    }
    protocol Iterator**Protocol** { ... }
    
  • Компенсируйте слабую информацию о типе для уточнения роли параметра.

    В особенности, когда тип параметра является NSObject, Any, AnyObject, или фундаментальным типом, таким как Int или String, информация о типе и контекст в месте использования могут не полностью передать намерение. В этом примере объявление может быть ясным, но место использования неясно.

    func add(_ observer: NSObject, for keyPath: String)
    
    grid.add(self, for: graphics) // vague
    

    Для восстановления ясности, предваряйте каждый слабо типизированный параметр существительным, описывающим его роль:

    func add**Observer**(_ observer: NSObject, for**KeyPath** path: String)
    grid.addObserver(self, forKeyPath: graphics) // clear
    

Стремитесь к плавному использованию

  • Предпочитайте имена методов и функций, которые в местах использования образуют грамматически корректные английские фразы.

    x.insert(y, at: z)          <span class="commentary">“x, insert y at z”</span>
    x.subviews(havingColor: y)  <span class="commentary">“x's subviews having color y”</span>
    x.capitalizingNouns()       <span class="commentary">“x, capitalizing nouns”</span>
    
    x.insert(y, position: z)
    x.subviews(color: y)
    x.nounCapitalize()
    

    Допустимо, что плавность может ухудшиться после первого-второго аргумента, если эти аргументы не являются центральными для смысла вызова:

    AudioUnit.instantiate(
      with: description,
      **options: [.inProcess], completionHandler: stopProgressBar**)
    
  • Начинайте имена методов-фабрик со слова “make”, например, x.makeIterator().

  • Первый аргумент вызова инициализаторов и методов-фабрик не должен образовывать фразу, начинающуюся с базового имени, например, x.makeWidget(cogCount: 47)

    Например, первые аргументы этих вызовов не читаются как часть одной фразы с базовым именем:

    let foreground = **Color**(red: 32, green: 64, blue: 128)
    let newPart = **factory.makeWidget**(gears: 42, spindles: 14)
    let ref = **Link**(target: destination)
    

    В следующем примере автор API пытался создать грамматическую связь с первым аргументом.

    let foreground = **Color(havingRGBValuesRed: 32, green: 64, andBlue: 128)**
    let newPart = **factory.makeWidget(havingGearCount: 42, andSpindleCount: 14)**
    let ref = **Link(to: destination)**
    

    На практике, данное руководство, наряду с рекомендациями по меткам аргументов, означает, что у первого аргумента будет метка, если вызов не выполняет преобразование типа с сохранением значения.

    let rgbForeground = RGBColor(cmykForeground)
    
  • Называйте функции и методы в соответствии с их побочными эффектами

    • Функции без побочных эффектов должны читаться как существительные, например, x.distance(to: y), i.successor().

    • Функции с побочными эффектами должны читаться как императивные глагольные фразы, например, print(x), x.sort(), x.append(y).

    • Называйте пары методов `mutating`/`nonmutating` согласованно. Метод `mutating` часто имеет аналогичный по семантике вариант `nonmutating`, но который возвращает новое значение вместо обновления экземпляра на месте.

      • Когда операция естественно описывается глаголом, используйте императивную форму глагола для метода `mutating` и добавьте суффикс «ed» или «ing» для названия его `nonmutating` варианта.

        Mutating Nonmutating
        x.sort() z = x.sorted()
        x.append(y) z = x.appending(y)
        • Предпочтительнее называть `nonmutating` вариант с использованием прошедшего причастия глагола (обычно добавляя «ed»):

          /// Reverses `self` in-place.
          mutating func reverse()
          
          /// Returns a reversed copy of `self`.
          func revers**ed**() -> Self
          ...
          x.reverse()
          let y = x.reversed()
          
        • Когда добавление «ed» не является грамматически корректным, потому что глагол имеет прямой объект, назовите `nonmutating` вариант, используя настоящее причастие глагола, добавив «ing».

          /// 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()
          
      • Когда операция естественно описывается существительным, используйте существительное для `nonmutating` метода и добавьте префикс «form» для названия его `mutating` варианта.

        Nonmutating Mutating
        x = y.union(z) y.formUnion(z)
        j = c.successor(i) c.formSuccessor(&i)
  • Использование булевых методов и свойств должно читаться как утверждения о получателе, когда использование является не изменяющим, например, x.isEmpty, line1.intersects(line2).

  • Протоколы, описывающие, что нечто представляет собой, должны читаться как существительные (например, Collection).

  • Протоколы, описывающие возможность, должны называться с использованием суффиксов able, ible или ing (например, Equatable, ProgressReporting).

  • Имена других типов, свойств, переменных и констант должны читаться как существительные.

Используйте терминологию правильно

Термин искусства
существительное - слово или фраза, имеющая точное, специализированное значение в конкретной области или профессии.
  • Избегайте неясных терминов, если более распространённое слово передает тот же смысл. Не говорите «эпидермис», если «кожа» подойдёт. Термины, используемые в специальных областях, являются важным инструментом коммуникации, но должны использоваться только для передачи критически важного смысла, который в противном случае будет утерян.

  • Придерживайтесь установленного значения, если используете термины, применяемые в специальных областях.

    Единственная причина использования технического термина вместо более распространённого слова заключается в том, что он точно выражает то, что в противном случае было бы неоднозначным или неясным. Следовательно, API должен использовать этот термин строго в соответствии с его общепринятым значением.

    • Не удивляйте эксперта: любой, уже знакомый с термином, удивится и, вероятно, рассердится, если у нас, похоже, есть новое значение для него.

    • Не сбивайте с толку новичка: любой, пытающийся изучить термин, вероятно, выполнит поиск в интернете и найдёт его традиционное значение.

  • Избегайте сокращений. Сокращения, особенно нестандартные, являются эффективными терминами, используемыми в специальных областях, потому что понимание зависит от правильного перевода их в полную форму.

    Предполагаемое значение любого используемого сокращения должно быть легко найдено с помощью поиска в интернете.

  • Следуйте прецеденту. Не оптимизируйте термины для абсолютных новичков в ущерб соответствию существующей культуре.

    Лучше назвать непрерывную структуру данных Array, чем использовать упрощённый термин, такой как List, даже если новичок может легче понять значение List. Массивы являются фундаментальными в современной вычислительной технике, поэтому каждый программист знает — или скоро узнает — что такое массив. Используйте термин, с которым знакомы большинство программистов, и их поиски в интернете и вопросы будут вознаграждены.

    В определённой области программирования, такой как математика, широко используемый термин, такой как sin(x), предпочтительнее пояснительной фразы, такой как verticalPositionOnUnitCircleAtOriginOfEndOfRadiusWithAngle(x). Обратите внимание, что в этом случае прецедент перевешивает рекомендацию избегать сокращений: хотя полное слово — это sine, «sin(x)» используется программистами уже десятилетия, а математиками — веками.

Конвенции

Общие конвенции

  • Документируйте сложность любого вычисляемого свойства, которое не является O(1). Люди часто предполагают, что доступ к свойству не подразумевает значительных вычислений, потому что они воспринимают свойства как хранилища. Убедитесь, что вы предупреждаете их, когда это предположение может быть нарушено.

  • Предпочитайте методы и свойства свободным функциям. Свободные функции используются только в особых случаях:

    1. Когда нет очевидного self:

      min(x, y, z)
      
    2. Когда функция является неограниченным обобщением:

      print(x)
      
    3. Когда синтаксис функции является частью установленной нотации области:

      sin(x)
      
  • Следуйте правилам именования. Имена типов и протоколов — UpperCamelCase. Всё остальное — lowerCamelCase.

    Аббревиатуры и сокращения, которые обычно пишутся заглавными буквами в американском английском, должны единообразно записываться с прописными или строчными буквами в соответствии с правилами именования:

    var **utf8**Bytes: [**UTF8**.CodeUnit]
    var isRepresentableAs**ASCII** = true
    var user**SMTP**Server: Secure**SMTP**Server
    

    Другие аббревиатуры должны рассматриваться как обычные слова:

    var **radar**Detector: **Radar**Scanner
    var enjoys**Scuba**Diving = true
    
  • Методы могут использовать общее имя, когда они имеют одинаковый базовый смысл или работают в разных областях.

    Например, следующее рекомендуется, так как методы выполняют по существу одинаковые действия:

    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 { ... }
    }
    

    И поскольку геометрические типы и коллекции являются разными областями, это также допустимо в одной программе:

    extension Collection where Element : Equatable {
      /// Returns `true` if `self` contains an element equal to
      /// `sought`; otherwise, `false`.
      func **contains**(_ sought: Element) -> Bool { ... }
    }
    

    Однако, эти index методы имеют разные семантики и должны быть названы по-другому:

    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 { ... }
    }
    

    Наконец, избегайте «перегрузки по типу возвращаемого значения», поскольку это приводит к неоднозначности при наличии вывода типов.

    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? { ... }
    }
    

Параметры

func move(from **start**: Point, to **end**: Point)
  • Выбирайте имена параметров для документации. Несмотря на то, что имена параметров не отображаются в месте использования функции или метода, они играют важную объяснительную роль.

    Выбирайте эти имена, чтобы сделать документацию лёгкой для чтения. Например, эти имена делают документацию естественной для чтения:

    /// 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<Index>, with **newElements**: [E])
    

    Эти же имена, однако, делают документацию неудобной и грамматически неправильной:

    /// 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<Index>, **with**: [E])
    
  • Используйте параметры с значениями по умолчанию, когда это упрощает часто используемые случаи. Любой параметр с одним часто используемым значением является кандидатом для значения по умолчанию.

    Параметры по умолчанию улучшают читаемость, скрывая нерелевантную информацию. Например:

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

    может стать гораздо более простым:

    let order = lastName.**compare(royalFamilyName)**
    

    Параметры по умолчанию, как правило, предпочтительнее использования семейств методов, потому что они накладывают меньшую когнитивную нагрузку на всех, кто пытается понять API.

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

    Вышесказанное может быть не простым, но оно намного проще, чем:

    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<Index>) -> Ordering
      /// *...description 4...*
      public func **compare**(
         _ other: String, options: StringCompareOptions,
         range: Range<Index>, locale: Locale) -> Ordering
    }
    

    Каждый член семейства методов должен быть отдельно задокументирован и понят пользователями. Чтобы выбрать между ними, пользователю нужно понять все из них, а иногда неожиданные отношения — например, foo(bar: nil) и foo() — не всегда являются синонимами — делают этот процесс выявления незначительных различий в большей части идентичной документации утомительным. Использование одного метода с параметрами по умолчанию обеспечивает гораздо лучший опыт программиста.

  • Предпочитайте размещать параметры с значениями по умолчанию в конце списка параметров. Параметры без значений по умолчанию, как правило, более важны для семантики метода и обеспечивают стабильный начальный шаблон использования, где методы вызываются.

  • Если ваш API будет использоваться в рабочей среде, предпочтительнее #fileID вместо альтернатив. #fileID экономит место и защищает конфиденциальность разработчиков. Используйте #filePath в API, которые никогда не используются конечными пользователями (например, вспомогательные тесты и скрипты), если полный путь упростит рабочие процессы разработки или будет использоваться для ввода-вывода файлов. Используйте #file для сохранения обратной совместимости со Swift 5.2 или более ранними версиями.

Метки аргументов

func move(**from** start: Point, **to** end: Point)
x.move(**from:** x, **to:** y)
  • Опускайте все метки, когда аргументы невозможно различить, например, min(number1, number2), zip(sequence1, sequence2).

  • В инициализаторах, выполняющих преобразования типов с сохранением значения, опускайте метку первого аргумента, например, Int64(someUInt32)

    Первый аргумент всегда должен быть источником преобразования.

    extension String {
      // Convert `x` into its textual representation in the given radix
      init(**_** x: BigInt, radix: Int = 10)   <span class="commentary">← Note the initial underscore</span>
    }
    
    text = "The value is: "
    text += **String(veryLargeNumber)**
    text += " and in hexadecimal, it's"
    text += **String(veryLargeNumber, radix: 16)**
    

    Однако при «сужающих» преобразованиях типов рекомендуется метка, описывающая сужение.

    extension UInt32 {
      /// Creates an instance having the specified `value`.
      init(**_** value: Int16)            <span class="commentary">← Widening, so no label</span>
      /// 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)
    }
    

    Преобразование типа с сохранением значения — это мономорфизм, т.е. каждое различие в значении источника приводит к различию в значении результата. Например, преобразование из Int8 в Int64 сохраняет значение, потому что каждое раздельное Int8 значение преобразуется в раздельное Int64 значение. Однако преобразование в обратном направлении не может сохранять значение: Int64 имеет больше возможных значений, чем может быть представлено в Int8.

    Примечание: возможность извлечения исходного значения не влияет на то, сохраняет ли преобразование значение.

  • Если первый аргумент является частью предложного оборота, используйте метку для этого аргумента. Метка аргумента обычно начинается с предлога, например, x.removeBoxes(havingLength: 12).

    Исключение возникает, когда первые два аргумента представляют части одного абстракции.

    a.move(**toX:** b, **y:** c)
    a.fade(**fromRed:** b, **green:** c, **blue:** d)
    

    В таких случаях начинайте метку аргумента после предлога, чтобы сохранить ясность абстракции.

    a.moveTo(**x:** b, **y:** c)
    a.fadeFrom(**red:** b, **green:** c, **blue:** d)
    
  • В противном случае, если первый аргумент является частью грамматического оборота, опустите его метку, присоединив любые предшествующие слова к основному имени, например, x.addSubview(y)

    Это руководство подразумевает, что если первый аргумент не является частью грамматического оборота, он должен иметь метку.

    view.dismiss(**animated:** false)
    let text = words.split(**maxSplits:** 12)
    let studentsByName = students.sorted(**isOrderedBefore:** Student.namePrecedes)
    

    Важно, чтобы оборот передавал правильное значение. Следующее было бы грамматически правильным, но выражало бы неверное значение.

    view.dismiss(false)   <span class="commentary">Don't dismiss? Dismiss a Bool?</span>
    words.split(12)       <span class="commentary">Split the number 12?</span>
    

    Также обратите внимание, что аргументы с значениями по умолчанию можно опустить, и в этом случае они не являются частью грамматического оборота, поэтому всегда должны иметь метки.

  • Укажите метки для всех остальных аргументов.

Специальные инструкции

  • Укажите метки для членов кортежей и имена параметров замыканий там, где они появляются в вашем API.

    Эти имена обладают пояснительной силой, могут быть упомянуты в комментариях к документации и обеспечивают выразительный доступ к членам кортежей.

    /// 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&lt;Void&gt;
    ) -> (**reallocated:** Bool, **capacityChanged:** Bool)
    

    Имена, используемые для параметров замыканий, должны выбираться так же, как имена параметров для функций верхнего уровня. Метки для аргументов замыканий, которые появляются в месте вызова, не поддерживаются.

  • Обращайте особое внимание на неявное полиморфизм (например, Any, AnyObject и неявные параметры обобщения), чтобы избежать неоднозначности в наборах перегрузок.

    Например, рассмотрите следующий набор перегрузок:

    struct Array<Element> {
      /// 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<S: SequenceType>(_ newElements: S)
        where S.Generator.Element == Element
    }
    

    Эти методы образуют семантическую семью, и типы аргументов на первый взгляд резко различаются. Однако, когда Element является Any, один элемент может иметь тот же тип, что и последовательность элементов.

    var values: [Any] = [1, "a"]
    values.append([2, 3, 4]) // [1, "a", [2, 3, 4]] or [1, "a", 2, 3, 4]?
    

    Чтобы устранить неоднозначность, более явно укажите имя второй перегрузки.

    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<S: SequenceType>(**contentsOf** newElements: S)
        where S.Generator.Element == Element
    }
    

    Обратите внимание, как новое имя лучше соответствует комментариям к документации. В этом случае сам процесс написания комментариев к документации обратил внимание автора API на эту проблему.

The Swift Programming Language, Copyright © 2014-2025 Apple Inc.
Swift and the Swift logo are trademarks of Apple Inc.

Documentation for Swift 6.0.3


https://www.swift.org/documentation/api-design-guidelines/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API