人によって好みが分かれるけど、僕の好きな Swift の書き方: Swift の extension をどこに書くか?

Swift の extension ってどこに書きますか? 例えば String の extension を書く場合.

  • StringExtensions.swift みたいなファイルにまとめて書く
  • StringHoge.swiftStringHuga.swift みたいに意味のある単位で分けて書く
  • 使うところで private extension に書く

などなど色々あると思います。 実際はある一つの方法だけを選ぶのではなく状況に合わせて書く事が多いと思います。

僕はどんな事を考えながら extension を書く場所を決めているかを紹介したいと思います。

個人的な基本的な考えでは framework が用意している型に対して広いスコープの extension を作るのは結構メンテナンス性が悪いと思うので気をつけて実装するようにしています。

めちゃくちゃ汎用的でコードベースのどこからでも使うもの

  • 前提としてこういう機能は最初から String にある場合が多いので書くことは少ないです
  • このパターンの場合は StringExtensions.swift のようなグローバルっぽい名前のファイルに作ります
/// StringExtensions.swift

extension String {
  var firstOrEmpty: String {
    return first.map { String($0) } ?? ""
  }
}

ある特定の機能のまとまりがあり、コードベースのいろいろな場所で使う場合

例えば何らかの Hoge があってこれの RxSwift の extension だけまとめたい場合とかは、それと分かるような名前をつけます。 命名規則はコードベース内で統一されていればいいと思うので、 Hoge+Reactive.swift でも HogeReactiveExtensions.swift でもなんでもいいと思います。

// HogeReactiveExtensions.swift 
extension Reactive where Base: Hoge {

    // 幾つかのメソッド..
    
}

UI の表示に使うもの (UI の仕様を実装) & テスト不要なもの

例えば3文字ごとにスペースを入れた文字列を表示したいという(謎の)仕様がある場合。

この場合はその UI の要素(View or ViewController) の中に private extension として書くことが多いです。 スコープ的にテストコードから見えなくなるのでテストが不要なぐらい簡単な場合にはこの方法を取ります。

この方法の利点はこの extension が、この View からしかアクセスできないので安全です。例えば View の仕様が変わっても安心して修正できます。 また、他の箇所から使うことを想定していないので、プロパティ・メソッド名が短くて済みます。命名の負荷が減るのはいいこと。

// HogeHogeView.swift

private extension String {
  var insertSpacedString: String {
      return "処理"
  }
}

例としてこれが private じゃない場合に何が起こるかを説明します。

private じゃない場合、このメソッドは複数の箇所(View_A, View_B) から使うことができます。 このとき View_B の仕様だけ変わった場合に View_A でも使ってることを知らずに extension のコードを修正してしまうと View_A に予期せぬ変更が発生してしまいます。

「テストしとけば大丈夫でしょ」という意見もあると思いますが、現実問題で網羅するのは大変なので間違いが発生しにくいコードを書いたほうが有益だと考えています。

他の箇所からも使いたくなったら?

まず、他の View からも使いたくなったら、 View そのものが再利用できないか考えましょう。 別の View から使う必要が出たらそれと分かるような名前の extension のファイルを作ります 例: StringDisplayHogeExtension.swift

でもこうすることは実際のところあんまりなくて通常は Entity に対して extension を生やすことが多いと思います. 例えば User という Entity があったら User+UIExtension.swift みたいにするなど.

もちろん Extension を作らない解決方法もありです。例えば HogeFormatter.swift みたいなものを作るなど.

UI の表示に使うもの (UI の仕様を実装) & テストが必要なもの

この場合は ViewModelPresenter などの View と Model をつなげるレイヤーの private extension として実装します。

この場合も extension 自体のテストはできませんが、 ViewModelPresenter のテストができるのでそこからテストします。

// HogeViewModel.swift

class HogeViewModel {
}

private extension String {
  var insertSpacedString: String {
      return "処理"
  }
}

ユーザーからの入力をバリデーションする場合もこの書き方をすることが多いです。

議論

「UI の表示に使うもの」の2パターンについて「なんで extension にするの? 」、「View や ViewModel のメソッドとして書いたらいいのでは?」と思う人もいるかも知れないです。

僕的にはそういう方法はなんかイケてないのでしません。extension のメソッドが View や ViewModel の property などに依存していない場合は extension として切り出しておいたほうが依存関係がスッキリしてあとからの修正に耐えられるコードになります。

class HogeViewModel {

    // イケてない
    func insertSpacedString(_ str:String) -> String {
         return "処理"
    }

}

残念ながら

弊社ではこの書き方は普通なのですが、世界は広いのでこの書き方をするとレビューなどで 「class の中に書いてください」とレビューで指摘されることがあって泣く泣く治す場合もあります。

良くないと思うもの

  • StringExtensions.swift みたいなグローバルっぽい名前のファイルに全部書いてある

まとめ

ほんの一部ですが extension をどこに書くのかの方針を書きました。

前提として、なんの extension にするのか?ある機能は Date の extension? String の extension ? のどれに生えているべき? という話もあるなと思ったのですが、今回はそれには触れませんでした。

コードを書く際の参考になれば幸いです。