[Swift API Design Guidelines] 用途が明確になるよう心がけた命名

はじめに

こんぬづは、先日知人宅で食べたチーズが美味しすぎて時間が経っても忘れられず、業務中もチーズのことばかり考えてしまっている田中です。リコッタとブッラータ...。

C8dLtu2U0AAMcUm

最近自分の考えるSwiftのclassやstruct、関数などの命名に疑問を感じています。その疑問を解消すべく、Swift.orgAPI Design Guidelinesを読むことにしました。そしてせっかくなので読んだ内容をブログ化してみました。

今回はその中のPromote Clear Usageより。

この記事の内容はAPI Design Guidelinesから一部抜粋して筆者の解釈で和訳・意訳したものになります。理解の補助として、適切だと思われる箇所には実際に筆者が書いているコードを載せています。参考になれば幸いです。

用途が明確になるよう心がけた命名

曖昧さを避けるために必要な単語は全て含める

例えば、あるコレクションの中にある特定のインデックスの要素をremoveするとき。下の例はオッケー。

extension List {
    public mutating func remove(at position: Index) -> Element
}
employees.remove(at: x)

もしatというキーワードをメソッドシグネチャから無くした場合、特定のインデックスの要素をremoveするという意味ではなく、xと等価の要素をコレクションから探し出してremoveするという意図として読めてしまう可能性がある。ので、下はダメな例。

employees.remove(x) // コレクションの中の`x`という要素をremoveしたいのか?と誤解してしまう

不要な単語は除く

語彙を多く使うことは意味を明確に伝える上で必要なことかもしれないが、冗長さが出てしまったり意味が重複している単語は使用を控えるべき。

public mutating func removeElement(_ member: Element) -> Element?
allViews.removeElemenet(cancelButton)

この場合においてElementという単語はそれが顕著な例で、下の書き方がより良い。

public mutating func remove(_ member: Element) -> Element?
allViews.remove(cancelButton) // より明確になった

実コード例

以下はTweetImageDataStoreというstruct名から、ツイートの中の画像情報を扱うstructであることが明確なので、第一引数のimageDataが余計な情報になっているという例。

struct TweetImageDataStore {
    func save(imageData: [Data], withTweetID id: String, completion: @escaping (Result<Void>) -> ()) {}
}
save(imageData: [], withTweetID: "12345") { result in }

↓は改善例。

struct TweetImageDataStore {
    func save(_ imageData: [Data], withTweetID id: String, completion: @escaping (Result<Void>) -> ()) {}
}
save([], withTweetID: "12345") { result in }

それらの型が何であるかよりも、役割をもとにして命名を考える

悪い例。

var string = "Hello"
protocol ViewController {
  associatedtype ViewType : View
}
class ProductionLine {
  func restock(from widgetFactory: WidgetFactory)
}

良い例。

var greeting = "Hello"
protocol ViewController {
  associatedtype ContentView : View
}
class ProductionLine {
  func restock(from supplier: WidgetFactory)
}

associated typeがプロトコルで扱われていてプロトコル名がassociated typeと名前が衝突する場合はTypeを後ろにつけて回避する。

protocol Sequence {
  associatedtype IteratorType : Iterator
}

引数の役割を明確にするために、型情報の弱いものには情報を補う

特にNSObject, Any, AnyObjectなど型や、Int, Stringなどの基本的な型が引数となる場合は、扱うシーンに対する型情報やコンテキストが欠如している。下の例はダメな例。

func add(_ observer: NSObject, for keyPath: String)
grid.add(self, for: graphics) // forにあたるものの役割があいまい

こういう場合に意味を明確にするには型情報の弱い引数にはその役割を表す名詞を補う。下の例はforに渡す引数の役割がKeyPathであることを示すために、forKeyPathと書き換えられている。

func addObserver(_ observer: NSObject, forKeyPath path: String)
grid.addObserver(self, forKeyPath: graphics) // forKeyPathにあたるものの役割が明確

実コード例

以下は第二引数のwith tweetID: Stringが呼び出し元から見た時に何を意味した文字列なのかがわからないという例。

struct TweetImageDataStore {
    func save(_ imageData: [Data], with tweetID: String, completion: @escaping (Result<Void>) -> ()) {}
}
save([], with: "12345") { result in }

↓は改善例。

struct TweetImageDataStore {
    func save(_ imageData: [Data], withTweetID id: String, completion: @escaping (Result<Void>) -> ()) {}
}
save([], withTweetID: "12345") { result in }

まとめ

なにが必要な情報で、なにが不要な情報なのか。「意味が重複する名前付け」はどういうパターンで、「命名が重複した場合」にはどう対処するのかなどが説明されていました。

より具体的なワーディングなどはGitHubで気になった単語を「language:swift」に絞って検索してみると良いかもしれません。自分は最近そうしています。

参考・関連