はじめに
これは、Swift Advent Calendar 2019 9日目の記事です。
コードレビューの際、何気に指摘が多いのが命名についてです。
命名に関するPRのコメントだけでも、対応すればCIが走るし、開発にかかる総時間は伸びてしまいます。
開発人数が多い場合はもちろん、一人で開発する場合でも、あとから見たときに混乱しない命名にするということは大切です。
しかしながら、命名に関しては頻繁な技術的アップデートが必要だったりするものではないので、一度コツをつかんで適切な命名がシュッとできるようになれば、レビュワーにとってもレビュイーにとっても負担が減ることになります。
今回は、多くのコードレビューを受けてきて、Swiftの命名について気をつけたいと感じたポイントを紹介します。命名規則と題していますが、規則というほどかっちりしたものではないです。
こちらの公式リファレンスも参考にしています。
Swift.org API Design Guidelines
型の名前を変数名に含めない
Swiftは型セーフな言語なので、変数定義の際に必ず何かしらの型に決まり、その変数には明示しなくとも型の情報が持たれることになります。
そのため、たとえローカル変数であっても、型名を変数名として使用するのは避け、何が入っている変数なのかという情報を与えるほうがベターです。
// Bad
let array = ["dog", "cat", "fox"]
// Good
let mammals = ["dog", "cat", "fox"]
ただ、UI部品の命名に関してはその次第ではなく、Apple標準ライブラリに倣い型名をSuffixとしてつけるのが一般的です。そうすることで、命名の重複を避けるという目的もあります。
振る舞いに合ったメソッド名をつける
たとえば返り値が無いメソッドなのにgetHoge
みたいな命名をしてしまうと、これを見た別の開発者は何かしらのオブジェクトが返ってくるgetterの挙動を期待していまうかもしれません。
Swift Standard Libraryでは、何かしらの返り値がある場合は動詞の過去分詞形や名詞形・既存のオブジェクト等に対する操作をする場合には動詞の命令形で命名されていることが多いので、それらも参考に命名することで読む人の期待する振る舞いと実際の振る舞いが一致しやすくなります。
特に先の例のように、get・setといったワードは、安易にメソッド名に含めてしまうと読む人が期待するインターフェイスにならないことがあるので注意が必要です。
公開する情報が最小限になるようにする
アーキテクチャにも依りますが、TableViewのIndexPath
の持つ情報や、ViewControllerのviewWillAppear
などのdelegateメソッドをトリガーとしたアクションを他のレイヤーに伝えたいとなったときに、これらの名前を直接公開するような命名をするのは推奨されません。
View以外に公開する命名では、Viewだけが知る情報名は隠蔽し、期待されるアクションに言及するような名前をつけるべきです。
import RxSwift
import RxCocoa
class ViewController: UITableViewController {
// MARK: Internal
// Bad
let indexPathTrigger = PublishRelay<IndexPath>()
let viewWillAppearTrigger = PublishRelay<Void>()
// Good
let selectTrigger = PublishRelay<Int>()
let refreshTrigger = PublishRelay<Void>()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
refreshTrigger.accept(())
}
}
import RxSwift
import RxCocoa
class Presenter {
weak var viewController: ViewController
init(_ viewController: ViewController) {
self.viewController = viewController
viewController.refreshTrigger.asSignal()
.emit(onNext: { _ in
// ・・・
})
}
}
文章として読めることを意識する
例えば、キーワードで記事を検索するためのメソッドに対して以下のような命名をしたとしましょう。
すると、アクションを起こそうとしている対象がずれてしまっているように受け取れます。
// Bad
func search(name: String) {} // nameを探すかのように受け取れる
ここで外部引数に適切な情報を入れ込むことで、上記よりもずっとSwiftらしい命名になります。
呼び出しの部分を見ても、振る舞いがわかりやすくなっていることが分かるかと思います。
// Good
func search(byName name: String) {}
search(byName: "hoge")
ここではこのように「何で探すか」という情報を外部引数に入れていますが、外部引数にどこまで情報を入れるかというところは、実際の引数の型などに依ります。
また、Bool値を返すプロパティは、isEmpty
、isHidden
などに習い、アクセスしたときにインスタンスの状態が一文で表されるような命名が良いとされます。
文章として読める命名にすることで、コードリーディングの際に必要な情報が名前に含まれ、余計なコメントを書く必要がなくなります。
まとまりがあるものには同じ命名・Prefix・Suffixを用いる
これはSwiftに限った話ではないかもしれませんが、共通項を持つメソッド・プロパティ達には命名にも共通項をもたせておくことで、特にコメントが書かれていなくてもそのことが他の開発者に伝わりやすくなります。
わかりやすい例として、引数に渡す型が異なるが施すアクションや返る結果が同じという場合には、オーバーロードを活用してメソッド名や引数名は同じものを用いてしまうというのが良いでしょう。
これにより、読む側はその引数の型が何であるかを意識せずに挙動だけを見れるというメリットもあります。
おわりに
この記事では、自分がこの1年命名に関して指摘を頂いてきたポイントをまとめてみました。
つまるところ命名規則というのは、読む人・共同開発者への思いやりだと思います。(主観です)
Swiftコードのレビューをお願いする前に、レビュワーや読み手を思いやり、是非今一度上記ポイントを思い出して確認してもらえると嬉しいです。
最後まで読んでいただきありがとうございました!
もっとこうしたほうが良い!私はこうしてる!等ありましたら是非コメントお願いします。