zoukankan      html  css  js  c++  java
  • 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 (01.png+ 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

  • 相关阅读:
    PAT (Advanced Level) 1010. Radix (25)
    PAT (Advanced Level) 1009. Product of Polynomials (25)
    PAT (Advanced Level) 1008. Elevator (20)
    PAT (Advanced Level) 1007. Maximum Subsequence Sum (25)
    PAT (Advanced Level) 1006. Sign In and Sign Out (25)
    PAT (Advanced Level) 1005. Spell It Right (20)
    PAT (Advanced Level) 1004. Counting Leaves (30)
    PAT (Advanced Level) 1001. A+B Format (20)
    PAT (Advanced Level) 1002. A+B for Polynomials (25)
    PAT (Advanced Level) 1003. Emergency (25)
  • 原文地址:https://www.cnblogs.com/motoyang/p/4946910.html
Copyright © 2011-2022 走看看