17条 Swift 最佳实践规范

本文由CocoaChina译者小袋子(博客)翻译自schwa的github主页
原文作者:schwa 
这是一篇 Swift 软件开发的最佳实践教程。

前言



这篇文章是我根据在 SwiftGraphics 工作时的一系列笔记整理出来的。文中大多数建议是经过深思熟虑的,但仍可以有其他类似的解决方法。因此,如果其他方案是有意义的,这些方案会被添加上去。

这个最佳实践不是强加或者推荐 Swift 在程序、面向对象或者函数风格上的应用。更重要的是,这里要讲述的是务实的方法。如有需要的话,某些建议可能会集中在面向对象或者实用的解决方法。

这篇文章讲述的范围主要针对 Swift 语言以及 Swift 标准库。即便如此,如果能提出一个独特的 Swift 的视角和见解,我们依然会提供诸如 Swift 在 Mac OS、iOS、 WatchOS 以及 TV OS 上使用的特别建议。而如何在 Xcode 和 LLDB 上有效地使用 Swift,这样的建议也会以 Hints & tips 的风格提供。

这个过程需要付出很多的努力,非常感谢为本文做出贡献的那些人。

此外,可以在[Swift-Lang slack]里面讨论。

贡献者须知

请先确保所有的示例是可以运行的(某些示例可能不是正确)。这个 markdown 能够转换成一个 Mac OS X playground。

黄金准则

1. 一般来说,Apple 都是正确的,遵循 Apple 喜欢的或者示范的处理方式。在任何情况下,你都应该遵循 Apple 的代码风格,正如他们The Swift Programming Language" 这本书里面的定义一样。然而 Apple 是个大公司,我们将会看到很多在示例代码中的差异。

2. 永远不要仅仅为了减少代码量而去写代码。尽量依赖Xcode中的自动补全代码,自动建议 , 复制和粘贴。详尽的代码描述风格对其他代码维护者来说是非常有好处的。即便如此,过度的冗余也会失去 Swift 的重要特性:类型推断。

最佳实践



1.命名

正如 Swift Programming Language 中的类型名称都是以大驼峰命名法命名的(例如:VehicleController)。

变量和常量则以小驼峰命名法命名(例如:vehicleName)。

你应该使用 Swift 模板去命名你的代码而不是使用 Objective-C 类前缀的风格(除非和 Objective-C 接连)。

不要使用任何匈牙利标识法( Hungarian notation )命名(例如:k为常量,m为方法),应使用简短的命名并且使用 Xcode 的类型 Quick Help (+ click) 去查明变量的类型。同样地,不要使用小写字母+下划线( SNAKE_CASE )的命名方式。

唯一比较特别的是 enum 值的命名,这里需要使用大驼峰命名法(这个也是遵循 Apple 的 Swift Programming Language 风格):


1

2

3

enum Planet {

    case Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune

}

在所有可能的情况里,名称的不必要减少和缩写都应该避免,将来你应该能在没有任何损害和依赖 Xcode 的自动补全功能的情况下,确切地指出类型特征" ViewController "。非常普遍的缩写如 URL 是允许的。缩写应该用所有字母大写( URL )或者所有字母小写( url )表示。对类型和变量使用相同的规则。如果 url 是个类型,则应该为大写,如果是个变量,则应该为小写。

2.注释

注释不应该用来使代码无效,注释代码会使代码无效且影响代码的整洁。如果你想要移除代码,但是仍想保留以防代码在以后会用到,你应该依赖 git 或者 bug tracker 。

3.类型推断

在可能的地方,使用Swift的类型推断以减少多余的类型信息。例如,正确的写法:


1

var currentLocation = Location()

而不是:


1

var currentLocation: Location = Location()

4.Self 推断

让编译器在所有允许的地方推断 self 。在 init 中设置参数以及 non-escaping closures 中应该显性地使用 self 。例如:


1

2

3

4

5

6

7

struct Example {

    let name: String

    

    init(name: String) {

        self.name = name

    }

}

5.参数列表类型推断

在一个闭包表达式( closure expression )中指定参数类型可能导致代码更加冗长。只有当需要指定类型时。


1

2

3

4

5

6

7

8

9

10

let people = [

    ("Mary", 42),

    ("Susan", 27),

    ("Charlie", 18),

]

let strings = people.map() {

    (name: String, age: Int) -> String in

    return "\(name) is \(age) years old"

}

如果编译器能够推断类型,则应该去掉类型定义。


1

2

3

4

