エウレカ流Swift Style Guideを公開しました
こんにちは!CouplesでiOSエンジニアをしている丹です。
Swiftがリリースされてから約1年半が経ち、チーム内に知見が溜まってきたので、
エウレカ流のSwift Style Guideを公開しました!
レポジトリはこちらです。英語と日本語を用意しています。
現在、52のルールが書かれています!
他のスタイルガイドと何が違うのか
本スタイルガイドが目指したのは、
- チーム開発
- プロダクションコード
の2点において実用的なスタイルガイドです。
以下に参考にしたスタイルガイドとブログを紹介しますが、上記2点を満たすスタイルガイドが見つからなかったことが、作成のきっかけです。
参考にしたスタイルガイド
1. github/swift-style-guide
Swiftのスタイルガイドの中で一番有名だと思います。参考にすべき良いルールがたくさんありますが、チームでスタイルを統一していくにはルールが少ないです。また、Githubのスタイルガイドではselfを省略するルールがありますが、弊社はチーム開発ではselfを明示的に書くべきというスタンスを取っており(理由は後述します)、この点も異なります。
2. raywenderlich/swift-style-guide
raywenderlich/swift-style-guide
こちらも有名なスタイルガイドですが、ルールの対象は出版物やチュートリアルとなっています。そのため、弊社のチームで、プロダクション開発をするためのルールとは異なる部分があります。
3. A handful of Swift style rules #swiftlang | Erica Sadun
A handful of Swift style rules #swiftlang | Erica Sadun
弊社のiOSチームで度々話題になるErica Sadunさんのブログです。
他のスタイルガイドと違う9つのルール
selfのルール
Swiftコミュニティではselfを書かないことが多いですが、チーム開発では可読性に欠けると弊社は考えています。
例えば、コードレビューをするときに
- selfのプロパティなのか、同じスコープ内の変数なのか
- selfのメソッドなのか、同じスコープ内のクロージャなのか
を瞬時に判別できるようになります。Githubのシンタックスハイライトは完璧ではないので、selfを書かない場合、上記2点の判別は少しコードを遡る必要があります。
以下、Style Guideのルールを紹介します。
1. インスタンスのプロパティとメソッドはクロージャ内を含めて、必ずselfを付ける。
このルールは先に述べた理由からです。
OK
NG
self.animatableViews.forEach { view in
self.animateView(view)
}
animatableViews.forEach { view in
animateView(view)
}
2. @noescapeとアニメーションのクロージャ以外のクロージャ内でselfにアクセスする場合、必ず[weak self]を使用する。
上記のselfが必須となるルールと合わせて、循環参照が起きうる箇所を簡単に特定できるようになります。selfにアクセスしているクロージャを探し、[weak self]が抜けていないかを確認するだけです。
OK
NG
self.request.downloadImage(
url,
completion: { [weak self] image in
self?.didDownloadImage(image)
}
)
self.request.downloadImage(
url,
completion: { image in
self.didDownloadImage(image) // 循環参照
}
)
3. クロージャ内で参照をキャプチャするのにunownedを使用しない。
weakよりもunownedの方が便利ですが(Optionalとして扱う必要がないため)、クラッシュを起こす可能性があります。どちらを使うか判別するより、weakを使う方が安全です。
OK
NG
self.request.downloadImage(
url,
completion: { [weak self] image in
self?.didDownloadImage(image)
}
)
self.request.downloadImage(
url,
completion: { [unowned self] image in
self.didDownloadImage(image)
}
)
4. クロージャ内でweakなselfをアンラップする必要がある場合、`self`に対してバインドする。
シンタックスハイライトを有効にするためです。こちらのルールは賛否両論あると思いますが、現在はこのスタイルを推奨しています。
OK
NG
self.request.downloadImage(
url,
completion: { [weak self] image in
guard let `self` = self else {
return
}
self.didDownloadImage(image)
self.reloadData()
self.doSomethingElse()
}
)
self.request.downloadImage(
url,
completion: { [weak self] image in
guard let strongSelf = self else {
return
}
strongSelf.didDownloadImage(image)
strongSelf.reloadData()
strongSelf.doSomethingElse()
}
)
コードを探しやすくするルール
プロダクションのコードは大規模になる傾向があります。
特にViewControllerの肥大化を防ぐことに苦心しているチームは多いのではないでしょうか。肥大化を防ぐと言っても限度があり、5, 600行のファイルが出来てしまうことはよくあります。
また、チームで開発している場合、他人が書いたコードを読む必要があります。そのため、コードを探しやすくするルールは必須です。以下で、いくつか紹介します。
5. class、struct、enum、extension、protocolなどの全ての宣言は// MARK: - 宣言の名前を付ける。
XcodeのSource Navigatorを使用して、特定の型にジャンプすることが容易になります。
OK
NG
// MARK: - Icon
class Icon {
// MARK: - CornerType
enum CornerType {
case Square
case Rounded
}
// ...
}
// Icon
class Icon {
// MARK: CornerType
enum CornerType {
case Square
case Rounded
}
// ...
}
6. 全てのプロパティとメソッドはスーパークラスやプロトコル毎にグループ分けをして、// MARK: スーパークラス/プロトコルの名前を付ける。その他はアクセスレベルに応じて、// MARK: Public、// MARK: Internal、// MARK: Privateをそれぞれ付ける。
ソースコード上のどこにプロパティやメソッドが宣言されているかを容易に特定できます。
OK
// MARK: - BaseViewController
class BaseViewController: UIViewController, UIScrollViewDelegate {
// MARK: Internal
weak var scrollView: UIScrollView?
// MARK: UIViewController
override func viewDidLoad() {
// ...
}
override func viewWillAppear(animated: Bool) {
// ...
}
// MARK: UIScrollViewDelegate
@objc dynamic func scrollViewDidScroll(scrollView: UIScrollView) {
// ...
}
// MARK: Private
private var lastOffset = CGPoint.zero
}
7. // MARK:タグによるグルーピングは次の順番にする:
// MARK: Public
// MARK: Internal
- Classの継承 (最上位の親から子へ)
// MARK: NSObject
// MARK: UIResponder
// MARK: UIViewController
- Protocolの継承 (最上位の親から子へ)
// MARK: UITableViewDataSource
// MARK: UIScrollViewDelegate
// MARK: UITableViewDelegate
// MARK: Private
publicとinternalの宣言はAPI利用者に最も参照される部分であるため、一番上に配置しています。
8. 上記のグルーピングの内部では以下の順序にする:
@が付くプロパティ(@NSManaged, @IBOutlet, @IBInspectable, @objc, @nonobjcなど)
lazy var
- Computed property(
var)
- Stored property(
var)
letプロパティ
@が付く関数(@NSManaged, @IBAction, @objc, @nonobjcなど)
- その他の関数
@が付くプロパティと関数は最も参照されやすいため(KVCのキーやSelector、Interface Builderと相互に参照し合うなど)、上部に定義しています。
9. 一時的にローカライズされていない文字列は// TODO: localizeを付ける。
実装した機能をデバッグしてテストをする場合、大抵はネイティブの言語を使用します。そして、翻訳された文字列は分けてテストされます。このガイドラインにより、全ての未翻訳の文字列が説明され、後で見つけることが容易になります。
OK
NG
self.titleLabel.text = "Date Today:" // TODO: localize
self.titleLabel.text = "Date Today:"
今後
今年の秋にはSwift3.0の公開も控えているので、ベストなスタイルも変わる可能性があります。本スタイルガイドはSwiftの進化に伴って、今後もアップデートしていく予定です。
ルールを追加したいという要望や、ルールへのご意見がございましたら、日本語だけでも構いませんので、プルリクエストをいただけると助かります!
レポジトリはこちらです。
eure/swift-style-guide
エウレカでは、一緒に働いていただける方を絶賛募集中です。募集中の職種はこちらからご確認ください!皆様のエントリーをお待ちしております!
こんにちは!CouplesでiOSエンジニアをしている丹です。
Swiftがリリースされてから約1年半が経ち、チーム内に知見が溜まってきたので、
エウレカ流のSwift Style Guideを公開しました!
レポジトリはこちらです。英語と日本語を用意しています。
現在、52のルールが書かれています!
他のスタイルガイドと何が違うのか
本スタイルガイドが目指したのは、
- チーム開発
- プロダクションコード
の2点において実用的なスタイルガイドです。
以下に参考にしたスタイルガイドとブログを紹介しますが、上記2点を満たすスタイルガイドが見つからなかったことが、作成のきっかけです。
参考にしたスタイルガイド
1. github/swift-style-guide
Swiftのスタイルガイドの中で一番有名だと思います。参考にすべき良いルールがたくさんありますが、チームでスタイルを統一していくにはルールが少ないです。また、Githubのスタイルガイドではselfを省略するルールがありますが、弊社はチーム開発ではselfを明示的に書くべきというスタンスを取っており(理由は後述します)、この点も異なります。
2. raywenderlich/swift-style-guide
raywenderlich/swift-style-guide
こちらも有名なスタイルガイドですが、ルールの対象は出版物やチュートリアルとなっています。そのため、弊社のチームで、プロダクション開発をするためのルールとは異なる部分があります。
3. A handful of Swift style rules #swiftlang | Erica Sadun
A handful of Swift style rules #swiftlang | Erica Sadun
弊社のiOSチームで度々話題になるErica Sadunさんのブログです。
他のスタイルガイドと違う9つのルール
selfのルール
Swiftコミュニティではselfを書かないことが多いですが、チーム開発では可読性に欠けると弊社は考えています。
例えば、コードレビューをするときに
- selfのプロパティなのか、同じスコープ内の変数なのか
- selfのメソッドなのか、同じスコープ内のクロージャなのか
を瞬時に判別できるようになります。Githubのシンタックスハイライトは完璧ではないので、selfを書かない場合、上記2点の判別は少しコードを遡る必要があります。
以下、Style Guideのルールを紹介します。
1. インスタンスのプロパティとメソッドはクロージャ内を含めて、必ずselfを付ける。
このルールは先に述べた理由からです。
| OK | NG |
|---|---|
self.animatableViews.forEach { view in
self.animateView(view)
}
|
animatableViews.forEach { view in
animateView(view)
}
|
2. @noescapeとアニメーションのクロージャ以外のクロージャ内でselfにアクセスする場合、必ず[weak self]を使用する。
上記のselfが必須となるルールと合わせて、循環参照が起きうる箇所を簡単に特定できるようになります。selfにアクセスしているクロージャを探し、[weak self]が抜けていないかを確認するだけです。
| OK | NG |
|---|---|
self.request.downloadImage(
url,
completion: { [weak self] image in
self?.didDownloadImage(image)
}
)
|
self.request.downloadImage(
url,
completion: { image in
self.didDownloadImage(image) // 循環参照
}
)
|
3. クロージャ内で参照をキャプチャするのにunownedを使用しない。
weakよりもunownedの方が便利ですが(Optionalとして扱う必要がないため)、クラッシュを起こす可能性があります。どちらを使うか判別するより、weakを使う方が安全です。
| OK | NG |
|---|---|
self.request.downloadImage(
url,
completion: { [weak self] image in
self?.didDownloadImage(image)
}
)
|
self.request.downloadImage(
url,
completion: { [unowned self] image in
self.didDownloadImage(image)
}
)
|
4. クロージャ内でweakなselfをアンラップする必要がある場合、`self`に対してバインドする。
シンタックスハイライトを有効にするためです。こちらのルールは賛否両論あると思いますが、現在はこのスタイルを推奨しています。
| OK | NG |
|---|---|
self.request.downloadImage(
url,
completion: { [weak self] image in
guard let `self` = self else {
return
}
self.didDownloadImage(image)
self.reloadData()
self.doSomethingElse()
}
)
|
self.request.downloadImage(
url,
completion: { [weak self] image in
guard let strongSelf = self else {
return
}
strongSelf.didDownloadImage(image)
strongSelf.reloadData()
strongSelf.doSomethingElse()
}
)
|
コードを探しやすくするルール
プロダクションのコードは大規模になる傾向があります。
特にViewControllerの肥大化を防ぐことに苦心しているチームは多いのではないでしょうか。肥大化を防ぐと言っても限度があり、5, 600行のファイルが出来てしまうことはよくあります。
また、チームで開発している場合、他人が書いたコードを読む必要があります。そのため、コードを探しやすくするルールは必須です。以下で、いくつか紹介します。
5. class、struct、enum、extension、protocolなどの全ての宣言は// MARK: - 宣言の名前を付ける。
XcodeのSource Navigatorを使用して、特定の型にジャンプすることが容易になります。
| OK | NG |
|---|---|
// MARK: - Icon
class Icon {
// MARK: - CornerType
enum CornerType {
case Square
case Rounded
}
// ...
}
|
// Icon
class Icon {
// MARK: CornerType
enum CornerType {
case Square
case Rounded
}
// ...
}
|
6. 全てのプロパティとメソッドはスーパークラスやプロトコル毎にグループ分けをして、// MARK: スーパークラス/プロトコルの名前を付ける。その他はアクセスレベルに応じて、// MARK: Public、// MARK: Internal、// MARK: Privateをそれぞれ付ける。
ソースコード上のどこにプロパティやメソッドが宣言されているかを容易に特定できます。
| OK |
|---|
// MARK: - BaseViewController
class BaseViewController: UIViewController, UIScrollViewDelegate {
// MARK: Internal
weak var scrollView: UIScrollView?
// MARK: UIViewController
override func viewDidLoad() {
// ...
}
override func viewWillAppear(animated: Bool) {
// ...
}
// MARK: UIScrollViewDelegate
@objc dynamic func scrollViewDidScroll(scrollView: UIScrollView) {
// ...
}
// MARK: Private
private var lastOffset = CGPoint.zero
}
|
7. // MARK:タグによるグルーピングは次の順番にする:
// MARK: Public// MARK: Internal- Classの継承 (最上位の親から子へ)
// MARK: NSObject// MARK: UIResponder// MARK: UIViewController- Protocolの継承 (最上位の親から子へ)
// MARK: UITableViewDataSource// MARK: UIScrollViewDelegate// MARK: UITableViewDelegate// MARK: Private
publicとinternalの宣言はAPI利用者に最も参照される部分であるため、一番上に配置しています。
8. 上記のグルーピングの内部では以下の順序にする:
@が付くプロパティ(@NSManaged,@IBOutlet,@IBInspectable,@objc,@nonobjcなど)lazy var- Computed property(
var) - Stored property(
var) letプロパティ@が付く関数(@NSManaged,@IBAction,@objc,@nonobjcなど)- その他の関数
@が付くプロパティと関数は最も参照されやすいため(KVCのキーやSelector、Interface Builderと相互に参照し合うなど)、上部に定義しています。
9. 一時的にローカライズされていない文字列は// TODO: localizeを付ける。
実装した機能をデバッグしてテストをする場合、大抵はネイティブの言語を使用します。そして、翻訳された文字列は分けてテストされます。このガイドラインにより、全ての未翻訳の文字列が説明され、後で見つけることが容易になります。
| OK | NG |
|---|---|
self.titleLabel.text = "Date Today:" // TODO: localize |
self.titleLabel.text = "Date Today:" |
今後
今年の秋にはSwift3.0の公開も控えているので、ベストなスタイルも変わる可能性があります。本スタイルガイドはSwiftの進化に伴って、今後もアップデートしていく予定です。
ルールを追加したいという要望や、ルールへのご意見がございましたら、日本語だけでも構いませんので、プルリクエストをいただけると助かります!
レポジトリはこちらです。
eure/swift-style-guide
エウレカでは、一緒に働いていただける方を絶賛募集中です。募集中の職種はこちらからご確認ください!皆様のエントリーをお待ちしております!