本コーディングガイドは、他社様の規約を参考にしつつ、リクルートライフスタイルのルールを盛り込んだものです。
これから増えていくであろうSwift製のアプリケーションのソースコードについて、下記の達成を目標として策定しています。
- 出来る限り厳密で、プログラマ・レビュワーが誤解する可能性が少ないこと
- 冗長さが排除されていること
- 同一アプリ内はもちろんのこと、各アプリ毎のソースコードにおいても一貫性があること
なお、参考にさせて頂いた規約については項番13. に記載しています。
参考にさせて頂いた規約と記述内容が似た箇所がありますが、本規約をご確認頂いて了承を頂いた上で公開しております。
原則省略なしのキャメルケースで命名する。 Classe・Structure・Enumとcase・Protocol名は大文字で、 メソッド名・変数・定数は小文字で始める。
Preferred:
protocol RobotProtocol {
func jump()
}
private let numberOfHands = 2
private let numberOfLegs = 2
class Robot {
enum RobotHeightType {
case Low, Middle, High
}
var jumpButton = UIButton()
let weightKilograms: CGFloat = 1.2
}
Not Preferred:
private let NUM_OF_HANDS = 2
private let kNumOfLegs = 2
protocol robotprotocol {
func Jump()
}
class robot {
enum robotHeightType {
case low, middle, high
}
var Jump_Button = UIButton()
let WeightKilograms: CGFloat = 1.2
}
Swiftでは名前空間が存在するため、クラス名の衝突を避けるためにプレフィックスを用いる必要がない。
クラス名のプレフィックスは付与しないこととする。
Preferred:
class SwiftClass {
// Do something
}
Not Preferred:
class RLSSwiftClass {
// Do something
}
Not Preferred:
class User {
var lastName = ""
var firstName = ""
func changeName(lastName: String, firstName: String) {
self.lastName = lastName
self.firstName = firstName
}
}
Preferred:
// 定義
func customMessageFromString(originalString: String) -> String
// 呼び出し
customMessageFromString(string)
// 定義
func changeColors(borderColor borderColor: UIColor, backgroundColor: UIColor)
// 呼び出し
changeColors(borderColor: UIColor.whiteColor(), backgroundColor: UIColor.redColor())
customMessageFromString
メソッドはString
を引数とすることがメソッド名から明らかであり、外部引数名を記載すると冗長になる。
一方、changeColors
メソッドは第一引数に外部引数名を指定しないとどの部分の色を指定しているのか明確でないため、
外部引数名を利用するべきである。
Not Preferred:
// 定義
func dateFromString(dateString dateString: String) -> NSDate
// 呼び出し
dateFromString(dateString: "2014-03-14")
// 定義
func changeColors(borderColor: UIColor, backgroundColor: UIColor)
// 呼び出し
changeColors(UIColor.whiteColor(), backgroundColor: UIColor.redColor())
optionalValue
・unwrappedView
・actualLabel
のように変数名にオプショナル関連の意味を持たせない。
Preferred:
var shopNameLabel: UILabel?
var shopName: String?
Not Preferred:
var optionalShopNameLabel: UILabel?
var wrappedShopName: String?
Appleのリファレンスに合わせて、アメリカ英語表記を使うこと。
Preferred:
let flavor = "sweet"
Not Preferred:
let flavour = "sweet"
そうできる限り常にvar
ではなくlet
を使う。
理由:
値が変更されないにも関わらずvar
を使うと、本当に値が変わらないかどうかのチェックが必要となり、非効率的かつ不要なリスクを孕むことになる。
let
を指定することにより、値が変化し得ないことが明確になり、より安全なコードを書くことができる。
CGFloat
やInt16
など明示する必要がある場合以外はコンパイラの型推論を利用する。
Preferred:
let titleString = "Recruit Lifestyle"
let titleLabel = UILabel()
var groups = [String]()
let maxRatio: CGFloat = 106.5
Not Preferred:
let titleString: String = "Recruit Lyfestyle"
let titleLabel: UILabel = UILabel()
var groups: [String] = [String]()
let maxRatio = 106.5
型指定はシンタックスシュガーを使って短い書き方を採用する。
Preferred:
var companyNames: [String]
var elements = [Int]()
var namesOfIntegers = [Int: String]()
Not Preferred:
var companyNames: Array<String>
var elements: [Int] = []
var elements: [Int] = [Int]()
var elements = Array<Int>()
var namesOfIntegers: [Int: String] = [Int: String]()
var namesOfIntegers = Dictionary<Int, String>()
そうできる限り常にSwiftネイティブな型を使う。
Preferred:
let percent: Double = 90.5 // Double
let percentString = String(percent) // String
Not Preferred:
let percent: NSNumber = 90.5 // NSNumber
let percentString: NSString = percent.stringValue // NSString
Preferred:
let name = data["rls"] as String
Not Preferred:
let name: String = data["rls"] as String
SomeType?
もしくはSomeType!
型の変数foo
がある場合、
foo
に対してForced Unwrappingを行うのは可能な限り避ける。
Preferred
var userNameLabel = UILabel()
if let foo = foo {
self.userNameLabel.text = foo
} else {
// 必要であればnil時の処理
}
もしくは
guard let bar = foo else { return }
self.userNameLabel.text = bar
Not Preferred
var userNameLabel = UILabel()
self.userNameLabel.text = foo!
また、上記のfoo
にアクセスする場合、またメソッドを呼び出す場合は可能な限りOptional Chainingを使用する。
Preferred
foo?.doSomeTask()
Not Preferred
foo!.doSomeTask()
もしfoo
がnil
になる場合があるなら、可能な限りlet foo: SomeType?
を使う。
(大抵の場合、!
の代わりに?
を使えるはず。)
Implicitly Unwrapped Optional型は暗黙的にアンラップされるため、nilが入っていた場合
ランタイム時のクラッシュが発生してしまう。
下記のうち1つ、もしくはいずれかに当てはまる場合、Class
ではなくStruct
として定義する。
- 複数個の値型のデータをひとまとめにして扱う場合
- 継承が必要無い場合
- メソッドを持っているが、プロパティの簡単な操作を行うだけの処理である場合
structの方が良いケースの例
public struct DtoUser {
var lastName = "" // property数が少なく、値型のみを持っている
var firstName = ""
public func displayName() -> String {
return "\(self.lastName) \(self.firstName)"
}
}
例えばValue ObjectやDTOは、基本的には単純にInt
や String
, struct
等の値型を格納するだけ(そして場合によっては、propertyに対して文字列連結などの
簡単な処理を行う)のものである。
このような場合、意図しない値の変更を防ぐためにもstruct
として実装するべきである。
ただし、以下の場合についてはclass
にするか検討する必要がある。
- propertyに参照型がある場合
- 一意性の必要がある場合
- deinitializerが必要な場合
- 継承が必要な場合
classの方が良いケースの例
public class DtoUser {
var lastName = ""
var firstName = ""
var userAttribute = ""
var userGroup = UserGroupClass() // Class(参照型)をpropertyとして持っている
public func displayName() -> String {
return "\(self.lastName) \(self.firstName)"
}
}
public let globalNumber: Int
internal struct Company {}
private func doTasks(tasks: [Task]) {}
しかし、トップレベルの定義の中のものについてのアクセス制御は問題なければ暗黙的で良い。
internal struct Company {
var establishedString = "2016/02/08"
}
理由:
トップレベルの定義がinternal
で適切な場合はあまりない。
アクセス制御に対して意識することにも繋がる。
クラス定義はまずfinal
指定して始める。 明確に継承の必要性が出た場合にのみ、final
を外す。
理由:継承よりコンポジションの方が、好ましい場合が多い。継承の選択を決定する前にコンポジションで実現できないか再考したほうが良い。 また、動的ディスパッチを減らしてパフォーマンスを改善するという意味でも継承が必要でない場合はfinal指定をするのが望ましい。
更に、プロトコル指向の考え方を用いてstruct
とprotocol
で実現できないかも考える余地がある。
理由:本規約ではプロパティ名とローカル変数名、メソッドの引数名に同一の名前を付けることを禁止していない(名前を変えるために冗長な名前を付けるのを避けるため、また、guard let
や if let
で同一の名前を付けることが一般的に行われるため)。
そのため、selfを付けてコーダー自身が値を誤らないよう、また、レビュワーも誤らないようにself
を付けることとする。
class MyCompany {
let name: String
let genre: String
init(name: String, genre: String) {
self.name = name
self.genre = genre
let closure = {
print(self.name)
}
}
}
プロトコルの実装はclass
部とは別に切り出し、// MARK: -
も付ける。
理由:関連メソッドをまとめることによる可読性の向上と、プロトコルの追加の容易さ向上のため。
Preferred:
class SampleViewController: UIViewController {
// class contents
}
// MARK: - OtherViewControllerDelegate
extension SampleViewController: OtherViewControllerDelegate {
// OtherViewControllerDelegate methods
}
Not Preferred:
class SampleViewController: UIViewController, OtherViewControllerDelegate {
// All methods
}
可能な限り、読み取り専用のプロパティと添字付けではgetキーワードを省く。
Preferred:
var sampleNumber: Int {
return 10
}
var squaredSampleNumber: Int {
return self.sampleNumber * self.sampleNumber
}
Not Preferred:
var sampleNumber: Int {
get {
return 10
}
}
var squaredSampleNumber: Int {
get {
return self.sampleNumber * self.sampleNumber
}
}
定数についてはグローバルでも化。
Not Preferred:
var globalValue = "init value"
class Hoge {
// use global value
}
Preferred:
func noReturnValueMethod(text: String) {
// Do something
}
Not Preferred:
func noReturnValueMethod(text: String) -> () {
// Do something
}
func noReturnValueMethod(text: String) -> Void {
// Do something
}
処理を続けるために特定の条件が必要な場合、guardによる早期リターンを行う。
Preferred:
guard text.isEmpty else { return }
// Use text
Not Preferred:
if text.isEmpty {
// Use text
} else {
return
}
nilの判定にif
も使用することができるが、guard
を使う。guard
はreturn
, break
, continue
, throw
(メソッドにthrows
を付けている場合のみ)と共に使用しなければコンパイルエラーとなり、exitすることが保証されるため、こちらの方が好ましい。
Preferred:
UIView.animateWithDuration(5.0) {
view.frame.size = CGSize(width: 100.0, height: 100.0)
}
UIView.animateWithDuration(5.0, animations: {
view.frame.size = CGSize(width: 100.0, height: 100.0)
}, completion: { bool in
view.frame.size = CGSizeZero
})
Not Preferred:
UIView.animateWithDuration(5.0, animations: {
view.frame.size = CGSize(width: 100.0, height: 100.0)
}) { bool in
view.frame.size = CGSizeZero
}
Preferred:
sampleArray.sort {
$0 > $1
}
Not Preferred:
sampleArray.sort { (lhs, rhs) -> Bool in
return lhs > rhs
}
Preferred:
if a == b {
// Do something
}
Not Preferred:
if (a == b) {
}
Preferred:
for _ in 0..<3 {
print("Hello three times")
}
for (index, member) in members.enumerate() {
print("No. \(index) is \(member)")
}
Not Preferred:
for var i = 0; i < 10; i++ {
print("\(i) times loop!!")
}
for var i = 0; i < members.count; i++ {
let member = members[i]
print("No. \(i) is \(member)")
}
Note: Not Preferredに記載した、所謂C-styleのforループはSwift3.0で廃止される予定。 Swift2系以前のバージョンでコーディングをする際には、後でコンパイルエラーを起こさないためにも使用しないこと。
インクリメント、デクリメントについては +=
, -=
を用いる。
Note:
++
, --
の演算子はSwift3.0で廃止、もしくは前置と後置の差分が無くなる予定。
後でコンパイルエラーを起こさないためにも、また、前置後置の差分が紛らわしいので使用しないようにする。
NSStringのlengthはUTF-16を2バイトコードとみなしてカウントしているため実際の文字数と異なることがある
Preferred:
"Swift text".characters.count
Not Preferred:
("Swift text" as NSString).length
Preferred:
if "Hoge".isEmpty {
//
}
Not Preferred:
if "Swift text".characters.count == 0 {
// Do something
}
if ("Swift text" as NSString).length == 0 {
// Do something
}
Preferred:
enum Result {
case Success, Error
}
Not Preferred:
enum Result {
case Success
case Error
}
ただし、rawValueが必要な場合はcaseごとに改行する。
Preferred:
myButton.addTarget(self, action: "someAction:", forControlEvents: .TouchUpInside)
var alignment: NSTextAlignment = .Center
Not Preferred:
myButton.addTarget(self, action: "someAction:", forControlEvents: UIControlEvents.TouchUpInside)
var alignment: NSTextAlignment = NSTextAlignment.Center
コードを読めば分かる内容のコメントは逆に分かりやすいコーディングを妨げるので不要。 必要な箇所に「なぜそのように書いたのか」を記述する。
以下にはMarkdownで役割・使い方・注意事項などを記述する。
- クラス定義
- プロトコル
- Enumerations
- Public Functions
コメントサンプル
/// ここにメソッドの説明を書く。
/// - parameter foo: 1つ目の引数。
/// - parameter bar: 2つ目の引数。
/// - returns: 処理結果が◯◯ならtrue, ××ならfalseを返す。
func doSomething(foo: String, bar:Int) -> Bool {
// do something
return false
}
1行1文で、文末尾にはセミコロンを付けない。
Preferred:
let text = "Do not write semicolon at the end of the line"
Not Preferred:
let text = "Do not write semicolon at the end of the line";
- enumerations
- Properties
- IBOutlets
- IBActions
- Initializers
- LifeCycle
- Public Methods
- Internal Methods
- Private Methods
- Segues
Preferred:
if myInstance.isEmpty {
// Do something
} else {
// Do something else
}
Not Preferred:
if myInstance.isEmpty
{
// Do something
}
else {
// Do something else
}
Preferred:
let radius: CGFloat = 5.0
var shapes = [Int: String]()
var checkList = [
1: true,
2: false
]
Not Preferred:
let radius:CGFloat = 5.0
var shapes = [Int :String]()
var checkList = [
1 : true,
2 :false
]
NSLocalizedString("Error.Auth.Title", comment:"Authentication error")
NSLocalizedString("AmountDetail.Title.Starting", comment: "starting")
NSLocalizedString("DailyVoucher.Alert.Title", comment:"Title")
NSLocalizedString("DailyVoucher.Alert.Cancel", comment:"Cancel")