19
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Swift】読みやすいコードを書くためのコーディングルール(基本編)

Last updated at Posted at 2021-10-18

概要

  • 弊社のiOSチームではコーディング規則がある程度細かく定められている
    • コードの可読性を上げ、コーディング品質を属人化させない目的
  • 転職当時「どの順序で宣言するのがいいのかわからない...!」のような悩みが大きかったので、参考としてまとめます

プロパティ名

  • プロパティは Lower Camel Case (最初は小文字、語頭が大文字)とする
  • 宣言時には : を変数名に続けて入力し、空白1文字ののち型を宣言する
var playerId: Int
  • アルファベットの省略語は大文字を用いる
var webURL: URL?
  • ただし、変数名の頭にきた場合には小文字とする
var urlString: String?
  • アクセスレベルによって _ をつけるといったことは行わない
private var someFlag: Bool

var otherFlag: Bool

// NG
private var _someFlag: Bool

クロージャ

  • 関数等の下は1行空けて、下のクロージャの間は開けない
func functionA() -> String? {

    return "Hello"
}
  • 引数の型は基本的に省略しない
  • ({ の間、 }) の間は省略なし
  • { の後ろ、 } の前は離す
  • キャプチャの前後は離す
let names: [String] = ["田中", "佐藤", "鈴木"]

names.enumurated().foreach({ [unowned self] (index: Int, name: String) in

    print("index: \(index), name: \(name), \(self.functionA())")
})
  • switch のクロージャは例外的に {の直下の行から始める
switch status {
case .good: print("good!")
case .bad: print("bad...")
}
  • クロージャーが複数重なる場合には例外的に } の上に一行開ける
self.test(onSuccess: {

    print("OK")

}, onError { 

    print("NG")
})

switch

  • 全ての case の処理が1行で簡潔にかける場合には1行にする
