Google Swift Style Guide 浓缩版

栏目: Swift · 发布时间: 6年前

内容简介:所有 swift 源文件都使用 .swift 扩展名。通常使用源文件中主体(primary entity)命名。如果是扩展现有类型,在现有类型有使用 + 号,带上扩展的内容。

参考文档: Google Swift Style Guide

源文件组织

文件名

所有 swift 源文件都使用 .swift 扩展名。

通常使用源文件中主体(primary entity)命名。如果是扩展现有类型,在现有类型有使用 + 号,带上扩展的内容。

例子:

  • 如果是一个单一的类型 MyType,那么文件名为 MyType.swift。

  • 如果是扩展 MyType 实现 MyProtocol,那么文件名为 MyType+MyProtocol.swift。

  • 如果是给 MyType 增加了一下扩展方法,那么文件的前缀是 MyType+ ,比如可以是 MyType+Additions.swift。

  • 如果文件里的声明不是在一个相关类型下,可以描述这些声明的作用、场景。比如 Math.swift。

Import 声明

默认直接 import 最高级的 module。显式导入使用的所有模块,比如虽然 UIKit 依赖 Foundation,但是如果使用 UIKit,还是会分别导入这两个 module。

只有在 import 最高级不能访问到需要的对象,或者为了避免污染到现有的命名空间里的对象才会 import 子模块。

Import 声明在文件的顶部。

不同的 import 类型用换行隔开。

例子:

import CoreLocation
import MyThirdPartyModule
import SpriteKit
import UIKit

import func Darwin.C.isatty

@testable import MyModuleUnderTest

Type, Variable, Function 的声明

通常一个源文件里只有一个顶级的类型声明,特别是这个类型的内容很多的时候。不过有的场景下一个文件里包含几个有关联的类型也是合理的,比如:

  • 一个类和它的代理协议定义在同一个文件里。

  • 一个类型和它相关的 helper 方法有时会定义在同一个文件里。这个时候通常会使用 fileprivate 修饰 helper 方法。

类型、变量、函数在源文件中的布局顺序对代码的可读性有很大的影响。但是这些的顺序并没有一个固定的形式,不同的类型、不同的文件可能会有不同的组织形式。

重要的是__每一个文件、类型必须按照一定的逻辑来组织这些元素__,也就是说如果别人问起为什么这样分布可以有一个说法。比如往一个类型里增加了一些方法,肯定不能只是简单追加在文件的末尾。当然你也可以说是按照方法写的时间顺序来组织,然而时间顺序并不是一种逻辑上的组织。

在组织这些元素时建议使用 // MARK: 来为分组写上注释。

class MovieRatingViewController: UITableViewController {

  // MARK: - View controller lifecycle methods

  override func viewDidLoad() {
    // ...
  }

  override func viewWillAppear(_ animated: Bool) {
    // ...
  }

  // MARK: - Movie rating manipulation methods

  @objc private func ratingStarWasTapped(_ sender: UIButton?) {
    // ...
  }

  @objc private func criticReviewWasTapped(_ sender: UIButton?) {
    // ...
  }
}

重载的声明放在一起

如果一个类型有多个初始化方法或者 subscript,或者一个文件、类型有多个基本名字相同的方法(参数标签不同),要把它们写在一起。

Extension 里的方法也要按照逻辑组织

Extension 的方法可能被几个不同的场景使用到。按照逻辑组织 Extension 里的成员也同样重要。也要按照一定的逻辑关系组织里面的元素。

通用格式

见文档: 通用格式

专有结构的格式

非文档类注释

非文档类注释使用 // ,不使用 C 风格的 /* ... */ 。

属性

变量声明的地方尽量靠近在使用的地方。

每一行只声明一个变量。

×
var a = 5, b = 10

Switch

Case 的声明和 switch 保持同样的缩进,case 里的内容换行后有 2 个缩进。

√
switch order {
case .ascending:
  print("Ascending")
case .descending:
  print("Descending")
case .same:
  print("Same")
}
×
switch order {
  case .ascending:
    print("Ascending")
  case .descending:
    print("Descending")
  case .same:
    print("Same")
}
×
switch order {
case .ascending:
print("Ascending")
case .descending:
print("Descending")
case .same:
print("Same")
}

Enum

通常 enum 下的每个 case 都是单独一行。只有在确认未来不会扩展,不需要给 case 单独写注释,没有使用关联类型,没有 rawValue 值的 enum 才有可能写成一行的。

