注: 本文摘自 Swift API设计指南

一、基本原则

  • 通俗易懂的API是设计者最重要的目标。实体、变量、函数等都具有一次申明、重复使用的性质,所以一个好的API设计,应该能够使用少量的解读和示例就可以清晰的表达它的语意和用途。
  • 应该将代码的设计重点放在如何使其逻辑更加清晰之上,而不是追求简短。Swift确实可以写出非常简短的代码,但是这不应该是设计者的首要目的。毕竟在代码的简短之道上,Swift的语言特质其实已经帮我们做了太多了。
  • 每一个API都应该注释必要的文档。 这在短期内可能效果不大,但是长远影响深远。(如果在简单的描述API功能的方面遇到困难,可能是因为设计了错误的API。)   
  • 注释的内容最好使用英文。

注释文档示例:

  • 使用 Swift 专门的注释方式。下面这张图表示注释的内容在PlayGround上的对应关系。

在首行描述API的总体信息。 如:

/// Returns a "view" of `self` containing the same elements in
/// reverse order.
func reversed() -> ReverseCollection
  • 或者,使用分段式的注释,每一种类型作为一个段落,段落之间以空行分割。如:

  /// 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")  

另: 使用系统识别的项目符号进行注释,使用规范的用词对应符合的内容, 使读者更容易明白API的细节含义。附表。

Attention    核心、划重点 Author   注明作者 Authors   一群作者(开发者) Bug     bug标记
Complexity  复杂度著名 ,比如循环复杂度 Copyright   著作全申明 Date   日期 Experiment  试验?
Important  强调 Invariant   常量 Note   注意事项 Parameter  参数
Parameters  多参数 Postcondition   后置条件 Precondition  前提条件 Remark  标注评论
Requires  必须的 Returns  返回 SeeAlso   参考-参见 Since  自。。开始
Throws   抛出、反馈 Todo   接下来做 Version  版本 Warning  警告

二、API命名规则

1. 促进通俗使用。

  • API中的名字中,应该包含所有的必要的名词,并尽量消除歧义。

一个不好的例子:   

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

改正:

extension List {
public mutating func remove(at position: Index) -> Element
} employees.remove(at: x)
  • 消除不必要的话,API中每个单词都应该有其显著的意思。

有时候需要陈述一些东西使得API的语意,但是不要重复,下面这个就显得很啰嗦了

public mutation func removeElement(_ member:Element) - >元素?  包含两次元素的描述了

allViews.removeElement(cancelButton)

可以改正:

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

  allViews.remove(cancelButton) // clearer
  • 根据角色命名,而不是其类型

错误的示例 :

 ClassProductionLine {
func restock(from widgetFactory: WidgetFactory) widgetFactory -- 类型
}

改正:

ClassProductionLine {
func restock(from supplier: WidgetFactory) supplier -- 角色
}
  • 补偿弱类型,用于澄清参数的作用

Swift中并没有包含所有你要的类型,比如 keyPath, 尽管它被表示路径,但是实际上它的真实类型是String,而我们所理解的keyPath"类型",就是弱类型。对于弱类型,为了恢复其清晰度,在每个   弱类型参数之前加上描述其作用的名词。

错误的示例:

func add(_ observer: NSObject, for keyPath: String)     路径 -- 弱类型
grid.add(self, for: graphics) // vague

改善:

func addObserver(_ observer: NSObject, forKeyPath path: String)    forKeyPath:用于描述参数的作用
grid.addObserver(self, forKeyPath: graphics) // clear

2.争取使得使用者可以流利的使用

  • 尽量使整个API的读取趋近于英文中的语法使用

这种方式能够让使用者看上一遍就基本上明白API的作用了。来看一个比较不错的例子:

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”

相对来说下面的这种方式会显得不那么友好:

 x.insert(y, position: z)
x.subViews(color: y)
x.nounCapitalize()
  • 使用‘ make ’作为创建对象的前缀

 eg. x.makeIterator() 
  • 初始化程序和工厂方法调用应该形成一个不包含第一个参数的短语