switch result {
case .success: print("success")
case .failure: print("failure")
  • case によって2行以上必要な場合には case から改行して記載
    • この場合各 case の間は1行開ける
switch result {
case .success:
    print("success")

case .failure:
    self.presentDialog(title: "Error!!", message: "result is error", completion: {
    
        self.dismiss()
    })
}

プロトコル/クラス/構造体/列挙型の定義

  • 基本的には1ファイルにつき1つの定義に留める
    • VIPERなどの設計思想を取り入れると必然的にプロトコル/クラスなどが増えるが、まとめてしまわない
  • 定義時には // MARK: をつける
    • // MARK の上は2行、下は1行空けることを基本とする
  • 複数のプロトコルの適合時、なるべくメソッドに関しては extension で宣言を分割する
    • プロパティは extension で宣言できないため、宣言部分に // MARK: をつけてわかるようにしておく
PseudoViewInterface.swift
import Foundation


// MARK: - PseudoViewInterface

protocol PseudoViewInterface {

    /// 仮プロパティ
    var title: String? { get }
    /// 仮関数
    func pseudoFunction() -> String?
}
PseudoViewController.swift
import UIKit


// MARK: - PseudoViewController

final class PseudoViewController: UIViewController {

    
    // MARK: - PseudoViewController

    var title: String? = nil
}


// MARK: - PseudoViewInterface

extension PseudoViewController: PseudoViewInterface {

    func pseudoFunction() -> String? {

        return "Hello!"
    }
}

宣言の順序

  • アクセスレベルで大分する
    • グループ開発において private 以下の実装は意識する必要がないという状態を作る
  • static let > lazy var > @Outlet > private(set) var > var > let > init > deinit > function > デリゲートメソッド の順に宣言する
  • privateMARK: - Private 以下に宣言する
  • private にできるものは private で宣言する
  • @IBOutlet はUIの配置順に上から下になるように宣言する
    • ただし [NSLayoutConstraint] などの配列は後に回す
final class PseudoViewController: ViewController {
    
    /// ViewController の ID などの static にすべきもの
    static let identity: String = "PseudoViewController"
    /// 外部に出す必要がある Observable など
    var someSignal: Signal<Void> {
        
         self.someRelay.asSignal()
    }

    deinit {
    
        self.disposable?.dispose()
    }

    func inject(dependency: PseudoDependencyProtocol) {
    
        self.dependency = dependency
    }

   
    // MARK: - UIViewController

    override func viewDidLoad() {
    
        super.viewDidLoad()
        
        self.setupUI()
    }

    
    // MARK: - Private

    private lazy var dependencyId: String? = {

        guard let dependency = self.dependency else { return nil } 
        
        return "prefix ****" + dependency.id
    }

    @IBOutlet private dynamic weak var someButton: UIButton!

    @IBOutlet private dynamic var someConstraints: [NSLayoutConstraint]!

    private var dependency: PseudoDependencyProtocol?

    private var someFlag: Bool = false
    
    private let someRelay: PublishRelay<Void> = PublishRelay<Void>()

    private func setupUI() {

        self?.navigationController.setNavigationBarHidden(true, animated: false)
    }
}

プロパティの宣言

  • /// を利用して外部から参照した際にプロパティの利用目的がすぐわかるように宣言する
  • プロトコルの宣言においては、 Signal などの外部ライブラリによる型を返却値に利用する場合には RxCocoa.Signal のように出自を明確にする
PseudoPresentation.swift
import RxSwift
import RxCocoa

import Foundation


// MARK: - PseudoPresentation

protocol PseudoPresentation {

    /// デバイス名を表す Driver.
    var name: RxCocoa.Driver<String?> { get }
    /// デバイスIDを表す Driver.
    var id: RxCocoa.Driver<Int> { get }

    /// 入力をバインドする.
    func bind(input: PseudoInput)
}
Player.swift
import Foundation


// MARK: - Player

struct Player {

    /// プレイヤー名
    let name: String
    /// プレイヤーID
    let playerId: Int
    /// 体力
    let hitPoint: Int
    /// 魔力
    let magicPoint: Int
}

コメント

  • // MARK: - // FIXME: // OPTIMIZE: // MEMO: /// // を使う
  • 文章になっている場合には最後に . をつける

// MARK: -

  • クラス名などの宣言、デリゲートメソッド、 Private の区分けなどに用いる
  • MiniMap 等に線が表示されるようになるため便利だが乱用はしないこと
  • // MARK: - の上2行下1行は空ける
// MARK: - PseudoStruct

struct PseudoStruct {

}

// FIXME:

  • 実装中の仮対応コードなどにつける
  • リリース時には // FIXME: は存在しないことが基本

func fetchData() -> Single<Result<Void, Error>> {

    // FIXME: API定義後修正すること.
    return .just(.success())
}

// TODO:

  • 実装中の仮対応コードなどにつける
  • モジュールの実装順等で対応不能な箇所や将来的な追加実装を見越す
    • // FIXME: // OPTIMIZE との違いは「タスク」として残っている意味合いが強い点
    // TODO: アイコン変更機能が実装された場合にはここで変更する.
    self.iconImage = R.image.icon_id_1()

// OPTIMIZE:

  • レビューにおいて正常に動くため工数などの都合で一旦パスしたがリファクタリングが必要とされた箇所
    // OPTIMIZE: 命名を整理する.
    private let somethingHasTooLongNameBehaviorRelay: BehaviorRelay<Bool>

// MEMO:

  • 端末ごとの特殊処理や複雑なロジックなど「なぜこうなったのか」が一目でわからない箇所につける

    // MEMO: フォントサイズを iPhone SE(2nd) のウィンドウ横幅(375)を基準として調整する.
    let ratio: CGFloat = UIScreen.main.bounds.size.width / 375

///

  • クラスの internal プロパティなど外部から参照するものにつける
    • /// でコメントを書くことで参照時にそのコメントが表示される
    /// プレイヤーID
    let playerId: Int

//

  • 複数行に渡ってコメントをする必要がある場面で利用
  • どのコメントも並列的に重要、 // MEMO: で強調するほどではないなど
    • 割と柔軟に使うイメージ、後で読んだ人が困りそうなところにはとりあえず書いておく
func fibonacciNumber(number: Int) -> Int {

    // フィボナッチ数は2個前までの項を足した値.
    // number が負の値の場合には -1 を返却する(不正).
    if number < 0 { return -1 }
    // 第0項は0とする(定義).
    if number == 0 { return 0 }
    // 第1項は1とする(定義).
    if number == 1 { return 1 }
    // 第n項(n>1)は f(n-1) + f(n-2).
    return self.fibonacciNumber(number - 1) + self.fibonacciNumber(number - 2)
}
19
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?