エウレカ流Swift Style Guideを公開しました

swift_style_guide_og

こんにちは!CouplesでiOSエンジニアをしている丹です。
Swiftがリリースされてから約1年半が経ち、チーム内に知見が溜まってきたので、
エウレカ流のSwift Style Guideを公開しました!

レポジトリはこちらです。英語と日本語を用意しています。
現在、52のルールが書かれています!

eure/swift-style-guide

他のスタイルガイドと何が違うのか

本スタイルガイドが目指したのは、

  • チーム開発
  • プロダクションコード

の2点において実用的なスタイルガイドです。
以下に参考にしたスタイルガイドとブログを紹介しますが、上記2点を満たすスタイルガイドが見つからなかったことが、作成のきっかけです。

参考にしたスタイルガイド

1. github/swift-style-guide

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. classstructenumextensionprotocolなどの全ての宣言は// MARK: - 宣言の名前を付ける。

XcodeのSource Navigatorを使用して、特定の型にジャンプすることが容易になります。

swift-style-guide-001

swift-style-guide-002

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

publicinternalの宣言は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

  • このエントリーをはてなブックマークに追加

エウレカでは、一緒に働いていただける方を絶賛募集中です。募集中の職種はこちらからご確認ください!皆様のエントリーをお待ちしております!

Recommend

入社1ヶ月で感じたエウレカデザイナー3つの強み

オウンドメディアの古い記事を整理して、トラフィックを約2倍にした話