不好的方式举例:

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

改正

let foreground = Color(red: , green: , blue: )
let newPart = factory.makeWidget(gears: , spindles: )
  • 根据函数的功能和方法的效果命名

1. 没有具体作用的函数和方法应该看作名词短语,例如 x.distance(to:y),i.successor()。

2. 具有具体作用的函数和方法应该将其视为必需的动词短语,例如print(x),x.sort(),x.append(y)。

3. 根据是否会对调用者发生突变进行适当的修改名字。 突变方法通常应当具有类似语义的非显式变体,它的结果会返回一个新值,而不是就地更新实例。       

(1)突变和非突变函数命名的区别。 (当用动词描述函数时,将函数命名为动词,并应用“ed”或“ing”后缀来命名非突变的函数。下表1示例)

(2)突变和非突变函数命名的区别。 (当使用名词描述时,将函数命名为名词,并应用“form”前缀命名其突变函数。下表2示例)

Mutating  突变 Nonmutating   非突变
x.sort() z = x.sorted()
x.append(y) z = x.appending(y)

表1

Nonmutating  非突变 Mutating  突变
x = y.union(z) y.formUnion(z)
j = c.successor(i) c.formSuccessor(&i)

 表2

4.对于返回为bool值的函数,应当使用肯定的语气进行断言。(描述的不是很到位,总之不能产生歧义。)

 e.g.   x.isEmpty, line1.intersects(line2)    只读的情况下。 

5. 描述协议的时候,应该直接使用名词来表示。

 e.g. Collection

6. 描述 应使用、可以使用的的时候,使用后缀追加命名方式。

e.g. Equatable,ProgressReporting

7. 其他类型,属性,变量和常量的名称应以名义显示。

3.更好的使用术语

术语,是指在特定的领域中,用一些简短的、非常用的词语描述这个领域中某些东西。比如我们在计算机中经常使用的CPU,它代表中央处理器,显然这是个人尽皆知的名词,人们一眼就能看懂它代表的是什么,这就是一个术语。

在程序中,能够使用编程中的术语的时候我们尽可能的使用它,而不是应该自己去编写一段其他的名词代替。当然,不仅仅是在计算机领域,在其他领域的专业术语也可以使用,最好的前提是,我们正在编写与这个领域相关的程序。比如在编写图片处理的程序的时候,我们不可避免的会使用到位图这个概念,于是我们在程序中将有 ‘bitMap’这个术语了。

大部分术语源自于一个比较长的单词的首字母缩写,但这不代表我们在开发中的描述也可以这么做,术语有它众所周知的语意,相反,而随意的缩写并不具备这个功能。所以我们在考虑缩短编写内容的时候,应该还要考虑到其他人是否能明白你要表达的意思。  术语就是典型的例子。

三、符合常识的设计准则

1.遵循公众习惯

  •  使用无参数描述的函数       

这种情况只在某些特定的范围之下。

1. 当函数是一个无约束的泛型时。 我们不知道它的具体用途,只知道它的操作过程。比如:

min(x, y, z)

2.当函数的名字已经是公众的常识的一部分的时候。 比如:

print(x)    -- 所有人都知道这是一个打印的函数

3.使用了特定领域的类似于公式的时候。为了让特定领域的编程更加符合使用者的常识,我们不应该更改它的结构。 比如:

sin(x)     -- 如果使用 sin(value:x),很多人会觉得多此一举了。
  • 符合日常读写习惯

如果在API中需要用到 NBA 这个名字的时候,那么应该直接使用NBA,不管这个单词出现在命名的那一部分。 而不是 Nba、nba之类。
还有一些例子:

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

除此之外,其余的函数书写应该按照驼峰式书写 -- 非首个单词的首字母大写。

  • 功能类似的函数,可使用同一个名词作为基础

1.例如,swift鼓励以下方法,因为方法基本上是相同的:

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

当然,他们其实可以使用一个函数来替代: (这是一个高阶函数)

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

