Swift 最佳实践(未完待续)

使用 Swift 进行软件开发的最佳实践.html

本文档的英文版在这里,感谢Swift社区(频道为 #bestpractices )为咱们提供如此优质的文档。ios

前言

这个文档的产生得益于我在创做Swift Graphics时作的一系列的手记。本指南中的大部分建议也考量了是否能够为其它的观点和论点。固然,感受其余的方法必须存在时除外。git

这些最佳实践没有规定或推荐 Swift 是否应该在一个程序上以面向对象的或者函数式的方式来使用。github

本文档更多的是关注 Swift 语言及其标准库。也就是说,以一个纯粹的 Swift 的角度提供可提供的关于在 Mac OS, iOS, WatchOS 和 TVOS 上如何使用 Swift 的具体建议。 同时也会提供一些如何在 Xcode 和 LLDB中有效利用 Swift 的提示和技巧。swift

这项工做正在进行中,很是欢迎你们经过 Pull Request 或 Issues 的方式来贡献内容。api

你也能够在 Swift-Lang slack(位于 #bestpractices 频道) 上参与讨论。缓存

贡献者注意事项

请确保全部的例程是可运行的 (这可能不适用于现有的例程)。这个 markdown 文件会转化成一个 Mac OS X 的 playground.安全

黄金法则

  • Apple 一般是对的。应紧随苹果所推荐的或他的 Demo 中所展现的方式。您应该尽量地遵照 Apple 在 The Swift Programming Language 一书中所定义的代码风格。但咱们仍是能够看到他们的示例代码中有不符合这些规则的地方,毕竟 Apple 是一家大公司嘛。markdown

  • 不要仅仅为了减小字符的键入数量而使用模棱两可的简短命名,较长的命名均可以依赖自动完成、自我暗示、复制粘贴来减低键入的难度。命名的详细程度每每对代码维护者颇有帮助。但过于冗长的命名却会绕过Swift的主要特性之一: 类型推导,因此命名的原则应该是简洁明了。闭包

最佳实践

命名

按照 The Swift Programming Language 所推荐的命名法则,类型名称应该使用首字母大写的驼峰命名法 (例如: "VehicleController")。

变量与常量应该使用首字母小写的驼峰命名法(例如: " vehicleName " )。

推荐使用 Swift 模块来定义代码的命名空间,而非在 Swift 代码上使用 Objective-C 样式的类前缀(除非接口要与 Objective-C 交互)。

不推荐使用任何形式的匈牙利命名法(好比:k 表明常量,m 表明方法),取代代之咱们应该使用短而简洁的名字并使用 Xcode 的类型快速帮助 (⌥ + 左击)。一样咱们也不要使用相似 SNAKE_CASE 这样的名字。

这些法则之上,惟一例外的状况就是枚举值了,枚举值在这里应该首字母大写(这是 Apple 的 The Swift Programming Language 中的规范):

enum Planet {case Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Nepture
}

有必要的话命名不要缩写,实际上在 Xcode 的"文本自动补全"功能下你能够垂手可得地键入 相似 ViewController 的长命名。极为常见的缩写,例如: URL, 是很好的。缩写应该是所有大写 ( "URL" )或者酌情所有小写( "url" )。URL 的类型和变量命名推荐的规则: 若是 url 是一个类型,它应该被大写,若是是一个变量,那么应该小写。

注释

不该该使用注释来禁用代码。被注释掉的代码会污染你的源代码。若是你当前想要删除一段代码,但未来又可能会用到,推荐你依赖 git 或你的 bug 追踪系统来管理。

(TODO: 追加一个关于文档注释的小节,使用 nshipster 的连接)

类型推导

若是可能的话,使用 Swift 的类型推导,以免冗余的类型信息。例如:

推荐:

var currentLocation = Location()

而非:

var currentLocation: Location = Location()

内省

让编译器自动推断全部的状况,这是能够作到的。在一些领域 self 应该被显式地使用,包括在 init 中设置参数,或者 non-escaping闭包。例如:

struct Example {let name: Stringinit(name: String) {self.name = name
    }
}

捕获列表的类型推导

在一个捕获列表( capture list )中指定参数类型会致使代码冗余。若是须要的话,仅指定类型便可。

let people = [
    ("Mary", 42),
    ("Susan", 27),
    ("Charlie", 18),
]let strings = people.map() {
    (name: String, age: Int) -> String inreturn "\(name) is \(age) years old"}

若是编译器能够推导出来的话,彻底能够把类型删掉:

let strings = people.map() {
    (name, age) inreturn "\(name) is \(age) years old"}

使用编号的参数名 ("$0") 进一步下降冗长,每每能完全消除捕获列表的代码冗余。在闭包中当参数名没有附带任何更多信息时仅使用编号形式便可( 如很是简单的映射和过滤器 )。

Apple 可以而且将会改变闭包的参数类型,经过他们的 Objective-C 框架的 Swift 变种提供出来。例如,optionals 被删除或更改成 auto-unwrapping 等。故意 under-specifying 可选并依赖 Swift 来推导类型,能够减小在这些状况下代码被破译的风险。

你应该避免指定返回类型,例如这个捕获列表( capture list )就是彻底多余的:

dispatch_async(queue) {
    ()->Void inprint("Fired.")
}

(以上内容也能够参考:这里)

常量

类型定义中使用的常量应当被申明成静态类型。例如:

struct PhysicsModel {static var speedOfLightInAVacuum = 299_792_458}class Spaceship {static let topSpeed = PhysicsModel.speedOfLightInAVacuumvar speed: Doublefunc fullSpeedAhead() {
        speed = Spaceship.topSpeed
    }
}

将常量标示为 static ,容许它们能够被无类型的实例引用。

通常应该避免生成全局范围的常量,单例除外。

计算属性

若是你只是为了实现一个 getter,请使用短版 (short version) 的计算属性。例如:

推荐这样:

class Example {var age: UInt32 {return arc4random()
    }
}

不要:

class Example {var age: UInt32 {get {return arc4random()
        }
    }
}

若是你在属性里添加一个 set 或者 didSet, 那么你须要显示提供一个 get

class Person {var age: Int {get {return Int(arc4random())
        }set {
            pint("That's not your age.")
        }
    }
}

实例的转换

将一种类型的实例转换为到另外一种类型实例时的init()方法:

extension NSColor {convenience init(_ mood: Mood) {super.init(color: NSColor.blueColor)
    }
}

如今在 Swift 标准库中实现这种转换 init 方法彷佛是首选。

"to" 方法是另外一种合理的方法(虽然你应该遵循 Apple 的指引使用 init 方法)。

struct Mood {func toColor() -> NSColor {return NSColor.blueColor()
    }
}

虽然你可能会使用 getter, 例如:

struct Mood {var color: NSColor {return NSColor.blueColor()
    }
}

通常来讲 getter 应该被限定返回接收类型的组件。例如,返回一个 Circle 实例的面积就很适合使用 getter,可是将一个Circle 转换为一个 CGPath 使用 "to" 函数或者一个 CGPath 的 init() 扩展会更好。

单例

在 Swift 中,生成单例很简单:

class ControversyManager {static let sharedInstance = ControversyManager()
}

Swift 的 runtime 将确保以一种线程安全的方式来建立和访问单例。

单例通常仅经过 sharedInstance 静态属性来访问,除非你有一个使人信服的理由不把它命名为 sharedInstance。不要使用静态函数或者全局函数来访问单例。

( 由于在 Swift 中生成单例是如此简单,而且由于统一的命名为您节省了大量的时间,您将有更多的时间去抱怨单例如何如何 '反模式' 而且应该不惜一切代价避免使用。您的开发伙伴们会感激你的 <译者注:反话?> 。)

代码组织的扩展

扩展应该被用于帮助组织代码。

当方法和属性是一个实例的外围延伸时,应该被迁移到某个扩展。注意,目前并非全部的属性类型均可以被迁移到扩展 --- 在这个限制范围内尽你所能。

你应该使用扩展来帮助组织你的实例的定义。这方面很好的一个例子就是:一个实现了表视图数据源和委托协议的视图控制器。它没有将全部的表视图代码混成一个类,而是把数据源和委托方法放到了相关的协议扩展中。

能够根据你的以为最好的方式将一个源文件随意分解并定义成任何的扩展,以从新组织这堆问题代码。别担忧主类中的方法或扩展里指向方法和属性的结构定义。只要它们在一个 Swift 文件中就没事。

相反,主实例定义不该该指向在主 Swift 文件以外的主扩展中定义的元素。。。

链式 Setters

不要为了图方便而使用链式 setter 来替代简单的属性 setter :

推荐:

instance.foo = 42instance.bar = "xyzzy"

不推荐:

instance.setFoo(42).setBar("xyzzy")

比起链式 setter 传统的 setter 更简单而且须要的样板代码更少。

错误处理

Swift 2 的 do/ try/ catch 机制很是棒,推荐使用!(TODO: 拟定并提供示例)

避免使用 try!

一般推荐:

do {try somethingThatMightThrow()
}catch {
    fatalError("Something bad happened.")
}

不推荐:

try! somethingThatMightThrow()

虽然这种形式显得有点啰嗦,但它为其余的开发者进行代码评审提供了清晰的上下文语境。

在没有更好的错误处理策略进化出来以前,使用 try! 做为一种临时的错误处理也是 OK 的。可是建议你按期审查你的代码是否错误的使用了 try!,由于它极可能上次悄悄地躲过了代码审查。

尽可能避免 try?

try?是用来屏蔽(译者注:产生了错误但你不想作任何错误处理相关的事情)错误的,只有在你真的不关心错误的产生时,对你而言才有用。一般状况下你应该捕获错误并至少打印错误日志。

提早返回与Guards

若是可能的话,使用 guard 申明来处理提早返回或退出 (例如: 致命错误(fatal errors) 或 抛出错误 (thrown errors))。

推荐:

guard let safeValue = criticalValue else {
    fatalError("criticalValue cannot be nil here")
}
someNecessaryOperation(safeValue)

不推荐:

if let safeValue = criticalValue {
    someNecessaryOperation(safeValue)
} else {
    fatalError("criticalValue cannot be nil here")
}

也不推荐:

if criticalValue == nil {
    fatalError("criticalValue cannot be nil here")
}
someNecessaryOperation(criticalValue!)

这让代码的逻辑扁平化,若是不符合则进入 if let 代码块,而且提早退出的语句放置 在接近他们相关条件的地方,而非下放到一个 else 代码块中。

即便你没有捕捉值( guard let ),这种模式也会在编译时强制执行提早退出。在第二个(不推荐的) if 例程中,虽然代码扁平得跟 guard 同样,但一个致命错误或其余返回的一些非退出操做无心中的改变将致使崩溃 (亦或状态无效,这取决于确切的状况)。从一个 guard 申明的 else block 中移除提早退出将会当即显示错误(编译器提示错误)。

"超前地" 控制访问

即便你的代码没有分解为各个独立的模块,你也应该老是考虑其访问控制。标记一个定义为 "private" 或者 "internal" 能够轻量化代码文档。任何阅读这代码的人将知道这些元素是能够暂时放一边不予考虑的。相反,标记一个定义为 "public"就代表其余的代码能够访问这个标记元素。最好能有明确的指示而非依赖 Swift 的默认访问权限 ("internal")。

若是你的代码库慢慢膨胀,最终它可能被拆分为 N 个子模块。(而拆分为 N 个子模块这种工做若是)在一个已经分配了访问控制权限信息的代码库上作会更快更容易。

"限制性地" 控制访问

在感受使用 "private" 好过 "internal" ,或使用 "internal" 比 "public" 更合适的时,给代码添加更严格的访问控制权限一般会更好。

这将使得以后放宽代码的访问控制权限变得更容易(权限从窄到宽: "private" ---> "internal" ---> "public")。代码的访问控制权限若是放得太宽就有可能会被其余的代码不恰当地使用。给代码更严格的访问控制可以排除一些不恰当或不正确的调用从而提供出更好的接口。这是一种在天马行空以后关上一扇稳定之门的风格尝试。公开地暴露一个内部缓存正是这方面的一个例子。

此外,限制代码的访问权限能够限制 "曝光面积" 而且代码在重构时对其余代码的影响更少。另外的技术像 "协议驱动开发" 也能提供帮助。

TODO sections

未来可能会展开讨论的一系列主题。

协议 & 协议驱动开发

隐式 Unwrapped Optionals

参考 VS 值类型

异步闭包

unowned VS weak

Cocoa Delegates

不可变的结构体

实例初始化

日志与打印

计算属性 vs 功能

值类型和相等性


来源: <https://github.com/KevinHM/ios-good-practices-the-lastest-version/blob/master/Swift-Best-Practices.md>

 

来自为知笔记(Wiz)

相关文章
相关标签/搜索