√
public enum Token {
  case comma
  case semicolon
  case identifier
}

public enum Token {
  case comma, semicolon, identifier
}

public enum Token {
  case comma
  case semicolon
  case identifier(String)
}
×
public enum Token {
  case comma, semicolon, identifier(String)
}

如果每一个 case 都是 indirect,那么直接在 enum 上声明 indirect。

√
public indirect enum DependencyGraphNode {
  case userDefined(dependencies: [DependencyGraphNode])
  case synthesized(dependencies: [DependencyGraphNode])
}
×
public enum DependencyGraphNode {
  indirect case userDefined(dependencies: [DependencyGraphNode])
  indirect case synthesized(dependencies: [DependencyGraphNode])
}

枚举的选项顺序必须有意义。如果没有特别的逻辑可以参考,按照每项的首字母顺序。

下面的例子用空行分组,整体上按照状态码从小到达 排序 看起来很清楚:

√
public enum HTTPStatus: Int {
  case ok = 200

  case badRequest = 400
  case notAuthorized = 401
  case paymentRequired = 402
  case forbidden = 403
  case notFound = 404

  case internalServerError = 500
}

如果直接按照首字母的词典书序则会降低了可读性。

×
public enum HTTPStatus: Int {
  case badRequest = 400
  case forbidden = 403
  case internalServerError = 500
  case notAuthorized = 401
  case notFound = 404
  case ok = 200
  case paymentRequired = 402
}

尾闭包

如果两个函数只有最后一个闭包参数不同,那么就不应该重载。需要声明不同的函数名。如果只是尾参数闭包不同,那么如果使用了尾闭包的语法糖调用,会造成歧义,不知道调用的是哪个函数。

比如下面的例子,调用 greet 的时候就不知道调用的是哪个函数:

×
func greet(enthusiastically nameProvider: () -> String) {
  print("Hello, \(nameProvider())! It's a pleasure to see you!")
}

func greet(apathetically nameProvider: () -> String) {
  print("Oh, look. It's \(nameProvider()).")
}

greet { "John" }  // error: ambiguous use of 'greet'

如果函数调用的参数分成了很多行,这个时候不使用尾闭包的语法糖。

√
UIView.animate(
  withDuration: 0.5,
  animations: {
    // ...
  },
  completion: { finished in
    // ...
  })

×
UIView.animate(
  withDuration: 0.5,
  animations: {
    // ...
  }) { finished in
    // ...
  }

如果参数闭包符合尾闭包的定义,那么应该总是使用尾闭包的语法糖。只有两种例外情况:

  • 前面提到的有重载的函数,使用尾闭包会导致歧义。

  • 如果尾闭包用在条件控制语句中,尾闭包会引起语法的冲突。

√
Timer.scheduledTimer(timeInterval: 30, repeats: false) { timer in
  print("Timer done!")
}

if let firstActive = list.first(where: { $0.isActive }) {
  process(firstActive)
}
×
Timer.scheduledTimer(timeInterval: 30, repeats: false, block: { timer in
  print("Timer done!")
})

// This example fails to compile.
if let firstActive = list.first { $0.isActive } {
  process(firstActive)
}

使用尾闭包时,删除尾闭包外的括号:

√
let squares = [1, 2, 3].map { $0 * $0 }

×
let squares = [1, 2, 3].map({ $0 * $0 })
let squares = [1, 2, 3].map() { $0 * $0 }

尾部逗号

赋值数组、字典时每个元素分别占有一行时,最后一个选项后面也添加逗号。这样未来如果有元素加入会更加方便。

√
let configurationKeys = [
  "bufferSize",
  "compression",
  "encoding",                                    
]
×
let configurationKeys = [
  "bufferSize",
  "compression",
  "encoding"                                     
]

数字字面量

如果是一个很长的数字时,建议使用下划线按照语言习惯三位或者四位一组分割连接。

√
let number = 100_0000

Attribute

带参数的特性(比如 @availability(...)、@objc(...))单独声明在一行里。

√
@available(iOS 9.0, *)
public func coolNewFeature() {
  // ...
}
×
@available(iOS 9.0, *) public func coolNewFeature() {
  // ...
}

不带参数的特性(比如 @IBOutlet、@NSManaged)则声明在同一行里。如果写到同一行后代码长度超过长度限制,则将特性标签单独声明一行。

√
public class MyViewController: UIViewController {
  @IBOutlet private var tableView: UITableView!
}

命名