2.如果使用过头的话也是不对的,比如下面这种方式:

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

错误的原因是: 不应该在返回类型上重载, 编译器会不知道如何选择类型推断的。

2.参数

  • 应该在注释文档中描述具体的参数。

尽管在文档中,参数没有任何实际的作用,但是它可以更好的帮助其他人理解API的含义。

/// 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])

下面这个就显得让人费解。

/// 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])
  • 使用默认参数

有时候我们可能会使用到一个函数,其中的参数不是每次都需要的,也许有些开发者会这样:

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
}

似乎是把所有的函数都包含进来了,但是。。。 太繁琐了!
可以进行改进嘛,如果不需要的参数,我们可以使用nil或者空的对象代替。像这样:

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

确实有点效果,至少这里看起来简单多了。 但是一想到有的参数90%的时间都没有用上的时候,是不是觉得很尴尬?
试试下面的方法:

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

这样是不是舒服多了。 采用默认参数的方式,让参数的内容变得更加明了。

  • 默认参数应该放在函数参数表的最后面

3.参数标签(参数名)

  • 如果不能确认参数的类型, 应该省略参数标签

e.g. min(number1, number2), zip(sequence1, sequence2) 
  • 对象执行类型转换并保存的函数中,应该省略第一个参数的标签

e.g. Int64(someUInt32)
  • 当第一个参数形成介词短语的一部分时,给它一个参数标签 

a.moveTo(x: b, y: c)

 a.fadeFrom(red: b, green: c, blue: d)
  • 如果一个函数的命名中已经包含了第一个参数的语意,则第一个参数的标签可以省略

 e.g. x.addSubview(y)

同时这意味着,如果第一个参数没有被命名语法包含,那么他将必须要有一个标签,就像这样:

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

错误的示范:

view.dismiss(false)   Don't dismiss? Dismiss a Bool?  不删除 还是删除一个BOOL
words.split() Split the number ? 拆分 ?
  • 其余的情况,参数都应该带上标签

四 、特别说明

  • 闭包中的标签应当同样作为正常的参数标签,写入文档中注释 。

  • 具有重载参数的函数中,应当给予适当的标签。用于避免重载引起的歧义。

正确的例子:

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
}

不好的例子:

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
}
  
最后,请注意新名称如何更好地符合文档注释。 在这种情况下,撰写文档注释的行为实际上是将问题转化成了API作者的关注。

 