let strings = people.map() {

    (name, age) in

    return "\(name) is \(age) years old"

}

使用排序好的参数编号命名("$0","$1","$2")能更好地减少冗余,这经常能够完整匹配参数列表。只有当closure的参数名称中没有过多的信息时,使用编号命名。(例如特别简单的 maps 和 filters )。

Apple 能够并将会改变由 Objective-C frameworks 转换过来的 Swift 的参数类型。例如,选项被移除或者变为自动展开等。我们应有意地指定你的选项并依赖 Swift 去推断类型,减少在这种情况下程序中断的风险。

你总是应该有节制地指定返回类型。例如,这个参数列表明显过分冗余:


1

2

3

4

dispatch_async(queue) {

    () -> Void in

    print("Fired.")

}

6.常量

在类型定义的时候,常量应该在类型里声明为 static 。例如:


1

2

3

4

5

6

7

8

9

10

11

12

struct PhysicsModel {

    static var speedOfLightInAVacuum = 299_792_458

}

class Spaceship {

    static let topSpeed = PhysicsModel.speedOfLightInAVacuum

    var speed: Double

    

    func fullSpeedAhead() {

        speed = Spaceship.topSpeed

    }

}

使用 static 修饰常量可以允许他们在被引用的时候不需要实例化类型。

除了单例以外,应尽量避免生成全局常量。

7.计算型类型属性(Computed Properties)

当你只需要继承 getter 方法时,返回简单的 Computed 属性即可。例如,应该这样做:


1

2

3

4

5

class Example {

    var age: UInt32 {

        return arc4random()

    }

}

而不是:


1

2

3

4

5

6

7

class Example {

    var age: UInt32 {

        get {

            return arc4random()

        }

    }

}

如果你在属性中添加了 set 或者 didSet ,那么你应该显示地提供 get 方法。


1

2

3

4

5

6

7

8

9

10

class Person {

    var age: Int {

        get {

            return Int(arc4random())

        }

        set {

            print("That‘s not your age.")

        }

    }

}

8.实例转换(Converting Instances)

当创建代码去从一个类型转换到另外的 init() 方法:

extension NSColor {

convenience init(_ mood: Mood) {

super.init(color: NSColor.blueColor)

}

}

在 Swift 标准库中,对于把一个类型的实例转换为另外一种,现在看来 init 方法是比较喜欢用的一种方式。

"to" 方法是另外一种比较合理的技术(尽管你应该遵循 Apple 的引导去使用 init 方法):


1

2

3

4

5

struct Mood {

    func toColor() -> NSColor {

        return NSColor.blueColor()

  }

}

而你可能试图去使用一个getter,例如:


1

2

3

4

5

struct Mood {

    var color: NSColor {

        return NSColor.blueColor()

    }

}

getters 通常由于应该返回可接受类型的组件而受到限制。例如,返回了 Circle 的实例是非常适合使用 getter 的,但是转换一个 Circle 为 CGPath 最好在 CGPath 上使用"to"函数或者 init() 扩展。

9.单例(Singletons)

在Swift中单例是很简单的:


1

2

3

class ControversyManager {

    static let sharedInstance = ControversyManager()

}

Swift 的 runtime 会保证单例的创建并且采用线程安全的方式访问。

单例通常只需要访问"sharedInstance"的静态属性,除非你有不得已的原因去重命名它。注意,不要用静态函数或者全局函数去访问你的单例。

(因为在 Swift 中单例太简单了,并且持续的命名已经耗费了你太多的时间,你应该有更多的时间去抱怨为什么单例是一个反模式的设计,但是避免花费太多时间,你的同伴会感谢你的。)

10.使用扩展来组织代码

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

一个实例的次要方法和属性应该移动到扩展中。注意,现在并不是所有的属性类型都支持移动到扩展中,为了做到最好,你应该在这个限制中使用扩展。

你应该使用扩展去帮助组织你的实例定义。一个比较好的例子是,一个 view controller 继承了 table view data source 和 delegate protocols 。为了使table view中的代码最小化,把 data source 和 delegate 方法整合到扩展中以适应相应的 protocol 。

在一个单一的源文件中,在你觉得能够最好地组织代码的时候,把一些定义加入到扩展中。不要担心把 main class 的方法或者 struct 中指向方法和属性定义的方法加入扩展。只要所有文件都包涵在一个 Swift 文件中,那就是没问题的。

反之,main 的实例定义不应该指向定义在超出 main Swift 文件范围的扩展的元素。

11.链式 Setters