Apple’s API Style Guidelines

认真贯彻学习苹果官方发布的 API design guidelines。

不要使用约定命名样式代替访问控制

如果要控制访问权限应该使用访问控制(internal、fileprivate、private),不用使用自定义的命名方式来区分,比如在方法前前下划线表示私有。

只有在极端的情况下才会采用这种自定义命名表示。比如有一个方法只是为了某个模板调用才公开的,这种情况下本意是私有的,但是又必须声明成 public,可以使用自定义的命名惯例。

标识符

通常,标识符只包含 7 位 ASCII 字符。如果 Unicode 标识符在代码库的问题域中有明确含义(例如,表示数学概念的希腊字母),并且能被使用该代码的团队很好地理解,则可以使用它们。

Google Swift Style Guide 浓缩版

初始化方法

如果初始化参数和自身的属性是一一对应的关系,参数名和属性名保持一致。

√
public struct Person {
  public let name: String
  public let phoneNumber: String

  // GOOD.
  public init(name: String, phoneNumber: String) {
    self.name = name
    self.phoneNumber = phoneNumber
  }
}
×
public struct Person {
  public let name: String
  public let phoneNumber: String

  // AVOID.
  public init(name otherName: String, phoneNumber otherPhoneNumber: String) {
    name = otherName
    phoneNumber = otherPhoneNumber
  }
}

静态属性

如果一个静态属性返回的是自身的类型,不用在属性名称后缀中重复声明类型。

√
public class UIColor {
  public class var red: UIColor {                // GOOD.
    // ...
  }
}

public class URLSession {
  public class var shared: URLSession {          // GOOD.
    // ...
  }
}
×
public class UIColor {
  public class var redColor: UIColor {           // AVOID.
    // ...
  }
}

public class URLSession {
  public class var sharedSession: URLSession {   // AVOID.
    // ...
  }
}

如果是表示单例的静态属性,一般命名为 shared 或者 default。

全局常量

就像正常变量一样使用匈牙利命名方式,不要在前面加上 g、k 或其他特别的格式。

√
let secondsPerMinute = 60
×
let SecondsPerMinute = 60
let kSecondsPerMinute = 60
let gSecondsPerMinute = 60
let SECONDS_PER_MINUTE = 60

代理方法

代理方法的命名和正常函数命名会有明显的区别,命名的方式参考了 Cocoa 的 protocol。

代理的主体作为方法第一个参数。

只有一个参数

如果返回值是 void,方法名以主体开头,接一个动词短语表示发生的事件。隐藏参数名称标签。

√
func scrollViewDidBeginScrolling(_ scrollView: UIScrollView)

如果返回值是 Bool,方法名以主体开头,接一个表示判断的短语。隐藏参数名称标签。

√
func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool

如果返回的是其他类型的值,方法名则是一个描述了返回值角色的名词短语。参数标签会尽量使用介词或者词组来连接方法名和主体。

√
func numberOfSections(in scrollView: UIScrollView) -> Int

多个参数

代理方法多个参数的情况下,第一个参数总是隐藏标签。

命名的分类与上面提到的相似,区别只是方法的基础名字都是主体,对于方法的具体作用描述声明在参数标签里。

√
func tableView(
  _ tableView: UITableView,
  willDisplayCell cell: UITableViewCell,
  forRowAt indexPath: IndexPath)

func tableView(
  _ tableView: UITableView,
  shouldSpringLoadRowAt indexPath: IndexPath,
  with context: UISpringLoadedInteractionContext
) -> Bool

func tableView(
  _ tableView: UITableView,
  heightForRowAt indexPath: IndexPath
) -> CGFloat

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Java多线程编程实战指南(设计模式篇)

Java多线程编程实战指南(设计模式篇)

黄文海 / 电子工业出版社 / 2015-10 / 59.00

随着CPU 多核时代的到来,多线程编程在充分利用计算资源、提高软件服务质量方面扮演了越来越重要的角色。而 解决多线程编程中频繁出现的普遍问题可以借鉴设计模式所提供的现成解决方案。然而,多线程编程相关的设计模式书籍多采用C++作为描述语言,且书中所举的例子多与应用开发人员的实际工作相去甚远。《Java多线程编程实战指南(设计模式篇)》采用Java(JDK1.6)语言和UML 为描述语言,并结合作者多......一起来看看 《Java多线程编程实战指南(设计模式篇)》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

MD5 加密
MD5 加密

MD5 加密工具