Swift API设计原则的更多相关文章

  1. javascript的api设计原则

    前言 本篇博文来自一次公司内部的前端分享,从多个方面讨论了在设计接口时遵循的原则,总共包含了七个大块.系卤煮自己总结的一些经验和教训.本篇博文同时也参考了其他一些文章,相关地址会在后面贴出来.很难做到 ...

  2. GOTO Berlin: Web API设计原则

    在邮件列表和讨论区中有很多与REST和Web API相关的讨论,下面仅是我个人对这些问题的一些见解,并没有绝对的真理,InnoQ的首席顾问Oliver Wolf在GOTO Berlin大会上开始自己的 ...

  3. Atitit. Api 设计 原则 ---归一化

    Atitit. Api 设计 原则 ---归一化 1.1. 叫做归一化1 1.2. 归一化的实例:一切对象都可以序列化/toString  通过接口实现1 1.3. 泛文件概念.2 1.4. 游戏行业 ...

  4. API设计原则

    译序 Qt的设计水准在业界很有口碑,一致.易于掌握和强大的API是Qt最著名的优点之一.此文既是Qt官网上的API设计指导准则,也是Qt在API设计上的实践总结.虽然Qt用的是C++,但其中设计原则和 ...

  5. API设计原则(觉得太合适,转发做记录)

    API设计原则 对于云计算系统,系统API实际上处于系统设计的统领地位,正如本文前面所说,K8s集群系统每支持一项新功能,引入一项新技术,一定会新引入对应的API对象,支持对该功能的管理操作,理解掌握 ...

  6. RESTful API设计原则与规范

    RESTful API设计原则与规范 一.背景与基础概念 2 二.RESTful API应遵循的原则 3 1.协议(Protocol) 3 2.域名(ROOT URL) 3 3.版本(Versioni ...

  7. 【JS】327- javascript 的 api 设计原则

    点击上方"前端自习课"关注,学习起来~ 前言 本篇博文来自一次公司内部的前端分享,从多个方面讨论了在设计接口时遵循的原则,总共包含了七个大块.系卤煮自己总结的一些经验和教训.本篇博 ...

  8. JavaScript 的 API设计原则

    一.接口的流畅性 好的接口是流畅易懂的,他主要体现如下几个方面: 1.简单 操作某个元素的css属性,下面是原生的方法: document.querySelectorAll('#id').style. ...

  9. 从编译器对指令集的要求看API设计原则

    摘要:最近看<计算机体系结构:量化研究方法(第五版)>,发现指令集设计中的一些原则,对API设计也同样适用,给大家分享一下. 本文中的所有内容来自工作和学习过程中的心得整理,如需转载请注明 ...

随机推荐

  1. js进阶 13-5 jquery队列动画如何实现

    js进阶 13-5 jquery队列动画如何实现 一.总结 一句话总结:同一个jquery对象,直接写多个animate()就好. 1.什么是队列动画? 比如说先左再下,而不是左下一起走 2.怎么实现 ...

  2. Django中pycharm中 报错 ---ValueError: The field admin.LogEntry.user was declared with a lazy reference to 'system.sysuser', bu

    问题是:已经在settings.py文件中注册过app仍旧提示没有安装,并且使用makegirations命令时会抛出如下异常 解决方法: 找到自己的python3.x,进入site-packages ...

  3. postman--下载及使用入门

    安装 本文只是基于 Chrome 浏览器的扩展插件来进行的安装,并非单独应用程序. 首先,你要台电脑,其次,安装有 Chrome 浏览器,那你接着往下看吧. 1. 官网安装(别看) 打开官网,http ...

  4. Hamming correct

    从数的最左边开始,并标记为1 将2的平方的位置留出来,做为校验位例如,8位2进制数10011010===>_ _ 1 _ 0 0 1 _ 1 0 1 0 位置1用来校验最右边的位位1的位置1 3 ...

  5. 【习题5-3 UVA-10935】Throwing cards away I

    [链接] 我是链接,点我呀:) [题意] 在这里输入题意 [题解] 用STL的queue写 [代码] #include <bits/stdc++.h> using namespace st ...

  6. VSX(翻译)Moving Code Blocks Among Code Regions using VS 2010 Extensions

    Moving Code Blocks Among Code Regions using VS 2010 Extensions (翻译)使用VS 2010 扩展性将代码块移至Region区域中 Down ...

  7. Maven基础教程 分类: C_OHTERS 2015-04-10 22:53 232人阅读 评论(0) 收藏

    更多内容请参考官方文档:http://maven.apache.org/guides/index.html 官方文档很详细,基本上可以查找到一切相关的内容. 另外,快速入门可参考视频:孔浩的maven ...

  8. Java反射学习总结三(静态代理)

    反射最常见的应用就是代理模式了. 本文先简单介绍一下代理模式,并写一个静态代理的例子.为下一篇重要的动态代理做点铺垫 代理模式的作用是: 为其他对象提供一种代理以控制对这个对象的访问. 另外在某些情况 ...

  9. css3-10 css3中的边框样式有哪几种

    css3-10 css3中的边框样式有哪几种 一.总结 一句话总结:1.border-radius 2. box-shadow 3.border-image三种,box一种border两种 1.css ...

  10. 使用C#版本的gdal库打开hdf文件

    作者:朱金灿 来源:http://blog.csdn.net/clever101 最近应同事的请求帮忙研究下使用C#版的gdal库读取hdf文件,今天算是有一点成果,特地做一些记录. 首先是编译C#版 ...