对于简单的 setters 属性,不要使用链式 setters 方法当做便利的替代方法。

正确的做法:


1

2

instance.foo = 42

instance.bar = "xyzzy"

错误的做法:


1

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

相较于链式setters,传统的setters更为简单和不需要过多的公式化。

12.错误处理

Swift 2.0 的 do/try/catch 机制非常棒。

13.避免使用try! 

一般来说,使用如下写法:


1

2

3

4

5

6

do {

    try somethingThatMightThrow()

}

catch {

    fatalError("Something bad happened.")

}

而不是:


1

try! somethingThatMightThrow()

即使这种形式特别冗长,但是它提供了context让其他开发者可以检查这个代码。

在更详尽的错误处理策略出来之前,如果把 try! 当做一个临时的错误处理是没问题的。但是建议你最好周期性地检查你代码,找出其中任何有可能逃出你代码检查的非法try!。

14.避免使用try?

try?是用来“压制”错误,而且只有当你确信对错误的生成不关心时,try?才是有用的。一般来说,你应该捕获错误并至少打印出错误。

15.过早返回&Guards

可能的话,使用guard声明去处理过早的返回或者其他退出的情况(例如,fatal errors 或者 thorwn errors)。

正确的写法:


1

2

3

4

guard let safeValue = criticalValue else {

    fatalError("criticalValue cannot be nil here")

}

someNecessaryOperation(safeValue)

错误的写法:


1

2

3

4

5

