1. 1.长城汽车Swift编程规约
1.1. 前言
好的代码有一些特性:简明,自我解释,优秀的组织,良好的文档,良好的命名,优秀的设计以及可以被久经考验。参与长城系列APP开发的团队成员应严格遵照规约编写代码。规约会越来越完善,初期先按照以下规范。
第一次编辑时间:2020-01-28
1.2. 核心原则
最重要的目标:每个元素都能够准确清晰的表达出它的含义。做出 API 设计、声明后要检查在上下文中是否足够清晰明白。
清晰比简洁重要。虽然 swift 代码可以被写得很简短,但是让代码尽量少不是 swift 的目标。简洁的代码来源于安全、强大的类型系统和其他一些语言特性减少了不必要的模板代码。而不是主观上写出最少的代码。
为每一个声明写注释文档。编写文档过程中获得的理解可以对设计产生深远的影响,所以不要回避拖延。
如果你不能很好的描述 API 的功能,很可能这个 API 的设计就是有问题的。
1.3. 命名规约
不要使用约定命名样式代替访问控制
如果要控制访问权限应该使用访问控制(internal、fileprivate、private),不用使用自定义的命名方式来区分,比如在方法前前下划线表示私有。 只有在极端的情况下才会采用这种自定义命名表示。比如有一个方法只是为了某个模板调用才公开的,这种情况下本意是私有的,但是又必须声明成 public,可以使用自定义的命名惯例。
代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。
✅ var productDiskDataArray: Array? var productDiskDataString: String! ❌ var chanpinDataArray: Array? var chanpinDataString: String!
类, 结构体, 枚举, 协议命名使用 UpperCamelCase 风格,必须遵从驼峰形式。特别注意类名开头大写。
✅ class GWElecFenceFlowLayout: UICollectionViewFlowLayout ❌ class gwElecFenceFlowLayout: UICollectionViewFlowLayout
资源文件按照匈牙利命名法。
模块名+图片特征描述+状态 描述清晰有章法即可。
✅ nav_add_normal@2X.png ❌ navAddNormal@2X.png NAV_ADD_NORMAL@2x.png
方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase 风格,必须遵从驼峰形式。
✅ func viewDidLoad() func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int let automaticDimension: CGFloat let gradientShapeLayer = CAShapeLayer() ❌ func ViewDidLoad() func tableView(_ tableV: UITableView, numberOfRowsInSection section: Int) -> Int var FILLCOLOR: CGColor? let gradientshapeLayer = CAShapeLayer()
全局常量就正常变量一样使用匈牙利命名方式,不要在前面加上 g、k 或其他特别的格式。
✅ let secondsPerMinute = 60 ❌ let SecondsPerMinute = 60 let kSecondsPerMinute = 60 let gSecondsPerMinute = 60 let SECONDS_PER_MINUTE = 60
杜绝完全不规范的缩写,避免望文不知义。
❌ var abcAry: Array?
保证英文拼写正确
❌ class AysncDat: NSObject
单例对象一般命名为 shared 或者 default。
✅ /// 路由单例 public static let shared = GWNavigator() /// 屏蔽实现方式(可根据具体使用情况灵活调整) private override init() {}
1.4. 格式规约
括号
非空的 block 花括号默认使用 K&R style。比如:
while (x == y) { something() somethingelse() }
除了一些 Swift 特别要求的情况:
- 左花括号( { )前的代码不会换行,除非超过前面提到的代码长度超过限制。
- 左花括号后是一个换行,除非:
- 后面要声明闭包的参数,改为在 in 关键字后面换行。
- 符合每行只声明一件事里情况,忽略换行,把内容写在一行里。
- 如果是空的 block ,直接声明为
{ }
。
- 如果右花括号( } )结束了一个声明,后面接上一个换行。比如如果右花括号后面跟的是 else ,那么后面就不会跟换行,而会写成这样
} else {
衔接。
- 如果右花括号( } )结束了一个声明,后面接上一个换行。比如如果右花括号后面跟的是 else ,那么后面就不会跟换行,而会写成这样
每行只声明一件事
每行最多只声明一件事,每行结尾用换行分隔。除非结尾跟的是一个总共只有一行声明的闭包。
✅ guard let value = value else { return 0 } defer { file.close() } switch someEnum { case .first: return 5 case .second: return 10 case .third: return 20 } let squares = numbers.map { $0 * $0 } var someProperty: Int { get { return otherObject.property } set { otherObject.property = newValue } } var someProperty: Int { return otherObject.somethingElse() } required init?(coder aDecoder: NSCoder) { fatalError("no coder") }
如果闭包是提前返回一个值,写在一行里可读性就会好一些。如果是一个正常的操作,可以视情况是否写在一行里。因为未来也有可能里面再增加代码的操作。
代码换行
代码中的空格
除了语言或者其他样式的要求,文字和注释之外,一个Unicode空格也只出现在以下地方:
条件关键字后面和跟着的括号
✅ if (x == 0 && y == 0) || z == 0 { // ... } ❌ if(x == 0 && y == 0) || z == 0 { // ... }
如果闭包中的代码在同一行,左花括号的前面、后面,右花括号的前面有空格
✅ let nonNegativeCubes = numbers.map { $0 * $0 * $0 }.filter { $0 >= 0 } ❌ let nonNegativeCubes = numbers.map { $0 * $0 * $0 } .filter { $0 >= 0 } ❌ let nonNegativeCubes = numbers.map{$0 * $0 * $0}.filter{$0 >= 0}
在任何二元或三元运算符的两边
还有以下的情况:
使用于赋值,初始化变量、属性,默认参数的等号两边。
✅ var x = 5 func sum(_ numbers: [Int], initialValue: Int = 0) { // ... } ❌ var x=5 func sum(_ numbers: [Int], initialValue: Int=0) { // ... }
表示在协议中表示合成类型的 & 两边。
✅ func sayHappyBirthday(to person: NameProviding & AgeProviding) { // ... } ❌ func sayHappyBirthday(to person: NameProviding&AgeProviding) { // ... }
自定义运算符的两边。
✅ static func == (lhs: MyType, rhs: MyType) -> Bool { // ... } ❌ static func ==(lhs: MyType, rhs: MyType) -> Bool { // ... }
表示返回值的 -> 两边。
✅ func sum(_ numbers: [Int]) -> Int { // ... } ❌ func sum(_ numbers: [Int])->Int { // ... }
例外:表示引用值、成员的点两边没有空格。
✅ let width = view.bounds.width ❌ let width = view . bounds . width
例外:表示区域范围的 ..< 和 …两边没有空格。
✅ for number in 1...5 { // ... } ❌ let substring = string[index..<string.endIndex] for number in 1 ... 5 { // ... } ❌ let substring = string[index ..< string.endIndex]
参数列表、数组、tuple、字典里的逗号后面有一个空格
✅ let numbers = [1, 2, 3] ❌ let numbers = [1,2,3] let numbers = [1 ,2 ,3] let numbers = [1 , 2 , 3]
冒号的后面有一个空格
✅ // 类型声明 struct HashTable: Collection { // ... } struct AnyEquatable<Wrapped: Equatable>: Equatable { // ... } // 参数标签 let tuple: (x: Int, y: Int) func sum(_ numbers: [Int]) { // ... } // 变量声明 let number: Int = 5 // 字典声明 var nameAgeMap: [String: Int] = [] // 字典字面量 let nameAgeMap = ["Ed": 40, "Timmy": 9]
代码后的注释符号 // 与代码有两个空格距离
✅ let initialFactor = 2 // Warm up the modulator. ❌ let initialFactor = 2 // Warm up the modulator.
表示字典、数组字面量的中括号外面有一个空格
✅ let numbers = [1, 2, 3] ❌ let numbers = [ 1, 2, 3 ]
禁止变量、属性水平对齐
水平对齐是明确禁止的,除非是在写明显的表格数据时,省略对齐会损害可读性。引入水平对齐后,如果添加一个新的成员可能会需要其他成员再对齐一次,这给维护增加了负担。
✅ struct DataPoint { var value: Int var primaryColor: UIColor } ❌ struct DataPoint { var value: Int var primaryColor: UIColor }
空行逻辑
- 在组织代码逻辑关系时,可以用空行隔开进行分组。
- 函数结尾不空行
- 函数内作用不同代码块空一行
- 规范里其他地方要求有空行的地方。
括号
最顶级的 if、guard、while、switch 的条件不使用括号。
✅ if x == 0 { print("x is zero") } if (x == 0 || y == 1) && z == 2 { print("...") } ❌ if (x == 0) { print("x is zero") } if ((x == 0 || y == 1) && z == 2) { print("...") }
在有复杂的条件表达式,只有作者和 review 的人同时认为省略括号不会影响代码的可读性才会省略。不能假设每个读者都完全了解对 swift 的运算符优先级,所以这种情况下的括号提示用户的计算优先级是合理的。
1.5. 集合处理
不要在 forin 循环里进行元素的 remove/add 操作。
❌ var someInts:[Int] = [10, 20, 30] for index in someInts { someInts.insert(44 + index, at: index) }
1.6. 并发处理
获取单例对象需要保证线程安全,其中的方法也要保证线程安全。
创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。
推荐:
dispatch_queue_t gwhUpdateQueue = dispatch_queue_create("", DISPATCH_QUEUE_SERIAL); let queue = DispatchQueue(label: "com.gwh.app.update", attributes: .concurrent)
高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。
对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造成死锁。
并发修改同一记录时,避免更新丢失,要么在应用层加锁,要么在缓存加锁,要么在数据库层使用乐观锁,使用 version 作为更新依据。
1.7. 控制语句
提前返回使用 guard
✅ func discombobulate(_ values: [Int]) throws -> Int { guard let first = values.first else { throw DiscombobulationError.arrayWasEmpty } guard first >= 0 else { throw DiscombobulationError.negativeEnergy } var result = 0 for value in values { result += invertedCombobulatoryFactory(of: value) } return result } ❌ func discombobulate(_ values: [Int]) throws -> Int { if let first = values.first { if first >= 0 { var result = 0 for value in values { result += invertedCombobulatoryFactor(of: value) } return result } else { throw DiscombobulationError.negativeEnergy } } else { throw DiscombobulationError.arrayWasEmpty } }
for-where 循环
如果整个 for 循环在函数体顶部只有一个 if 判断,使用 for where 替换:
✅ for item in collection where item.hasProperty { // ... } ❌ for item in collection { if item.hasProperty { // ... } }
Switch 中的 fallthrough
Switch 中如果有几个 case 都对应相同的逻辑,case 使用逗号连接条件,而不是使用 fallthrough:
✅ switch value { case 1: print("one") case 2...4: print("two to four") case 5, 7: print("five or seven") default: break } ❌ switch value { case 1: print("one") case 2: fallthrough case 3: fallthrough case 4: print("two to four") case 5: fallthrough case 7: print("five or seven") default: break }
换句话说,不存在 case 中只有 fallthrough 的情况。如果 case 中有自己的代码逻辑再 fallthrough 是合理的。
1.8. 注释规约
所使用的任何注释必须保持最新否则删除掉。代码修改的同时,注释也要进行相应的修改,尤其是参数、返回值、异常、核心逻辑等的修改。
类、类属性、类方法的注释必须使用 appledoc 规范,使用/*内容/格式。option+command+/。对于注释的要求:第一、能够准确反应设计思想和代码逻辑;第二、能够描述业务含义,使别的程序员能够迅速了解到代码背后的信息。完全没有注释的大段代码对于阅读者形同天书,注释是给自己看的,即使隔很长时间,也能清晰理解当时的思路;注释也是给继任者看的,使其能够快速接替自己的工作。 好的命名、代码结构是自解释的,注释力求精简准确、表达到位。避免出现注释的一个极端:过多过滥的注释,代码的逻辑一旦修改,修改注释是相当大的负担。
1.9. 补充规范
- 代码警告⚠️应该尽可能去除。除非明确为了提醒作用。
小代码块 保证逻辑的完整与连贯性
使用便于理解的API
无用注释与代码的删除尽量删除便于理解
优先使用工具类方法
服务器定义的字段为大写,客户端model 也应该使用小写
实现逻辑尽量才用普遍好理解的方法
import头文件的排版, 当import超过7个
✅ #import "GWHProductViewController.h" //model #import "GWHProductModel.h" #import "GWHCarModel.h" #import "GWHCommunityModel.h" //view #import "GWHProducPackageView.h" #import "GWHCarCell.h" #import "GWHCommunityCell.h" //tool #import <AVFoundation/AVFoundation.h> #import "MJRefresh.h" #import "NSDateFormatter+Utility.h" #import "Masonry.h" #import "HttpUtils+GWHNetTool.h" #import "GWHUserManager.h" //viewController #import "GWHServiceProductRecViewController.h" #import "GWHPageController.h" ❌ #import "GWHProductViewController.h" #import "GWHProductModel.h" #import "GWHCarCell.h" #import "GWHCommunityCell.h" #import <AVFoundation/AVFoundation.h> #import "MJRefresh.h" #import "NSDateFormatter+Utility.h" #import "HttpUtils+GWHNetTool.h" #import "GWHUserManager.h" #import "GWHServiceProductRecViewController.h" #import "GWHCarModel.h" #import "GWHCommunityModel.h" #import "GWHProducPackageView.h" #import "Masonry.h" #import "GWHPageController.h"
1.10. 代码提交逻辑
提交 commit 的类型
feat: 其他
fix: 修复bug
目前除了fix需要附带bug编号, 别的统一用feat.