if let safeValue = criticalValue {

    someNecessaryOperation(safeValue)

else {

    fatalError("criticalValue cannot be nil here")

}

或者:


1

2

3

4

if criticalValue == nil {

    fatalError("criticalValue cannot be nil here")

}

someNecessaryOperation(criticalValue!)

这个flatten code以其他方式进入一个if let 代码块,并且在靠近相关的环境中过早地退出了,而不是进入else代码块。

甚至当你没有捕获一个值(guard let),这个模式在编译期间也会强制过早退出。在第二个if的例子里,尽管代码flattend得像guard一样,但是一个毁灭性的错误或者其他返回一些无法退出的进程(或者基于确切实例的非法态)将会导致crash。一个过早的退出发生时,guard声明将会及时发现错误,并将其从else block中移除。

16."Early"访问控制

即使你的代码没有分离成独立的模块,你也应该经常考虑访问控制。把一个定义标记为 private 或者 internal 对于代码来说相当于一个轻量级的文档。每一个阅读代码的人都会知道这个元素是不能“触碰”的。反之,把一个定义为 public 就相当于邀请其他代码去访问这个元素。我们最好显示地指明而不是依赖 Swift 的默认访问控制等级。( internal )

如果你的代码库在将来不断扩张,它可能会被分解成子模块.这样做,会使一个已经装饰着访问控制信息的代码库更加方便、快捷。

17.限制性的访问控制

一般来来说,当添加访问控制到你的代码时,最好有详尽的限制。这里,使用 private 比 internal 更有意义,而使用 internal 显然比 public 更好。(注意: internal 是默认的)。

如有需要,把代码的访问控制变得更加开放是非常容易的(沿着这样的途径: "private" to "internal" to "public") 。过于开放的访问控制代码被其他代码使用可能不是很合适。有足够限制的代码能够发现不合适和错误的使用,并且能提供更好的接口。一个例子就是一个类型公开地暴露了一个internal cache。

而且,代码的限制访问限制了“暴露的表面积”,并且允许代码在更小影响其他代码的情况下重构。其他的技术如:Protocol Driven Development 也能起到同样的作用。

TODO Section


    • This is a list of headings for possible future expansion.
    • Protocols & Protocol Driven Development
    • Implicitly Unwrapped Optionals
    • Reference vs Value Types
    • Async Closures
    • unowned vs weak
    • Cocoa Delegates
    • Immutable Structs
    • Instance Initialisation
    • Logging & Printing
    • Computed Properties vs Functions
    • Value Types and Equality
时间: 2024-10-13 22:05:19

17条 Swift 最佳实践规范的相关文章

iOS开发入门——17条 Swift 最佳实践规范(下)

文章来源:http://www.zretc.com/technologyDetail/433.html 承接上文:iOS开发入门--17条 Swift 最佳实践规范(上) 9.单例(Singletons) 在Swift中单例是很简单的: class ControversyManager { static let sharedInstance = ControversyManager()} Swift 的 runtime 会保证单例的创建并且采用线程安全的方式访问. 单例通常只需要访问"share

iOS开发入门——17条 Swift 最佳实践规范(上)

文章来源:http://www.zretc.com/technologyDetail/432.html 前言 这篇IOS开发入门文章是我根据在 SwiftGraphics 工作时的一系列笔记整理出来的.文中大多数建议是经过深思熟虑的,但仍可以有其他类似的解决方法.因此,如果其他方案是有意义的,这些方案会被添加上去. 这个最佳实践不是强加或者推荐 Swift 在程序.面向对象或者函数风格上的应用.更重要的是,这里要讲述的是务实的方法.如有需要的话,某些建议可能会集中在面向对象或者实用的解决方法.

Swift 最佳实践(未完待续)

使用 Swift 进行软件开发的最佳实践. 本文档的英文版在这里,感谢Swift社区(频道为 #bestpractices )为我们提供如此优质的文档. 前言 这个文档的产生得益于我在创作Swift Graphics时做的一系列的手记.本指南中的大部分建议也考量了是否可以为其它的观点和论点.当然,感觉其他的方法必须存在时除外. 这些最佳实践没有规定或推荐 Swift 是否应该在一个程序上以面向对象的或者函数式的方式来使用. 本文档更多的是关注 Swift 语言及其标准库.也就是说,以一个纯粹的

2019年网络安全最佳实践规范

网络安全风险投资研究预测,仅勒索软件将使全球企业在2019年损失超过115亿美元.更糟糕的是,研究还预测,到2019年,企业将每14秒遭遇一次网络***,而2016年则是每40秒一次.所以你能做些什么来减轻网络***的威胁?以下是五个可以提供帮助的IT安全最佳实践:1.调整您的安全策略以满足您的业务需求.您需要做的第一件事是评估您的内部安全人员.专门的安全和风险管理(SRM)团队可以评估组织中集中可用性的安全隐患;该团队还可以审查数字业务计划的机密性和灵活性,并探索降低风险的方法,这将有助于促进

《C++编程规范:101条规则、准则与最佳实践》学习笔记

转载:http://dsqiu.iteye.com/blog/1688217 组织和策略问题 0. 不要为小事斤斤计较.(或者说是:知道什么东西不需要标准化) 无需在多个项目或者整个公司范围内强制实施一致的编码格式.只要规定需要规定的事情:不要强制施加个人的喜好或者过时的做法. C++不应该使用匈牙利命名法.在有智能指针的情况下,单入口单出口可能不是必须的.代码要有自注释性. 1. 在高警告级别下干净地编译代码. 要把警告放在心上:使用你的编译器的最高警告级别.要求干净(没有警告)的构建.理解所

给javascript初学者的24条最佳实践

1.使用 === 代替 == JavaScript 使用2种不同的等值运算符:===|!== 和 ==|!=,在比较操作中使用前者是最佳实践. “如果两边的操作数具有相同的类型和值,===返回true,!==返回false.”——JavaScript:语言精粹 然而,当使用==和!=时,你可能会遇到类型不同的情况,这种情况下,操作数的类型会被强制转换成一样的再做比较,这可能不是你想要的结果. 2.Eval=邪恶 起初不太熟悉时,“eval”让我们能够访问JavaScript的编译器(译注:这看起

jQuery--[编码规范与最佳实践]

以下是书写jQuery代码的基本规范和最佳实践,这些不包括原生JS规范与最佳实践. 加载jQuery 1.尽量使用CDN加载jQuery. ? 1 2 <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script> <script>window.jQuery || document.

编写Shell脚本的最佳实践,规范二

需要养成的习惯如下: 代码有注释 #!/bin/bash # Written by steven # Name: mysqldump.sh # Version: v1.0 # Parameters : 无 # Function: mysqldump备份mysql # Create Date: 2016-08-27 缩进有规矩 编码要统一 在写脚本的时候尽量使用UTF-8编码 太长要分行 巧用heredocs 学会查路径 script_dir=$(cd $(dirname $0) && pw

jQuery编码规范与最佳实践

p{text-indent:2em;}前端开发whqet,csdn,王海庆,whqet,前端开发专家 翻译自:http://lab.abhinayrathore.com/ 翻译人员:前端开发whqet,意译为主,不当之处欢迎大家指正. 译者说:临近期末,大部分的基础教学内容已经讲解完毕,在进行比较大型的项目训练之前,如果能让学生了解甚至遵循一些前端开发的编码规范将会是一件非常有意义的事情.因此,本博客准备于近期整理一个编码规范与最佳实践的系列文章,包括html.css.javascript.jq